# Delegation contract (minimum stable Pi 1.5.2)

Reference for operators and extension/UI consumers. Authoritative types and execution registry: [`src/coordinator.ts`](../src/coordinator.ts); profiles/grants: [`src/profiles.ts`](../src/profiles.ts); finite policy: [`src/scheduling.ts`](../src/scheduling.ts); public model/auth adapter: [`src/model-adapter.ts`](../src/model-adapter.ts); model tool/lifecycle: [`src/pi-extended-subagents.ts`](../src/pi-extended-subagents.ts). The native interfaces remain supported alongside the bounded orchestration compatibility surface below; this is not full OMP/pi-subagents API parity.

## Supported host

Delegated execution requires **stable Pi 1.5.2 or newer**, checked through the SDK's public
`VERSION` export. Newer stable patch, minor and major versions are allowed; standard build
metadata has equal precedence. **Tested host: Pi 1.5.2**, independently of the minimum;
allowing future stable versions is not a tested compatibility guarantee.
Below-minimum, prerelease, malformed and unknown versions fail closed before launch/preflight
content reads, child identity allocation, task-directory setup or child model requests.
This covers `delegate_task`, compatibility `subagent` launches, resume, followup, revive,
steering/model-delivering messages and direct coordinator execution. Public tool arguments
and results are unchanged; unsupported execution errors name the detected version and minimum stable version.

Registration is not rejected wholesale: root status, stop, cleanup and diagnostics remain
available where the host can load the extension. This does not promise module-load
compatibility with arbitrary ancient SDKs. Required `"*"` host peer ranges follow Pi's
packaging contract, not the execution support window; development SDK packages remain pinned.
This restriction is a breaking change in 0.2.0. To migrate from 0.1.x, let active tasks drain,
install stable Pi 1.5.2 or newer, update the extension to `@howarewoo/pi-subagents@0.2.0`
and restart Pi.

## Native orchestration compatibility

The model-only `subagent` tool is registered and described through Pi's public `promptSnippet`/`promptGuidelines` at startup. These describe native worker delegation to the model without requiring any external workflow package or provider-specific user reminder. With an explicit tool allowlist, include `subagent`. Load only one provider of that name: Pi uses the first registered extension, and this package does not disable, reprioritize or uninstall other providers.

- Launch: `{agent,task,cwd?,model?,limits?,async?}`. `agent` is required: `general`, `scout`, `review`, `implementation`, or explicit compatibility name `worker` for the configured `implementation` profile. Unknown/custom agent definitions are rejected, not silently mapped. Authority and prompts still resolve through the existing profiles; no ambient Markdown agent loading or role/model fallback is added.
- Batch: `{tasks:[{agent,task,cwd?,model?,limits?}],cwd?,async?}`. Shared cwd is the default for items lacking one. Existing whole-batch preflight, FIFO limits, ordered outcomes and global writer serialization apply.
- `async` defaults true; false waits for final results. Accepted calls return immediately with exact `id:"UUID@generation"`, `taskId` (original UUID), generation and normal snapshot fields/artifact references. Control handles never prefix-match or resolve to the latest generation. Native `delegate_task` retains its UUID+generation output and foreground default.
- Read/control: `{action:"agents"}` or `"list"`; `{action:"status"|"stop",id}`; `{action:"steer"|"resume",id,message}`. Status can inspect a historical generation; mutations refuse stale handles. Resume continues a drained paused assignment in the same SDK conversation and generation. For completed idle/parked presence it still starts a fresh generation in the original workspace, requiring current narrowing checks; include needed prior conclusions for that fresh assignment. Native coordination remains available for richer inbox/wait/park behavior.
- Model overrides accept the native exact selection object or `provider/id[:effort]`. Omitted effort uses the parent's selected effort; overrides still require exact operator grants, availability/auth/scope and supported effort. Bare model IDs and unknown providers are not guessed. Omitting model retains ordinary parent/profile routing.

An async compatibility launch sends one window-outcome custom message per single/batch call after actual drain (paused or completed), using public `sendMessage(...,{triggerTurn:true,deliverAs:"followUp"})` to wake the parent. Compatibility resume does the same once for its new execution window or fresh completed-agent generation. This may cause a parent model turn and provider charges; no polling or detached worker survives parent shutdown. The original native tool's notification-only behavior is unchanged. Neither a notification nor a receipt proves the model understood the outcome.

The coordinator owns workspace allocation, exclusive writer handoff, Git/PR publication and retirement under the active workflow. Supply a complete task and accessible skill/reference paths: general inherits available skills and restricted reviewed leaf capabilities as specified below; workers never inherit parent instructions/history or context files. No scripts/workflow engine, supervisor RPC, automatic worktree lifecycle, external CLI worker, retained-run migration or blanket competitor API parity is promised.

## Explicit working directory

Native task items and compatibility calls accept optional `cwd`, an existing directory relative to the root parent cwd or absolute. Admission canonicalizes it before allocating any identity or session. Missing/non-directory paths reject the whole batch. Nested tasks default to their immediate parent's workspace; renewal retains the exact original workspace and cannot switch it. Snapshots expose `cwd`, and the child SDK/session prompt and tools use that directory. No process-global cwd is changed.

The public root dispatcher has no per-call cwd override. For a different workspace, [`workspaceArguments`](../src/workspace.ts) resolves builtin file paths against the child directory (including absent search/list paths), and prefixes bash with a quoted `cd -- <cwd> || exit $?` before the original command. A failed cd never executes the command in the root. Root validation, callable builtin provenance checks, permission/result hooks, cancellation and nested-call IDs remain active. Hooks see the actual absolute paths or prefixed command, and their context cwd remains the root parent's. A path approval based on the old relative spelling is not equivalent and may reject; the extension never bypasses that decision.

The adapter rejects `~`/`@` shorthand and nonbreaking-space path spellings when rebinding, rather than silently reproducing private builtin path normalization. Ordinary absolute paths and `..` retain normal filesystem meaning through the parent hooks; there is no path containment or malicious symlink-race protection. This is execution in an explicit workspace, not an OS sandbox, copied filesystem, managed worktree or apply/conflict protocol. The single global model-tool writer lease remains unchanged even across separate worktrees. Manual/external writers remain uncoordinated.

Model/auth, role/skill grants, scheduling and trusted-project policy still resolve in the original parent context. Selecting another cwd does not transfer project trust, load its `.pi/delegation.json`, activate tools or grant resources. Task files are checked relative to the selected workspace, but their content still comes only through the root read dispatcher.

## Input and atomic preflight

