The Unity host for CoreAI: DI wiring, a drop-in UI Toolkit chat panel, streaming HTTP/SSE clients (remote APIs and LLMUnity's local server), WebGL transports, persistence, and Editor setup menus.
The agent logic itself lives in the engine-free com.neoxider.coreai. This
package is the adapter layer that puts it on a scene.
| Package | Depends on | Version |
|---|---|---|
com.neoxider.coreaiunity |
com.neoxider.coreai (same version, lockstep) |
see package.json |
- You are shipping a Unity game and want NPCs, an in-game teacher, or agents that change a running world — this package plus the core is the whole base install.
- You want the chat UI without writing UI — one Editor menu item produces a working scene.
- You are on WebGL — this package owns the browser-safe transports and the async guards that make that work; the core alone does not.
If your host is not Unity, you do not need this package at all. See
tools/portable.
1. Install (Unity Package Manager → Add package from Git URL, core first):
https://github.com/NeoXider/CoreAI.git?path=Assets/CoreAI
https://github.com/NeoXider/CoreAI.git?path=Assets/CoreAiUnity
Then CoreAI → Setup → Install Git Dependencies (VContainer / MessagePipe / UniTask, and LLMUnity —
remove ai.undream.llm from Packages/manifest.json afterwards if you only use HTTP APIs) and install
Microsoft.Extensions.AI 9.10.2 via NuGetForUnity (10.x cannot load under Unity; see INSTALL.md §2.1).
To call a real model, enable the provider module: CoreAI → Setup → Modules → LLM Providers → Enable Providers (adds COREAI_LLM). Step-by-step: INSTALL.md.
2. Make a scene:
CoreAI → Setup → Create Chat Demo Scene
That creates CoreAILifetimeScope, a CoreAiChatPanel, panel settings and a CoreAiChatConfig_Demo
asset. Set your backend in CoreAI → Settings and press Play.
3. Call the model from any script — no DI boilerplate:
using CoreAI;
string reply = await CoreAi.AskAsync("Hello!"); // Task<string?>
await foreach (string chunk in CoreAi.StreamAsync("Tell a story", "SmartChat"))
{
label.text += chunk;
}4. Build an agent that calls your code:
using System;
using System.Collections.Generic;
using CoreAI;
using CoreAI.Ai;
Dictionary<string, int> stock = new() { ["fire sword"] = 0, ["iron sword"] = 3 };
AgentConfig blacksmith = new AgentBuilder("Blacksmith")
.WithSystemPrompt("You are a blacksmith. Sell weapons and remember purchases.")
.WithTool(new DelegateLlmTool("stock_of", "How many of an item are in stock.",
// Unity compiles C# 9: a lambda passed as Delegate must be wrapped in a concrete delegate type.
new Func<string, string>(item => stock.TryGetValue(item, out int count) ? count.ToString() : "0")))
.WithMemory()
.WithChatHistory()
.WithMode(AgentMode.ToolsAndChat)
.Build();
string answer = await CoreAi.AskAsync("Got any fire swords?", "Blacksmith");Build() registers the role with the live AgentMemoryPolicy when one exists (that is, once
CoreAILifetimeScope has built). Use BuildDetached() when you want the AgentConfig without that
side effect, and AgentConfig.ApplyToPolicy(policy) to register it later against a policy you hold.
Reference: AGENT_BUILDER · COREAI_SINGLETON_API · QUICK_START
| Area | What you get |
|---|---|
| Composition | CoreAILifetimeScope (VContainer), CoreServicesInstaller, MessagePipe brokers, link.xml for IL2CPP |
| Static facade | CoreAi.AskAsync / StreamAsync / StreamChunksAsync / OrchestrateAsync, tool-call events, AddSkillForRole |
| Chat UI | CoreAiChatPanel (UI Toolkit), CoreAiChatService, typing indicator, cancel, error presentation split between player and log |
| Providers | MeaiLlmClient (streaming tool loop), OpenAiChatLlmClient (HTTP APIs, and on-device GGUF through LLMUnity's built-in OpenAI-compatible server via LlmUnityServerHttpSettings), routing/timeout/retry/fallback decorators |
| WebGL | FetchSseOpenAiTransport + CoreAiSseFetch.jslib for real incremental SSE, UnityWebRequestOpenAiTransport fallback, UnityMainThreadLlmAsyncMarshaler |
| Persistence | FileAgentMemoryStore, FileConversationSummaryStore, FileSkillStore, persistentDataPath; on WebGL the engine persists (config.autoSyncPersistentDataPath = true) and CoreAiWebGlPersistence only reports whether it is armed |
| Unity-only tools | world_command (WorldLlmTool), component_command (ComponentLlmTool), scene_tool (SceneLlmTool), camera (Vision.CameraLlmTool: camera_capture / screenshot), camera_tool (Infrastructure.World.CameraLlmTool: capture_camera) |
| Editor | CoreAI → Setup → … scene/asset wizards, Settings window, Agent Session Inspector, production-settings validation |
Tools that are not in this package: memory, game_config, game_state, get_inventory,
read_skill, call_skill_tool, manage_skills, wait — those are in the engine-free core.
execute_lua / manage_mods are in com.neoxider.coreaimods.
Base install is core + this package. Everything else is independent and additive:
| Package | Adds | If absent |
|---|---|---|
com.neoxider.coreaimods |
Lua sandbox, execute_lua, manage_mods, mod runtime, RbxApi |
Nothing in the base references it; the project compiles unchanged |
com.neoxider.coreaihub |
Tabbed Hub window (Chat / AI Settings / Statistics / Mods) | HubPageRegistry still exists in the core; the Mods↔Hub bridge assembly compiles out via defineConstraints: ["COREAI_HAS_HUB"] — silently, so check the Hub page is really gone before filing a bug |
com.neoxider.coreaimcp |
In-game MCP server | Leaf package — nothing depends on it |
com.neoxider.coreaimirror |
Mirror networking transport | Assemblies carry defineConstraints: ["MIRROR"]; without Mirror installed they never build |
com.neoxider.coreaibenchmark |
PlayMode game-creation benchmark (G1–G8) | Only the benchmark scenarios disappear; the report types ship inside the core |
ai.undream.llm (LLMUnity) |
On-device GGUF inference | COREAI_HAS_LLMUNITY stays undefined; the HTTP paths are unaffected |
Two project-level scripting symbols, both opt-in and absent by default: COREAI_LLM (provider-backed
HTTP/MEAI/LLMUnity execution) and COREAI_LUA. Toggle them from CoreAI → Setup → Modules (the menu
changes the currently selected build target only). CI builds all four combinations (core / llm /
lua / full). Details: OPTIONAL_MODULES.
Tool calls execute while the model is still generating. MeaiLlmClient.CompleteStreamingAsync
hands each native FunctionCallContent to ToolExecutionPolicy.ExecuteStreamedAsync as its deltas
arrive; the turn does not have to finish first. Proof:
MeaiStreamingToolCallEditModeTests.CompleteStreamingAsync_NativeToolCallMidStream_ExecutesBeforeStreamEnds.
Text-shaped tool calls from small local models are extracted at the end of each roundtrip instead.
Streaming is the default execution path, not a chat-only feature. With
ICoreAISettings.EnableStreaming on, AiOrchestrator.RunTaskAsync also runs through
CompleteStreamingAsync and collapses the stream back into an LlmCompletionResult, so headless task
execution uses the same tool loop as the chat panel. See ARCHITECTURE.
Tool calls run in parallel, bounded, with mutations serialized.
ICoreAISettings.MaxParallelToolCalls (default 4; 1 = sequential). Mutating built-ins (memory,
manage_mods, manage_skills, world_command, component_command, execute_lua,
call_skill_tool) are serialized against each other and result order is preserved. Proof:
CompleteStreamingAsync_TwoNativeToolCallsWithParallelLimit_OverlapAndResultsInCallOrder.
Primitives that are dead in the browser cannot creep back in. Task.Run, Task.Delay,
CancelAfter, RunContinuationsAsynchronously and ConfigureAwait(false) are rejected in
WebGL-reachable runtime code by a source-scanning guard, and delays/timeouts go through
ILlmAsyncMarshaler → UniTask player-loop timing instead. Proof:
WebGlUnsafeAsyncPrimitivesEditModeTests (including Allowlist_HasNoStaleEntries),
CoreAiWebGlAsyncGuardEditModeTests, plus the single CAIU001 Roslyn analyzer shipped in
RoslynAnalyzers/ and gated by the CI job analyzer. The guard holds a frozen
allowlist of inherited exceptions, so it stops new violations in clean files rather than certifying
the whole tree. What this does not claim: see Limits below.
A typed completion receipt, not a string you have to guess about.
CoreAiChatPanel.SubmitMessageFromExternalResultAsync returns CoreAiChatExternalSubmitResult, which
separates admitted from completed: Admitted == false carries a Rejection and implies no model
turn; Admitted == true owns its TurnGeneration even if the panel is later disabled; Completion
exposes Ok, ErrorCode, HTTP/retry hints, token usage and executed tool receipts. Partial streamed
text can be visible while Ok is false — a non-empty string is never proof of success. Headless
equivalents: IAiTaskResultService.RunTaskResultAsync, CoreAiChatService.SendMessageResultAsync
(check SupportsTaskResults first; decorators throw NotSupportedException rather than fake a
receipt).
Memory is isolated per tenant / user / session / topic. Assign an
AgentMemoryScopeProviderBehaviour on CoreAILifetimeScope, or call
SetAgentMemoryScopeProvider(...) before the container is built. Keys are opaque full SHA-256, so no
user identifier reaches a filename or an error log. Both setters throw after build on purpose. Proof:
ScopedAgentMemoryStoreDecoratorEditModeTests, AgentMemoryActorScopeKeyEditModeTests,
CoreAILifetimeScopeConversationStoreEditModeTests. Details: ARCHITECTURE
§Runtime Context And Memory Scope.
- "Never blocks the frame" is not a guarantee this package makes. The async guards above prevent
browser-dead primitives and keep continuations on the main thread; they do not ban
.Result,.Wait()orGetAwaiter().GetResult(), and no test measures frame time.FileAgentMemoryStorestill takes synchronousSemaphoreSlimwaits on its sync API.CAIU001ships as a warning with recorded debt in KNOWN_ISSUES. Useawait; do not block on CoreAI tasks from the main thread. LocalModel(LLMUnity) does not work on WebGL. Browser builds useServerManagedApifor production, orClientOwnedApionly where key exposure is acceptable.- A WebGL build stops unless the web template arms browser storage.
config.autoSyncPersistentDataPath = trueincreateUnityInstance()is the only channel that carriesApplication.persistentDataPathinto IndexedDB, and Unity's stock templates ship that line commented out. Add the line to your own template, or runCoreAI → Setup → Install WebGL Template— it copies the template shipped inside this package intoAssets/WebGLTemplates/CoreAIand selects it. A player that deliberately ships without CoreAI's persistent storage opts out with the scripting define symbolCOREAI_WEBGL_NO_PERSISTENCEon the Web platform; the refusal is logged on every build. - Cross-origin endpoints need CORS, and a non-empty
API Keyon aCoreAISettingsasset underResources/aborts the build on purpose — inject keys at runtime. - Model quality is your problem, not the framework's. Deterministic EditMode suites use stubs; live PlayMode suites need a configured backend and are stochastic. Re-run the scenarios your game depends on against your own model before shipping.
- Authority rejection and invalid structured output currently share
LlmErrorCode.InvalidRequest. Present a general failure status rather than branching on diagnostic wording. - Do not automatically replay an admitted turn. It may already have produced text or executed tools.
Unity → Window → General → Test Runner
├── EditMode — large deterministic suite, no real LLM (streaming, tools, skills, memory scopes,
│ Lua, rate limits, WebGL async guards, architecture fitness)
└── PlayMode — needs a backend (HTTP env vars or a local GGUF)
├── FastNoLlm/ — stubs: skill pipeline, catalog injection, meta-tool registration
└── LlmVerification/ — real model: tool discovery, resilience, benchmark
The engine-free subset also runs without Unity at all —
dotnet test tools/portable/Tests/CoreAI.Portable.Tests.csproj -c Release executed 1,317 cases with
0 failures on 2026-09-09, and runs on every push as CI job portable-core. EditMode counts are
profile-dependent (COREAI_LLM / COREAI_LUA / Hub change which assemblies compile), so compare the
number of executed cases between runs, not just the colour.
Requirements for writing tests here: Tests/README.md and ARCHITECTURE §Test Integrity Rule.
| Level | Documents |
|---|---|
| Start | QUICK_START · QUICK_START_FULL · COREAI_SINGLETON_API · COREAI_SETTINGS · EXAMPLES |
| Chat & streaming | README_CHAT · STREAMING_ARCHITECTURE · HTTP_TRANSPORT_SPEC |
| Agents, tools, memory | AGENT_BUILDER · TOOL_CALL_SPEC · MemorySystem · AI_AGENT_ROLES |
| Backends & shipping | RUNTIME_BACKEND_SWITCHING · LLMUNITY_SETUP_AND_MODELS · SHIPPING_PLAYER_MACHINES · CLOUD_COST_BUDGETING · OPTIONAL_MODULES |
| Architecture | ARCHITECTURE · DEVELOPER_GUIDE · DGF_SPEC · MULTIPLAYER_AI |
| When it breaks | TROUBLESHOOTING · KNOWN_ISSUES · WEBGL_BUILD_TROUBLESHOOTING |
Full map: DOCS_INDEX. Release notes: CHANGELOG.md — versions live
in package.json, not in this file.
Author: Neoxider · License: PolyForm Noncommercial 1.0 · Commercial licensing: neoxider@gmail.com