@arnilo/prism 0.5.6 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/CHANGELOG.md +81 -5
  2. package/README.md +10 -10
  3. package/dist/agent-approval.js +7 -6
  4. package/dist/agent-definitions.js +1 -0
  5. package/dist/agent-loops.js +51 -12
  6. package/dist/agent-run-lifecycle.js +11 -0
  7. package/dist/agent-run-state.d.ts +6 -0
  8. package/dist/agent-run-state.js +29 -9
  9. package/dist/agent-session/session/assemble.js +33 -2
  10. package/dist/agent-session/session/persist.js +6 -2
  11. package/dist/agent-session/session/tool-round.js +1 -0
  12. package/dist/agent-session/session/types.d.ts +10 -0
  13. package/dist/agent-session/session.d.ts +15 -0
  14. package/dist/agent-session/session.js +59 -4
  15. package/dist/agent-tool-dispatch.js +5 -4
  16. package/dist/artifacts.d.ts +39 -1
  17. package/dist/artifacts.js +73 -0
  18. package/dist/attention-compiler.d.ts +121 -0
  19. package/dist/attention-compiler.js +479 -0
  20. package/dist/cli-init.js +20 -6
  21. package/dist/content.d.ts +3 -16
  22. package/dist/content.js +9 -99
  23. package/dist/context-budget.d.ts +32 -2
  24. package/dist/context-budget.js +51 -19
  25. package/dist/contracts-core/agent.d.ts +18 -0
  26. package/dist/contracts-core/agent.js +4 -1
  27. package/dist/contracts-core/attention.d.ts +66 -0
  28. package/dist/contracts-core/attention.js +2 -0
  29. package/dist/contracts-core/compaction.d.ts +59 -0
  30. package/dist/contracts-core/compaction.js +77 -1
  31. package/dist/contracts-core/provider.d.ts +4 -0
  32. package/dist/contracts-core.d.ts +1 -0
  33. package/dist/contracts-core.js +1 -0
  34. package/dist/contracts-protocol.d.ts +29 -0
  35. package/dist/contracts-run-state.d.ts +6 -0
  36. package/dist/host-composition.d.ts +78 -0
  37. package/dist/host-composition.js +248 -0
  38. package/dist/index.d.ts +11 -8
  39. package/dist/index.js +6 -5
  40. package/dist/input.d.ts +19 -1
  41. package/dist/input.js +52 -2
  42. package/dist/media-types.d.ts +34 -0
  43. package/dist/media-types.js +158 -0
  44. package/dist/pinned-fetch.d.ts +2 -2
  45. package/dist/pinned-fetch.js +11 -12
  46. package/dist/redaction.js +74 -1
  47. package/dist/secure-agent.d.ts +2 -0
  48. package/dist/secure-agent.js +6 -1
  49. package/dist/session-stores.d.ts +11 -0
  50. package/dist/session-stores.js +23 -8
  51. package/dist/tool-result-fold.d.ts +12 -0
  52. package/dist/tool-result-fold.js +13 -6
  53. package/dist/tools.d.ts +10 -0
  54. package/dist/tools.js +41 -0
  55. package/docs/acp-agent.md +42 -11
  56. package/docs/acp.md +3 -2
  57. package/docs/ag-ui.md +9 -5
  58. package/docs/agent-definitions.md +9 -1
  59. package/docs/agent-events.md +6 -1
  60. package/docs/agent-loops.md +1 -1
  61. package/docs/agent-session-runtime.md +9 -7
  62. package/docs/attention-compiler.md +272 -0
  63. package/docs/browser-automation.md +5 -2
  64. package/docs/cli-rpc.md +4 -2
  65. package/docs/coding-agent-tools.md +1 -1
  66. package/docs/coding-security.md +5 -3
  67. package/docs/coding-tools.md +1 -1
  68. package/docs/coding-workspaces.md +22 -0
  69. package/docs/compaction-and-retry.md +36 -4
  70. package/docs/compaction-observational-memory.md +62 -9
  71. package/docs/context-and-skills.md +4 -2
  72. package/docs/contributing.md +37 -0
  73. package/docs/conversations.md +1 -1
  74. package/docs/core.md +2 -0
  75. package/docs/dev-inspector.md +4 -0
  76. package/docs/device-adapters.md +1 -0
  77. package/docs/document-reader.md +12 -2
  78. package/docs/documents.md +11 -3
  79. package/docs/enterprise-postgres-state.md +2 -2
  80. package/docs/evaluations.md +168 -4
  81. package/docs/execution-timeline.md +180 -0
  82. package/docs/graft.md +3 -1
  83. package/docs/history/0.7.0-primitive-review.md +254 -0
  84. package/docs/history/migration-0.0.md +2 -2
  85. package/docs/history/release-handoffs.md +70 -1
  86. package/docs/host-compositions.md +147 -0
  87. package/docs/host-security.md +2 -2
  88. package/docs/hosted-sandboxes.md +94 -0
  89. package/docs/index.md +73 -41
  90. package/docs/input-and-prompt-assembly.md +5 -4
  91. package/docs/knowledge-sync.md +84 -0
  92. package/docs/language-intelligence.md +2 -2
  93. package/docs/live-testing.md +4 -1
  94. package/docs/mcp-tools.md +2 -1
  95. package/docs/memory-fabric.md +416 -0
  96. package/docs/migrate-to-0.5.md +8 -3
  97. package/docs/migrate-to-0.6.md +90 -0
  98. package/docs/migrate-to-0.7.md +345 -0
  99. package/docs/migration.md +43 -1
  100. package/docs/model-registry.md +1 -1
  101. package/docs/model-routing.md +79 -4
  102. package/docs/multi-agent-patterns.md +20 -6
  103. package/docs/multimodal-content.md +1 -1
  104. package/docs/obscura.md +3 -1
  105. package/docs/observability.md +52 -1
  106. package/docs/operations.md +13 -1
  107. package/docs/options-index.md +298 -0
  108. package/docs/peer-dependencies.md +96 -0
  109. package/docs/performance.md +34 -2
  110. package/docs/ponytail.md +2 -0
  111. package/docs/postgres-persistence.md +3 -1
  112. package/docs/process-sessions.md +3 -1
  113. package/docs/prompt-registry.md +1 -1
  114. package/docs/provider-caching.md +4 -2
  115. package/docs/provider-conformance.md +2 -2
  116. package/docs/provider-packages.md +23 -23
  117. package/docs/provider-primitives.md +2 -1
  118. package/docs/providers/ai-sdk.md +5 -2
  119. package/docs/providers/bedrock.md +71 -7
  120. package/docs/providers/openai.md +1 -1
  121. package/docs/public-contracts.md +2 -2
  122. package/docs/rag.md +24 -8
  123. package/docs/realtime-voice.md +87 -0
  124. package/docs/release-and-install.md +78 -56
  125. package/docs/runs-and-usage.md +3 -2
  126. package/docs/server.md +6 -4
  127. package/docs/session-stores.md +3 -1
  128. package/docs/speech.md +2 -0
  129. package/docs/sqlite-persistence.md +2 -0
  130. package/docs/supervisors.md +33 -5
  131. package/docs/testing.md +38 -0
  132. package/docs/thinking-and-reasoning.md +3 -1
  133. package/docs/tools.md +7 -6
  134. package/docs/web-tools.md +2 -1
  135. package/docs/wiki.md +1 -1
  136. package/docs/work-artifacts-and-review.md +14 -4
  137. package/docs/work-connectors.md +3 -1
  138. package/docs/work-tools.md +14 -4
  139. package/docs/workflows.md +69 -1
  140. package/docs/working-and-semantic-memory.md +25 -14
  141. package/package.json +5 -5
  142. package/templates/README.md +2 -0
  143. package/templates/business-worker/README.md.tmpl +19 -0
  144. package/templates/business-worker/env.example.tmpl +1 -0
  145. package/templates/business-worker/gitignore.tmpl +11 -0
  146. package/templates/business-worker/manifest.json +11 -0
  147. package/templates/business-worker/package.json.tmpl +23 -0
  148. package/templates/business-worker/src/agent.ts.tmpl +92 -0
  149. package/templates/business-worker/src/index.ts.tmpl +13 -0
  150. package/templates/business-worker/src/tests/agent.test.ts.tmpl +77 -0
  151. package/templates/business-worker/tsconfig.json.tmpl +15 -0
  152. package/templates/personal-assistant/README.md.tmpl +18 -0
  153. package/templates/personal-assistant/env.example.tmpl +1 -0
  154. package/templates/personal-assistant/gitignore.tmpl +11 -0
  155. package/templates/personal-assistant/manifest.json +11 -0
  156. package/templates/personal-assistant/package.json.tmpl +23 -0
  157. package/templates/personal-assistant/src/agent.ts.tmpl +65 -0
  158. package/templates/personal-assistant/src/index.ts.tmpl +13 -0
  159. package/templates/personal-assistant/src/tests/agent.test.ts.tmpl +28 -0
  160. package/templates/personal-assistant/tsconfig.json.tmpl +15 -0