`delegate_task` is **model-only**, both on the root and on spawn-enabled children. Native input accepts either:

```json
{ "assignment": "Locate startup code", "profile": "scout", "background": false }
```

or an ordered batch:

```json
{
  "tasks": [
    { "assignment": "Locate startup code", "profile": "scout" },
    { "assignment": "Review lifecycle tests", "profile": "review", "limits": { "requests": 20 } }
  ],
  "background": true
}
```

Exactly one shape is allowed; additional properties are rejected. Assignments must contain non-whitespace text. Each assignment accepts optional exact `model` selection and `models` allowlist narrowing ([advanced policy contract](advanced-policies.md)), plus `spawn` (role allowlist, including `[]` to disable), `coordination` (selected actions, including `[]` to disable) and `limits` (`deadlineMs`, `requests`, `tools`, `depth`, `spawns`), each of which can only **narrow** the effective grants/limits. `background` belongs to the call, not individual batch items. Nested background calls are explicitly unsupported; use nested foreground.

Every profile/skill/tool/ancestor/limit/batch/queue check for the **whole batch** completes before any new registry identity, setup directory or SDK child is created. Invalid later entries cannot leave earlier launches. Inputs capture the parent physical model/effort by default, or an explicitly granted exact selection; scope and auth registry remain parent-owned; optional cwd follows the explicit-workspace contract above. All active source/target model availability, configured-auth, scope and effort checks precede admission. Authentication/provider setup failures after admission are per-assignment failures, not rollback of other accepted work. No launch from incomplete/streamed JSON or speculation exists. Checked file text is resolved before admission (below).

## Checked task-file input

Each item must supply **exactly one** of `assignment` or `taskFile`. The same alternative works for single, batch and authorized nested foreground tasks:

```json
{
  "taskFile": "tasks/review.txt",
  "profile": "review",
  "resultSchema": {
    "type": "object",
    "properties": { "summary": { "type": "string" } },
    "required": ["summary"],
    "additionalProperties": false
  }
}
```

`taskFile` is an explicit path relative to the selected **task workspace cwd** (root parent cwd by default), not the session/transcript directory. Absolute paths, traversal, hidden path components and `node_modules` components are rejected. The denied-component rule applies to both caller spelling and the resolved workspace-relative target before any content dispatch; an ordinary-looking symlink cannot alias hidden or `node_modules` resources. Canonical paths must stay inside that workspace; escaping symlinks fail. Nonescaping aliases to otherwise allowed ordinary files remain supported. Metadata checks require a regular file with 1–50000 bytes. The effective parent must expose genuine callable builtin `read`; the extension obtains **all content through `ctx.executeTool("read",...)`**, preserving validation, permission denials and result hooks. Node is used only for path/metadata checks. No raw filesystem content read, ambient discovery or credential/ignored-tree copying occurs. Operators should explicitly select reviewed task-text files, never secrets.

Only complete single-block text is accepted: native read truncation (including the 2000-line limit), images, empty/whitespace text, NUL, replacement characters and oversized content fail. Consequently valid UTF-8 containing a literal replacement character is conservatively rejected too. Files are instructions as literal **task text**; embedded JSON/frontmatter/tool names never grant profiles, tools, spawn roles, schemas or limits. Nested file reads require both the admitted and currently resolved read grants of the initiator and every ancestor, independently of spawn authorization, and consume the initiator's executable-tool budget in addition to the local delegation invocation. Rejected preflight may have performed permission-checked reads, but creates **no new identities/children** and consumes no spawn quota.

Preflight reads are awaited sequentially so a later failure cannot detach an earlier permission hook. Canonical path and identity/size/mtime/ctime are checked before/after reading. The accepted text is an in-memory admission snapshot: subsequent edits do not update queued tasks. These checks detect ordinary changes, not malicious filesystem races; public read has no atomic no-follow handle/content-identity interface. Trusted operator hooks may transform the read/result. This is not a sandbox or a freshness guarantee against concurrent hostile processes. Do not edit/swap task inputs during preflight.

## Required structured results

`resultSchema` is optional **per assignment**, never authority. Schema-free tasks retain ordinary final text/status/error behavior; no prose is parsed into a purported validated value. When supplied, the child receives the model-only native `submit_result` tool with input `{"value": <actual JSON value>}`. It is child-local, not registered on the parent or callable through the root dispatcher. The tool uses public `@earendil-works/pi-schema` **strict, non-coercing** validation of the value (`"42"` is not an integer). Native tool input validates only the envelope and cannot coerce the inner value towards the requested schema.

Supported finite JSON Schema subset (2020-12 semantics): boolean schemas; `type` with one of `object|array|string|number|integer|boolean|null`; `properties`, `required` (distinct strings), `items`, `additionalProperties` (boolean/schema), `const`, nonempty `enum`, `title`, `description`; finite numeric `minimum`, `maximum`, `exclusiveMinimum`, `exclusiveMaximum`, positive `multipleOf`; nonnegative integer `minLength`, `maxLength`, `minItems`, `maxItems`, `minProperties`, `maxProperties`. Schemas are limited to 16384 serialized characters, 256 schema nodes and depth 16. Unknown keywords, malformed keyword values, references of **any** kind, regexes/patterns, formats, compositions, dynamic/executable schemas and non-JSON type extensions fail before any batch identity/setup. No network reference resolution or arbitrary code evaluation is used. The subset guard only checks supported schema shape; native Pi validation owns value semantics.

Invalid submission returns actionable JSON-Pointer errors (up to 32) and permits correction inside existing request/tool/deadline limits. It does not force retries or give extra budget. The first valid submission is immutable; duplicate submissions are errors and cannot replace it. Submission itself is counted like other executable child tools. The model normally continues to a final assistant response; this tool does not terminate the run or bypass remaining work.

`ChildSnapshot.outcome? = {validation:"awaiting"|"invalid"|"submitted",attempts,errors,value?,finalized}` separates submission from ordinary `result`, `status` and `error`. Attempts count native value-validation attempts, not malformed envelopes or rejected duplicates. `submitted` means the value passed validation, **not task success**. Until prompt/tools/descendants drain, `finalized:false` and no final artifact reference exists. After disposal `finalized:true` means the protocol is closed, including missing/invalid/cancelled/setup-failed cases. A successful schema-required task requires terminal `status:"settled"`, `disposed:true`, `outcome.validation:"submitted"`, and successful artifact persistence. A missing/invalid submission at normal prompt completion fails with a missing-required-result error. Provider failures, cancellation, limits and shutdown take priority over submission; retained submitted data on a failed/cancelled snapshot is evidence, never successful output. A captured child closure checks its current registry entry/lifetime before acceptance; no UUID/generation-targetable submission API exists. Each renewed generation starts with fresh submission state.

