Merge remote-tracking branch 'origin/codex/package-readme-limitations-audit-20260712' into codex/model-experience-readmes-20260712
# Conflicts: # packages/core/tools/README.md
This commit is contained in:
@@ -11,11 +11,11 @@ tools:
|
||||
mode: native # native (default) | code | both
|
||||
```
|
||||
|
||||
`native` contributes the calling agent's visible end capabilities as wire function definitions. Under `code`, this registry's canonical contribution is the reserved `run_code` transport plus the generated `tools:sdk` prompt section (see [Code Mode](#code-mode)); an assembly listener may still deliberately add unrelated schemas. `both` contributes the visible native definitions, `run_code`, and the SDK section. In non-native modes both infrastructure pieces are owner-final rather than filterable capabilities: restrictions and assembly listeners cannot remove them, a scoped section cannot shadow the globally owner-final `tools:sdk`, and registering, shadowing, or explicitly filtering `run_code` fails loudly. Non-native modes require a loaded `ctx.codeRuntime` with `language: 'typescript'`; a missing or mismatched runtime rejects every prompt assembly with an actionable error, and a `systemPrompt.toolOrder` naming tools the mode no longer contributes rejects the assembly the same way.
|
||||
`native` contributes the calling agent's visible end capabilities as wire function definitions. Under `code`, this registry contributes the reserved `run_code` transport plus the generated `tools:sdk` prompt section (see [Code Mode](#code-mode)); `both` contributes the visible native definitions and both infrastructure pieces. Restrictions cannot remove `run_code`, and registering, shadowing, or explicitly filtering that reserved name fails loudly. An expert `system-prompt/assemble` listener may replace any prompt or schema contribution; its returned assembly is authoritative, so the listener owns preserving Code Mode when the protocol should remain active. Non-native modes require a loaded `ctx.codeRuntime` with `language: 'typescript'`; a missing or mismatched runtime rejects every prompt assembly with an actionable error, and a `systemPrompt.toolOrder` naming tools the mode no longer contributes rejects the assembly the same way.
|
||||
|
||||
### Public API
|
||||
|
||||
- `ctx.tools.register(definition: ToolDefinition): () => void` Register a trusted typed same-process definition. The layer is the calling context's scope: a plain plugin context registers globally; an agent's `agent.ctx` registers for that agent alone, shadowing a same-named global tool there. Duplicate names within one layer throw; non-native modes also reject the reserved `run_code` transport name. `timeoutMs`, when present, must be positive and finite. `ownerFinal: true` makes the tool's canonical wire presence or absence survive prompt assembly listeners. Disposed with the calling fiber.
|
||||
- `ctx.tools.register(definition: ToolDefinition): () => void` Register a trusted typed same-process definition. The layer is the calling context's scope: a plain plugin context registers globally; an agent's `agent.ctx` registers for that agent alone, shadowing a same-named global tool there. Duplicate names within one layer throw; non-native modes also reject the reserved `run_code` transport name. `timeoutMs`, when present, must be positive and finite. Disposed with the calling fiber.
|
||||
- `ctx.tools.restrict(filter: ToolRestriction): () => void` Scoped-only (throws on a plain context): mask the global end-capability surface for the calling agent — `allow` keeps only the listed global tools, `deny` removes them; multiple restrictions intersect; scope-local registrations are merged afterward. The readonly arrays compile once into private sets. Every listed name must exist in the current pre-restriction global registry; scope-local, unknown, and reserved `run_code` names fail loudly. A deny-list admits a later global tool unless it names that tool; an allow-list excludes later names; neither filters a later scope-local registration. `restrict({})` rejects. This is live registration composition, not a parent-derived authority ceiling; see the [agent-scope security non-goal](../../../docs/rfc/implemented/architecture/2026-07-08-agent-scope-contexts.md#security-and-authority-are-explicit-non-goals).
|
||||
- `ctx.tools.get(name: string, scope?: ScopeKey): ToolDefinition | undefined` Resolution as one scope sees it (shadowing applied; a restricted-away global reads as absent) — presenters pass the calling agent so the card matches what executed.
|
||||
- `ctx.tools.schemas(scope?: ScopeKey): ToolSchema[]` Schemas of everything the scope can see (without the `execute` functions). The shipped tools' schemas are catalogued in [docs/tool-catalog.md](../../../docs/tool-catalog.md), generated by booting each tool plugin and harvesting this method (see [the tool-schema-catalog RFC](../../../docs/rfc/implemented/process/2026-07-02-tool-schema-catalog.md)).
|
||||
@@ -28,11 +28,11 @@ tools:
|
||||
|
||||
### Live events
|
||||
|
||||
The live registry pipeline has three transformable waterfalls followed by the owner-final `tools/result` observation boundary; registry changes are deliberately unfiltered shared-state notifications. Exact signatures, dispatch modes, scope filtering, and failure-containment contracts live in the generated [Cordis event catalog](../../../docs/cordis-catalog/events.md), while the complete ordering is visualized in the generated [tool execution pipeline](../../../docs/tool-execution-pipeline.md). `tools/result` is live and observe-only; the similarly named `tool/result` is the durable session event the agent loop appends afterwards.
|
||||
The live registry pipeline has three transformable waterfalls followed by the observe-only `tools/result` boundary; registry changes are deliberately unfiltered shared-state notifications. Exact signatures, dispatch modes, scope filtering, and failure-containment contracts live in the generated [Cordis event catalog](../../../docs/cordis-catalog/events.md), while the complete ordering is visualized in the generated [tool execution pipeline](../../../docs/tool-execution-pipeline.md). `tools/result` is live; the similarly named `tool/result` is the durable session event the agent loop appends afterwards.
|
||||
|
||||
### Key types
|
||||
|
||||
- `ToolDefinition` — `ToolSchema` + `execute(args, exec)`, optional presentation callbacks, cooperative `timeoutMs`, and optional `ownerFinal`. `ownerFinal` is reserved for protocol tools such as `run_code` and structured-output capture whose canonical wire state must survive assembly listeners.
|
||||
- `ToolDefinition` — `ToolSchema` + `execute(args, exec)`, optional presentation callbacks, and cooperative `timeoutMs`.
|
||||
- `ToolExecutionInput` — the caller-supplied call description: `{ callId, name, arguments, agent?, parent?, signal? }`; callers may pass an enclosing execution's opaque token as `parent` but never choose the new execution's own token.
|
||||
- `ToolExecutionToken` — a fresh branded `Symbol` assigned by the registry. It supports equality correlation only and never crosses a model, log, or worker boundary.
|
||||
- `ToolExecution` — the pipeline-owned call: immutable `{ token, callId, name, arguments, agent?, parent? }` identity plus optional operational `signal`, which an around wrapper may add, replace, remove, and restore. A nested call's `parent` is a `ToolExecutionToken`, not an execution object.
|
||||
@@ -132,17 +132,17 @@ const bash = defineTool({
|
||||
|
||||
Under `mode: code` (or `both`) the registry turns the tool surface into a programming API, per the [Code Mode RFC](../../../docs/rfc/implemented/feature/2026-06-15-code-mode.md): the model writes a TypeScript program (the body of an async function) and passes it to the reserved wire transport `run_code`; the program runs in `ctx.codeRuntime` (the [code-execution seam](../../code-runtime/README.md) — the shipped backend is a worker thread) with one async binding per visible end-capability tool (`await tools.bash({...})`), and ONLY what it prints or returns re-enters the model's context. Scope restrictions change those SDK bindings but cannot remove or replace the transport itself.
|
||||
|
||||
- **The SDK section** (`tools:sdk`, order 150): a lazy prompt section regenerating, at each assembly, a `declare const tools: {...}` TypeScript declaration of the calling scope's visible end capabilities (exotic names via quoted keys), plus fixed usage instructions. The registry marks this section and the `run_code` wire schema owner-final, so Code Mode cannot silently lose either half of its transport during assembly. Deterministic — lexicographic tool order, byte-identical text for an unchanged tool set (prefix-cache-friendly). The codegen (`jsonSchemaToTs`, exported) is total: constructs outside the `defineTool` subset degrade to `unknown`, never throw.
|
||||
- **The SDK section** (`tools:sdk`, order 150): a lazy prompt section regenerating, at each assembly, a `declare const tools: {...}` TypeScript declaration of the calling scope's visible end capabilities (exotic names via quoted keys), plus fixed usage instructions. Deterministic — lexicographic tool order, byte-identical text for an unchanged tool set (prefix-cache-friendly). The codegen (`jsonSchemaToTs`, exported) is total: constructs outside the `defineTool` subset degrade to `unknown`, never throw.
|
||||
- **The dispatch bridge** (`run_code`'s execute): every binding call is JSON-normalized before dispatch (a value that does not survive — `BigInt`, circulars — rejects that one call, so the dispatched form and logged form are the same JSON value by construction), serialized through a per-run queue (even `Promise.all` executes underlying calls one at a time in submission order), given the outer execution's opaque token as `parent`, and run through the complete pre-execute → guards → execute → post-execute → result pipeline. A denial reaches the program as a binding rejection, and each sub-call is logged as a `tool/code-dispatch` session event with deterministic id `<parent>:code:<n>`; `deriveMessages()` does not surface that event. Token correlation lets commit-style observers defer an inner success until the final `run_code` result without exposing the live outer execution; ordinary tool side effects are not rolled back. A sub-call's `additionalContext` is deliberately dropped because inserting it inside a running parent call would break tool-call/result adjacency.
|
||||
- **Settlement discipline**: the bridge owns a run-scoped abort that follows the outer signal in and fires when the run settles for any reason, so a budget expiry aborts an in-flight sub-tool instead of orphaning it; the bridge then drains its queue BEFORE returning, so every `tool/code-dispatch` lands inside the open turn. A failed run throws `CodeRunFailedError` (`code: 'CODE_RUN_FAILED'`, message = the failure kind + captured logs), which the pipeline converts to a structured `isError` the model self-corrects from.
|
||||
|
||||
The wire collapse is the registry's own contribution (`systemPrompt.tools()` is mode-aware), so the logged `request/header` records it for free. With no deliberate schema-adding assembly listener, `code` assembles exactly `[run_code]`, pinned by tests and the snapshot goldens; contribution-owned finality guarantees that `run_code` and `tools:sdk` remain present, not that unrelated listener additions are erased. Try it: `pnpm run demo:code-mode` ([the coding-agent example's Code Mode overlay](../../../examples/coding-agent/README.md#code-mode)); `pnpm run demo:code-mode acp` serves the same mode over ACP instead of the REPL.
|
||||
The wire collapse is the registry's own contribution (`systemPrompt.tools()` is mode-aware), so the logged `request/header` records it for free. When no assembly listener changes the registry's prompt or schema contributions, `code` assembles exactly `[run_code]`, pinned by tests and the snapshot goldens. Try it: `pnpm run demo:code-mode` ([the coding-agent example's Code Mode overlay](../../../examples/coding-agent/README.md#code-mode)); `pnpm run demo:code-mode acp` serves the same mode over ACP instead of the REPL.
|
||||
|
||||
## Model Experience
|
||||
|
||||
| Context surface | What the model sees | Token effect |
|
||||
|---|---|---|
|
||||
| Tool schemas and Code Mode SDK | In normal mode the model sees each visible definition's name, description, and JSON schema. Code Mode instead contributes one owner-final `run_code` wire schema and a generated owner-final TypeScript `tools` SDK section; `both` exposes both forms. Agent-scoped restrictions and shadows change that agent's end-tool set but cannot remove or replace the transport. | Fixed per-request cost proportional to the visible definitions. Code Mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction. |
|
||||
| Tool schemas and Code Mode SDK | In normal mode the model sees each visible definition's name, description, and JSON schema. Code Mode instead contributes one reserved `run_code` wire schema and a generated TypeScript `tools` SDK section; `both` exposes both forms. Agent-scoped restrictions and shadows change that agent's end-tool set without filtering the transport. The expert `system-prompt/assemble` waterfall can replace the final assembly and then owns preserving Code Mode protocol coherence. | Fixed per-request cost proportional to the visible definitions. Code Mode trades end-tool schemas for generated SDK text plus one transport schema rather than promising a universal reduction. |
|
||||
| Tool-call history and results | The loop retains model-emitted arguments and the registry's final normalized content or structured error. Post-execute listeners may append source-attributed context after the result. Code Mode exposes only the outer program's printed or returned value; inner dispatch events stay log-only. | Arguments, results, and additional context are data-dependent and resent until compaction. Restrictions that hide tools also remove their schemas before the model can call them. |
|
||||
|
||||
## Known Limitations and Deferred Work
|
||||
|
||||
Reference in New Issue
Block a user