package/dist/tools.d.ts CHANGED
@@ -56,5 +56,15 @@ export interface ToolRegistryOptions extends DuplicateRegistrationOptions {
56
56
  }
57
57
  export declare function createToolRegistry(tools?: readonly ToolDefinition[], options?: ToolRegistryOptions): ToolRegistry;
58
58
  export declare function filterTools(tools: readonly ToolDefinition[], filter?: ToolFilterInput): readonly ToolDefinition[];
59
+ /** Cap matches tool-search index; run allow-lists never exceed the disclosed set. */
60
+ export declare const HARD_RUN_TOOL_NAMES = 1024;
61
+ /**
62
+ * Per-run allow-list. Omitted grant → unchanged list. Checkpointed grant cannot widen.
63
+ * Fresh unknown names fail closed; resume drops names the current registry no longer has.
64
+ */
65
+ export declare function selectRunTools(listed: readonly ToolDefinition[], requested: readonly string[] | undefined, checkpoint?: readonly string[]): {
66
+ readonly tools: readonly ToolDefinition[];
67
+ readonly grant: readonly string[] | undefined;
68
+ };
59
69
  export declare function dispatchToolCall(options: DispatchToolCallOptions): Promise<ToolResult>;
60
70
  export declare function resolveToolEffectDeclaration(tool: ToolDefinition, args: JsonObject, context: ToolExecutionContext): ToolEffectDeclaration | undefined;
package/dist/tools.js CHANGED
@@ -58,6 +58,47 @@ export function filterTools(tools, filter) {
58
58
  .filter((item) => Boolean(item));
59
59
  return tools.filter((tool) => !denied.has(tool.name) && allows.every((allow) => allow.has(tool.name)));
60
60
  }
61
+ /** Cap matches tool-search index; run allow-lists never exceed the disclosed set. */
62
+ export const HARD_RUN_TOOL_NAMES = 1024;
63
+ const MAX_RUN_TOOL_NAME_CHARS = 256;
64
+ function assertRunToolNames(names) {
65
+ if (names.length > HARD_RUN_TOOL_NAMES) {
66
+ throw new TypeError(`RunOptions.toolNames exceeds ${HARD_RUN_TOOL_NAMES} entries`);
67
+ }
68
+ const out = [];
69
+ const seen = new Set();
70
+ for (const name of names) {
71
+ if (typeof name !== "string" || name.length === 0 || name.length > MAX_RUN_TOOL_NAME_CHARS) {
72
+ throw new TypeError(`RunOptions.toolNames entries must be non-empty strings of at most ${MAX_RUN_TOOL_NAME_CHARS} characters`);
73
+ }
74
+ if (!seen.has(name)) {
75
+ seen.add(name);
76
+ out.push(name);
77
+ }
78
+ }
79
+ return out;
80
+ }
81
+ /**
82
+ * Per-run allow-list. Omitted grant → unchanged list. Checkpointed grant cannot widen.
83
+ * Fresh unknown names fail closed; resume drops names the current registry no longer has.
84
+ */
85
+ export function selectRunTools(listed, requested, checkpoint) {
86
+ const req = requested === undefined ? undefined : assertRunToolNames(requested);
87
+ const grant = checkpoint === undefined ? req : req === undefined ? checkpoint : req.filter((name) => checkpoint.includes(name));
88
+ if (grant === undefined)
89
+ return { tools: listed, grant };
90
+ if (grant.length === 0)
91
+ return { tools: [], grant };
92
+ const available = new Set(listed.map((tool) => tool.name));
93
+ if (checkpoint === undefined) {
94
+ for (const name of grant) {
95
+ if (!available.has(name))
96
+ throw new TypeError(`Unknown run tool: ${name}`);
97
+ }
98
+ return { tools: filterTools(listed, { allow: grant }), grant };
99
+ }
100
+ return { tools: filterTools(listed, { allow: grant.filter((name) => available.has(name)) }), grant };
101
+ }
61
102
  function toolExecutionMetadata(startedAt, status) {
62
103
  return { durationMs: Math.max(0, Date.now() - Date.parse(startedAt)), status };
63
104
  }