## Full output artifacts and previews

New task and successor-generation directories are created exclusively with POSIX mode `0700`, as are their extension-owned `agent/` and `transcripts/` directories. The SDK manages transcript files inside that private directory. Existing records/files, parent session directories and global storage permissions are not changed; Windows privacy depends on filesystem ACLs rather than these POSIX mode bits (see [security policy](../SECURITY.md)). After prompt resolution and descendant/tool drain, the registry enters `draining` while it publishes fixed-name artifacts alongside `agent/` and `transcripts/`:

- `result.txt`: exact concatenation (no added separators or trimming) of text blocks in the **last assistant message**, including an empty result for a tool-only/aborted last message. It is not the entire conversation. Existing `result` still uses Pi's final-assistant-text helper and retains its historical whitespace trimming/fallback semantics.
- `structured.json`: JSON serialization of the first accepted structured value, if any; it is not reconstructed from prose or a schema. Read it as JSON to recover the exact submitted value, not original wire formatting.

`artifacts? = {text?:absolutePath,structured?:absolutePath,error?:string}` references only fully written files. Temporary files use exclusive creation and mode 0600; hard-link publication is atomic and refuses existing destinations, then removes its temporary name. Fixed filenames are runtime-checked, never model-selected. No overwrite, old-record reuse, migration, generalized backend, logging/upload or provider/credential-object dump is added. Persistence errors are explicit, cannot yield successful full-artifact claims and fail an otherwise successful task; earlier provider/limit reasons remain primary. A successfully written text artifact can remain available if structured persistence fails. Setup/queued cancellation has no final artifacts; failed/cancelled prompt completion may retain output as evidence. Availability is indicated by references, not inferred from path existence or `submitted` state. Atomic publication is not an fsync/crash-durability promise; shared-checkout tools/external processes can still tamper with files outside this protocol's guarantees.

Model-facing previews clip large text fields and structured JSON to 2048 characters with explicit truncation labels, then apply native 50KB/2000-line truncation. An ordered references header precedes output bodies so controls/artifact/transcript locations are discoverable even when prose is long. Extreme batches/path lengths can still require `/subagent-status <id> <generation>`; tool `details` retain full detached snapshots, values, final text and each batch identity. Background still sends only one final completion message per call. These files can contain sensitive task/model/tool output and need the same handling as SDK transcripts.

## Subagent Profiles and explicit spawn grants

`Profile = {name,description,prompt,tools,skills,leafTools?,spawn?,coordination?}`; omitted spawn is empty. `BuiltinTool` is `read|grep|find|ls|write|edit|bash`. Bundled `scout` and `review` have ceilings of the first four; `general` and `implementation` have ceilings of all seven. These are maximum allowlists, not required tool loadouts. Central admission intersects each role's effective operator/project/request ceiling with **live `ExtensionToolContext.tools`**, then checks genuine builtin provenance before writer, ancestry and renewal checks. Registered-but-inactive direct builtins are not callable rights and are never auto-activated. Under Pi's ordinary default `read,bash,edit,write` loadout, scouts receive only `read`; general and implementation receive `read,write,edit,bash` (role order). `ChildSnapshot.tools` records the admitted effective **builtin** set; owned schema submission, explicit coordination and nested delegation capabilities remain separately granted as before. An empty effective builtin set rejects the entire batch before identity/setup, even when owned tools exist. A profile narrowed to read-only builtins with no admitted leaf tools needs no writer lease. All four bundled profiles default to **`scout` and `review` spawn targets only**, bounded by total absolute depth two (root 0, child 1, grandchild 2). These grants do not bypass callable-builtin intersection, permission hooks, ancestor subset checks, spawn budgets or immediate nested-capacity admission.

`resolveProfile(name,cwd,trusted,operatorDir?,availableSkills?)` resolves bundled → operator → trusted project. Operator config is public `getAgentDir()/delegation.json`; trusted project config is `CONFIG_DIR_NAME/delegation.json`. Root keys are the four bundled profile names and optional `scheduling`. Profile keys: `description`, `prompt`, `tools`, `skills`, `leafTools`, `spawn`, `coordination`. Absent arrays inherit; explicit `[]` removes grants. Operator tools can only narrow bundled tools. Operator skills grant exact absolute readable, distinct Pi skill Markdown files. Only the operator can add spawn role grants beyond the bundled `scout`/`review` defaults. For example, this intentionally lower-concurrency configuration adds general-to-general nesting:

```json
{
  "scheduling": { "concurrency": 2, "depth": 2, "spawns": 4 },
  "general": { "tools": ["read", "grep", "find", "ls"], "spawn": ["general"] }
}
```

This read-only narrowing permits a general child to delegate a general grandchild if one additional slot is available. It does not enable arbitrary tools, profiles, models or ambient resources. A trusted project may only narrow operator tools, leaf tools, skills, spawn roles and policy maximums; it cannot re-enable anything removed. Untrusted project config is ignored. Project prompt/description specialization never expands executable authority. Unknown fields, malformed selected profiles, missing skills and loader diagnostics fail visibly. Custom role names remain unsupported. General supports only the restricted leaf grants below. Exact model/effort and builtin one-way prewalk policy are specified in the [advanced policy ledger](advanced-policies.md).

Descendant preflight requires an active, non-cancelling ancestor, an explicitly granted target role, allowed absolute depth and remaining direct-child spawn count. Target builtin tools, reviewed leaf tools, skills **and spawn role grants** must all be subsets of the delegating ancestor's effective grants; incompatible profiles reject rather than regain root authority. Request narrowing is inherited. Before nested task-file content dispatch and again before child admission, current operator/trusted-project spawn grants and capability ceilings are checked for the initiator and every ancestor, in addition to their original admitted ceilings. Revoked read or spawn authority denies before the task-file content reaches the root read dispatcher; malformed policy fails closed. Each child's admitted direct-child count is lifetime cumulative (including cancelled/failed children); rejected batches consume no spawn count.

## Capacity and bounded execution

`SchedulingPolicy` values are non-negative safe integers, except concurrency/batch/deadline/requests/tools must be positive. There is no unlimited sentinel.

