com.pie.agent is the Pie Unity host package built on top of PuerTS V8.
It uses a runtime-first architecture: the agent core runs in Runtime, while the Editor provides a chat UI and authoring workflow.
- Streaming chat and tool execution inside Unity
- Runtime and Editor share the same agent core
- Project assets live under
Assets/Pie - Runtime state lives under
Application.persistentDataPath/Pie - Project skills, project extensions, and project AGENTS.md context are first-class package concepts
Unity forwards shared attention_requested / attention_resolved events through PieBridge.OnJsEvent. This is a semantic bridge, not a native attention presenter: consumers must supply any in-game bubbles, badges, audio or OS notifications. Ordinary completion is explicitly OFF in production, with no supported host toggle. The low-level user-initiated coordinator policy requires caller-owned accepted-delivery bookkeeping not supplied by the production controller. Generic confirmation is input (explicit false is an answer); timeout, skipped, unavailable and missing responses cancel attention consistently with CLI. Only the interaction owner resolves requests; suppressing presentation must not resolve them. The monorepo's docs/public-api/unity-host-adapter.md#attention-events-experimental records the underlying host contract.
com.pie.agent exposes a local RPC/REST host for agent-driven Unity work.
Stable HTTP entrypoints:
GET /healthGET /instancesGET /manifestPOST /tool/{name}POST /rpc/{method}
Recommended host tools:
unity_scene_queryunity_scene_object_inspectunity_scene_object_editunity_refreshunity_log_readunity_script_run
Tool naming rule:
- all model-visible tool names are lowercase
- only
a-z,0-9, and_are used - do not assume dot-delimited names like
unity.query
This keeps provider compatibility predictable across OpenAI-compatible gateways and other hosts.
PieChat's default agent surface should use one primary tool per concept:
- Files:
read_file,write_file,edit_file,list_dir - Search:
find_files,grep_text - Skills: prompt-indexed files consumed through
read_file,grep_text,find_files, andbash - Unity project/runtime context:
unity_project_inspect - Unity scene reads:
unity_scene_query,unity_scene_object_inspect - Unity scene object edits:
unity_scene_object_edit - Unity script tasks:
unity_script_run - Unity diagnostics:
unity_log_read - Interaction/state:
ask_user_multi,manage_todo_list
Dev RPC no longer exposes duplicate project-file, target/result, skill, or editor convenience tools as primary manifest entries. External helper scripts should use local filesystem access for Unity project files and call unity_refresh when Unity must import changed assets; inside PieChat, prefer the normal file tools with project-root resolution.
unity_scene_object_edit is a scene object edit patch tool, including common component lifecycle/property patches such as add/remove/enable/set public field or property. Reads belong to unity_scene_query / unity_scene_object_inspect; logs belong to unity_log_read; short multi-frame Unity composition belongs to unity_script_run.
Script waits and ctx.frame advance once per Unity frame in Play Mode, and once
per host update outside Play Mode. maxFrames includes time spent waiting in
waitFrames and waitSeconds; it is not a count of completed waits. Cancellation
stops subsequent script edits but preserves changes already applied. Use explicit
ctx.rollback() to remove objects created by the task; it is not a general undo
for changes to pre-existing objects.
Pie Chat renders user messages and assistant replies with headings, paragraphs, bold/italic text, inline code, fenced code blocks, lists/task lists, quotes, dividers, and simple pipe tables. Links show their label and URL; images/HTML and full CommonMark nesting are not supported. Unclosed code fences remain readable while streaming.
Tool results and thinking are collapsed by default. Expand a tool to inspect its arguments and full result. Message Copy preserves the original Markdown; code blocks have their own Copy button. Text wraps to the panel width and uses measured font layout, including Chinese text. This presentation layer is Editor-only and does not alter model output or persisted session content.
Editor session sync restores tool arguments and details directly from the full session messages, independently of the runtime timeline's 50-turn/500-item limit. Search tools use the same effective-argument normalization during live execution and history restoration; the sync payload also retains the original arguments.
For repeatable real-Unity script coverage, see the monorepo's
scripts/fixtures/pie-unity-script-game/README.md (Three Sparks fixture).
It covers Editor, Play Mode, and a standalone macOS IL2CPP Development Player.
ARCHITECTURE.md is the shared architecture source of truth for Pie.
com.pie.agent is responsible for mapping shared Pie semantics into Unity-native product experiences:
- shared commands become Unity buttons, menus, or runtime actions
- shared tool and status semantics become Unity panels, logs, or widgets
- shared interaction requests become Unity dialogs, selections, or custom editor/runtime flows
Unity can and should provide host-specific UX and richer workflows, but it should not redefine shared protocol meaning or push Unity-shaped UI APIs back into the shared layer.
Example:
- Positive: a shared
commandcan be rendered as a toolbar action or menu item in Unity - Negative: Unity-specific dialog or widget APIs should not become the shared cross-host contract
PieChat reload recovery is intentionally interrupt-and-resume, not transparent network-stream resume.
- Before Unity assembly reload, abort the active turn, cancel C# HTTP/file bridge work, close the Dev RPC listener, and wait only for a bounded time.
- After reload, create a fresh bridge and load the interrupted session state. Do not continue the old streaming response or reuse old AppDomain worker tasks.
SESSION_RECOVERY_WAS_STREAMING=truemeans "the previous turn was interrupted by reload"; it does not mean "resume the old HTTP stream".GET /healthmust stay a readiness probe. It may check bounded main-thread responsiveness and request counts, but it must not call into PuerTS/JS or run agent/session code. Calling PuerTS from health previously caused native crash/reload hangs.- During reload, connection refused, transient unavailable, or bounded
main_thread_timeouthealth responses are acceptable. A socket that accepts requests but blocks indefinitely is not acceptable. - PieChat rejects a second
sendwhile a turn is already busy; do not queue overlapping streaming turns unless a real queue is designed. - Future
AgentSessionControllerreuse must go through a Unity adapter. The adapter has to keep Unity's pending-user recovery as the source of truth before delegating to controller persistence, otherwise reload recovery can duplicate the interrupted user message.
Hosts that run their own game/mod JavaScript on the embedded PuerTS VM (as PieRunner and mod hosts such as PieArcade do) should know these four pitfalls. All were observed live while debugging IL2CPP players; only the linker stripping in pitfall 2 is build-specific — the rest are general PuerTS/rendering traps that can bite in the editor too:
- PuerTS has no JavaScript operator overloading.
vectorA - vectorBdoes not throw — it is plain JS coercion and silently producesNaN. In the observed case the NaN flowed intotransform.eulerAnglesand the rotation simply never changed, with no error anywhere. Use the bound static operators (UnityEngine.Vector3.op_Subtraction(a, b)) or component-wise math (new UnityEngine.Vector3(a.x - b.x, ...)), and treat any unexplained frozen transform as a NaN suspect first. - Reflection-called engine overloads are stripped by IL2CPP. Host C#
almost never references every overload of e.g.
Quaternion.LookRotation, so the linker removes the ones only JS calls.Runtime/link.xmlships aUnityEngine.CoreModulepreservation list for the math/helper surface (Vector/Quaternion/Matrix/Mathf/Color/ColorUtility/Rect/Bounds/Random/Time); extend your ownlink.xmlwith the same pattern when your JS reflects over further engine APIs. - Linear color space changes what code-authored colors mean. A
Coloris an untagged numeric value; consumers that expect linear light (shaders, rendering in aPlayerSettings.colorSpace = Linearproject) will render an intended sRGB0.05as ~0.26, washing the whole UI out. Convert with.linearat the boundary where a linear-expecting consumer receives the color — and only there, so consumers that already convert are not double-corrected. Captures had the same class of bug:unity_screenshotPNGs from a linear project carried bytes/metadata that disagreed with the display-referred sRGB a PNG must hold, and framebuffer alpha of 0 made image viewers composite captures onto white — two independent causes of the same washed-out look. The tool now encodes opaque sRGB PNGs in both color spaces. - Player logs are reachable over RPC. When a player build misbehaves,
call
unity_log_read(source: "player"or"active") instead of huntingPlayer.logby hand;contains+tailLinescover the usual triage. For scene captures that must exclude chat/HUD overlay canvases, callunity_screenshotwithincludeUI: false.
See PUERTS-BINDINGS.md for the Standalone xIl2cpp preset, install-time generation, freshness stamp and strict CI build gate. Other player targets are not supported by this new generation pipeline yet.
PieModHost<TApi> runs trusted plain-script mods on a caller-owned JsEnv.
It caches game_init(api), optional game_update(api, dt),
game_onKey(api, key) and game_dispose() delegates. Install guards with
InstallStepGuardsFromResources() and check its result before loading mods.
The bundled pie/mod-guards resource uses the same Acorn loop injector as
unity_script_run; hosts no longer need to ship their own copy.
- Budgets cover top-level evaluation/init (30s), updates (2s), keys (2s) and
cleanup (2s). Hosts can tune the public budget fields and update budget via
PerfTool. These are cooperative limits, not a sandbox or hard VM timeout: native calls, recursion, dynamic code or deliberately caught errors can evade termination. Only use trusted mods; a missing guard installation is reported but does not itself prohibitLoadMod. - Loading unloads the previous hooks; failed initialization also gets best-effort
cleanup. Hook globals are reset between loads, but arbitrary mod globals share
the VM. Use top-level
var, not redeclarablelet/const, for reloadable state. ModFailedfires after teardown, with a phase and error. The caller owns scene/UI cleanup,JsEnv.Tick()and disposal. Unload the mod before disposing its VM.- Telemetry exposes a 600-sample rolling percentile window plus total frame/jank
and budget-abort counters since reset. The first two update ticks execute but
are excluded as warmup; a loading overlay can wait for
IsWarmingUp == false.PerfTool({"reset":true})returns the old snapshot before resetting it. - Agent and mod work share Unity's main thread: budgets detect runaway code, not schedule independent CPU quotas. Batch/ArrayBuffer channels remain unshipped; the dogfood's 600-entity update measured p95 0.12ms without them.
com.pie.agent depends on PuerTS Unity 2.2.2 with the V8 backend:
- Install
com.tencent.puerts.coreversion2.2.2. - Use the PuerTS V8 backend/native plugin for your target platform.
- Do not install or depend on
PuerTS_Agent,Puerts.AI, orcom.tencent.puerts.agent; pie-unity owns the agent host layer. - Do not use remote
https://*.tar.gzdependencies or git tag/path dependencies for PuerTS in release validation.
Recommended manifest.json dependency shape:
{
"scopedRegistries": [
{
"name": "OpenUPM",
"url": "https://package.openupm.com",
"scopes": ["com.tencent.puerts.core"]
}
],
"dependencies": {
"com.tencent.puerts.core": "2.2.2"
}
}The exported com.pie.agent package manifest also declares com.tencent.puerts.core
2.2.2 as a Unity Package Manager dependency.
If PuerTS 2.2.2 or its V8 runtime is missing, PieBridge will not initialize and neither Pie Chat nor PieRunner will work.
After installation:
- Open
Tools > Pie > Pie Chat. - Run
/self-checkand confirm the model, tool route, web search, and file-understanding sections are healthy. - Send a short message and verify a response appears in PieChat.
- Run a Unity scene query through PieChat.
- Trigger an assembly reload and confirm PieChat reconnects without duplicating the interrupted prompt.
Optional local package checks (no Unity Editor or live RPC required):
npm run verify:unity:package-releaseThe shipped runtime bridge is a tracked Unity TextAsset contract, not an ad-hoc local file.
- Canonical runtime asset:
Resources/pie/core.bytes - Required tracked generated artifacts:
core.bytes,core.bytes.meta,typescript-compiler.bytes,typescript-compiler.bytes.meta - Source maps are not shipped or tracked in release artifacts
core.jsis not a supported shipped runtime asset and must not appear in source or exported packages- Run
npm run build:unityafter changingsrc/or the Unity bundle build pipeline - Run
npm run check:unity-bundle-driftto confirm the tracked generated bundle stays stable after rebuild npm run verify:unity:package-releaseis an optional local package check: it builds, checks bundle policy/drift, exports a standalone package, and checks static payload/dependency contracts. It does not launch Unity or resolve UPM dependencies.
unity_script_run is a cooperative step-bounded runner: define export async function run(ctx, args), await frame boundaries and host calls, and expect long synchronous loops to fail with STEP_TIMEOUT after a guard check rather than being arbitrarily preempted.
Sequential awaited host/engine calls also automatically yield delivery of an already computed result through a one-frame gate after a task-local 4ms soft wall-time slice. Actions still dispatch synchronously once; applied edits may precede cancellation of delivery. Slice starts reset only on actual gate release/new task, not each call or gate creation. Native-frame dedup prevents same-frame resume. Explicit wait counts are unchanged, and automatic yields still consume total timeout/maxFrames; hard step checks win before yielding. The slice clock uses Unity Time.realtimeSinceStartupAsDouble (fallback performance.now/clamped Date.now), separate from deadline/ISO clocks. No new request option is exposed.
This is not a global frame-time guarantee or native preemption. Promise.all/unawaited calls retain immediate side effects, synchronous ctx helpers do not auto-yield, rollback's internal destroy loop is not split, and one expensive native call cannot be interrupted. Use sequential awaits and explicit pacing. Catching cancellation/terminal gate rejection cannot authorize further edits; inspect appliedOperations before retrying.
Runtime debugging is zero-registration by default. When a PlayMode/runtime host is alive, discoverability prefers the runtime owner, and unity_script_run exposes ctx.runtime for controlled same-JsEnv inspection and path-based calls such as ctx.runtime.call("IJsEnvironment.Environment.ReloadAllMods", []). Prefer ctx.runtime over assuming raw globals like VX, CS, or puer are part of the public contract.
com.pie.agent includes a runtime behavior-tree capability backed by a vendored source copy of NPBehave. Agents create controlled JSON behavior-tree specs, games write runtime context into typed blackboard entries, and leaf action nodes execute only game-side receivers registered through PieBehaviorTreeActionRegistry.
Manifest namespace:
behavior_tree
Stable tools:
behavior_tree_createbehavior_tree_startbehavior_tree_stopbehavior_tree_destroybehavior_tree_statusbehavior_tree_set_blackboardbehavior_tree_get_blackboard
Use the packaged pie-unity-behavior-tree skill for agent workflow guidance. In PieChat, call these tools through unity_tool_call after inspecting the manifest. For smoke tests, the built-in action receiver pie.debug.record_action records execution in status/log output and updates the tree blackboard key lastDebugAction.
Long-running strategies should use the JSON repeat node with an explicit intervalSeconds, and fast leaf actions can set minIntervalSeconds to prevent accidental action spam. condition.stopsOnChange defaults to none; set immediate_restart only for branches that must interrupt lower-priority work.
Runtime health exposes both runtimeActive and playModeActive. runtimeActive means a Pie runtime owner is registered; it is not the same as Unity Play Mode. Use playModeActive and behaviorTreeActiveCount when validating Play Mode behavior-tree dogfood. External scripts should call through the packaged RPC helper, or implement the same one-shot token refresh on RPC_UNAUTHORIZED after domain reload or Play Mode transitions.
Install through Unity Package Manager:
- Local monorepo development:
Add package from disk...and selectproducts/pie-unity/package.json - Git release repository:
Add package from git URL... - Current public Git URL:
https://github.com/Cydream-Tech/PieUnity.git
After installation, you can import the Extension Demo sample from the Package Manager Samples section.
- Open
Tools > Pie > Pie Chat - Enter your
apiKey,provider,model, and optionalbaseUrl - Start chatting
For CLI or agent-driven Unity control, use the package-contained RPC skill from
the current Unity project's resolved com.pie.agent package. Prefer the copy at
Packages/com.pie.agent/Skills/pie-unity-rpc, or the matching
Library/PackageCache/com.pie.agent*/Skills/pie-unity-rpc entry when Unity
resolves the package into the cache. The CLI no longer ships or installs a
global pie-unity-rpc copy; com.pie.agent is the canonical source.
Then run:
node <resolved-com.pie.agent>/Skills/pie-unity-rpc/pie-unity-rpc.js health --project "/abs/path/to/project"
node <resolved-com.pie.agent>/Skills/pie-unity-rpc/pie-unity-rpc.js manifest --project "/abs/path/to/project" --namespace unity
node <resolved-com.pie.agent>/Skills/pie-unity-rpc/pie-unity-rpc.js tool --tool chat_send --data '{"text":"respond with exactly pong"}'
node <resolved-com.pie.agent>/Skills/pie-unity-rpc/pie-unity-rpc.js tool --tool chat_get_stateAgents should inspect /manifest first and choose a host by the capability
needed for the user's task. When a runtime host is alive, pie-unity exposes the
runtime owner as the primary discoverable instance so runtime debugging does not
get preempted by editor heartbeats. Prefer project namespaces such as voxmod
over generic Unity editing tools when they are available, but zero-registration
runtime inspection and calls are available through unity_script_run via
ctx.runtime. Do not edit
prefab YAML or serialized object references directly when an RPC tool exists.
Available local commands include:
/new/resume <sessionId>/tree/forkor/fork <sessionId>/self-check/session-check/model/skills/skills reload/memory/init
- Add
PieRunnerto a GameObject - Optionally assign
Settings Override - Call
SetConfig(...) - Call
SendChatMessage(...) - Listen to
OnAssistantMessage,OnAssistantDelta,OnToolStart,OnToolEnd, andOnError
Runtime chat UIs can use the additive structured API without parsing those legacy string events:
runner.OnTimelineItemChanged += item => Debug.Log($"{item.kind}: {item.summary}");
runner.OnSessionChanged += snapshot => Debug.Log($"Session: {snapshot.id}");
runner.OnStatusChanged += status => Debug.Log(status.phase);
runner.OnTurnFinished += result => Debug.Log(result.outcome);
runner.OnRuntimeError += error => Debug.LogError($"{error.code}: {error.message}");
runner.SendChatMessage(new Pie.PieChatRequest
{
content = "Full model-facing runtime contract and prompt",
displayContent = "Create a voxel item",
clientTurnId = "operation-42",
});
// Restores the persisted messages and emits a complete PieSessionSnapshot.
runner.ResumeSession("sess_xxx");displayContent is persisted with the user message and is used for the
player-facing timeline and session title. content remains the model input.
The send_message wire action rejects an overlapping accepted turn with
SESSION_TURN_ACTIVE before starting another one; terminal finalization is
fenced to the accepted clientTurnId. Use an explicit host queue until a
public queue/steering contract exists.
One session snapshot contains at most the latest 50 turns / 500 timeline
items; thinking blocks are never projected into the runtime timeline.
ResumeSession / NewSession return dispatch success, not binding completion.
For per-mod/workspace binding, subscribe to OnSessionBindingResult and pass a
fresh request id via ResumeSession(id, false, requestId) or NewSession(requestId).
Only a matching successful result authorizes saving its sessionId or sending on
that binding; an arbitrary OnSessionChanged snapshot does not. The monorepo's
docs/maintenance/unity-bridge.md#correlated-runtime-session-binding documents
result fields, supersession and retry semantics. Legacy overloads remain supported.
Example:
using System.IO;
using UnityEngine;
public class Demo : MonoBehaviour
{
[SerializeField] private Pie.PieRunner runner;
private void Start()
{
runner.Reinitialize(Path.Combine(Application.persistentDataPath, "PieWorkspace"));
runner.SetConfig("YOUR_API_KEY", "openai", "gpt-4.1-mini", "https://api.openai.com/v1");
runner.OnAssistantMessage += msg => Debug.Log("AI: " + msg);
runner.SendChatMessage("Create a cube at (0,1,0).");
}
}Runtime voice input is exposed as a static Begin/End API. It does not require adding a voice component to the scene:
For macOS Player builds, set Player Settings > Other Settings > Microphone Usage Description to your application's explanation of microphone use. Pie preserves its runtime assembly for PuerTS/IL2CPP, which also retains the voice API; Unity requires this description even when the build's scene does not start a recording.
Pie.PieVoice.RequestMicrophonePermission(granted =>
{
if (!granted) return;
var recording = Pie.PieVoice.BeginRecording(new Pie.PieVoiceRequest
{
VirtualKey = voiceVirtualKey,
Mode = Pie.PieVoiceMode.Structure,
ContextHint = Pie.PieVoiceContextHint.Task,
Tone = Pie.PieVoiceTone.Concise,
}, result =>
{
if (result.Ok)
Pie.PieRunner.ActiveRunner?.SendChatMessage(result.PolishedText);
});
// Call recording.End() when the player releases the talk button.
});Runtime TypeScript generation is supported through a lazy compiler bundle. The first version is intentionally single-file: it transpiles TypeScript to JavaScript/MJS without Node.js, npm resolution, or runtime imports.
var result = Pie.PieTypeScript.CompileFile(new Pie.PieTypeScriptCompileRequest
{
SourcePath = Path.Combine(Application.dataPath, "Pie/Generated/demo.ts"),
OutputPath = Path.Combine(Application.dataPath, "Pie/Extensions/demo.mjs"),
Module = Pie.PieTypeScriptModule.ESNext,
Target = Pie.PieTypeScriptTarget.ES2022,
});
if (!result.Ok)
Debug.LogError(result.ErrorMessage);Files under configured Pie extension paths may use .js, .mjs, or .ts.
TypeScript extensions are compiled in memory before activation. Runtime imports
such as import lodash from "lodash" are rejected; generate a single-file
module or inline dependencies for now.
pie-unity now reads models only from ~/.pie/models.json. The file must use top-level profiles, not providers, and each profile must explicitly declare:
apibaseUrlapiKeyorapiKeyEnvmodels
Responses profiles may use api: "openai-responses" with optional profile/model
transport: "sse" | "websocket" | "auto" (default SSE). Enable
compat.responsesContinuation: true for verified same-connection WS continuation.
Model transport overrides the profile. Editor and native Player both use
PieWebSocketBridge / UnityWebSocketClient; WebGL is unsupported. Runtime
pause and bridge disposal cancel native connections. Unknown native handshake
failures are surfaced because ClientWebSocket does not reliably expose an HTTP
status for safe auto fallback. The monorepo's
docs/maintenance/responses-transport.md records tested platforms, isolated
smoke commands and live-test limitations.
If no valid profile is configured, Unity starts in an unconfigured model state and asks you to configure ~/.pie/models.json before sending messages.
Android runtime discovery and Pie logs are also stored below Application.persistentDataPath; Android's UserProfile location is not assumed to be writable. For Player builds, the package's Editor linker processor explicitly supplies Runtime/link.xml to UnityLinker, preserving the PuerTS-visible bridge surface that embedded JavaScript calls through CS.Pie.* on IL2CPP.
Use Pie's PuerTS xIl2cpp binding workflow, shipped with the package. Generate full-wrapper bindings through Tools > Pie > PuerTS Bindings > Generate (xIl2cpp, full wrapper), or explicitly in batch mode:
Unity -batchmode -projectPath <project> -buildTarget StandaloneOSX \
-executeMethod Pie.Editor.PiePuerTSBindingGenerator.GenerateAllBatch -quitUse the same active Standalone target for generation and the separate Player
build. Exit 0 means ready; exit 2 requires a new Unity process to compile and
repeat generation; exit 1 is failure. Strict CI (PIE_PUERTS_GEN_STRICT=1)
never silently regenerates stale bindings. Pie manages
PUERTS_DISABLE_IL2CPP_OPTIMIZATION; do not set it manually or substitute raw
PuerTS generator commands, which do not produce Pie's validated stamp.
The productized IL2CPP pipeline currently supports active Standalone targets
(macOS validated; Windows/Linux need platform validation). iOS, WebGL and other
non-Standalone IL2CPP targets are unsupported; do not infer support from the
upstream PuerTS defaults. For the isolated PieSmoke player, explicitly run
Pie.Editor.PiePlayerSmokeBuild.PrepareSmokeBindingsBatch before building; see
the Smoke workflow.
Project assets:
Assets/Pie/PieSettings.assetAssets/Pie/SkillsAssets/Pie/ExtensionsAGENTS.md
Runtime state:
Application.persistentDataPath/Pie/sessionsApplication.persistentDataPath/Pie/...
Imported UPM samples are only staging content. If Unity imports a sample into Assets/Samples/..., the actual working files for pie-unity should still end up under Assets/Pie/....
Create a settings asset from:
Tools > Pie > Create Pie Settings Asset
PieSettings currently exposes:
Extension Search PathsSkill Search PathsFile Tool RootsDefault File Tool Root EditorDefault File Tool Root Runtime
File tool roots let you expose named filesystem roots to read_file, write_file, edit_file, list_dir, grep_text, and find_files.
Built-in roots:
persistent: maps toApplication.persistentDataPathproject: maps to the Unity project root and is Editor-only
Each configured root can define:
NameDescriptionRelative Path From Project RootorAbsolute PathAvailable In EditorAvailable In RuntimeDefault Prefixes
Paths are scanned in order, and later paths override earlier items with the same skill or extension name.
Connection settings:
apiKeyauthenticates with the selected providerproviderselects the built-in API familymodelselects the model IDbaseUrlis optional; when set, it overrides the built-in provider endpoint
When changing config through pie_chat.set_config from the CLI helper, the helper waits briefly for the Unity-side bridge to apply the new settings before returning.
Default project skill directory:
Assets/Pie/Skills
Supported layouts:
Assets/Pie/Skills/<skill-name>/SKILL.mdAssets/Pie/Skills/<skill-name>.md
Skills are listed in the system prompt with ordinary entry and baseDir paths. Read the entry with read_file; resolve skill-local relative paths against baseDir and use ordinary file or bash tools.
Player builds bake skills into Assets/Resources/Pie/SkillManifest.asset at
build time (pie-delivery: frontmatter selects all / player-only /
editor-only). The runtime inlines the SKILL.md body into the system prompt
and, when the skill directory carries companion files (scripts,
references — caps: 32 files, 96KB each, 512KB per skill), serves them as
virtual files under pie-skill/<name>/<relative-path>; read_file and the
other file tools resolve those paths normally.
Skills can also be installed at runtime into the player-writable layer
(<persistentDataPath>/.agents/skills/<name>/SKILL.md) and picked up with
reload_skills — via the dev-RPC tools skills_install /
skills_reload (tool kind: pie-unity-rpc.js tool --tool skills_install --data '{"name":...,"content":...}'), or CS.Pie.PieRunner.ActiveRunner.InstallSkill
from C#. These register only when a runtime host (PieRunner) is present.
Filesystem entries win over baked manifest entries with the same name, so a
hot-installed skill overrides the baked one.
Lint skills before shipping them to a build:
node products/pie-unity/Skills/pie-unity-rpc/pie-unity-rpc.js skill-lint \
--file Assets/Pie/Skills/my-skill/SKILL.md # static checks
node products/pie-unity/Skills/pie-unity-rpc/pie-unity-rpc.js skill-lint \
--file Assets/Pie/Skills/my-skill/SKILL.md --probe --project . # + live runtime path checkThe baker also warns (build log) when a skill calls runtime.call without
the required CS. prefix.
Player builds resolve their model endpoint, left to right by priority:
SetConfig(apiKey, provider, model, baseUrl)on the active runner — C# method; call it from your own bootstrap/UI code (in the editor thepie_chat.set_configRPC reaches the same path). Configuration pushed this way lives for the process lifetime.~/.pie/models.jsonon the machine running the player — the same profiles the CLI uses (profileswithbaseUrl/apiKey/models).
Distribution stays open by design: decide yourself whether shipped players
embed a gateway URL/key via SetConfig at startup or read the local
models.json.
Default project extension directory:
Assets/Pie/Extensions
Each extension is a *.js file and should export a module function. Use either:
module.exports = (api) => { ... }export default function (api) { ... }
The api object is a Unity host binding over @pie/agent-framework's runtime extension contract. It includes the shared runtime fields:
manifestdirconfiglog()registerTool()registerBeforeToolCall()registerAfterToolCall()registerOnError()appendSystemPrompt()callHost()
Unity also adds:
projectRootisEditorhost.listCapabilities(namespace?)host.call(namespace, name, args?)
Minimal example:
module.exports = (api) => {
api.appendSystemPrompt("Use the hello_world tool for greeting demos.");
api.registerTool({
name: "hello_world",
label: "hello_world",
description: "Return a greeting string.",
parameters: {
type: "object",
properties: {},
},
async execute() {
return {
content: [{ type: "text", text: "Hello from project extension." }],
details: {},
};
},
});
};After opening Tools > Pie > Pie Chat, run these local commands in order:
/self-check/skills/extensions/extensions reload/session-check
Then send two real prompts:
演示一下 ask 工具Expected: Unity should show an interaction UI instead of appearing to hang.演示一下 todo 工具,并持续执行直到 todo 结束Expected: todo state should progress and clear when complete.
For repeatable regression steps and reliability runs, see:
- TESTING.md (included in standalone package exports)
Project context:
- Stored at the Unity project root as
AGENTS.md - Shared with the Unity project
- Intended for project conventions and persistent guidance
/initcreates or replaces the rootAGENTS.mdand immediately refreshes the active PieChat system prompt/memoryreads the rootAGENTS.md
Session and runtime state:
- Stored under
Application.persistentDataPath/Pie - Intended for local session history and runtime-owned state
Migration note:
read_project_memoryandwrite_project_memoryhave been removed.- Use
/init,/memory, or normal file tools such asread_fileandwrite_fileagainstAGENTS.md.
pie-unity is developed inside the pie monorepo and exported to a standalone Git repository for distribution.
From the monorepo root:
npm run export:pie-unity -- \
--target /absolute/path/to/pie-unity-release \
--repo-url https://github.com/Cydream-Tech/PieUnity.gitThis export flow:
- rebuilds
Resources/pie/core.bytes - copies only the distributable Unity package payload
- rewrites
package.jsonfor standalone distribution
pie-unity is distributed under Apache-2.0.