package/docs/acp-agent.md CHANGED
@@ -24,22 +24,36 @@ The config file is the trust boundary: unknown keys are rejected (a typo cannot
24
24
  | --- | --- | --- |
25
25
  | `userId` | yes | Ownership user id for every session (single-local-user `authorize`). |
26
26
  | `cwd` | yes | Workspace root the coding tools are bound to (must be an existing directory). Sessions always operate on this root — a client-supplied `cwd` never moves the tools. |
27
- | `sessionStore` | no | `{ "type": "sqlite", "path": ".prism/sessions.db" }` or `{ "type": "memory" }` (default). SQLite persists sessions, runs, checkpoints, and leases (`createSqlitePersistence`). |
28
- | `mcp.allow` | no | MCP allow-list. http/sse servers must have a `url` starting with an allow entry; stdio servers require the marker `"stdio"`. The UNSTABLE `acp` transport is never approved. |
27
+ | `model` | yes* | Model selection `{ "provider": "<name>", "model": "<id>" }`. Supported providers: `openai`, `anthropic`, `google`, `deepseek`, `openrouter`, `ollama`, `xai`, `zai`, `alibaba`, `kimi`, `clinepass`, `commandcode`, `neuralwatt`, `opencode-go`, `hyper`, and `mock`. *Required unless `provider` is passed programmatically. |
28
+ | `credentialRef` | yes* | Reference (env var name or host secret identifier) used to resolve API credentials. *Required when using a non-mock provider without an injected provider instance. |
29
+ | `sessionStore` | no | `{ "type": "sqlite", "path": ".prism/sessions.db" }` or `{ "type": "memory" }` (default). SQLite persists sessions, runs, checkpoints, and leases (`createSqlitePersistence`), and supports session agent reconstruction across restarts. |
30
+ | `mcp.allow` | no | MCP allow-list. http/sse servers must match an allow origin or path-segment subtree; stdio servers require the marker `"stdio"`. The UNSTABLE `acp` transport is never approved. |
29
31
  | `modes` | no | Mode table `{ "modes": [{ "id", "name", "description?" }], "defaultModeId"? }`; ids unique, `defaultModeId` must name a mode. |
30
32
  | `configOptions` | no | `{ "options": [{ "type": "boolean" \| "select", "id", "name", "defaultValue", ... }] }`; ids unique. Select options are advertised/settable per the B3 gate (see [acp.md](acp.md)). |
31
33
  | `limits` | no | AG-UI/ACP caps passthrough (`AgUiLimitOptions`). |
32
34
 
33
- Example:
35
+ Real-provider example:
34
36
 
35
37
  ```json
36
38
  {
37
39
  "userId": "local",
38
40
  "cwd": ".",
41
+ "model": { "provider": "openai", "model": "gpt-4o" },
42
+ "credentialRef": "OPENAI_API_KEY",
39
43
  "sessionStore": { "type": "sqlite", "path": ".prism/sessions.db" },
40
44
  "mcp": { "allow": ["https://mcp.example.com"] },
41
45
  "modes": { "modes": [{ "id": "edit", "name": "Edit" }], "defaultModeId": "edit" },
42
- "configOptions": [{ "type": "boolean", "id": "verbose", "name": "Verbose", "defaultValue": false }]
46
+ "configOptions": { "options": [{ "type": "boolean", "id": "verbose", "name": "Verbose", "defaultValue": false }] }
47
+ }
48
+ ```
49
+
50
+ Explicit offline mock mode example:
51
+
52
+ ```json
53
+ {
54
+ "userId": "local",
55
+ "cwd": ".",
56
+ "model": { "provider": "mock", "model": "mock" }
43
57
  }
44
58
  ```
45
59
 
@@ -48,31 +62,48 @@ Example:
48
62
  The binary is pure wiring (~200 lines) — no protocol code lives here. It builds:
49
63
 
50
64
  - `authorize` — single local user; every inbound call is scoped by session id.
51
- - `sessionFactory` — real Prism sessions over `createAgent` with the nine coding tools (`createCodingTools(config.cwd)`), durable `runState` (`interruptBeforeTool`, checkpoints), ownership-scoped to `userId`.
52
- - `lifecycle` — `createAgentRunLifecycle` over the same checkpoint store, so approvals suspend/resume durably.
53
- - `mcp` — allow-list `select` gate with http/sse transports.
65
+ - `sessionFactory` — real Prism sessions over `createAgent` with the nine coding tools (`createCodingTools(config.cwd)`), durable `runState` (`interruptBeforeTool`, checkpoints), ownership-scoped to `userId`. When the client advertises filesystem capabilities, a per-session agent with client-backed buffer tools is constructed and bound to the session id.
66
+ - `lifecycle` — `createAgentRunLifecycle` over the same checkpoint store. Durable SQLite checkpoints enable interrupted runs and approval state to be reconstructed across restarts with the selected model and provider intact.
67
+ - `mcp` — allow-list `select` gate with origin- and path-segment subtree checking for http/sse transports.
54
68
  - `modes` / `configOptions` — from config.
55
- - Provider — **mock by default** (full lifecycle, no tokens). Wire a real provider programmatically:
69
+ - Provider — **fail closed before startup**. Unlike earlier releases where missing configuration silently defaulted to mock mode (Trap C), Prism 0.7.0 requires either an explicit `model` in config or an injected provider. For real providers, credentials are resolved lazily on demand via dynamic import of `@arnilo/prism-providers/<adapter>`. Offline mock mode must be explicitly specified (`model: { provider: "mock", model: "mock" }`).
70
+
71
+ Programmatic usage:
56
72
 
57
73
  ```ts
58
74
  import { createSpawnableAgent, loadConfig } from "@arnilo/prism-acp-agent";
59
- import { createOpenAIResponsesProvider } from "@arnilo/prism-providers/openai";
60
75
 
76
+ // Driven by config with optional custom credential resolver
61
77
  const agent = createSpawnableAgent({
62
78
  config: loadConfig("prism-acp-agent.json"),
63
- provider: createOpenAIResponsesProvider({ apiKey: process.env.OPENAI_API_KEY }),
79
+ credentialResolver: (ref) => process.env[ref],
80
+ });
81
+
82
+ // Or programmatic provider override (must match config.model.provider)
83
+ const customAgent = createSpawnableAgent({
84
+ config: loadConfig("prism-acp-agent.json"),
85
+ provider: customProvider,
64
86
  });
65
87
  ```
66
88
 
67
89
  ## Library surface
68
90
 
69
91
  - `loadConfig(path)` / `parseConfig(text, baseDir)` — read + validate; throw `ConfigError` (code `PRISM_ACP_AGENT_CONFIG`) with a clear message.
70
- - `createSpawnableAgent({ config, provider? })` — build the ACP `AgentApp`.
92
+ - `createSpawnableAgent({ config, provider?, model?, credentialResolver? })` — build the ACP `AgentApp`.
71
93
  - `selectMcpServers(allow, servers)` — the allow-list gate, exported for reuse in custom hosts.
94
+ - `SUPPORTED_PROVIDERS` — list of supported provider adapter identifiers.
72
95
 
73
96
  ## Security posture
74
97
 
75
98
  - Config file = trust boundary: validated shape, no arbitrary code execution.
76
99
  - MCP servers only from the allow-list; the UNSTABLE `acp` transport is never bridged.
100
+ - **Origin and path-segment destination matching**:
101
+ - Exact origin matching normalizes scheme, hostname (punycode IDN), and effective port (e.g. 443 on https). Lookalike hosts (`https://mcp.example.com.attacker.invalid`) are strictly rejected.
102
+ - Path matching enforces exact path or path-segment subtree: `https://mcp.example.com/mcp` admits `/mcp`, `/mcp/`, and `/mcp/sub`, but rejects `/mcp-other` and `/other`.
103
+ - Config entries with credentials (`user:pass@`), query parameters, fragment identifiers, or ambiguous path forms (`%2e%2e`, `%2f`, `%5c`, `..`, backslashes) fail validation.
104
+ - Candidate URLs embedding credentials or ambiguous encoded path forms fail closed at selection.
105
+ - Stdio servers require the explicit `"stdio"` marker; URL entries cannot authorize `stdio` processes, and `"stdio"` cannot authorize remote servers.
77
106
  - Coding tools are bound to `config.cwd` only; session ownership is fixed to `userId`.
78
107
  - Session store paths are resolved against the config directory and fail closed on invalid config.
108
+ - **Credential isolation**: Secret values never enter config persistence, argv flags, stdout protocol streams, events, or model context. Identity carries only the non-secret `credentialRef` name.
109
+ - **Trust model**: The ACP agent config is designed as a single-local-user trust boundary (workstation / editor agent), not a multi-tenant business boundary. Cross-tenant credential sharing or multi-user elevation must not be multiplexed through a single spawnable ACP agent process.
package/docs/acp.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- `@arnilo/prism-ag-ui/acp` (stable ACP **v1**, `@agentclientprotocol/sdk@1.3.0` root exports only) exposes two adapters:
5
+ `@arnilo/prism-ag-ui/acp` (stable ACP **v1**, `@agentclientprotocol/sdk@1.4.0` root exports only) exposes two adapters:
6
6
 
7
7
  - `createPrismAcpAgent(options)` — serves ACP as an **agent**: an editor/AI client connects through the SDK transport and drives host-owned Prism sessions with `session/new`, `session/load`, `session/resume`, `session/prompt`, `session/set_mode`, `session/set_config_option`, `session/list`, `session/delete`, `session/close`, and `session/cancel`. The agent is a thin protocol adapter: every capability, decision, and byte cap is wired from host seams, and there is **no second policy engine** on the agent side.
8
8
  - `createAcpEventMapper(options)` — maps a Prism `AgentEvent` stream (or `CoWorkEvent`) to ACP `SessionUpdate`s for hosts that stream through their own transport.
@@ -57,6 +57,7 @@ In-stream `SessionUpdate`s:
57
57
  | `permission_denied` lifecycle | `tool_call_update` status `failed` (never raw args; synthesized id `prism:denied:<approvalId>` when no `toolCallId`) |
58
58
  | `configuration_changed` lifecycle | `config_option_update` with the full current set, per streaming session |
59
59
  | Plan lifecycle (F5, UNSTABLE-gated) | `plan_changed` → `plan_update` with `plan: { type: "items", planId = planPath, entries: [{ content, priority: "medium", status }] }` — the complete entry list per update (client replaces its plan wholesale); `plan_removed` → `plan_removed` with `planId = planPath`. Emitted only when the client advertised `ClientCapabilities.plan`; mapper stays capability-agnostic (gate in the agent wiring). Entries come from `writeCodingPlanFile`'s `onEvent` (parsed via `parseCodingPlanTodos`) or host-emitted through their `CodingLifecycleEmitter`; text passes the shared redactor and byte caps. |
60
+ | Subagent lifecycle | `subagent_started` / `subagent_stopped` → `agent_message_chunk` with only redacted child/delegation ids and terminal status. Wire `observeSupervisorLifecycle()` to emit them; child inputs, outputs, paths, and error text stay absent. |
60
61
  | Session title (F6) | `sessions.title({ sessionId, prompt, signal })` resolves on `session/prompt`; a defined value differing from the last emitted title produces `session_info_update` with `{ sessionUpdate: "session_info_update", title }`. Best-effort: `undefined` or a throw means no title and no update (requests never fail on titles); the host owns title storage. Titles pass the shared redactor and are truncated at `maxTextBytes`/`maxEventBytes`. |
61
62
  | Slash commands (F9) | `commands.list({ sessionId, signal })` on `session/new`/`load`/`resume` produces `available_commands_update` with `{ name, description, input?: { hint } }` (SDK `AvailableCommand`; description is required). Names/descriptions/hints pass the shared redactor and `maxTextBytes`; the list is sliced at `acpCommandsPerUpdate`. Best-effort: a throw or non-array omits the update (session start never fails on commands). |
62
63
  | Session mode/config switch | `current_mode_update` / `config_option_update` |
@@ -115,7 +116,7 @@ const agent = createPrismAcpAgent({
115
116
  - **Client fs/terminal are adapters, not a second implementation.** `AcpClientFilesystem` / `AcpClientTerminals` wrap the client's `fs/*` and `terminal/*` methods behind the Phase 9 `ProcessSession`-flavored interfaces; the agent pre-generates the session id so terminal requests can carry it. `createAcpFilesystemOperations` from `@arnilo/prism-coding-tools/agent` maps that filesystem seam onto the coding tools' `read`/`write`/`edit` operations. This editor-buffer mode is intentionally hybrid: `repo_list`, `repo_search`, `glob`, `delete`, and `move` remain disk-backed unless the host supplies separate operations; binary/image/document handling never falls back to local disk. Host repo operations remain default when the client fs is absent.
116
117
  - **Spawnable ACP coding registry (Task 6).** `@arnilo/prism-acp-agent` wires `createAcpClientFilesystem` and creates a separate coding tool registry per ACP session when the client advertises `fs/read_text_file` or `fs/write_text_file`. That session's `read`/`write`/`edit` operations use editor buffers; without fs advertisement, the existing disk registry is used. `shell`, repository search/list/glob, `delete`, and `move` remain disk-backed in this hybrid mode. Durable approvals resolve the same per-session agent, so one session cannot resume through another session's buffer adapter.
117
118
  - **Modes and config options are a pure host overlay.** The agent stores only a thin per-session registry; `apply`/`onChange` hooks narrow the host's own behavior. Mode switches can narrow or host-authorized widen — never a parallel policy evaluator, never a client-enabled tool.
118
- - **Lifecycle wiring.** Pass your `createCodingLifecycleEmitter()` as `coding.lifecycle`; `file_changed` etc. then flow to streaming sessions. `configuration_changed` broadcasts `config_option_update` (agent-message fallback if the SDK rejects the kind).
119
+ - **Lifecycle wiring.** Pass your `createCodingLifecycleEmitter()` as `coding.lifecycle`; `file_changed` etc. then flow to streaming sessions. Call `observeSupervisorLifecycle(supervisor, { onEvent: lifecycle.emit })` for redacted child start/stop milestones. `configuration_changed` broadcasts `config_option_update` (agent-message fallback if the SDK rejects the kind).
119
120
  - **Stream budgets.** Every lifecycle update counts against the same per-run stream event/byte budget as prompt updates; overflowing closes the update, never the run.
120
121
 
121
122
  ### Persistence and ownership
package/docs/ag-ui.md CHANGED
@@ -1,11 +1,13 @@
1
1
  # Frontend interoperability (AG-UI and ACP)
2
2
 
3
+ > **Required peer install:** `zod` (the pinned ACP SDK peers it) — see [Optional peer dependencies](peer-dependencies.md).
4
+
3
5
  ## What it does
4
6
 
5
7
  `@arnilo/prism-ag-ui` is an optional, framework-free protocol adapter over Prism's existing redacted `AgentEvent`, session, durable-run, and persistence seams.
6
8
 
7
9
  - Root export maps Prism events to AG-UI `@ag-ui/core` **0.0.59** events and offers `createAgUiHandler()` (`Request` → SSE `Response`), compatible `createPersistenceAgUiReplay()` pages, distributed `createAgentEventSourceAgUiReplay()` follow, and explicit `createAgUiMcpAdapter()` / `createAgUiMcpAppHandler()` / `createAgUiA2AAdapter()` protocol handshakes.
8
- - `@arnilo/prism-ag-ui/acp` is the stable ACP **v1** sibling: `createAcpEventMapper()` and `createPrismAcpAgent()` over `@agentclientprotocol/sdk` **1.3.0** root exports. ACP is a protocol adapter — sessions, modes, MCP, fs/terminal, lifecycle mapping, and caps live on the host seams. See [ACP coding-host interop](acp.md) for the full reference; this page covers AG-UI only.
10
+ - `@arnilo/prism-ag-ui/acp` is the stable ACP **v1** sibling: `createAcpEventMapper()` and `createPrismAcpAgent()` over `@agentclientprotocol/sdk` **1.4.0** root exports. ACP is a protocol adapter — sessions, modes, MCP, fs/terminal, lifecycle mapping, and caps live on the host seams. See [ACP coding-host interop](acp.md) for the full reference; this page covers AG-UI only.
9
11
  - Core remains protocol-free. `resumeAgentRunStream()` / `AgentRunLifecycle.resumeStream()` are generic durable-resume streams shared by adapters.
10
12
 
11
13
  ## When to use it
@@ -38,7 +40,7 @@ npm install @arnilo/prism @arnilo/prism-ag-ui
38
40
  | `projection` | Explicit safe tool/state/messages/activity/reasoning/raw/custom/interrupt projection. Omit each callback for default deny. Prefer `composeAgUiProjections(createMessagesFromSessionProjection(...), createStateFromStoreProjection(...), createActivityFromToolProgressProjection(), host)` for standard families. |
39
41
  | `a2ui` | Opt-in A2UI painting middleware (`{ catalogId, mode, renderToolName?, allowedCatalogIds?, limits? }`). Detects `a2ui_operations` tool results and/or streams from `render_a2ui` args; paints `a2ui-surface` activity events. Absent = inert. |
40
42
  | `capabilities` | Optional host declaration narrowed to implemented SSE/projector/lifecycle features; read `handler.capabilities`. |
41
- | `redactor`, `limits` | Host redaction and narrowing-only finite caps. |
43
+ | `redactor`, `limits` | Host redaction and narrowing-only finite caps (`AgUiLimitOptions`, with its A2UI variant). |
42
44
 
43
45
  The handler accepts only `POST` JSON validated with official AG-UI `RunAgentInputSchema`. Every aggregate is bounded before a callback runs. With no `input.project`, it preserves compatibility: final text user message only; non-empty state or frontend tools fail before authorization/session lookup. With a projector, all current roles/history, context, state, forwarded props, multimodal parts, parent lineage, and tool-result continuations are available as untrusted input. The projector must apply Prism media URL/SSRF/MIME policy before forwarding media. Start a run with no `resume` and no `?cursor=`; replay supplies `?cursor=`.
44
46
 
@@ -46,9 +48,9 @@ The handler accepts only `POST` JSON validated with official AG-UI `RunAgentInpu
46
48
 
47
49
  The handler returns `text/event-stream`, one `data: <AG-UI event>\n\n` frame per output. Mapper lifecycle is ordered: `RUN_*`, `STEP_*`, `TEXT_MESSAGE_*`, and `TOOL_CALL_*` are deterministic Prism mappings. Host projectors may additionally prove and emit `STATE_SNAPSHOT`/`STATE_DELTA`, `MESSAGES_SNAPSHOT`, `ACTIVITY_*`, current `REASONING_*`, `RAW`, and named `CUSTOM` values.
48
50
 
49
- `delegated_agent_step` maps by default to bounded `ACTIVITY_SNAPSHOT` metadata with activity type `prism.delegated_agent_step`; `includeCustomEvents: true` also emits `CUSTOM prism.delegated_agent_step`. The safe payload contains adapter/conversation identifiers, step index/state/kind, duration, token counts, tool/subagent names, and opaque detail references only. Normal assistant text remains `TEXT_MESSAGE_*`; delegated events never duplicate transcript text. Raw event bodies, tool arguments/results, paths, URIs, logs, and hidden thought text remain absent unless a host explicitly supplies a projection. All values revalidate against official `EventSchemas`; deprecated `THINKING_*` and convenience chunk events are not produced. Active message/tool/reasoning/step sequences close before error, interruption, or finish.
51
+ `delegated_agent_step` maps by default to bounded `ACTIVITY_SNAPSHOT` metadata with activity type `prism.delegated_agent_step`; `includeCustomEvents: true` also emits `CUSTOM prism.delegated_agent_step`. The safe payload contains adapter/conversation identifiers, step index/state/kind, duration, token counts, tool/subagent names, and opaque detail references only. Coding hosts can supply `observeSupervisorLifecycle(..., { delegatedAgentStep })` output here for supervisor child activity. Normal assistant text remains `TEXT_MESSAGE_*`; delegated events never duplicate transcript text. Raw event bodies, tool arguments/results, paths, URIs, logs, and hidden thought text remain absent unless a host explicitly supplies a projection. All values revalidate against official `EventSchemas`; deprecated `THINKING_*` and convenience chunk events are not produced. Active message/tool/reasoning/step sequences close before error, interruption, or finish.
50
52
 
51
- A Prism durable `agent_suspended` returns `RUN_FINISHED` with core interrupt id `${runId}:${version}` and a strict `{ decision: "approve" | "deny" }` schema. `projection.interrupt` may attach bounded expiry/metadata or additional host policy interrupts but must retain that core id. Without `interrupts.resume`, one exact entry is required; `cancelled` means deny. An aggregate policy may validate bounded multiple entries, then returns one current-version core decision. Payloads containing `editedArgs`/`args` always deny: Prism does not mutate persisted tool calls. The adapter checks host authorization, selected run, suspended status, and checkpoint version, then calls `AgentRunLifecycle.resumeStream()` once. Claimed/dispatched tools are never replayed.
53
+ A Prism durable `agent_suspended` returns `RUN_FINISHED` with core interrupt id `${runId}:${version}` and a strict `{ decision: "approve" | "deny" }` schema (extended with `editedArgs`/`modifiedArguments`, `approvalId`, and `reason` when `capabilities.humanInTheLoop.approveWithEdits` is enabled). `projection.interrupt` may attach bounded expiry/metadata or additional host policy interrupts but must retain that core id. Without `interrupts.resume`, one exact entry is required; `cancelled` means deny. An aggregate policy may validate bounded multiple entries, then returns one current-version core decision. When `approveWithEdits` is configured, validated client edits map directly to core `RunDecision` with `outcome: "allow_once"` and `modifiedArguments`, executing revalidation against tool schemas under single atomic CAS. When `approveWithEdits` is omitted or false (default), payloads containing `editedArgs`/`args`/`modifiedArguments` safely fail closed and deny. The adapter checks host authorization, selected run, suspended status, and checkpoint version, then calls `AgentRunLifecycle.resumeStream()` once. Claimed/dispatched tools are never replayed.
52
54
 
53
55
  `createPersistenceAgUiReplay()` remains a compatible page adapter. `createAgentEventSourceAgUiReplay()` resolves exact ownership/run once per open, then consumes the shared durable source through terminal or live follow; it never attaches replica-local `session.subscribe()`. Every record must already be redacted. Mapped events carry stable `prismEventId` and bounded opaque `prismCursor`; records with no standard mapping emit `CUSTOM prism.replay_cursor`, so clients can persist progress. Terminal replay never creates a session or reruns a provider/tool.
54
56
 
@@ -82,7 +84,9 @@ Co-work uses bounded, redacted `CUSTOM prism.cowork.*` events through `mapCoWork
82
84
 
83
85
  ## Request/response example
84
86
 
85
- Resume a default single interrupt with `resume: [{ "interruptId": "run-1:4", "status": "resolved", "payload": { "decision": "approve" } }]`. Full history, client tool results, and mutable state need authorized `input.project` selection. This adapter is not a conversation database.
87
+ Resume a default single interrupt with `resume: [{ "interruptId": "run-1:4", "status": "resolved", "payload": { "decision": "approve" } }]`.
88
+ When `approveWithEdits` is advertised and configured, clients can approve with edited arguments: `resume: [{ "interruptId": "run-1:4", "status": "resolved", "payload": { "decision": "approve", "editedArgs": { "path": "approved.txt" } } }]` (or `modifiedArguments`). The edits map directly to core `RunDecision` (`outcome: "allow_once"`, `modifiedArguments`), validated against the tool schema under CAS `expectedVersion`.
89
+ Full history, client tool results, and mutable state need authorized `input.project` selection. This adapter is not a conversation database.
86
90
 
87
91
  ## Implementation example
88
92
 
@@ -30,11 +30,12 @@ Do not use the bundle loader to discover providers — provider/model packages s
30
30
  | `model?` | `ModelConfig` object, or a `"<provider>/<model>"` string resolved through `registries.models`. Optional at authoring time: when omitted, resolution falls back to `context.overrides.model` (host-injected selection); an explicit definition `model` drives registry resolution, and neither present fails closed with `Agent "<name>" has no model`. |
31
31
  | `tools?` | Tool names to activate from the active tool registry / `registries.tools`. Omitted means no active tools unless `activateAllCapabilities: true` is passed for migration. |
32
32
  | `skills?` | Skill names resolved via `resolveActiveSkills()`; omitted means no active skills unless `activateAllCapabilities: true` is passed for migration. `toolNames` enforcement applies at activation. |
33
- | `context?` | Context provider names from `registries.contextProviders`. |
33
+ | `context?` | Context provider names from `registries.contextProviders`. A host registers whichever providers it wants under its own names — for example a memory fabric's provider as `"memory-fabric"`; the definition contract itself carries no provider-specific field (see [Memory fabric](memory-fabric.md)). |
34
34
  | `systemPrompt?` | `SystemPromptConfig` layer (see [System prompts](system-prompts.md)). |
35
35
  | `instructions?` | Base prompt text. |
36
36
  | `loop?` | `AgentLoopStrategy` or `AgentLoopOptions` (see [Agent loops](agent-loops.md)). |
37
37
  | `metadata?` | Free-form metadata. |
38
+ | `attentionCompiler?` | Opt-in attention compiler for the resolved config: `true` for defaults, an `AttentionCompilerOptions` object to tune ratios/depth (see [Attention compiler](attention-compiler.md)). Copied verbatim onto `AgentConfig.attentionCompiler` and nothing else; `RunOptions.attentionCompiler` may later disable or relax it. |
38
39
  | `create?(config?)` | Optional escape hatch. When present, overrides declarative resolution: the helper builds a base `AgentConfig` from the declarative fields, calls `create(config)`, then merges `context.overrides`. |
39
40
 
40
41
  ### `AgentDefinitionResolutionContext` (contract, `@arnilo/prism`)
@@ -116,6 +117,12 @@ instructions: You are a careful coding agent.
116
117
  Prefer minimal diffs. Cite the file you changed.
117
118
  ```
118
119
 
120
+ `context` names are whatever the host registered in `registries.contextProviders` — for example
121
+ `registries.contextProviders.register("memory-fabric", fabric.createContextProvider())` makes
122
+ `context: [memory-fabric]` work, while an unregistered name fails closed at resolution. Nothing about
123
+ the fabric is required by `AgentDefinition`; a host that never registers a provider resolves the same
124
+ definition unchanged.
125
+
119
126
  `discoverAgentBundles({ configRoot })` returns (paths only):
120
127
 
121
128
  ```json
@@ -244,6 +251,7 @@ Use `activateAllCapabilities: true` only while migrating old configs. It intenti
244
251
  - [System prompts](system-prompts.md): `composeSystemPrompt` source ranks and the `AGENT.md` body / `SYSTEM.md` / `AGENTS.md` prompt layering reused by `resolveAgentBundle`.
245
252
  - [Contribution discovery (workspace)](contribution-discovery.md): `discoverContributions` for repo `.agents/` contributions passed as `repoContributions`.
246
253
  - [Context and skills](context-and-skills.md): `resolveActiveSkills` and `RunOptions.activeSkills` activation that consumes discovered skills.
254
+ - [Memory fabric](memory-fabric.md): optional `fabric.createContextProvider()` registered as a `context` name, and `fabric.attach(session)` gating its tools and workers.
247
255
  - [Tools](tools.md): `ToolDefinition` / `(toolNames)` enforcement and host-owned tool execution.
248
256
  - [Agent loops](agent-loops.md): `resolveLoop` and loop strategies passed via `loop` / `context.overrides`.
249
257
  - [Extensions](extensions.md): `registerAgent()` programmatic registration of inert `AgentDefinition` values.
@@ -1,5 +1,7 @@
1
1
  # Agent events
2
2
 
3
+ > **Optional peer install:** `@nats-io/jetstream` + `@nats-io/transport-node` for the JetStream event source — see [Optional peer dependencies](peer-dependencies.md).
4
+
3
5
  ## What it does
4
6
 
5
7
  `AgentEvent` is the single observable stream every `AgentSession` run emits. Subscribers receive normalized, redacted, in-order events covering agent lifecycle, assistant message streaming, delegated-agent activity, tool execution, queue updates, subscriber overflow, compaction, retry, artifact validation/refinement, and terminal errors. The stream is in-memory, live-only, and bounded per subscriber by `SubscribeOptions`; there is no durable queue, no background work, and no extra dependency.
@@ -103,6 +105,8 @@ Agent / turn / message events:
103
105
 
104
106
  Adapters should call `createDelegatedAgentStep({ sessionId, runId, adapterId, externalConversationId, stepIndex, state, kind, usage })` rather than forwarding external JSON. The constructor allow-lists fields and fails closed on malformed or oversized identifiers/counters.
105
107
 
108
+ Coding hosts call `observeSupervisorLifecycle(supervisor, { onEvent, delegatedAgentStep })` to turn supervisor milestones into `subagent_started` / `subagent_stopped` coding lifecycle events. Both carry only redacted `childId`, `delegationId`, and `depth`; stopped events add terminal `AgentRunStatus`. Supplying `delegatedAgentStep` emits the bounded `delegated_agent_step` records AG-UI already maps. Child inputs, outputs, paths, and delegation error text never cross either bridge.
109
+
106
110
  `message_delta.content.type === "tool_call_delta"` carries `{ index, id?, name?, argumentsText? }`. Treat it as a streaming fragment. The runtime reconstructs and persists a final `tool_call` before executing tools. Deltas missing `id`/`name` at stream end fail the provider turn with `ErrorInfo.code: "incomplete_delta"` (typed `ProviderTransportError`); they never throw a bare `Error`. Malformed JSON with id+name present recovers as a blocked tool result (`invalid_json_arguments`) instead.
107
111
 
108
112
  Tool execution events:
@@ -132,6 +136,7 @@ Queue / subscriber / compaction / retry / provider events:
132
136
  | `event_subscriber_overflow` | `sessionId`, `droppedEvents: number`, `maxQueuedEvents: number`, `overflow: "close" \| "drop_oldest" \| "drop_newest"` |
133
137
  | `compaction_started` | `sessionId`, `runId?` |
134
138
  | `compaction_finished` | `sessionId`, `runId?`, `summary: string` |
139
+ | `attention_compiled` | `sessionId`, `runId?`, `used: number`, `usedAfter: number`, `inputCap: number`, `triggerRatio: number`, `droppedThinkingTurns: number`, `stubbedToolResults: number`, `stubbedBytes: number`, `truncated: boolean` — one per mutated turn of the opt-in [attention compiler](attention-compiler.md); counts only, never message text |
135
140
  | `retry_scheduled` | `sessionId`, `runId`, `attempt: number`, `delayMs: number`, `error: ErrorInfo` |
136
141
 
137
142
  Provider turn events (metadata only — see [Observability](observability.md)):
@@ -249,4 +254,4 @@ for await (const event of session.stream("draft", { loop: { strategy: "generate-
249
254
  - [Observability](observability.md): `ProviderTurnMetadata`; optional adapter builds one parented GenAI span tree from metadata-only lifecycle events and ignores message/progress deltas.
250
255
  - [Tools](tools.md): `tool_execution_*` variants.
251
256
  - [Compaction and retry policies](compaction-and-retry.md): `compaction_*` and `retry_scheduled` variants.
252
- - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): optional redacted mapping of this stream; durable replay is ledger-backed and at-least-once, never a live-subscriber substitute. [ACP coding-host interop](acp.md) additionally maps `CodingLifecycleEvent`s from `@arnilo/prism-coding-tools/agent` (`file_changed`, `worktree_changed`, `permission_denied`, `configuration_changed`, `plan_changed`, `plan_removed`; process events reuse `CodingProcessEvent`) into ACP session updates — locations/diff blocks only through projection allow-lists, terminal chunks under `process.outputChunkBytes`, plan updates only to clients that advertised the UNSTABLE `plan` capability.
257
+ - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): optional redacted mapping of this stream; durable replay is ledger-backed and at-least-once, never a live-subscriber substitute. [ACP coding-host interop](acp.md) additionally maps `CodingLifecycleEvent`s from `@arnilo/prism-coding-tools/agent` (`file_changed`, `worktree_changed`, `permission_denied`, `configuration_changed`, `plan_changed`, `plan_removed`, `subagent_started`, `subagent_stopped`; process events reuse `CodingProcessEvent`) into ACP session updates — locations/diff blocks only through projection allow-lists, terminal chunks under `process.outputChunkBytes`, plan updates only to clients that advertised the UNSTABLE `plan` capability.
@@ -233,7 +233,7 @@ await session.run(input, { loop: twoShotLoop });
233
233
  - `ArtifactValidation.errors[].message` may echo model text — `artifact_*` event payloads flow through the same `redactAgentEvent` path as other `AgentEvent`s (see [Agent events](agent-events.md)).
234
234
  - `generateValidateReviseLoop` makes at most `1 + maxRevisions + maxToolRounds` provider turns when bounded tools are enabled (otherwise `maxRevisions + 1`); it cannot loop forever. Each revision costs one provider turn plus one store append.
235
235
  - Bounded artifact tool calls run sequentially through `dispatchToolCall` (permission + validation + execute); their assistant call and result are persisted before the next provider request. `singleShotLoop` retains its bounded parallel worker pool and original call-order transcript behavior.
236
- - In a parallel single-shot batch, the worker pool stops claiming calls after the first dispatch error or abort, waits for every already-claimed worker with `Promise.allSettled`, appends no buffered tool-result rows for a failed batch, then rethrows the first failure. Already-claimed side effects may finish and are not rolled back; successful batches still append results in original call order. The round-level `chargeToolRound` approval gate runs before workers, so approval suspension starts no worker.
236
+ - In a parallel single-shot batch, the worker pool stops claiming calls after the first dispatch error or abort, waits for every already-claimed worker with `Promise.allSettled`, then persists rows in call order before rethrowing the first failure: real results for calls that finished, an error row carrying the failure for the call that threw, and a `tool_call_not_dispatched` row for calls the batch never started. A stopped batch therefore never ends the run with `tool_call` ids that have no `tool_result` (providers reject such a history on the next turn); run-level suspension errors (`AgentRunSuspended`, `ERR_PRISM_DELEGATION_SUSPENDED`, `ERR_PRISM_LOOP_*`) are the exception — their resume machinery appends the real result, so the failed call gets no synthetic row. Already-claimed side effects may finish and are not rolled back; successful batches still append results in original call order. The round-level `chargeToolRound` approval gate runs before workers, so approval suspension starts no worker.
237
237
  - The loop is a plain object/factory; no class hierarchy, no background work, no extra dependencies. `LoopContext` is a single object literal of bound arrows built once per run.
238
238
  - The host-domain-free boundary is guarded by tests: `src/` imports no host-domain package, and the `Artifact*`/`AgentLoop*`/`LoopContext` contracts contain no `workflow`/`node`/`step` field names. Hosts supply their own schema; no host domain type is imported by `src/`.
239
239
 
@@ -39,27 +39,29 @@ createAgentSession(config: AgentSessionConfig & { agent: Agent }): AgentSession
39
39
 
40
40
  `AgentConfig.provider` must contain the host-selected provider. Prism does not resolve providers from hidden globals. Alternatively, set `AgentConfig.providerSource: ProviderResolver` (or override per run with `RunOptions.providerSource`, which wins) to resolve the provider from `model.provider` each run; when `AgentConfig.provider` is set it takes first precedence and the resolver is bypassed. See [Provider layer § Provider resolver](provider-layer.md#provider-resolver).
41
41
 
42
- `session.run(input, options)` accepts the existing Prism input shape:
42
+ `session.run(input, options)` accepts the existing Prism input shape. `RunOptions.toolNames` optionally allow-lists registered tool names for that run (omit = full registry; empty = none). See [Tools](tools.md#per-run-tool-scoping).
43
43
 
44
44
  ```ts
45
45
  string | Message | readonly Message[]
46
46
  ```
47
47
 
48
- `AgentSessionConfig.store` overrides `AgentConfig.store`; otherwise the session gets a private memory store. `AgentSessionConfig.leafId` selects the branch leaf to resume from.
48
+ `AgentSessionConfig.store` overrides `AgentConfig.store`; otherwise the session gets a private memory store. `AgentSessionConfig.leafId` selects the branch leaf to resume from. `AgentSessionConfig.snapshotCacheTtlMs` tunes the in-memory branch cache behind `session.snapshot()`: default `DEFAULT_SNAPSHOT_CACHE_TTL_MS` (1000 ms), `0` disables caching so every read rebuilds from the store, maximum `HARD_MAX_SNAPSHOT_CACHE_TTL_MS` (30 s); values outside `0..hard` fail session construction with `TypeError`. The cache is invalidated by any new leaf or mutation, so the TTL only bounds reuse of an unchanged branch.
49
49
 
50
50
  `AgentConfig.limits` sets run ceilings; `RunOptions.limits` may only narrow configured agent values (`null` counts as no cap, so a configured finite ceiling still wins). Limits cover turns, provider attempts, tool rounds/calls, wall time, request/response bytes, tokens, and optional single-currency cost. Policy axes accept `null` (0.5.4) to disable the axis; request/response bytes reject `null` and stay process-hard at 64 MiB. A breach emits one `run_limit_exceeded` event and throws `AgentRunError` with `result.limit`; see [Runs and usage ledger](runs-and-usage.md#run-limits).
51
51
 
52
52
  `RunOptions.model` can override the request model for a run. Model overrides append a `model_change` entry. `AgentConfig.inputLayout` selects the default input assembly layout (`"cache_aware"` by default, or opt-in `"legacy"`); `RunOptions.inputLayout` wins for one run. `AgentConfig.thinkingLevel` / `RunOptions.thinkingLevel` (run wins) is the session thinking intent — Prism snaps it onto the request after host `providerOptions`. `AgentConfig.providerOptions`/`RunOptions.providerOptions` supply generic provider request options (session/cache/header/compat/extra hints only — provider-level timeout/retry hints were removed in 0.1.5). Kernel construction always stamps `options.sessionId`/`cacheKey` from `session.id` when missing; `createSessionCachePolicy` is an overlay, not required. Use `RunOptions.signal`/host abort controllers for timeouts and `AgentConfig.retry`/`RunOptions.retry` for retry. `AgentConfig.providerRequestPolicies`/`RunOptions.providerRequestPolicies` run before `AIProvider.generate()` and before `provider_request` middleware. `AgentConfig.systemPrompt` and `RunOptions.systemPrompt` add explicit layered system prompt contributions; `RunOptions.systemPrompt: false` disables configured prompt layers for that run while keeping `AgentConfig.instructions` as the base path. `RunOptions.compaction` can enable auto-compaction for that run or use `false` to disable configured auto-compaction. `RunOptions.retry` can enable provider-turn retry for that run or use `false` to disable configured retry. `RunOptions.metadata` is merged with agent/session metadata for assembly, provider requests, and tool contexts. Run tool-round limits via `RunOptions.limits.maxToolRounds`. `RunOptions.signal` is bridged into the per-run abort signal passed to assembly, providers, tools, auto-compaction, and retry backoff.
53
53
 
54
- `RunOptions.activeSkills` selects named skills from a configured `SkillRegistry`; `RunOptions.skills` replaces a plain `Skill[]` config for one run. When `AgentConfig.skills` is a registry and neither is set, **no skills activate** unless `activateAllSkills: true` (run or agent). `skillsDisclosure` (`"progressive"` default, `"eager"` opt-in; run wins) controls catalog vs full instruction bodies; the session-owned `LoadedSkillSet` is populated by `load_skill` when the host registers `createLoadSkillTool`. `toolResultFold` (off unless the host supplies `summarize`) optionally folds aged large tool results in provider input only. See [Context and skills](context-and-skills.md).
54
+ `RunOptions.activeSkills` selects named skills from a configured `SkillRegistry`; `RunOptions.skills` replaces a plain `Skill[]` config for one run. When `AgentConfig.skills` is a registry and neither is set, **no skills activate** unless `activateAllSkills: true` (run or agent). `skillsDisclosure` (`"progressive"` default, `"eager"` opt-in; run wins) controls catalog vs full instruction bodies; the session-owned `LoadedSkillSet` is populated by `load_skill` when the host registers `createLoadSkillTool`. `toolResultFold` (off unless the host supplies `summarize`) optionally folds aged large tool results in provider input only. `AgentConfig.attentionCompiler` (or `true` for defaults) opts into the per-turn attention compiler; `RunOptions.attentionCompiler: false` disables it for one run and an options object may only relax the agent setting — the session resolves it with the run's model before the first provider turn, and keeps one sticky frontier per session so a stub made once stays applied. See [Context and skills](context-and-skills.md) and [Attention compiler](attention-compiler.md).
55
55
 
56
56
  ## Outputs / response / events
57
57
 
58
+ `session.fork(options?)` / `session.clone(options?)` take `AgentSessionForkOptions` / `AgentSessionCloneOptions` (leaf id, new session id, metadata, and store overrides), and `session.steer(input, options?)` takes `SteerOptions`. See [Public contracts](public-contracts.md) for the field tables, and the [options index](options-index.md) for every session option surface.
59
+
58
60
  `session.run()` / `session.prompt()` resolve to an `AgentRunResult` with `sessionId`, `runId`, `status`, `text`, `content`, optional `message`/`usage`/`leafId`, and terminal `error`/`abortReason` when applicable. Callers may ignore the return value. Failed and aborted runs still emit their terminal events, then reject with `AgentRunError` whose `.result` carries the same shape.
59
61
 
60
62
  `session.stream(input, options?)` subscribes first, starts exactly one run, yields only that run's events, and terminates when the run succeeds, fails, or aborts. Early consumer return aborts the owned run and releases the session. `SubscribeOptions.maxQueuedEvents` / `overflow` may be passed alongside `RunOptions`.
61
63
 
62
- `resumeAgentRunStream(agent, ref, resume, options?)` does the same for one existing suspended durable run. It validates checkpoint ownership, revision/fingerprint, and `expectedVersion`, then subscribes before emitting `agent_started` / `agent_resumed` and resumed message/tool/terminal events. `AgentRunResumeStreamOptions` combines existing resume options with `signal`, `maxQueuedEvents`, and `overflow`; early return aborts only resumed execution. It does not replay a claimed/dispatched tool, poll a ledger, or retain a worker. `createAgentRunLifecycle().resumeStream(ref, resume, request?)` adds the same behavior after host agent-capability resolution.
64
+ `resumeAgentRunStream(agent, ref, resume, options?)` does the same for one existing suspended durable run. It validates checkpoint ownership, revision/fingerprint, and `expectedVersion`, then subscribes before emitting `agent_started` / `agent_resumed` and resumed message/tool/terminal events. `AgentRunResumeStreamOptions` combines `AgentRunResumeOptions` (including the optional `onSession` observer seam a supervisor uses to attach a child event pump to the rebuilt session) with `signal`, `maxQueuedEvents`, and `overflow`; early return aborts only resumed execution. It does not replay a claimed/dispatched tool, poll a ledger, or retain a worker. `createAgentRunLifecycle().resumeStream(ref, resume, request?)` adds the same behavior after host agent-capability resolution.
63
65
 
64
66
  `session.subscribe(options?)` remains available for hosts that want a long-lived subscriber across runs. Subscribe before `run()` to observe that run's events. The consumer loop and `session.run()` must run concurrently (e.g. start the `for await` consumer, then `await Promise.all([consumer, session.run("Hi")])`): events are only emitted during a live run, so awaiting the subscribe loop before calling `run()` deadlocks. Prefer `session.stream()` when you only need one run's events. `SubscribeOptions.maxQueuedEvents` defaults to `1024` (minimum `1`) and caps events queued while the consumer is not awaiting `next()`. `SubscribeOptions.overflow` defaults to `"close"`; it clears queued payload events, delivers one `event_subscriber_overflow` notice to that subscriber, then closes it. `"drop_oldest"` keeps newest events; `"drop_newest"` ignores new events while full.
65
67
 
@@ -77,7 +79,7 @@ For tool calls, the runtime streams provider `tool_call_delta` fragments as `mes
77
79
 
78
80
  Provider `thinking`/`reasoning` content emitted during a turn is preserved as `thinking` content blocks on the assistant message in session history. On the next turn, provider packages decide how to carry prior reasoning forward. For example, the NeuralWatt provider serializes prior `thinking` blocks under a `reasoning_content` field for reasoning-capable models (gated on `capabilities.reasoning` / `compat.preserve_thinking`, droppable via `compat.clear_thinking`); see [NeuralWatt provider](providers/neuralwatt.md). Non-reasoning providers/models receive no reasoning field, so prior thinking does not leak into providers that do not support it.
79
81
 
80
- `session.compact(options?)` runs the selected compaction strategy, appends one `kind: "compaction"` entry under the current leaf, updates the leaf, emits `compaction_started` and `compaction_finished`, and returns the appended `CompactionResult`. If `AgentConfig.compaction` or `RunOptions.compaction` includes `thresholdEntries`, auto-compaction checks once after input/model-change entries are appended and before provider input assembly; `RunOptions.compaction: false` skips that run's auto-compaction.
82
+ `session.compact(options?)` runs the selected compaction strategy, appends one `kind: "compaction"` entry under the current leaf, updates the leaf, emits `compaction_started` and `compaction_finished`, and returns the appended `CompactionResult`. If `AgentConfig.compaction` or `RunOptions.compaction` includes `thresholdEntries` (entry count) or `trigger` (entry count, input ratio against the compiler's cap, or a host callback), auto-compaction checks once after input/model-change entries are appended and before provider input assembly; `RunOptions.compaction: false` skips that run's auto-compaction. A branch that already ends with a `kind: "compaction"` entry is left alone.
81
83
 
82
84
  `entries()` returns the current branch entries. `checkout(leafId?)` moves the session to an existing leaf and rebuilds history. `fork()` returns a session on the same store/session id at the selected leaf without copying entries. `clone({ id })` copies the current branch to a new session id with new entry ids.
83
85
 
@@ -186,7 +188,7 @@ Set `runState` with a host-owned `CheckpointStore`, stable `definitionRevision`,
186
188
  - `decision: "approve" | "deny"` — legacy single-approval path. `approve` allows every pending decision once; `deny` terminates the run as `denied`.
187
189
  - `decisions: readonly RunDecision[]` — one atomic batch. Every entry validates against the recorded pending set (unknown/foreign `approvalId`, duplicates, stale `expectedVersion`, invalid outcomes fail the whole batch closed with `AgentDecisionError` and leave state and version untouched). Outcomes: `allow_once`, `allow_for_run`, `reject_once`, `reject_for_run`. `reject_*` continues the run with a blocked tool result carrying the bounded (2 KB) `reason`. `modifiedArguments` are revalidated (schema, then input guardrails; permission/trust re-run at dispatch) and produce a new arguments hash. `elicitation` payloads are validated against the pending decision's `elicitationSchema` (required keys plus the configured host validator) and resolve the suspended call without executing it. A batch deciding a strict subset persists the decided entries and re-suspends with the remainder pending at the bumped version.
188
190
 
189
- `*_for_run` outcomes append a `StickyDecision` to the durable run state: later calls in the same run matching the scope exactly (all recorded fields) proceed or are blocked without a new suspension, policy still enforced at dispatch. Sticky decisions expire when the run reaches any terminal status. Caps: 32 pending decisions per run (hard 128), 64 sticky decisions (hard 256), 2 KB decision reasons, 16 KB elicitation payloads.
191
+ `*_for_run` outcomes append a `StickyDecision` to the durable run state: later calls in the same run matching the scope exactly (all recorded fields) proceed or are blocked without a new suspension, policy still enforced at dispatch. Sticky decisions expire when the run reaches any terminal status. Caps: 32 pending decisions per run (hard 128), 64 sticky decisions (hard 256), 2 KB decision reasons, 16 KB elicitation payloads. Frontend adapters (such as AG-UI with `capabilities.humanInTheLoop.approveWithEdits`) and the server resume endpoint (`POST .../resume` with `modifiedArguments`) map human edits directly to `RunDecision` entries with `modifiedArguments` under single atomic CAS, revalidating tool parameter schemas and invalidating stale draft approvals.
190
192
 
191
193
  **Runtime input validation (0.2.0, plan 020 Task 2).** Every public resume entrypoint (`resumeAgentRun`, `resumeAgentRunStream`, `AgentRunLifecycle.resume()`/`resumeStream()`) validates the complete resume input in core before any checkpoint read/write, agent resolution, subscription, or tool execution: a non-null object, positive safe-integer `expectedVersion`, exactly one of `decision`/`decisions`, legacy `decision` exactly `approve`/`deny`, and a non-empty batch ≤ 128 entries whose entries are objects with a bounded non-empty `approvalId`, a whitelisted outcome, an optional string `reason` within the 2 KB limit, and JSON-object `modifiedArguments`/`elicitation` within the 16 KB limit. Unknown legacy decisions (e.g. `"sideways"`) and malformed untyped batches fail closed with `AgentDecisionError` (`ERR_PRISM_DECISION_INVALID`/`..._LIMIT`/`..._DUPLICATE`) under a **no-side-effect guarantee**: zero checkpoint writes/CAS changes, zero tool calls, zero resumed events. This holds for plain-JavaScript and `as any` callers; the server's transport parser is defense in depth, not the security boundary. State-dependent checks (foreign/stale approval ids, scope, schema, policy) still run in the atomic batch resolver.
192
194
 
@@ -201,7 +203,7 @@ if (result.status === "suspended") {
201
203
  }
202
204
  ```
203
205
 
204
- Resume requires exact checkpoint ownership, version, agent fingerprint, and revision. The fingerprint hashes the agent id/name, `definitionRevision`, model, instructions, system-prompt contributions, skills (name/instructions/tool names), tool definitions (name/parameters/exclusive), guardrail definitions (name/stage/revision), and loop strategy — changing any of them without bumping `definitionRevision` fails resume closed instead of silently continuing with different agent semantics. Prism CAS-claims approval before work, rechecks normal guardrail/permission/validation/limit paths, and marks a pending tool dispatched before its side effect. `createAgentRunLifecycle()` wraps the same core path for server/MCP hosts: adapters pass only authorized ownership, status returns only `{ state, version }`, and `resolveAgent()` supplies current agent/revision. `resumeStream()` uses that same claim path and bounded subscriber, so adapters do not poll or duplicate resume logic. Remote restart requires both checkpoint and session stores to be durable. A crash after that mark is ambiguous and is never replayed automatically; use host tool idempotency keyed by `runId`/`toolCallId` or resolve it manually. Checkpoints contain bounded redacted state plus session/leaf references, never provider objects, callbacks, signals, credentials, or raw secrets. State is bounded at save by `runState.maxStateBytes` (default 256 KB, at most the 1 MB hard cap); load bounds against the 1 MB hard cap only, so state saved with a raised limit stays resumable while oversized records are still rejected. Since 0.1.3 (plan 015 Task 4), durable runs may opt in to session-state persistence with `persistSessionState: true` on both the run and resume options: the loaded-skill **name catalog** (≤64 names, ≤256 chars each) rides the checkpoint and is restored into the resumed session's `LoadedSkillSet`; skill **bodies are never persisted** and re-resolve from the live registry via `load_skill`. Since 0.1.6 (plan 018 closeout `checkpoint-bodies`), `includeSkillBodies: true` on BOTH the run and resume options additionally persists the exact loaded-skill **instructions** (`{name, instructions}` pairs, redacted at the checkpoint boundary like all state, ≤64 bodies / ≤256-char names / ≤262144-byte bodies / ≤1 MiB total) so resume re-renders them registry-independently — no `load_skill` round-trip and no dependence on the registry still serving the same text; `maxStateBytes` (default 256 KB) refuses oversize bodies with a recorded error, never silently truncates. Default off keeps the checkpoint shape byte-identical to 0.1.3. Built-in loop options are durable; custom `AgentLoopStrategy` instances are durable when they declare `snapshot`/`restore` hooks (see [Agent loops § Durable runs](agent-loops.md#durable-runs)) and reject before provider work otherwise.
206
+ Resume requires exact checkpoint ownership, version, agent fingerprint, and revision. The fingerprint hashes the agent id/name, `definitionRevision`, model, instructions, system-prompt contributions, skills (name/instructions/tool names), tool definitions (name/parameters/exclusive), guardrail definitions (name/stage/revision), and loop strategy — changing any of them without bumping `definitionRevision` fails resume closed instead of silently continuing with different agent semantics. Prism CAS-claims approval before work, rechecks normal guardrail/permission/validation/limit paths, and marks a pending tool dispatched before its side effect. `createAgentRunLifecycle()` wraps the same core path for server/MCP hosts: adapters pass only authorized ownership, status returns only `{ state, version }`, and `resolveAgent()` supplies current agent/revision. `resumeStream()` uses that same claim path and bounded subscriber, so adapters do not poll or duplicate resume logic. Remote restart requires both checkpoint and session stores to be durable. A crash after that mark is ambiguous and is never replayed automatically; use host tool idempotency keyed by `runId`/`toolCallId` or resolve it manually. Checkpoints contain bounded redacted state plus session/leaf references, never provider objects, callbacks, signals, credentials, or raw secrets. State is bounded at save by `runState.maxStateBytes` (default 256 KB, at most the 1 MB hard cap); load bounds against the 1 MB hard cap only, so state saved with a raised limit stays resumable while oversized records are still rejected. Since 0.1.3 (plan 015 Task 4), durable runs may opt in to session-state persistence with `persistSessionState: true` on both the run and resume options: the loaded-skill **name catalog** (≤64 names, ≤256 chars each) rides the checkpoint and is restored into the resumed session's `LoadedSkillSet`; skill **bodies are never persisted** and re-resolve from the live registry via `load_skill`. Since 0.1.6 (plan 018 closeout `checkpoint-bodies`), `includeSkillBodies: true` on BOTH the run and resume options additionally persists the exact loaded-skill **instructions** (`{name, instructions}` pairs, redacted at the checkpoint boundary like all state, ≤64 bodies / ≤256-char names / ≤262144-byte bodies / ≤1 MiB total) so resume re-renders them registry-independently — no `load_skill` round-trip and no dependence on the registry still serving the same text; `maxStateBytes` (default 256 KB) refuses oversize bodies with a recorded error, never silently truncates. Default off keeps the checkpoint shape byte-identical to 0.1.3. Since 0.7.0 (plan 074 P3), `persistSessionState: true` also carries the opt-in [attention compiler](attention-compiler.md)'s sticky frontier (`sessionState.attentionSticky`: 32-hex thinking keys plus tool-call ids, newest 256 of each, redacted like all state) so a resumed run keeps its thinking strips and tool stubs instead of re-deciding its first turn from the ratio; a malformed frontier is dropped entry by entry and never blocks a resume. Since 0.7.0, `onSession` hands the reconstructed session to a caller-supplied observer before the resumed run starts, so an observer (the supervisor's child-event pump) can subscribe while the run is still live; it is called for every resume outcome, a throw fails closed before any event or tool work, and the session is valid only for the duration of that resume. Built-in loop options are durable; custom `AgentLoopStrategy` instances are durable when they declare `snapshot`/`restore` hooks (see [Agent loops § Durable runs](agent-loops.md#durable-runs)) and reject before provider work otherwise.
205
207
 
206
208
  ## Secure composition
207
209