| Field         | Default | Operator ceiling | Meaning                                                                                               |
| ------------- | ------: | ---------------: | ----------------------------------------------------------------------------------------------------- |
| `concurrency` |      16 |               16 | Active SDK children, including setup, waiting nested parents and draining                             |
| `queue`       |      32 |              256 | Waiting assignments, accounting for writer/FIFO blocking as well as capacity                          |
| `batch`       |      16 |               64 | Assignments admitted by one call                                                                      |
| `depth`       |       2 |                4 | Maximum absolute descendant depth (root = 0, child = 1, grandchild = 2); 0/1 disables nesting         |
| `spawns`      |       8 |               32 | Admitted direct children per child lifetime; 0 disables spawning                                      |
| `deadlineMs`  | 1800000 |          3600000 | Per-window elapsed time from admission/resume, including queue/setup/work drain; paused time excluded |
| `requests`    |     100 |             1000 | Direct child dispatches into the parent model registry                                                |
| `tools`       |     100 |             1000 | Executable child tool invocations, including local delegation                                         |

Operator `scheduling` can choose within ceilings; trusted project can only narrow operator values. Request `limits` can only narrow execution/depth/spawn values, not raise concurrency or queue capacity. Policy is resolved at invocation; changes affect subsequent admission/dequeue, not already running work or previously captured task limits. Reducing concurrency does not evict already admitted active children; no new setup starts until they drain below the new maximum.

One FIFO scheduler lives **inside the authoritative registry**, not in a second execution-state service. Identity allocation/order follows input order. Queue admission/dequeue is synchronous, before asynchronous SDK setup. A head writer waiting for its lease blocks later readers too: this conservative head-of-line policy prevents writer starvation. Parent model mutations already reserved at tool preflight reject a writer-containing batch before admission; otherwise sibling writers queue/serialize. Setup, running, nested waiting, pausing and draining each retain a permit until actual drain; only then may pause or disposal release it.

### Nested allocation: no lending and no overcommit

A parent waiting in its child-local `delegate_task` retains its permit. Nested batches are admitted **only if the entire batch can dispatch immediately**, with no older queued assignment, sufficient free slots, and all required writer leases available. Otherwise they fail visibly before descendant setup (`Nested capacity unavailable; no permit lending`). This also prevents nested siblings waiting on a full pool. There is no borrowed capacity, hidden fanout or simulated completion. A writer descendant under any writer ancestor is rejected explicitly, even if capacity is free. Use a non-writing coordinator role or independent root batch instead. Child-local model-only tools are registered through public SDK `customTools`; they are not calls to the root's model-only tool via `ctx.executeTool`.

### Limit outcomes and cancellation

Deadlines pause rather than cancel. `queued|setup|running → pausing → paused` stops new dispatch, aborts SDK work and waits for actual subtree drain before releasing capacity and writer leases. Snapshots expose `pauseReason` (`deadline` or `ancestor deadline`), `retainedContext`, `window`, cumulative request/tool/spawn counters, and unfinalized structured submission state. Paused is neither success nor failure; no final outcome artifacts are published. A queued assignment can pause before any session/transcript exists.

Root-only `delegate_coordinate` `{action:"resume",target:{id,generation},assignment:"Continue…"}` or `subagent` `{action:"resume",id:"UUID@generation",message:"Continue…"}` appends that message to the retained SDK conversation. UUID, generation, assignment, workspace, selected model/prewalk state, role instructions, all prior SDK messages/tool calls/results and structured submission state remain intact. Each explicit resume starts a fresh configured deadline window (still bounded by originally admitted and current limits). Cumulative requests/tools/spawns are **not** replenished. A pre-session queued pause starts the original assignment followed by the continuation when dispatched. No automatic replay, undo, process freezing, restart or cross-parent recovery is provided.

An ancestor deadline marks its entire active subtree before draining any member. Resume the exact original parent generation first, then explicitly resume selected children; resuming a parent does not resume descendants. Existing no-permit-lending, writer ancestry and admission rules still apply. Parent completion/stop retires its children, including paused sessions.

Resume rechecks operator/trusted-project limits and grants, ancestor spawn/capability ceilings, model availability/authentication, callable tools/provenance and writer/capacity admission. It never broadens original admission. A revoked retained grant rejects rather than rebuilding the SDK tool/resource graph; restore the grant or stop and launch a newly authorized identity. Policy/auth/capacity failure leaves the paused conversation intact with an actionable tool error; there is no fallback model or replacement agent. Concurrent duplicate resumes reject atomically. Stop/shutdown wins a concurrent pending resume and disposes retained sessions.

Requests/tools remain hard cancellation guards. The first committed hard-limit reason (`requests` or `tools`) is retained and produces final `status:"cancelled"`, `limitReason`, and explanatory `error`. Stop before setup cancels without SDK setup/transcript. Request guards check **before forwarding** to the parent's public registry. Counts include failed dispatches but not rejected over-limit dispatches, auth preflight or provider-internal retries/subrequests. Tool guards count entered, granted custom-tool executions, including permission-denied forwarded calls; malformed/unknown calls rejected by Pi before execution are not counted. Requests still bound loops of rejected tools.

Deadlines initiate abort; they do **not** promise a maximum drain time. Public auth/provider/permission code that ignores cancellation must return before setup/tools release their capacity/writer lease. No fake timeout races away from unabortable initialization, force-kills unrelated processes, or starts a replacement writer while old tools remain active. SDK-owned tool cancellation uses actual abort signals and public `abort()`/prompt completion. Abrupt process death is not recoverable cleanup.

## Public model/auth/resource boundary

`parentModelRuntime(context,agentDir,onRequest?)` creates a child runtime with empty `InMemoryCredentialStore`, `modelsPath:null`, scoped cache path and network catalog refresh disabled. Native adapters list only the captured selected physical model and any explicitly granted prewalk target. Its `stream` and `streamSimple` forward into the **root parent's public ModelRegistry**, preserving configured provider composition, auth, headers, environment and OAuth refresh. Preflight uses public `hasConfiguredAuth` and `getApiKeyAndHeaders`; values are never copied to artifacts. Every descendant uses this same captured public adapter, with its own admitted physical model/effort selection. No fallback model/cost substitution or private imports. Unsupported `pi-virtual` routing fails visibly because faithful child router reconstruction is not public.

`childResources(prompt,skills)` owns the complete loader. It supplies only the role prompt and explicit skill metadata, with empty extensions/MCP/context files/prompts/themes. Automatic compaction/retry are disabled. Each child uses the selected workspace cwd, with separate scoped agent/cache/transcript directories; grants and project trust remain parent-owned. This is **not a sandbox** against arbitrary JavaScript, process environment access or hostile code.

Forwarded root builtins must be callable, genuine `builtin:<name>` tools (not overrides). Only the effective subset is checked: an inactive replacement outside that subset is not forwarded, while a callable replacement rejects admission. Each invocation rechecks the invoked tool’s current callability and provenance, so subsequent deactivation/replacement cannot fall through to an old backend or a custom override. Descendant builtin definitions forward only their admitted tool names through the original parent's public `ctx.executeTool`, retaining schema validation, operator permission hooks, activation revocation and nested-call IDs. They do not call raw builtin `execute` implementations. Local `delegate_task` receives normal SDK model-tool validation/exposure and then full policy/ancestor admission checks; no parent model-only exposure is bypassed. General skill and leaf inheritance is restricted as described below.

## Registry, controls and final delivery

`DelegationCoordinator(root,parentId)` owns all generations/entries. Public seams:

- `ChildRef = {id:string,generation:number}`. UUIDs are never reassigned to another agent; generation starts at 1 and increases on admitted reassignment. Mutating controls reject unknown/stale references; historical snapshot reads remain pinned.
- `ChildSnapshot`: identity, real `parentId`, optional `parentGeneration` for descendants, absolute `depth`, `status`, profile, canonical workspace `cwd`, assignment, activity, bounded progress (4096 characters), final text, actual model/effort, optional `prewalk:{target,status}` (`armed`, `handed-off`, `suppressed-exact` or `no-op`), direct `telemetry`, optional error/transcript, `limits`, `limitReason?`, `toolCalls`, `writerLease`, and `disposed`.
- Lifecycle: `queued → setup → running → settled|failed|cancelled`, plus `pausing → paused → queued` for explicit deadline continuation; cancellation of active work exposes `draining` until it actually ends. Queued work can cancel directly. `disposed` and `writerLease` are resource facts, not guessed outcomes. Parents cannot finalize/dispose before descendants/tools drain.
- `RegistryEvent = {type:"changed"|"sdk",child,event?:AgentSessionEvent}`. Detached snapshots; observer exceptions cannot fail execution. `subscribe` returns unsubscribe. `list()` retains identity admission order and returns current generations only; `snapshot(ref?)` reads a pinned current/historical generation or latest identity; `generations(id)` returns detached immutable history plus current generation. `agents()` separately reports current presence. Pi event bus `delegation:registry` exposes the same events, including disposal at shutdown. Consumers unsubscribe on runtime replacement.
- `configure(policy)` validates finite scheduler policy. `admit(StartTask[])` synchronously validates the whole batch and returns ordered identities; `start(StartTask)` is the single helper, awaiting abortable setup if dispatched immediately, otherwise acknowledging queued admission. Model tool uses `admit` for both input shapes. Every `StartTask`, including direct SDK integration, requires `assertTools(names)`, a synchronous provenance validator backed by the originating extension’s public `pi.getAllTools()` metadata. Missing validators reject at runtime. This is a trusted integration seam, not a model/operator policy option or a sandbox for arbitrary extension JavaScript.
- `wait(ref)` resolves the current window snapshot after drained pause or disposal, including setup failure; `stop(ref)` aborts and drains a subtree. `stopMany(refs)` marks the entire cancellation set before any sibling dequeue and then drains it; already settled results are preserved. Repeated stop is harmless. `steer(ref,text)` requires running state and uses Pi `queued|handled` semantics.
- `shutdown()` closes admission/dequeue immediately, cancels/drains/disposes all entries and clears observers idempotently. Bare SDK `session.dispose()` does not replace host lifecycle dispatch. `reopenTranscript(ref)` inspects a new transcript; it does not revive work.

Prompt resolution, not low-level `agent_end`, accepts final results after SDK work settles. SDK assistant errors/setup exceptions are failed; abort/limit/shutdown intent cannot become success. Failure of one admitted assignment **does not cancel unrelated batch siblings**: every input receives its own outcome. Caller foreground cancellation marks all unsettled assignments then drains their subtrees; settled outcomes remain settled. Operator stop targets only its pinned subtree.

Foreground returns one ordered window-outcome batch after window drain (single input returns one snapshot), whether paused (`disposed:false`) or completed after disposal. Partial tool updates are observations, not window-outcome deliveries. Background returns ordered accepted snapshots/identities immediately (possibly queued/setup), and emits **one window-outcome custom message per call**, containing all ordered outcomes after window drain (paused or completed). Native delivery is notification-only; async compatibility delivery wakes the parent with `triggerTurn:true,deliverAs:"followUp"`. It does not also deliver per-item completion messages. Background no longer follows the initiating tool's signal after acceptance; stop/shutdown still own it. Old-runtime shutdown suppresses its final custom delivery; work never survives reload. Output previews use Pi 50KB/2000-line truncation. Tool `details` retain full snapshots/transcript references (`ChildSnapshot` for single, `{children:ChildSnapshot[]}` for batch); full model/tool evidence is in actual SDK JSONL; final text/structured artifacts follow the protocol above.

`/subagent-profiles` resolves current-workspace operator and trusted-project settings through the admission profile/skill validator. In TUI mode it opens a read-only list/details overlay (↑↓ select; j/k, PgUp/Dn, Home/End scroll; configured cancel/Esc closes). Narrow layouts stack; tiny layouts request resize. Reopening refreshes the snapshot. It shows prompts, descriptions, builtin and reviewed-leaf ceilings, skill paths, spawn/coordination and model/prewalk policy, not guaranteed session rights. General skill resolution uses the public command context’s base prompt skill metadata, never parent prompt text or context files; per-turn prompt changes can differ at admission. Read-only describes the viewer, not general’s tool authority. Command context cannot establish callable tools; active declarations are not proof of callability. RPC/JSON/print retain the structured `{toolPolicy, profiles}` message. Invalid config or granted skills fails resolution; snapshot/details `tools` reports each task’s admitted effective builtin set. Commands are `/subagent-profiles`, `/subagent-status [id generation]`, `/subagent-stop id generation`, `/subagent-steer id generation text`, `/subagent-hub`. Hub/widget are registry consumers: current fleet counts, ancestry/selected-generation history, readonly full transcript/tools/results and bounded pinned controls are described below. Queued/draining states and actual writer lease are shown without fabricated completion.

### Direct versus subtree telemetry

`telemetry = {requests:number|null,tokens:SessionStats["tokens"]|null,cost:number|null,context:ContextUsage|null}`. Null means unavailable, not zero; genuine reported zero stays zero. Token/cost/context values are SDK-reported; no precision promise for billing or provider internals. Request counts are direct registry dispatches for that entry. Each descendant has its **own direct telemetry**; ancestors never absorb descendant charges as tool `usage`. Sum direct registry values once across the chosen subtree. Parent tool usage/footer is not an aggregate delegation bill. No subtree total is synthesized.

## Live-parent messaging, waits and presence

`delegate_coordinate` is a native **model-only** tool registered with Pi conflict handling, not a pi-subagents alias. It must be enabled if the host uses an explicit tool allowlist. Root has current-registry controls; children get a child-local closure only when their effective profile grants at least one action. Inputs never accept sender/parent/runtime/generation overrides for the actor. Child invocation still counts against that generation's tool/deadline/request limits.

Operator `coordination` grants selected `message`, `inbox`, `wait` actions; absent/empty is off. Trusted project and task request arrays only narrow operator grants. Descendants cannot regain any ancestor action. Every message, inbox read and wait authorization reloads current initiator/ancestor policy and checks the original admitted action ceilings too. Denial sends no message, consumes no inbox and allocates no waiter. Cancellation, pause, shutdown and stale generations are rechecked after policy-loading awaits before protected effects. Live revocation governs subsequent actions: it does not forcibly cancel already in-flight work, retract accepted messages or roll back completed effects. Addressing is restricted to exact-generation parent, direct children and siblings whose parent UUID+generation match. Identities must be explicitly supplied in task/message context: there is no child registry enumeration, transcript read or arbitrary resource lookup. Only direct children may address root by omitting `target`; root cannot message itself. Root-only `status`, `stop`, `park`, `resume`, `followup`, `revive` are rejected from child closures even if model input names them.

| Action                | Inputs and behavior                                                                                                                                                                                                                                                                           |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `message`             | `target?:ChildRef`, nonblank `text` (at most 4096 UTF-8 bytes), `mode?:"inbox"                                                                                                                                                                                                                | "steer" | "followUp"` (default inbox). Requires a running recipient. Omitted target means root inbox, never arbitrary supervisor RPC. |
| `inbox`               | Destructively reads only the actor's mailbox. Successful tool output exposes messages to that actor's model. No automatic root inference is triggered.                                                                                                                                        |
| `wait`                | Required `timeoutMs:1..60000`; optional `target` for one drained window outcome (paused or terminal), or `messageId` for one known receipt, or neither for own inbox. Target and messageId together reject. Receipts are visible only to their originating sender/recipient.                  |
| `status`              | Root-only pinned `target`; returns `{snapshot,generations,agents}`. Snapshot/history describe assignments; agents describes current presence. Full detached details, bounded text preview.                                                                                                    |
| `stop`                | Root-only pinned current `target`; closes idle/parked presence or aborts/drains active subtree. Completed outcomes/files are unchanged. Stopped identities cannot revive.                                                                                                                     |
| `park`                | Root-only current disposed idle target; records parked presence. Active parking rejects: stop active work explicitly. No live SDK session, shell, permit or writer lease is retained while idle or parked.                                                                                    |
| `resume`              | Root-only drained paused `target` and nonblank `assignment` continuation. Returns the same identity/window snapshot and sends one notification after that resumed window drains. No schema replacement or descendant auto-resume.                                                             |
| `followup` / `revive` | Root-only current target and nonblank `assignment`, optional supported `resultSchema`. Followup requires idle; revive requires parked. Returns the new admitted snapshot, not final success. Use wait/status for completion; no unsolicited duplicate generation-completion message is added. |

A message returns `{id,sender,recipient,text,mode,status,error?}`. `accepted` means mailbox admission, `queued` means the public SDK accepted a steer/followUp, `handled` means an SDK input handler consumed it, `delivered` means the exact message entered the recipient transcript or an inbox tool/wait returned it. None means model acknowledgment, understanding or assignment completion. `not-running` means no running recipient (including an undelivered receipt whose recipient ended), and `error` preserves delivery errors. Unknown/foreign/stale identities or receipts reject visibly, not as apparent successful delivery. Inbox messages are not automatically steered; steer/followUp are also retained in the bounded mailbox. Their `delivered` state comes from actual SDK user-message events, not queue acceptance. Message text is literal prefixed data, not tool authority.

At most **64 messages per mailbox**, **512 admitted messages per parent runtime**, **64 pending waiters**, and **64 generations per identity**. Consumption frees mailbox slots, not receipt-retention budget; exhaustion rejects without silent eviction. All are parent-lifetime memory only, not a broker or durable service. Wait uses registry/message events and a native timeout/abort listener, never polling. It returns `outcome` (identity/status/optional limitReason and pauseReason only, no peer result/transcript), `messages`, `message` receipt, `timeout`, `cancelled` or `shutdown`. Cancellation/limits/shutdown close child action admission and wake pending waiters. Paused/terminal outcomes resolve immediately; SDK sessions waiting in tools retain their actual permits. Self/ancestor/dependency-cycle waits reject, including pending sibling delivery receipts; child waits on queued work reject rather than lend permits. Pending root-receipt waits from children are unsupported (a foreground parent may already be waiting for that child); send without waiting for acknowledgment or wait for an explicit reply in the child's inbox. Root outcome waits may await queued work, bounded by the explicit timeout. Timed-out/cancelled waits do not cancel targets; use stop explicitly.

### New assignments, generations and immutable history

Completed-agent renewal is distinct from paused continuation. After real tools/descendants/artifacts drain, resources dispose and a non-cancelled generation has **idle presence**; explicit park changes only presence to **parked**. Active presence covers queued/setup/running/draining; deadline transitions expose pausing/paused presence separately. Cancellation/shutdown/stop produces **stopped presence** without rewriting completed assignment status. Completed generations never gain more transcript entries or overwritten artifacts.

Followup/revive re-resolve operator/trusted-project policy and selected root model/auth/resources at invocation. Effective grants are intersected with the previous admitted/request-narrowed grants and, for descendants, each current effective ancestor definition and its admitted authority. Removed grants are not restored on later generations, including builtin tools absent from the original callable parent subset. Later parent activation can benefit a newly admitted identity, not expand old identity/generation ceilings. Missing resources/auth or an empty effective builtin set or incompatible policy fail honestly. A new assignment retains the prior schema unless a new supported schema is supplied; no previous submitted value/attempts/status/result/telemetry can satisfy it. Model/tool/deadline budgets restart per assignment, bounded by current policy and previous execution limits. Direct-child spawn consumption remains cumulative for the identity and each descendant renewal consumes another slot of the live parent's remaining lifetime spawn budget.

The UUID stays stable and generation increments atomically **only after** synchronous admission checks. Concurrent renewals race on the original reference; exactly one can replace it. A newly owned generation directory `<id>-g<N>` holds its SDK session, `agent/`, transcript and exclusive artifacts. Generation 1 keeps `<id>/`. For these completed-agent fresh generations, no old files are opened for continuation, consulted, replayed, appended or migrated. The new prompt contains only the new explicit assignment plus current role/resources and identity; provide any relevant prior conclusions explicitly. This deliberately avoids retained-conversation replay/tool execution and does not claim a warm resumed Pi session.

Descendant renewal requires its **exact original** `parentId+parentGeneration` still active, non-cancelling and authorized. Current ancestor spawn roles, depth/limits, remaining spawn count, immediate nested capacity and writer ancestry all revalidate before generation mutation/setup. Replaced/disposed/stopped ancestors reject; the caller must delegate a new identity through an authorized live parent, never reparent the old one. Parent completion/stop closes descendant idle/parked presence too. Old callbacks, steering, stops, submissions, message delivery and waits capture the originating entry/reference, never retarget by latest UUID. Wait outcomes remain pinned even if a later generation is admitted. Shutdown/reload closes all presence and waiters, drains actual resources and suppresses old-runtime final messages; no cross-parent/process revival exists.

## Writer coordination and unsupported shell boundary

Only `writerLease:true` identifies actual writer ownership. Queue entries hold no writer lease. `parentToolStart(id,name,readOnly?)` reserves model mutations at preflight and `parentToolEnd(id)` releases on every finalized call (including blocked calls). Genuine builtin read/grep/find/ls and owned delegation control calls are exempt. The compatibility control name is exempt only when public source metadata matches this extension's native task tool; an unrelated provider is not treated as this coordinator. Parent mutations reject while a child owns the writer; that writer's own forwarded permitted calls are recognized by public parent-tool-call ID prefix and continue through ordinary hooks. Batch siblings share the original root dispatcher context but only the admitted writer can expose mutating tools. Parent permissions and readonly tool hooks remain unchanged.

**Manual `!`/`!!`, external terminals/editors, unrelated sessions and arbitrary extension JavaScript remain uncoordinated.** Operators must not perform conflicting writes while children write. Pi 1.5.2 has no public interactive-shell completion notification; no wrappers, global shell bans, in-flight inference or OS lock are installed. A shared cwd is not filesystem isolation/apply.

## Storage and fleet observation

Only new `delegation/<parent-id>/<task-id>/{agent,transcripts,result.txt,structured.json}` data under the parent session directory is created (successors use `<task-id>-g<generation>`). No historical proof/run data is read, mutated or migrated. Registry lifetime is the parent runtime, never durable recovery. Transcripts/artifacts can contain sensitive content and require the same care as parent transcripts.

The joined fleet widget, ordered single/batch result cards and `/subagent-hub` consume this registry, not a second executor. `createdAt` is admission epoch milliseconds; `endedAt` is null until actual disposal, then its epoch. `writerLease` is actual ownership through drain, not inferred from a profile. Tree indentation follows real depth; inspector supplies full parent UUID+parentGeneration. Queue entries may have no transcript. Terminal status is separate from idle/parked/stopped presence. History retains immutable snapshots and detached finalized SDK messages when old SDK graphs are released; `messages(ref)` returns detached public SDK messages for exactly that current/historical generation, empty before session setup. No replay, arbitrary file reading or copied artifact store is involved. Full readonly transcript/tools omit opaque provider payloads, signatures and image data; image omission is explicit. Cards label truncation; inspector scrolls full text/structured values and artifact references. Usage is direct per selected generation, never parent-plus-descendant totals.

Subagent Hub is opened with `/subagent-hub`. The **Subagents** widget renders no rows while the current registry is empty, including before first use and after a runtime reset; admitted queued/setup work and retained terminal tasks remain visible. The bounded above-editor widget is a semantic subagent tree (role/assignment, actual status, direct stats, newest activity), not another authority surface. Queue counts are collapsed, live resources prioritized, recent terminal rows retained within budget and overflow counted. Unknown usage is omitted; requests are not labeled turns, no context percentage is synthesized, and elapsed is admission-to-disposal at render time without a new timer. Writer warnings depend only on actual leases. Visual-only pinned reference attribution is in the README; no reference runtime/source/assets are imported.

A full-width, border-colored rule separates the Hub panes from a visible footer, consuming one row within the existing height budget. Tiny browse layouts hide both footer and rule. Pinned target identities and their controls remain indivisible; insufficient space shows the resize/Escape placeholder instead of actionable controls.

Hub names are snapshot-derived assignment headlines plus role, not a naming registry or new identity. Every selection/control still uses the original UUID+generation; details and pinned controls expose that full reference, and identity filtering remains supported. The primary thread uses the same publicly exported user/assistant/tool component classes as main Pi chat, standard builtin renderer-only definitions and injected host TUI/cwd. Tools preserves complete sanitized arguments/result text even where native builtin presentation suppresses success prose. Native edit previews never read files here: only recorded sanitized diff/firstChangedLine details are admitted. No arbitrary tool details, opaque signatures or image payloads are forwarded to renderers. Public default Markdown/thinking/expanded-tool presentation is supported; parent-private renderer resolver chains, third-party overrides, Markdown transformers and private hide-thinking/outputPad state are unavailable, not emulated. Native SGR is retained; terminal-control output, hyperlinks and shell zones are excluded.

| Capability                                                                                                        | Supported surface / precise restriction                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                         |
| ----------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Fleet/roster/tree/history, physical model/effort/prewalk, limits, schema validation/value/artifacts, direct usage | Persistent widget + native cards + Hub thread/details/tools. Wide Hub: capped left generation roster, dominant right thread; compact Hub: selected-generation header and full-width thread. Current fleet counts exclude old generations; history rows remain individually selectable.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                          |
| Filter/search and scrolling                                                                                       | `/` searches roster content including result/progress, not a filesystem or full-transcript search engine. Tab cycles thread/details/tools (thread default). Thread opens/follows the latest output (LIVE); upward scroll/Home detaches (READING) without following appended output, End or reaching bottom resumes. Details/Tools open at top without auto-follow. j/k line-scroll, Ctrl+u/d and configured PgUp/PgDn page-scroll, Home/End top/bottom independently of ↑↓ roster selection. Aliases never intercept native input editing. Public fullscreen normalized wheel scrolls only the reading viewport; other wheel inside the Hub is consumed, not forwarded to main chat. Regular mode leaves wheel/scrollback to the terminal; keyboard fallback is required. No mouse-mode or host-setting changes. Prefilled filters start editing at the end using public native Input handling. |
| Steering                                                                                                          | Hub `s` or `/subagent-steer`, original UUID+generation, running only. Public SDK queued/handled is not model acknowledgment.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                    |
| Root inbox message                                                                                                | Hub `m` → existing root `message` authority, bounded native mailbox; receipt shows actual state, never model acknowledgment. Model tool additionally supports steer/followUp modes, inbox consumption and receipt waits.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| Outcome wait                                                                                                      | Hub `w` → native event wait with explicit 1000ms bound; native tool supports 1–60000ms outcome/inbox/receipt waits with existing deadlock/refusal rules.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                        |
| Stop subtree                                                                                                      | Hub `x` → pinned target → `y` confirmation → existing stop/drain, retaining capacity/writer until disposal. Queued/setup/running/paused UI targets supported; historical/pausing/draining/disposed UI targets refuse, including transitions during pinned input. `/subagent-stop` and native root stop retain existing semantics.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                               |
| Park                                                                                                              | Hub `p` or native root tool; only disposed idle current identity with valid original parent. No warm SDK retained.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| Followup/revive                                                                                                   | **Native model-issued `delegate_coordinate` or compatibility `subagent` resume**. Paused assignments continue the retained conversation; idle/parked assignments use fresh session/generation/current narrowed authority. Both require the exact active original parent for descendants. Hub command context lacks `tools/executeTool`: it cannot safely start a generation; f/r only explain refusal, allocate nothing and dispatch nothing.                                                                                                                                                                                                                                                                                                                                                                                                                                                   |
| Child coordination/spawn/model authority                                                                          | Coordination/model authority remains operator default-off; spawn defaults to scout/review only. All remain project/request/ancestor subset-only; the UI cannot mint grants or impersonate a child.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| Manual !/!!, external editors/terminals, arbitrary extension JS                                                   | Uncoordinated; model-tool-only writer boundary, never a shell ban/wrapper/sandbox.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                              |
| Supplied workspace / production isolation                                                                         | Explicit path/shell-cwd adaptation through root hooks is supported for existing supplied workspaces. Filesystem sandboxing, managed worktrees, copying/apply and cleanup remain unimplemented; cwd is not a resource grant.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                     |
| Retained incremental advisor                                                                                      | **EXCLUDED** from current product scope, not pending an owner contract or a current release gate. No advisor implementation/substitute; explicit ordinary `review` delegation remains supported. Reference evidence is retained in the advanced policy ledger, not an automatic implementation plan.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            |
| Probes/speculation/workpool/executable extensions/MCP/historical consultation                                     | Runtime loading/full MCP inheritance remains unsupported/parked per advanced policy ledger; restricted general leaf forwarding is specified below. No ambient/history/sensitive scan or new provider inference.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                 |
| Restart recovery, cross-parent messaging/revival, automatic idle TTL/warm replay                                  | Unsupported. Parent shutdown still aborts/drains/disposes.                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                      |

UI strings are sanitized against terminal controls/ANSI/bidi; native display-column helpers bound multiline layouts and resizing. Neutral backgrounds preserve active theme lightness with zero chroma and restore after truncation/style resets. This is not installed/full parity/production publication or human visual acceptance. Native draft stack [1,2,4] remains a linear UI fork relative to local scheduler/outcomes/coordination/advanced branches. No graph revision or publication was performed here.

## Restricted general inheritance

General retains its existing analysis prompt and the supplied assignment; it does **not** copy parent system instructions, context files or history. Available skills come from the parent's public `before_agent_start.systemPromptOptions.skills` metadata. Bundled → operator → trusted root-project narrowing still applies; explicit `skills:[]` removes inheritance. Skills advertise exact validated paths, not eagerly copied instruction bodies. Other roles keep their explicit skill grants and scout/review remain read-only.

Only the operator can mint `general.leafTools`, an exact-name allowlist of **reviewed leaf implementations**. For example, after reviewing an installed extension/MCP tool that never dispatches other Pi tools or alters session/tool/model state:

```json
{
  "general": {
    "leafTools": ["mcp__docs__lookup"]
  }
}
```

This does not connect/reload MCP, load extensions into children, or activate parent tools. The tool must already have public metadata and be live callable (`ctx.tools`). Trusted project configuration may remove leaf grants but cannot add them. Missing operator review is an explicit denial, not a universal ban on extension tools. Review is a trust assertion about code/server behavior, not a verified security annotation.

Classification is deliberately fail-closed:

- Genuine callable builtins are admitted through the existing builtin ceiling/provenance check.
- Reviewed callable non-builtin leaves are admitted only in the root workspace. Any admitted leaf reserves the writer lease, regardless of `readOnlyHint`.
- `reachesTools` tools are denied regardless of what that function returns or what is granted: the SDK cannot restrict their nested dispatcher to this child's ceiling.
- `codemode`, `tool_search`, `delegate_task`, `delegate_coordinate`, `subagent`, and `submit_result` cannot be forwarded. Child-owned controls still use their existing separate grants.
- Hidden/model-only tools, builtins outside the profile ceiling, builtin replacements, unreviewed/unknown implementations and non-builtin tools for a different cwd are denied. MCP namespace names or annotations alone do not authorize a tool.

`ChildSnapshot.tools` retains its **builtin-only** meaning. Additive `leafTools`, `skills` (exact paths), and `capabilityDenials:[{name,reason}]` describe the admitted ceiling and rejected tool inventory. These are admission records, not promises of continued availability. No builtin rights are manufactured by leaf grants; the existing nonempty effective builtin requirement remains.

Before each forwarded call the coordinator checks cancellation, live callability, builtin provenance or leaf classification/provenance, and current role/ancestor tool configuration. The public root `ctx.executeTool` preserves argument validation, permission/result hooks and SDK nested-call IDs; it is never replaced with raw tool execution. Revocation blocks subsequent dispatch, not effects already performed or already in flight. Renewal intersects previous leaf/skill ceilings and descendants must remain ancestor subsets. Writer serialization, per-child call budgets and cancellation/drain still cover the forwarded leaf call.

**Trust limitation:** extensions execute trusted JavaScript with full process permissions and their original root context/closures. The public SDK does not provide a child-specific execution context or enforce `reachesTools` declarations. A false leaf assertion, malicious extension, undeclared nested dispatch, or server-side orchestration cannot be sandboxed here. Do not grant such implementations. Metadata/provenance checks detect visibility/classification/source changes, not arbitrary code replacement under identical public metadata. Full automatic inheritance and general tool-to-tool forwarding remain unsupported. Custom cwd adaptation applies only to genuine builtins; custom tools are never represented as rebound.
