@arnilo/prism 0.6.0 → 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 (129) hide show
  1. package/CHANGELOG.md +44 -5
  2. package/README.md +10 -10
  3. package/dist/agent-definitions.js +1 -0
  4. package/dist/agent-run-lifecycle.js +11 -0
  5. package/dist/agent-run-state.d.ts +6 -0
  6. package/dist/agent-run-state.js +29 -9
  7. package/dist/agent-session/session/assemble.js +33 -2
  8. package/dist/agent-session/session/persist.js +6 -2
  9. package/dist/agent-session/session/tool-round.js +1 -0
  10. package/dist/agent-session/session/types.d.ts +10 -0
  11. package/dist/agent-session/session.d.ts +14 -0
  12. package/dist/agent-session/session.js +40 -3
  13. package/dist/artifacts.d.ts +39 -1
  14. package/dist/artifacts.js +73 -0
  15. package/dist/attention-compiler.d.ts +121 -0
  16. package/dist/attention-compiler.js +479 -0
  17. package/dist/cli-init.js +20 -6
  18. package/dist/context-budget.d.ts +20 -1
  19. package/dist/context-budget.js +10 -1
  20. package/dist/contracts-core/agent.d.ts +7 -0
  21. package/dist/contracts-core/attention.d.ts +66 -0
  22. package/dist/contracts-core/attention.js +2 -0
  23. package/dist/contracts-core/compaction.d.ts +59 -0
  24. package/dist/contracts-core/compaction.js +77 -1
  25. package/dist/contracts-core/provider.d.ts +4 -0
  26. package/dist/contracts-core.d.ts +1 -0
  27. package/dist/contracts-core.js +1 -0
  28. package/dist/contracts-protocol.d.ts +29 -0
  29. package/dist/contracts-run-state.d.ts +6 -0
  30. package/dist/host-composition.d.ts +78 -0
  31. package/dist/host-composition.js +248 -0
  32. package/dist/index.d.ts +9 -6
  33. package/dist/index.js +5 -4
  34. package/dist/input.d.ts +13 -1
  35. package/dist/input.js +40 -1
  36. package/dist/secure-agent.d.ts +2 -0
  37. package/dist/secure-agent.js +6 -1
  38. package/dist/tool-result-fold.d.ts +12 -0
  39. package/dist/tool-result-fold.js +13 -6
  40. package/dist/tools.d.ts +10 -0
  41. package/dist/tools.js +41 -0
  42. package/docs/acp-agent.md +42 -11
  43. package/docs/acp.md +2 -1
  44. package/docs/ag-ui.md +5 -3
  45. package/docs/agent-definitions.md +9 -1
  46. package/docs/agent-events.md +4 -1
  47. package/docs/agent-session-runtime.md +6 -6
  48. package/docs/attention-compiler.md +272 -0
  49. package/docs/cli-rpc.md +4 -2
  50. package/docs/coding-agent-tools.md +1 -1
  51. package/docs/coding-security.md +5 -3
  52. package/docs/coding-tools.md +1 -1
  53. package/docs/coding-workspaces.md +22 -0
  54. package/docs/compaction-and-retry.md +36 -4
  55. package/docs/compaction-observational-memory.md +62 -9
  56. package/docs/context-and-skills.md +4 -2
  57. package/docs/conversations.md +1 -1
  58. package/docs/dev-inspector.md +4 -0
  59. package/docs/device-adapters.md +1 -0
  60. package/docs/document-reader.md +11 -3
  61. package/docs/documents.md +10 -2
  62. package/docs/enterprise-postgres-state.md +2 -2
  63. package/docs/evaluations.md +168 -4
  64. package/docs/execution-timeline.md +180 -0
  65. package/docs/history/0.7.0-primitive-review.md +254 -0
  66. package/docs/history/migration-0.0.md +2 -2
  67. package/docs/history/release-handoffs.md +37 -1
  68. package/docs/host-compositions.md +147 -0
  69. package/docs/hosted-sandboxes.md +94 -0
  70. package/docs/index.md +58 -39
  71. package/docs/input-and-prompt-assembly.md +1 -0
  72. package/docs/knowledge-sync.md +84 -0
  73. package/docs/language-intelligence.md +1 -1
  74. package/docs/live-testing.md +4 -1
  75. package/docs/mcp-tools.md +2 -1
  76. package/docs/memory-fabric.md +416 -0
  77. package/docs/migrate-to-0.5.md +1 -1
  78. package/docs/migrate-to-0.6.md +1 -0
  79. package/docs/migrate-to-0.7.md +345 -0
  80. package/docs/migration.md +13 -1
  81. package/docs/model-routing.md +79 -4
  82. package/docs/multi-agent-patterns.md +20 -6
  83. package/docs/observability.md +52 -1
  84. package/docs/operations.md +13 -1
  85. package/docs/options-index.md +13 -1
  86. package/docs/peer-dependencies.md +6 -4
  87. package/docs/process-sessions.md +3 -1
  88. package/docs/prompt-registry.md +1 -1
  89. package/docs/provider-caching.md +4 -2
  90. package/docs/provider-conformance.md +1 -1
  91. package/docs/provider-packages.md +22 -22
  92. package/docs/providers/bedrock.md +71 -7
  93. package/docs/providers/openai.md +1 -1
  94. package/docs/rag.md +24 -8
  95. package/docs/realtime-voice.md +87 -0
  96. package/docs/release-and-install.md +36 -34
  97. package/docs/runs-and-usage.md +3 -2
  98. package/docs/server.md +5 -3
  99. package/docs/speech.md +2 -0
  100. package/docs/supervisors.md +33 -5
  101. package/docs/testing.md +1 -1
  102. package/docs/thinking-and-reasoning.md +3 -1
  103. package/docs/tools.md +6 -5
  104. package/docs/web-tools.md +2 -1
  105. package/docs/work-artifacts-and-review.md +14 -4
  106. package/docs/work-connectors.md +3 -1
  107. package/docs/work-tools.md +14 -4
  108. package/docs/workflows.md +69 -1
  109. package/docs/working-and-semantic-memory.md +25 -14
  110. package/package.json +1 -1
  111. package/templates/README.md +2 -0
  112. package/templates/business-worker/README.md.tmpl +19 -0
  113. package/templates/business-worker/env.example.tmpl +1 -0
  114. package/templates/business-worker/gitignore.tmpl +11 -0
  115. package/templates/business-worker/manifest.json +11 -0
  116. package/templates/business-worker/package.json.tmpl +23 -0
  117. package/templates/business-worker/src/agent.ts.tmpl +92 -0
  118. package/templates/business-worker/src/index.ts.tmpl +13 -0
  119. package/templates/business-worker/src/tests/agent.test.ts.tmpl +77 -0
  120. package/templates/business-worker/tsconfig.json.tmpl +15 -0
  121. package/templates/personal-assistant/README.md.tmpl +18 -0
  122. package/templates/personal-assistant/env.example.tmpl +1 -0
  123. package/templates/personal-assistant/gitignore.tmpl +11 -0
  124. package/templates/personal-assistant/manifest.json +11 -0
  125. package/templates/personal-assistant/package.json.tmpl +23 -0
  126. package/templates/personal-assistant/src/agent.ts.tmpl +65 -0
  127. package/templates/personal-assistant/src/index.ts.tmpl +13 -0
  128. package/templates/personal-assistant/src/tests/agent.test.ts.tmpl +28 -0
  129. package/templates/personal-assistant/tsconfig.json.tmpl +15 -0
package/docs/rag.md CHANGED
@@ -2,11 +2,11 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- The `@arnilo/prism-memory/rag` subpath is an optional surface for deterministic text/Markdown chunking (with ATX heading-stack metadata), bounded embedding/vector indexing with embedder-identity drift guards, atomic scoped source replacement/deletion with content-hash skip and generation visibility, hybrid vector+lexical retrieval with reciprocal-rank fusion (one embed / one RRF / one rerank across one or many exact scopes), focused text/Markdown/HTML/PDF parsing, bounded reranking (host seam plus a TEI REST adapter), ingestion status, attributable citations, content-trust metadata, and explicit `ContextProvider` injection. It reuses `Embedder` and `VectorStore` from the memory root entry; Prism core input assembly is unchanged.
5
+ The `@arnilo/prism-memory/rag` subpath is an optional surface for deterministic text/Markdown chunking (with ATX heading-stack metadata), bounded embedding/vector indexing with embedder-identity drift guards, atomic scoped source replacement/deletion with content-hash skip and generation visibility, hybrid vector+lexical retrieval with reciprocal-rank fusion (one embed / one RRF / one rerank across one or many exact scopes), host-verified document authorization on both query legs, paged knowledge-source synchronization with a Drive connector, focused text/Markdown/HTML/PDF parsing, bounded reranking (host seam plus a TEI REST adapter), ingestion status, attributable citations, content-trust metadata, and explicit `ContextProvider` injection. It reuses `Embedder` and `VectorStore` from the memory root entry; Prism core input assembly is unchanged.
6
6
 
7
7
  ## When to use it
8
8
 
9
- Use it when a host needs bounded replacement of one owned source, focused parsing after a host-authorized resource or host-selected web fetch, or a host-selected reranker over a finite candidate set. Do not use it for LaTeX parsing, semantic chunking, metadata extraction agents, a hosted reranker implementation, GraphRAG, crawling, URL fetching outside `@arnilo/prism-web-tools`, or filesystem discovery.
9
+ Use it when a host needs bounded replacement of one owned source, focused parsing after a host-authorized resource or host-selected web fetch, a host-selected reranker over a finite candidate set, or incremental Drive knowledge sync into that same source lifecycle. Do not use it for LaTeX parsing, semantic chunking, metadata extraction agents, a hosted reranker implementation, GraphRAG, crawling, URL fetching outside `@arnilo/prism-web-tools`, or filesystem discovery.
10
10
 
11
11
  ## Inputs / request
12
12
 
@@ -24,9 +24,11 @@ Document lifecycle:
24
24
 
25
25
  | API/field | Meaning |
26
26
  | --- | --- |
27
- | `replaceSource({ sourceId, chunks, store, scope, ... })` | Atomically replaces one source after all bounded embedding succeeds; the store must implement scoped `getBySource()` and `transaction()`. |
27
+ | `replaceSource({ sourceId, chunks, store, scope, ... })` | Atomically replaces one source after all bounded embedding succeeds; the store must implement scoped `getBySource()` and `transaction()`. `advanceGeneration: false` stamps the current generation without moving the scope pointer (multi-source sync). |
28
28
  | `deleteSource({ sourceId, store, scope })` | Deletes only matching IDs under exact tenant/resource/corpus scope. |
29
29
  | `replaceDocument({ uri, loader, parser, store, scope, ... })` | Loads through a host seam, parses, chunks, and atomically replaces. `sourceId` is required unless loader supplies one. |
30
+ | `syncKnowledge({ connector, checkpoints, checkpoint, store, embedder, scope })` | Paged connector import; cursor CAS only after each committed page. See [Knowledge synchronization](knowledge-sync.md). |
31
+ | `createGoogleDriveConnector({ tokenProvider, resolveAccess })` | Drive `files.list` + `changes.list` connector. Host maps permissions; watch payloads are not authorization. |
30
32
  | `DocumentLoader` / `Parser` | Small host-replaceable seams. `@arnilo/prism-memory/rag/loaders` and `/rag/parsers` export reference adapters. |
31
33
  | `textParser` / `markdownParser` / `htmlParser` / `pdfParser` | UTF-8 text, Markdown, script/style-stripping HTML, and uncompressed-text PDF parsers. |
32
34
 
@@ -40,7 +42,8 @@ Index/retrieve:
40
42
  | `topK` / `queryCandidates` | retrieval | Returned result count and bounded pre-filter candidates (`queryCandidates` is **per scope**) |
41
43
  | `lexical` | no | `"fts"` \| `"bm25"` \| `"off"` (default `"off"`); enables the lexical retrieval leg when the store advertises it |
42
44
  | `fusion` / `rrfK` | no | `"rrf"` fusion of vector+lexical legs (default `"rrf"` when `lexical` is on; `rrfK` default 60, hard cap 1,000) |
43
- | `filter` | no | Shallow JSON metadata equality filter |
45
+ | `filter` | no | Shallow JSON metadata equality filter. **Not authorization.** |
46
+ | `authorization` | no | Host-verified `{ principalId, tenantId, groupIds?, accessVersion? }`. Injected into both vector and lexical legs before ranking; rechecked before rerank and injection. Stores without `authorization: "acl"` fail closed. |
44
47
  | `reranker` | no | Host-owned `Reranker` receives redacted bounded `RagHit[]` and must return the same IDs once each, in preferred order. |
45
48
  | `maxRerankBytes` / `maxRerankMs` / `rerankConcurrency` | no | Reranker caps; defaults/hard limits are 64/256 KiB, 2/10 s, and 2/8 active calls per reranker object. |
46
49
  | `statusStore` | no | `IngestionStatusStore` records per-source pending/indexed/failed/partial byte/chunk progress; use `listIngestionStatus()` for capped exact-scope pages. |
@@ -56,9 +59,10 @@ Index/retrieve:
56
59
  - `indexChunks()` returns `{ indexed, sourceIds }` after bounded batch upserts.
57
60
  - `replaceSource()` / `deleteSource()` return `{ sourceId, deleted, indexed }`.
58
61
  - `replaceDocument()` carries loader parser metadata into chunk metadata; the web loader preserves web-tools citation ID and `untrusted: true`.
59
- - `retrieveContext()` returns `{ query, trust, text, hits, citations, truncated }`. Every hit/citation carries `{ provenance: { sourceId, chunkId, citationId, provider, tenantId, resourceId, corpusId, retrieval: "vector" | "lexical" | "hybrid", retrievedAt }, trust: { untrusted: true, inert: true, injectionCapable: true } }`; `retrieval` labels the leg(s) that surfaced the hit after RRF fusion, and `retrievalRank` preserves pre-rerank order. Rendered text uses `[citation-id] text` blocks.
62
+ - `retrieveContext()` returns `{ query, trust, text, hits, citations, truncated }`. Every hit/citation carries `{ provenance: { sourceId, chunkId, citationId, provider, tenantId, resourceId, corpusId, retrieval: "vector" | "lexical" | "hybrid", retrievedAt }, trust: { untrusted: true, inert: true, injectionCapable: true } }`; `retrieval` labels the leg(s) that surfaced the hit after RRF fusion, and `retrievalRank` preserves pre-rerank order. Rendered text uses `[citation-id] text` blocks. `evidenceFromRagCitation(citation, { contentHash, revision, excerpt? })` projects a hit into the shared `ArtifactCitation` evidence shape without refetching.
60
63
  - `replaceSource()` returns `{ sourceId, deleted, indexed, skipped? }` (skipped when the stored `contentHash` matched and no writes occurred). Records carry `embedderId` (from `Embedder.id`, the Task 2 identity contract) and `generation` (scope-level monotonically bumped index per replacement; `_rag` metadata carries `contentHash` when supplied). `store.getCurrentGeneration(scope)` / `store.setCurrentGeneration(scope, n)` let hosts read and roll back the visible generation; retrieval filters to the current generation while legacy generation-less rows stay visible.
61
- - `createMemoryIngestionStatusStore()` is a bounded in-memory reference adapter. `listIngestionStatus({ store, scope, limit, cursor })` returns capped status pages; hosts supply durable stores when status must survive process restart.
64
+ - `createMemoryIngestionStatusStore()` is a bounded in-memory reference adapter. `listIngestionStatus({ store, scope, limit, cursor })` returns capped status pages; hosts supply durable stores when status must survive process restart. Optional `freshness` is `current` / `stale` / `unavailable` for synchronized sources.
65
+ - `syncKnowledge()` returns `{ pages, upserted, deleted, skipped, withheld, cursor?, exhausted }`. Unchanged `contentHash` values skip embedding. Invalid Drive page tokens throw `RagSyncCursorError` (default: one bootstrap resync).
62
66
  - `createRagContextProvider()` returns one ordinary context provider. Empty queries/results contribute no block.
63
67
  - No events, tools, permissions, provider calls, loaders, or network requests are added.
64
68
 
@@ -97,12 +101,17 @@ const statusStore = createMemoryIngestionStatusStore();
97
101
  await indexChunks({ chunks, embedder, store, scope, statusStore });
98
102
  // For a replaceable source use `replaceSource`; it keeps previous chunks until embedding succeeds.
99
103
 
104
+ await store.setSourceAccess(
105
+ { tenantId: scope.tenantId, resourceId: scope.resourceId, threadId: scope.corpusId },
106
+ [{ sourceId: "security-guide", principalIds: ["alice"], groupIds: ["eng"], accessVersion: 1 }],
107
+ );
100
108
  const found = await retrieveContext("approval policy", {
101
109
  embedder,
102
110
  store,
103
111
  scopes: [scope], // or `scope` for one corpus
104
112
  topK: 4,
105
113
  filter: { category: "security" },
114
+ authorization: { principalId: "alice", tenantId: scope.tenantId, groupIds: ["eng"] },
106
115
  reranker: { rerank: async ({ hits }) => [...hits].sort((a, b) => b.score - a.score) },
107
116
  });
108
117
  console.log(await listIngestionStatus({ store: statusStore, scope }));
@@ -149,7 +158,8 @@ const found = await retrieveContext("leave balance", {
149
158
  ## Extension and configuration notes
150
159
 
151
160
  - Supply any Phase 7-conforming embedder/vector store, including the in-memory reference or PostgreSQL/pgvector adapter.
152
- - Metadata filtering is package-local after a bounded candidate query so existing vector contracts/adapters remain unchanged. Increase `queryCandidates` only when selective filters measurably need it.
161
+ - Metadata filtering is package-local after a bounded candidate query so existing vector contracts/adapters remain unchanged. Increase `queryCandidates` only when selective filters measurably need it. `filter` never grants document access.
162
+ - Document ACL is opt-in via `authorization` on `retrieveContext` / `store.query` / `store.lexicalQuery`. Reference memory and PostgreSQL adapters declare `authorization: "acl"` and apply principal/group predicates **before** top-K. `setSourceAccess` replaces grants per source (empty principal+group lists revoke). Access version is independent of embedding generation; an unresolved `accessVersion` denies. Missing grants deny. Stores that omit the capability throw rather than claim protection. Group lists cap at 32.
153
163
  - `Reranker` is a host seam, not a provider integration. Return each redacted candidate ID exactly once; Prism retains canonical hit/provenance/trust fields and exposes `retrievalRank` for diagnostics. Add a hosted reranker only when a host owns its credentials, quota, and retry policy.
154
164
  - `createTeiReranker({ baseUrl, model?, timeoutMs?, maxResponseBytes?, ssrf?, allowLoopback?, fetch? })` (`CreateTeiRerankerOptions`) adapts a Hugging Face TEI `POST <baseUrl>/rerank` endpoint (`{query, texts, raw_scores:false}` → `{results:[{index,score}]}`) into the `Reranker` seam. It returns a permutation-only reorder of the same hit objects, so provenance/trust move untouched. Response parsing is strict — short/duplicate/out-of-range indices, non-finite scores, HTTP errors, timeouts, and oversized bodies all fail closed; the `rerankHits` caps (`maxRerankBytes`, `maxRerankMs`, `rerankConcurrency`) still apply around it. The default transport is the core DNS-pinned `pinnedFetch` (redirect-free, byte-bounded to 65,536 by default); HTTPS is required unless `allowLoopback: true` (loopback dev/test) or the host supplies `ssrf`/`fetch` for cluster networking. The adapter validates URL shape only — SSRF policy enforcement stays host-side. No credentials are ever sent; there is no SaaS default URL.
155
165
  - Hosted rerank adapters over the same seam (plan 062): `createOpenAiCompatibleReranker({ baseUrl, model?, apiKey?, timeoutMs?, maxResponseBytes?, ssrf?, allowLoopback?, fetch? })` speaks the OpenAI-compatible `POST <baseUrl>/rerank` route (`{model, query, documents}` → `{results:[{index,relevance_score}]}`; pass the version segment in `baseUrl`, e.g. `https://api.jina.ai/v1`), and `createVoyageReranker({ baseUrl, model?, apiKey, … })` adapts Voyage AI (`…/v1/rerank` → `{data:[{index,relevance_score}]}`; `apiKey` required). Both send one request per rerank — no adapter-side batching — never send `top_k` (the retrieval seam owns top-K), return the same permutation-only reorder, and fail closed on the same malformed-response/HTTP/timeout/byte-bound cases. `apiKey` rides as `Authorization: Bearer …` and is never logged; errors carry status/host only. No SaaS default URL — hosts own credentials, quota, and retry policy.
@@ -162,12 +172,13 @@ const found = await retrieveContext("leave balance", {
162
172
  - `createRagContextProvider()` derives its query from latest user text by default; pass a fixed string or callback for host-controlled query generation.
163
173
  - `createResourceDocumentLoader({ loader })` calls one host-owned `ResourceLoader`; it scans nothing and performs no filesystem or network I/O itself. Pass the host's permission/trust context to that loader.
164
174
  - `createWebFetchDocumentLoader({ fetcher })` accepts an already-configured `@arnilo/prism-web-tools` fetch adapter. It never opens a socket, rejects file/local/private/IP-literal URLs, and carries normalized citation/trust metadata forward. The fetch adapter still owns DNS/SSRF policy.
165
- - `pdfParser` is deliberately limited to bounded, uncompressed PDF text. Provide a host parser through `Parser` for compressed, scanned, or complex PDFs; do not silently index partial text.
175
+ - `pdfParser` is deliberately limited to bounded, uncompressed PDF text. Provide a host parser through `Parser` for compressed, scanned, or complex PDFs; do not silently index partial text. Hosts that need OCR wrap `createMistralOcrParser` from `@arnilo/prism-coding-tools/document-reader` — it is never the default parser and never runs unless the host passes it to `replaceDocument({ parser })`.
166
176
  - Package is available directly or via the `@arnilo/prism-memory` family tarball; installation does not create an embedder, vector store, loader, parser, or context provider.
167
177
 
168
178
  ## Security and performance notes
169
179
 
170
180
  - Every index/query includes exact tenant/resource/corpus scope; returned records are rechecked and malformed/foreign records fail closed. `retrieveContext` accepts `scope` or `scopes` (never both, never neither). Empty `scopes` is the host “no allowed corpora” path — no embed, no search, no rerank. A hit whose stored scope is not in the requested list fails closed. Generation filters stay per scope.
181
+ - When `authorization` is set, unauthorized text, titles, citations, counts, and reranker payloads never leave the store. Recheck runs after fusion (before rerank) and again after rerank before injection, so revocation between those steps drops the candidate. `authorization.tenantId` must match every retrieve scope.
171
182
  - Embedding identity is a privacy/consistency boundary: records from a different embedder (or dimension) never silently mingle with new ones — retrieval fails closed and names the re-index path. Generation pointers are scope-scoped: a pointer row belongs to exactly one scope, and visibility is computed inside the store (SQL), never by post-filtering in JS.
172
183
  - Source IDs become citation/storage IDs and must be stable non-secret identifiers. Text and user metadata can be redacted before external embedding and persistence.
173
184
  - Heading metadata is document text only — it passes through the existing `maxMetadataBytes` cap as chunk metadata; no new content path is introduced.
@@ -200,12 +211,17 @@ PRISM_TEST_TEI_RERANKER_URL=http://tei.svc:8080 \
200
211
  | `PRISM_TEST_HOSTED_RERANK_URL` | OpenAI-compatible rerank base URL (`/rerank` appended, include `/v1`) |
201
212
  | `PRISM_TEST_HOSTED_RERANK_KEY` | Bearer credential for the hosted endpoint |
202
213
  | `PRISM_LIVE_HOSTED_RERANK_MODEL` | optional hosted model name |
214
+ | `PRISM_TEST_DRIVE_ACCESS_TOKEN` | delegated Drive readonly token for `memory/drive-sync-live` |
215
+ | `PRISM_TEST_DRIVE_FOLDER_ID` | optional folder scope |
216
+ | `PRISM_TEST_DRIVE_SHARED_DRIVE_ID` | optional shared drive |
203
217
 
204
218
  Probes send one non-sensitive rerank request per configured endpoint and assert the live response conforms (permutation-only reorder, scores non-increasing, credential never in error transcripts). Registered in `scripts/live-matrix.json` as `memory/rag-rerankers-live`.
205
219
 
206
220
  ## Related APIs
207
221
 
222
+ - [Knowledge synchronization](knowledge-sync.md): paged connector sync, Drive adapter, cursor CAS, source freshness.
208
223
  - [Working and semantic memory](working-and-semantic-memory.md): shared `Embedder`/`VectorStore` contracts and adapters.
224
+ - [Work artifacts and review](work-artifacts-and-review.md): `evidenceFromRagCitation` projects retrieved hits into shared citation evidence.
209
225
  - [Context and skills](context-and-skills.md): explicit `ContextProvider` injection and inert context semantics.
210
226
  - [Resource loading](resource-loading.md): host-owned trusted source loading.
211
227
  - [Multimodal content](multimodal-content.md): remote media SSRF/MIME/byte policies before text extraction.
@@ -0,0 +1,87 @@
1
+ # Realtime voice
2
+
3
+ ## What it does
4
+
5
+ `createRealtimeVoiceBridge` in `@arnilo/prism-core/runtime/realtime` runs an existing `RealtimeSession` through ordinary host tool dispatch, device admission, barge-in, reconnect dedupe, and transcript privacy. OpenAI Realtime (`createOpenAIRealtimeSession`) maps host `function_call` items onto `RealtimeEvent.tool_call`, emits `usage`, and returns results with `completeTool`.
6
+
7
+ ## When to use it
8
+
9
+ Use it when microphone audio should drive the same tools, approvals, usage ledger, and memory consent as a text run. Do not use it as a second agent loop or a voice-specific policy engine. One-shot TTS/STT stays on [Speech and transcription](speech.md).
10
+
11
+ ## Inputs / request
12
+
13
+ ```ts
14
+ import { resolveDevicePolicy } from "@arnilo/prism";
15
+ import { createOpenAIRealtimeSession } from "@arnilo/prism-providers/openai";
16
+ import { createRealtimeVoiceBridge } from "@arnilo/prism-core/runtime/realtime";
17
+
18
+ const policy = resolveDevicePolicy(
19
+ { kind: "voice", enabled: true, requireApproval: true, sandbox: "voice" },
20
+ { runLimits: { maxTurns: 8, maxToolCalls: 32 } },
21
+ );
22
+ const session = createOpenAIRealtimeSession({
23
+ ownerId: "user-1",
24
+ model: { provider: "openai", model: "gpt-realtime" },
25
+ apiKey,
26
+ tools: [{ name: "lookup", parameters: { type: "object" } }],
27
+ });
28
+ const bridge = createRealtimeVoiceBridge({
29
+ session,
30
+ policy,
31
+ admit: { approved: true, activeSessions: 0 },
32
+ toolNames: ["lookup"],
33
+ strictGovernance: true,
34
+ retainTranscripts: false,
35
+ execute: (call, ctx) => dispatchToolCall({ call, signal: ctx.signal }),
36
+ recordUsage: (usage) => router.recordUsage({ identity, provider: "openai", model, tokens: usage.totalTokens, kind: "generation" }),
37
+ });
38
+ ```
39
+
40
+ | Field | Meaning |
41
+ | --- | --- |
42
+ | `session` | Existing `RealtimeSession` (`sendAudio` / `events` / `interrupt` / `close`). |
43
+ | `policy` + `admit` | `assertDeviceAdmit` on construct, each `sendAudio`, and reconnect (new bridge). |
44
+ | `execute` | Host dispatch (approvals, `toolNames`, effect store). Not provider-hosted calls. |
45
+ | `toolNames` | Same names-only grant as `RunOptions.toolNames`. Omitted = all host tools; `[]` = none. |
46
+ | `strictGovernance` | Provider-hosted tools are unknown, never dispatched. |
47
+ | `seenCallIds` | Reconnect skip list. Duplicate ids are not replayed. |
48
+ | `retainTranscripts` | Default `false`. Audio is never retained. |
49
+
50
+ ## Outputs / response / events
51
+
52
+ `bridge.run()` consumes `session.events()` until close. `snapshot()` reports pending/completed/cancelled/unknown call ids, `interrupted`, `consent`, `effectAfterInterrupt`, and `usageMissing`. Barge-in aborts queued calls before `execute`; an execute that still succeeds after interrupt sets `effectAfterInterrupt` (072 invariant 0). Outbound `audio_delta` is dropped after interrupt until the next `sendAudio`.
53
+
54
+ ## Request/response example
55
+
56
+ ```json
57
+ {
58
+ "type": "response.function_call_arguments.done",
59
+ "call_id": "call_001",
60
+ "name": "lookup",
61
+ "arguments": "{\"q\":\"x\"}"
62
+ }
63
+ ```
64
+
65
+ ## Implementation example
66
+
67
+ See `examples/realtime-voice-host.ts` (network-free mock session, keyboard-free barge-in). Hosts that need a text approval UI call `execute` through the same `dispatchToolCall` / durable-approval path as a text run.
68
+
69
+ ## Extension and configuration notes
70
+
71
+ OpenAI `session.update` advertises at most 32 host function tools. `completeTool` sends `conversation.item.create` (`function_call_output`) then `response.create`. Optional `completeTool` on `RealtimeSession` is the generic gap; transports that cannot complete tools omit it. Transcript memory uses Task 16 `remember` only when `retainTranscripts` is true and the host passes `onTranscript`.
72
+
73
+ ## Security and performance notes
74
+
75
+ - Microphone consent is not tool approval. `revokeConsent` closes the session; later `sendAudio` throws `ERR_PRISM_REALTIME_CONSENT`.
76
+ - Re-admit on every reconnect. Side effects never replay from `seenCallIds`.
77
+ - Raw audio is not made safe by text redaction and is not uploaded or stored by the bridge.
78
+ - Ambiguous outcomes after interrupt stay `unknown`, never fabricated success.
79
+ - Event/audio caps stay on the Realtime session (`maxAudioEventsPerSecond` / `maxBytesPerSecond` / `maxWallMs`). Pending host calls cap at 32.
80
+
81
+ ## Related APIs
82
+
83
+ - [Device adapters](device-adapters.md): deny-by-default voice admission.
84
+ - [Speech and transcription](speech.md): one-shot TTS/STT.
85
+ - [Tools](tools.md): `RunOptions.toolNames` grant used by the bridge.
86
+ - [Runs and usage ledger](runs-and-usage.md): voice tokens settle as `kind: "generation"`.
87
+ - [OpenAI provider](providers/openai.md): `createOpenAIRealtimeSession`.
@@ -5,25 +5,27 @@
5
5
  ## What it does
6
6
 
7
7
 
8
- Prism's current **0.6.0** line has **10 publishable manifests**: the root `@arnilo/prism` core package plus **9 workspace packages** — **19 provider adapters** (19 provider adapter subpaths inside the `@arnilo/prism-providers` family), 3 `prism-*` family/profile packages, and 6 capability packages. (Generated by `node scripts/package-truth.mjs` → `scripts/package-truth.json` — the manifest-derived single source for counts, provider membership, umbrella closures, and profile closures.) The last lockstep cut was 0.3.0; Decision B now publishes changed packages independently inside `^0.3.0` — the plan 039 changed-package cut moved root `@arnilo/prism` and every plan-035+ changed package to **0.3.1**, and the plan 050 changed-package cut moved root plus four changed packages to **0.3.2**; the plan 041-044 changed-package cut moves root to **0.3.3** with `@arnilo/prism-memory@0.3.2` (composite recall scoring), `@arnilo/prism-evals@0.3.1` (trace-to-dataset curation), the three session-store packages at **0.3.1** (run-ledger `promptVersion` provenance), and the initial `@arnilo/prism-prompts@0.0.1` (independent opt-in, outside `prism-all`); plan 054 consolidation then folded `@arnilo/prism-browser` and `@arnilo/prism-obscura` into the `@arnilo/prism-web-tools` family as `/browser` and `/obscura` subpaths, folded `@arnilo/prism-rag`, both compaction strategies, `@arnilo/prism-graft`, and `@arnilo/prism-wiki` into the `@arnilo/prism-memory` family as `/rag`, `/compaction/llm`, `/compaction/observational-memory`, `/graft`, and `/wiki` subpaths (deleting the `@arnilo/prism-compaction` profile), and folded all 17 `@arnilo/prism-provider-*` packages into the `@arnilo/prism-providers` family as `/<adapter>` subpaths (Azure/Bedrock/Vertex stop being special all-only manifests); independent publication continues inside `^0.3.0` ranges (which satisfy 0.3.1, 0.3.2, and 0.3.3). This page describes how they are packed, what each tarball contains, how to install them, the required non-optional **caret** `@arnilo/prism@^0.6.0` peer range, the release workflow, and the offline test budget. The measurable 1.0 readiness gates (command-per-gate) live in [`0.1.0-readiness.md`](history/./0.1.0-readiness.md).
8
+ Prism's current **0.7.0** line has **10 publishable manifests**: the root `@arnilo/prism` core package plus **9 workspace packages** — **19 provider adapters** (19 provider adapter subpaths inside the `@arnilo/prism-providers` family), 3 `prism-*` family/profile packages, and 6 capability packages. (Generated by `node scripts/package-truth.mjs` → `scripts/package-truth.json` — the manifest-derived single source for counts, provider membership, umbrella closures, and profile closures.) The last lockstep cut was 0.3.0; Decision B now publishes changed packages independently inside `^0.3.0` — the plan 039 changed-package cut moved root `@arnilo/prism` and every plan-035+ changed package to **0.3.1**, and the plan 050 changed-package cut moved root plus four changed packages to **0.3.2**; the plan 041-044 changed-package cut moves root to **0.3.3** with `@arnilo/prism-memory@0.3.2` (composite recall scoring), `@arnilo/prism-evals@0.3.1` (trace-to-dataset curation), the three session-store packages at **0.3.1** (run-ledger `promptVersion` provenance), and the initial `@arnilo/prism-prompts@0.0.1` (independent opt-in, outside `prism-all`); plan 054 consolidation then folded `@arnilo/prism-browser` and `@arnilo/prism-obscura` into the `@arnilo/prism-web-tools` family as `/browser` and `/obscura` subpaths, folded `@arnilo/prism-rag`, both compaction strategies, `@arnilo/prism-graft`, and `@arnilo/prism-wiki` into the `@arnilo/prism-memory` family as `/rag`, `/compaction/llm`, `/compaction/observational-memory`, `/graft`, and `/wiki` subpaths (deleting the `@arnilo/prism-compaction` profile), and folded all 17 `@arnilo/prism-provider-*` packages into the `@arnilo/prism-providers` family as `/<adapter>` subpaths (Azure/Bedrock/Vertex stop being special all-only manifests); independent publication continues inside `^0.3.0` ranges (which satisfy 0.3.1, 0.3.2, and 0.3.3). This page describes how they are packed, what each tarball contains, how to install them, the required non-optional **caret** `@arnilo/prism@^0.7.0` peer range, the release workflow, and the offline test budget. The measurable 1.0 readiness gates (command-per-gate) live in [`0.1.0-readiness.md`](history/./0.1.0-readiness.md).
9
9
 
10
10
  Core `@arnilo/prism` ships runtime, CLI, templates, and docs. Every code package has a required `@arnilo/prism` peer inside the Decision B window — the caret current spec is `@arnilo/prism@^0.3.3` and every declared window peer satisfies it: packages republishing in the plan 050 cut carry `^0.3.2`; the plan 039 set keeps `^0.3.1`; unchanged packages keep their `^0.3.0` peer; profiles are pure manifests. The plan 050 republished set declares the required `@arnilo/prism@^0.3.2` peer; the plan 041-044 republished set keeps its existing `^0.3.0` window peer; unchanged packages keep their prior window. Installation activates no provider, listener, database, browser, credential, or tool capability.
11
11
 
12
+ The **0.6.0 and 0.7.0 lockstep cuts** each moved all ten manifests together: the current declared peer is `@arnilo/prism@^0.7.0` on every package, and `release.mjs` lockstep mode fails closed on any internal range that merely satisfies the cut version instead of matching it. The independent-publication history above (0.3.x, 0.4.x, 0.5.x) describes how the line grew when packages moved separately.
13
+
12
14
  <!-- generated:package-truth:inventory begin -->
13
15
  **10 publishable manifests** — root `@arnilo/prism` plus 9 workspace packages (3 `prism-*` family packages, 6 capability packages). Generated by `node scripts/package-truth.mjs --emit-docs` — do not hand-edit.
14
16
 
15
17
  | package | version | notes |
16
18
  | --- | --- | --- |
17
- | `@arnilo/prism` | 0.6.0 | core — runtime, CLI/RPC, templates, docs |
18
- | `@arnilo/prism-coding-tools` | 0.6.0 | family — /agent, /security, /document-reader, /openapi, /computer-use-linux, /dev, /caveman, /ponytail, /impeccable subpaths |
19
- | `@arnilo/prism-core` | 0.6.0 | family — /runtime, /sessions, /governance, /credentials, /enterprise, /work, /validation subpaths |
20
- | `@arnilo/prism-providers` | 0.6.0 | family — all provider adapters as `/<adapter>` subpaths |
21
- | `@arnilo/prism-acp-agent` | 0.6.0 | capability — ACP adapter |
22
- | `@arnilo/prism-ag-ui` | 0.6.0 | capability — AG-UI/A2A/A2UI adapter |
23
- | `@arnilo/prism-mcp` | 0.6.0 | capability — MCP client/server/OAuth interop |
24
- | `@arnilo/prism-memory` | 0.6.0 | capability — memory plus /rag, /compaction/*, /graft, /wiki subpaths |
25
- | `@arnilo/prism-office` | 0.6.0 | capability — /documents, /sheets, /diagrams subpaths |
26
- | `@arnilo/prism-web-tools` | 0.6.0 | capability — Brave/Exa/Firecrawl plus peer-gated /browser and /obscura subpaths |
19
+ | `@arnilo/prism` | 0.7.0 | core — runtime, CLI/RPC, templates, docs |
20
+ | `@arnilo/prism-coding-tools` | 0.7.0 | family — /agent, /security, /document-reader, /openapi, /computer-use-linux, /dev, /caveman, /ponytail, /impeccable subpaths |
21
+ | `@arnilo/prism-core` | 0.7.0 | family — /runtime, /sessions, /governance, /credentials, /enterprise, /work, /validation subpaths |
22
+ | `@arnilo/prism-providers` | 0.7.0 | family — all provider adapters as `/<adapter>` subpaths |
23
+ | `@arnilo/prism-acp-agent` | 0.7.0 | capability — ACP adapter |
24
+ | `@arnilo/prism-ag-ui` | 0.7.0 | capability — AG-UI/A2A/A2UI adapter |
25
+ | `@arnilo/prism-mcp` | 0.7.0 | capability — MCP client/server/OAuth interop |
26
+ | `@arnilo/prism-memory` | 0.7.0 | capability — memory plus /rag, /compaction/*, /fabric, /graft, /wiki subpaths |
27
+ | `@arnilo/prism-office` | 0.7.0 | capability — /documents, /sheets, /diagrams subpaths |
28
+ | `@arnilo/prism-web-tools` | 0.7.0 | capability — Brave/Exa/Firecrawl plus peer-gated /browser and /obscura subpaths |
27
29
  <!-- generated:package-truth:inventory end -->
28
30
 
29
31
 
@@ -32,26 +34,26 @@ Core `@arnilo/prism` ships runtime, CLI, templates, and docs. Every code package
32
34
 
33
35
  | adapter package | version |
34
36
  | --- | --- |
35
- | `@arnilo/prism-providers/ai-sdk` | 0.6.0 |
36
- | `@arnilo/prism-providers/alibaba` | 0.6.0 |
37
- | `@arnilo/prism-providers/anthropic` | 0.6.0 |
38
- | `@arnilo/prism-providers/azure` | 0.6.0 |
39
- | `@arnilo/prism-providers/bedrock` | 0.6.0 |
40
- | `@arnilo/prism-providers/clinepass` | 0.6.0 |
41
- | `@arnilo/prism-providers/commandcode` | 0.6.0 |
42
- | `@arnilo/prism-providers/deepseek` | 0.6.0 |
43
- | `@arnilo/prism-providers/google` | 0.6.0 |
44
- | `@arnilo/prism-providers/hyper` | 0.6.0 |
45
- | `@arnilo/prism-providers/kimi` | 0.6.0 |
46
- | `@arnilo/prism-providers/model-discovery` | 0.6.0 |
47
- | `@arnilo/prism-providers/neuralwatt` | 0.6.0 |
48
- | `@arnilo/prism-providers/ollama` | 0.6.0 |
49
- | `@arnilo/prism-providers/openai` | 0.6.0 |
50
- | `@arnilo/prism-providers/opencode-go` | 0.6.0 |
51
- | `@arnilo/prism-providers/openrouter` | 0.6.0 |
52
- | `@arnilo/prism-providers/vertex` | 0.6.0 |
53
- | `@arnilo/prism-providers/xai` | 0.6.0 |
54
- | `@arnilo/prism-providers/zai` | 0.6.0 |
37
+ | `@arnilo/prism-providers/ai-sdk` | 0.7.0 |
38
+ | `@arnilo/prism-providers/alibaba` | 0.7.0 |
39
+ | `@arnilo/prism-providers/anthropic` | 0.7.0 |
40
+ | `@arnilo/prism-providers/azure` | 0.7.0 |
41
+ | `@arnilo/prism-providers/bedrock` | 0.7.0 |
42
+ | `@arnilo/prism-providers/clinepass` | 0.7.0 |
43
+ | `@arnilo/prism-providers/commandcode` | 0.7.0 |
44
+ | `@arnilo/prism-providers/deepseek` | 0.7.0 |
45
+ | `@arnilo/prism-providers/google` | 0.7.0 |
46
+ | `@arnilo/prism-providers/hyper` | 0.7.0 |
47
+ | `@arnilo/prism-providers/kimi` | 0.7.0 |
48
+ | `@arnilo/prism-providers/model-discovery` | 0.7.0 |
49
+ | `@arnilo/prism-providers/neuralwatt` | 0.7.0 |
50
+ | `@arnilo/prism-providers/ollama` | 0.7.0 |
51
+ | `@arnilo/prism-providers/openai` | 0.7.0 |
52
+ | `@arnilo/prism-providers/opencode-go` | 0.7.0 |
53
+ | `@arnilo/prism-providers/openrouter` | 0.7.0 |
54
+ | `@arnilo/prism-providers/vertex` | 0.7.0 |
55
+ | `@arnilo/prism-providers/xai` | 0.7.0 |
56
+ | `@arnilo/prism-providers/zai` | 0.7.0 |
55
57
  <!-- generated:package-truth:providers end -->
56
58
 
57
59
 
@@ -143,7 +145,7 @@ A packed tarball contains only public compiled output and release files:
143
145
  - Code packages ship `README.md`, `LICENSE`, and `CHANGELOG.md`; family/profile packages ship `README.md` and `CHANGELOG.md`.
144
146
  - The core tarball additionally ships the full `docs/` directory (the docs hub), `templates/init/`, and the `templates/` gallery (e.g. `deep-research`) used by `prism init`.
145
147
  - `dist/cli.js` and the `bin` link in core.
146
- - **Tarball filenames.** npm strips the `@scope/` prefix, so the core package `@arnilo/prism` produces a tarball named `arnilo-prism-0.6.0.tgz`; family packages produce `arnilo-prism-core-0.6.0.tgz`, `arnilo-prism-coding-tools-0.6.0.tgz`, `arnilo-prism-providers-0.6.0.tgz` (all 19 adapters inside), `arnilo-prism-memory-0.6.0.tgz`, `arnilo-prism-web-tools-0.6.0.tgz`, and `arnilo-prism-office-0.6.0.tgz`; capability packages like `arnilo-prism-mcp-0.6.0.tgz` carry their own package version. Independent-package tags carry their own version. The CLI bin name `prism` is unaffected by the package name (`npx prism` still works; npm allows the bin field to differ from the package name).
148
+ - **Tarball filenames.** npm strips the `@scope/` prefix, so the core package `@arnilo/prism` produces a tarball named `arnilo-prism-0.7.0.tgz`; family packages produce `arnilo-prism-core-0.7.0.tgz`, `arnilo-prism-coding-tools-0.7.0.tgz`, `arnilo-prism-providers-0.7.0.tgz` (all 19 adapters inside), `arnilo-prism-memory-0.7.0.tgz`, `arnilo-prism-web-tools-0.7.0.tgz`, and `arnilo-prism-office-0.7.0.tgz`; capability packages like `arnilo-prism-mcp-0.7.0.tgz` carry their own package version. Independent-package tags carry their own version. The CLI bin name `prism` is unaffected by the package name (`npx prism` still works; npm allows the bin field to differ from the package name).
147
149
 
148
150
  Excluded from every tarball by `files` negation:
149
151
 
@@ -244,13 +246,13 @@ Frozen by Phase 12 Task 0 in `scripts/phase12-freeze-manifest.json` (schema gate
244
246
 
245
247
  | Runtime | Supported | Measured in CI |
246
248
  | --- | --- | --- |
247
- | Node | 22, 24 (`engines.node >=22`) | `verify` runs the full `sdk:ready` gate on Node 24; `node22-compat` builds and imports every public root `exports` target on Node 22. Node 20 support was dropped in 0.6.0 (`dev-006`; Node 20 reached upstream end-of-life 2026-04-30). |
249
+ | Node | 22, 24 (`engines.node >=22`) | `verify` runs the full `sdk:ready` gate on Node 24; `node22-compat` builds and imports every public root `exports` target on Node 22. Node 20 support was dropped in 0.6.0 (`dev-006`; Node 20 reached upstream end-of-life 2026-04-30); 0.7.0 keeps the same floor. |
248
250
  | PostgreSQL | 16 (`pgvector/pgvector:pg16`) | `postgres-integration` service container |
249
251
 
250
252
  ## Extension and configuration notes
251
253
 
252
254
 
253
- - **Required `@arnilo/prism` peer.** Every first-party code package declares a non-optional **caret** `@arnilo/prism@^0.3.3` peer (plan 041-044 republished set; the plan 039 set keeps `^0.3.1` and unchanged packages keep the prior `^0.3.0` window peer — all satisfy the root) (`peerDependenciesMeta` must not mark `@arnilo/prism` optional; other peers such as `playwright-core` may be optional). **Peer-version policy (plan 030, Decision B — independent packages):** internal ranges stay inside the 0.x `^0.3.0` window, so a package may patch independently while consumers remain on a compatible 0.3.x line. A package outside that window (for example `0.4.0`) is refused by the release gate until the next coordinated peer bump. Inside the workspace each package also declares `"@arnilo/prism": "file:../.."` in `devDependencies` so `npm install` resolves the peer locally; that devDependency is stripped from consumer installs and is not a runtime dependency.
255
+ - **Required `@arnilo/prism` peer.** Every first-party code package declares a non-optional **caret** `@arnilo/prism@^0.7.0` peer (the lockstep 0.7.0 cut rewrote every internal range; the version-literal gate rejects a declared range that only satisfies the cut version) (`peerDependenciesMeta` must not mark `@arnilo/prism` optional; other peers such as `playwright-core` may be optional). **Peer-version policy (plan 030, Decision B — independent packages):** internal ranges stay inside the caret window of the cut they shipped in, so a package may patch independently while consumers remain on a compatible 0.x line. A package outside that window is refused by the release gate until the next coordinated peer bump. Inside the workspace each package also declares `"@arnilo/prism": "file:../.."` in `devDependencies` so `npm install` resolves the peer locally; that devDependency is stripped from consumer installs and is not a runtime dependency.
254
256
  - **Public access.** All 56 manifests (root + 55 workspace packages: 49 code packages + 6 pure-manifest family/profile packages — the 10 `prism-*` family/profile set is the 6 pure-manifest profiles plus the 4 code packages `prism-caveman`, `prism-impeccable`, `prism-openapi-tools`, `prism-ponytail`) declare `"publishConfig": { "access": "public" }`; the publisher also passes `--access public` explicitly because scoped packages otherwise default to restricted on first publish.
255
257
  - **Shipped vs repository docs.** The npm tarball ships `docs/` pages linked from `docs/index.md` (public API, security, migration, providers, install). It excludes `docs/_evidence/` (per-phase evidence freezes, including `release-0.2.7-evidence.md`), `docs/release-*-evidence.md`, and `docs/api-page-template.md`. Those files remain in git for audit. `dist/__tests__` and `*.map` stay excluded.
256
258
  - **Map retention knob.** Source maps are emitted locally but stripped from tarballs by `!dist/**/*.map`. Removing that `files` negation ships maps in releases (larger tarballs, better consumer stack traces).
@@ -19,7 +19,7 @@ APIs:
19
19
 
20
20
  Configure `AgentConfig.runLedger` when you want every run of an agent to be persisted. Override it per run with `RunOptions.runLedger` if a single run needs a different adapter or no adapter at all. Use `runLedger` whenever you need durable observability, billing, audit replay, or run-scoped analytics.
21
21
 
22
- Do not use `RunLedger` as a replacement for `SessionStore` — messages, branches, and session entries continue to go through `SessionStore.append()`. Do not use it for live streaming; subscribers still receive `AgentEvent` through `session.subscribe()`.
22
+ Do not use `RunLedger` as a replacement for `SessionStore` — messages, branches, and session entries continue to go through `SessionStore.append()`. Do not use it for live streaming; subscribers still receive `AgentEvent` through `session.subscribe()`. Realtime voice tokens settle through `ModelRouter.recordUsage` with `kind: "generation"` (see [Realtime voice](realtime-voice.md)); missing usage stays unknown, never zero.
23
23
 
24
24
  ## Inputs / request
25
25
 
@@ -298,7 +298,8 @@ console.log(cacheUsageReport(aggregate?.usage));
298
298
  - Adapters that need upsert semantics can use `RunRecord.id` (== `runId`) as the stable key.
299
299
  - Use `cacheUsageReport(record.usage, model)` for cache diagnostics from normalized usage. It works when a provider reports `cacheReadTokens` without `cacheWriteTokens`; missing write tokens are reported as `0`, and unavailable hit rate/savings stay `undefined`.
300
300
  - **Provider-specific telemetry is package-owned.** Core `Usage` carries token counts and `cost`/`currency`; it has no energy or detailed cost-breakdown fields. Providers that surface extra telemetry (e.g. `@arnilo/prism-providers/neuralwatt` exposes `neuralWattEventsWithTelemetry()`, `parseNeuralWattComment()`, and `mapNeuralWattTelemetry()` for `: energy`/`: cost` SSE comments and non-streaming top-level fields) keep that data in package-specific helpers/types. Telemetry never enters `RunLedger` usage rows unless the host explicitly copies it in; it carries usage/cost numbers only — never prompts, API keys, or headers. Account-level quota is likewise package-owned: `@arnilo/prism-providers/neuralwatt` exports an explicit `getNeuralWattQuota()` helper that the host calls on demand (never during generation); NeuralWatt rate-limits that endpoint to 1 request per second per customer, so the caller owns throttling.
301
- - **Live timing metadata.** `provider_turn_*` events and `ToolExecutionMetadata` on terminal `tool_execution_*` events expose latency, retry `attempt`, and tool `durationMs` for subscribers and ledger replay — see [Observability](observability.md).
301
+ - **Governed provider lifecycle and reservation reconciliation.** For invocation-level accounting outside of or in addition to `RunLedger`, wrap providers with `createGovernedProvider` or `router.createGovernedProvider` from `@arnilo/prism-core/governance/model-router`. The adapter handles atomic admission reservations, bounds streaming, and guarantees explicit settlement: missing actual usage on an interrupted or EOF stream is committed as reserved liability (`unknownUsage: true`) rather than zero, avoiding budget leakages or unmetered oversubscriptions. See [Model routing](model-routing.md).
302
+ - **Aggregate task/tenant accounting across all paid work.** Complex agent tasks often span retries, model fallbacks, delegated children, background compactions, embedding jobs, and paid tools. Passing `taskId` and `kind` (`"generation" | "embedding" | "compaction" | "tool"`) coordinates all related calls under a single atomic task-level reservation and budget scope. Committed usage decomposes into separate `byModel` and `byKind` attributions (`router.readBudget({ identity, taskId })`) while preventing double-charging across parent/child boundaries or replayed events. Long-running holds can be safely renewed via `router.renewBudget({ ... })` before expiry without prematurely releasing live liability. See [Model routing](model-routing.md) and [Enterprise PostgreSQL state](enterprise-postgres-state.md).
302
303
 
303
304
  ## Security and performance notes
304
305
 
package/docs/server.md CHANGED
@@ -45,7 +45,7 @@ At least one non-empty ownership field must come from `authorize()`. Request JSO
45
45
  | `POST /prism/agents/:id/runs` | `agent.run` | `{ "input": string | Message | Message[] }` |
46
46
  | `POST /prism/agents/:id/stream` | `agent.stream` | same; SSE response |
47
47
  | `GET /prism/agents/:id/runs/:runId` | `agent.status` | none; redacted public state/version only |
48
- | `POST /prism/agents/:id/runs/:runId/resume` | `agent.resume` | `{ "decision": "approve" | "deny", "expectedVersion": number }` |
48
+ | `POST /prism/agents/:id/runs/:runId/resume` | `agent.resume` | `{ "decision": "approve" | "deny", "expectedVersion": number, "modifiedArguments"?, "approvalId"?, "reason"? }` or `{ "decisions": [...], "expectedVersion": number }` |
49
49
  | `GET /prism/agents/:id/runs/:runId/events?cursor=` | `agent.events` | none; durable SSE, also accepts `Last-Event-ID` |
50
50
  | `POST /prism/workflows/:id/runs` | `workflow.run` | `{ "input": unknown, "runId"?: string }` |
51
51
  | `POST /prism/workflows/:id/stream` | `workflow.stream` | same; SSE response |
@@ -109,7 +109,7 @@ const handler = createPrismHandler({
109
109
  - Workflow exposure requires its existing `WorkflowCheckpointAdapter`; no server-owned database exists.
110
110
  - Schedule exposure is optional and may be one service or an authorization-selected resolver. Returned service ownership must exactly match authorized tenant/account/user scope; otherwise request is forbidden.
111
111
  - `PrismWorkflowExposure.runOptions` can supply agent/tool/policy/resume-validator wiring. Server-owned ownership, signal, checkpoint, redactor, run ID, and event bus fields cannot be overridden.
112
- - The agent resume endpoint (`/prism/agents/{id}/runs/{runId}/resume`) accepts `{ decision: "approve" | "deny" }` or `{ decisions: [{ approvalId, outcome, reason?, modifiedArguments?, elicitation? }] }` next to `expectedVersion` — exactly one of `decision`/`decisions`. Entries are validated at the boundary (count ≤ 128, four outcomes, bounded reason/payloads) and core applies them atomically under the run's CAS; unknown ids, stale versions, and malformed batches fail closed without touching the run.
112
+ - The agent resume endpoint (`/prism/agents/{id}/runs/{runId}/resume`) accepts `{ decision: "approve" | "deny", modifiedArguments?, approvalId?, reason? }` or `{ decisions: [{ approvalId, outcome, reason?, modifiedArguments?, elicitation? }] }` next to `expectedVersion` — exactly one of `decision`/`decisions`. When `modifiedArguments` is passed with `decision: "approve"`, omitting `approvalId` correlates the run's single pending decision (multiple pending decisions without `approvalId` fail closed). Entries are validated at the boundary (count ≤ 128, four outcomes, bounded reason/payloads, schema validation) and core applies them atomically under the run's CAS; unknown ids, stale versions, deny with edits, and malformed batches fail closed without touching the run.
113
113
  - Host/origin checks and CORS headers activate only when their allow-lists are configured. Hosts still own reverse-proxy trust and canonical host handling.
114
114
 
115
115
  Default/hard ceilings:
@@ -224,7 +224,8 @@ Compose beside `createPrismHandler` — Prism starts no listener, container orch
224
224
  | Helper | Role |
225
225
  | --- | --- |
226
226
  | `createPrismHealthHandler` | `GET /health`, `/livez`, `/readyz`. Minimal JSON; `?detail=1` requires `authorizeDetail`. No secrets/tenant payloads by default. Ready fails while draining. |
227
- | `createPrismDrainController` | `beginDrain()` rejects admit ops (`agent.run`/`stream`/`resume`, workflow run/stream/enqueue/resume/replay, schedule create/trigger) with `503 ERR_PRISM_SERVER_DRAINING`. Status/cancel/list stay open. |
227
+ | `createPrismDrainController` | `beginDrain()` rejects admit ops (`agent.run`/`stream`/`resume`, workflow run/stream/enqueue/resume/replay, schedule create/trigger) with `503 ERR_PRISM_SERVER_DRAINING`. Status/cancel/list stay open. Snapshot includes finite `deadlineAt` / `expired`. |
228
+ | `createPrismOperatorHandler` | Authenticated `/ops/queue`, `/suspended`, `/failed`, `/unknown`, `POST /ops/cancel`, `POST /ops/reconcile`. Ownership-scoped. Reconcile accepts only `completed` / `failed_terminal` plus evidence — never retries unknown effects or unlocks leases. |
228
229
  | `rateLimit` on handler | Host adapter after authorize, before session create. Return denial `{ retryAfterMs, code, message }` → `429` + optional `Retry-After`. `createMemoryRateLimiter` is single-process only. |
229
230
  | `createPrismAgentEventReplay` | Shared `AgentEventSource` page/follow semantics for exact-owned runs. |
230
231
  | `createPrismEventReplay` / `createPrismReplayHandler` | Compatible ownership-scoped legacy `queryEvents` pages (`redacted: true`). Does not re-run work. Unauthorized replay denies. |
@@ -259,6 +260,7 @@ A2A routes are not added to `createPrismHandler()`. Install `@arnilo/prism-core/
259
260
  - [Performance](performance.md): capacity notes for concurrent runs and deployment probes.
260
261
  - [Agent/session runtime](agent-session-runtime.md): direct result and event stream semantics.
261
262
  - [Workflows](workflows.md): durable checkpoints, status, cancellation, exact-once resume, and `createWorkflowCoordinator` workers.
263
+ - [Operations runbook](operations.md): fair admission, drain deadline, operator queue/cancel/reconcile.
262
264
  - [MCP client and server exposure](mcp-tools.md): selected MCP capabilities and web-standard MCP transport.
263
265
  - [Host security guide](host-security.md): remote-boundary checklist.
264
266
  - [A2A interoperability](a2a.md): separately mounted A2A 1.0 handler/client.
package/docs/speech.md CHANGED
@@ -120,6 +120,8 @@ await runSpeechConformance({
120
120
  - [Realtime sessions](public-contracts.md): `RealtimeSession` for interactive
121
121
  bidirectional voice; `RealtimeEvent.transcript_delta` is this contract's
122
122
  naming anchor.
123
+ - [Realtime voice](realtime-voice.md): governed bridge from a Realtime session
124
+ into host tool dispatch, barge-in, and transcript privacy.
123
125
  - [Provider conformance](provider-conformance.md): `runSpeechConformance` /
124
126
  `runTranscriptionConformance` and the offline conformance matrix.
125
127
  - [Provider packages](provider-packages.md): subpath import rules for
@@ -10,22 +10,29 @@ Use a supervisor when a host or agent must choose a child dynamically. Use `@arn
10
10
 
11
11
  ## Inputs / request
12
12
 
13
+ **Option surfaces** — `CreateSupervisorOptions` (ownership, child catalog, hooks, `childEvents`, limits), `SupervisorLimits` / `ResolvedSupervisorLimits` (depth, active children, child events, bytes), `DelegationWaitOptions` (`timeoutMs`, `signal`), `CreateSpawnAgentToolOptions` / `CreateDelegationControlToolOptions` (supervisor, tool name, sync/async mode), `WorktreeChildFactoryOptions` (workspace lifecycle, repository, roots), and `ObserveSupervisorLifecycleOptions` (supervisor, emit, redactor, steps).
14
+
13
15
  | API/field | Meaning |
14
16
  | --- | --- |
15
17
  | `createSupervisor({ ownership, children })` | Creates one ownership-scoped supervisor. |
16
18
  | `SupervisorChild.createAgent(context)` | Child-owned factory; receives derived resource/thread IDs, narrowed permission, abort signal, and nested `delegate`. |
17
19
  | `delegate({ childId, input, threadId?, limits?, signal? })` | Invokes one allow-listed child. Input is text and byte-bounded. |
20
+ | `delegateAsync({ childId, input, threadId?, limits?, signal? })` | Starts one local child and returns `{ delegationId, status: "running" }` without waiting for its result. |
21
+ | `wait(delegationId)` / `cancel(delegationId)` | Joins one local async child (capped at supervisor timeout) or aborts it. Unknown and foreign IDs share one denial. |
22
+ | `createSpawnAgentTool({ supervisor, name? })` | Returns non-exclusive `spawn_agent` tool for a parent model. Its closed schema exposes only host child IDs, input, optional thread ID, and `mode`. |
23
+ | `createWaitAgentTool` / `createCancelAgentTool` | Return `wait_agent` / `cancel_agent` tools for host-owned async handles. |
24
+ | `Supervisor.childIds` | Frozen advertised child-id list the spawn tool's schema enum is built from; model arguments cannot extend it. |
18
25
  | `hooks.before` | May reject, modify redacted input, or narrow limits/policy. |
19
26
  | `hooks.after` | Observes redacted terminal summary; failures cannot alter settled result. |
20
27
  | `limits` | Depth 4/16, active children 4/32, input 64 KiB/1 MiB, steps 8/64, tools 32/256, tokens 20k/1m, timeout 60s/30m, event queue 128/4096, child events/delegation 256/4096, child-event bytes 32 KiB/256 KiB default/hard. Over-cap `delegate()` throws `SupervisorLimitError` before incrementing `activeChildren`. Hook rejection and timeout decrement the count exactly once (no leaked timers). |
21
28
 
22
29
  ## Outputs / response / events
23
30
 
24
- `delegate()` returns the child's `AgentRunResult` or throws its `AgentRunError`/a supervisor denial or limit error. `subscribe()` emits bounded `delegation_started`, `delegation_finished`, `delegation_rejected`, and `delegation_error` metadata events. Graceful close drains already-queued terminal events before the iterator completes (same core multiplexer contract). Hosts may project those events through observability `handleDelegation()` using the parent Prism run ID; no OpenTelemetry dependency enters this package.
31
+ `delegate()` returns the child's `AgentRunResult` or throws its `AgentRunError`/a supervisor denial or limit error. `delegateAsync()` returns a local running handle; `wait()` returns its result (or `{ status: "cancelled" }` after `cancel()`), and stays idempotent while its terminal record is retained (bounded by `limits.maxQueuedEvents`; an evicted or foreign id returns the same non-enumerating error). `subscribe()` emits bounded `delegation_started`, `delegation_finished`, `delegation_rejected`, and `delegation_error` metadata events. Graceful close drains already-queued terminal events before the iterator completes (same core multiplexer contract). Hosts may project those events through observability `handleDelegation()` using the parent Prism run ID; no OpenTelemetry dependency enters this package.
25
32
 
26
33
  ### Child event passthrough (opt-in)
27
34
 
28
- `createSupervisor({ childEvents: true })` projects a redacted, size-capped **milestone** subset of child `AgentEvent`s onto the same stream as `delegation_child_event` (tagged `childId`, `delegationId`, `depth`). v1 covers run start/finish/`suspended`/`denied` and tool-execution started/finished/error/blocked — not per-token `message_delta`. Default off: the stream is byte-identical to today (no subscribe, no allocation). Caps: `limits.maxChildEventsPerDelegation` (256/4096) and `limits.maxChildEventBytes` (32 KiB/256 KiB); exceeding either drops further child events and emits one `delegation_child_events_capped` marker (never throws). Events pass through the supervisor `redactor` before emission. Children never receive supervisor internals or store/subscription access. Resume-path rebuilds (`resumeNestedRun`) do not currently project child events — live passthrough is the initial `delegate()` session only.
35
+ `createSupervisor({ childEvents: true })` projects a redacted, size-capped **milestone** subset of child `AgentEvent`s onto the same stream as `delegation_child_event` (tagged `childId`, `delegationId`, `depth`). v1 covers run start/finish/`suspended`/`denied` and tool-execution started/finished/error/blocked — not per-token `message_delta`. Default off: the stream is byte-identical to today (no subscribe, no allocation). Caps: `limits.maxChildEventsPerDelegation` (256/4096) and `limits.maxChildEventBytes` (32 KiB/256 KiB); exceeding either drops further child events and emits one `delegation_child_events_capped` marker (never throws). Events pass through the supervisor `redactor` before emission. Children never receive supervisor internals or store/subscription access. Resume-path rebuilds (`resumeNestedRun`) attach the same pump to the rebuilt child session, so a delegation that suspended for approval keeps projecting milestones after the root run resumes; counters restart per pump, so each attempt gets the full cap.
29
36
 
30
37
  ## Request/response example
31
38
 
@@ -54,17 +61,36 @@ const supervisor = createSupervisor({
54
61
  const result = await supervisor.delegate({ childId: "research", input: "Check sources" });
55
62
  ```
56
63
 
64
+ ## Model-facing spawn tool
65
+
66
+ `createSpawnAgentTool({ supervisor })` turns the same host-owned child allow-list into non-exclusive `spawn_agent` tool calls, so independent calls use the parent session's `toolConcurrency`. The schema has only `childId`, `input`, optional `threadId`, and `mode: "sync" | "async"` (default `sync`); unknown children fail closed as standard tool errors before delegation. Model arguments cannot supply child tools, identity, scopes, or higher limits. Async returns only a local `{ delegationId, status: "running" }` handle. Install `wait_agent` once per handle for wait-all, or `cancel_agent` to abort it; cancellation is terminally reported by `wait_agent`. Parent-run abort propagates to running children. Handles are in-process, ownership-scoped, and bounded — they do not survive host restart.
67
+
68
+ ```ts
69
+ import { createAgent } from "@arnilo/prism";
70
+ import { createCancelAgentTool, createSpawnAgentTool, createWaitAgentTool } from "@arnilo/prism-core/runtime/supervisor";
71
+
72
+ const parent = createAgent({
73
+ /* parent model/provider */
74
+ tools: [createSpawnAgentTool({ supervisor }), createWaitAgentTool({ supervisor }), createCancelAgentTool({ supervisor })],
75
+ });
76
+ await parent.createSession().run("Research auth and billing", {
77
+ loop: { strategy: "single-shot", toolConcurrency: 2 },
78
+ });
79
+ ```
80
+
57
81
  > **Contract — child factories return `Agent`.** `createAgent` must return an `Agent`, not an `AgentSession` (or a plain object). Wrong type throws `SupervisorError: child "<id>" factory must return an Agent, got <type>` on both initial `delegate()` and nested resume. Nested approvals also need a **stable config** plus a **durable (or rebuild-stable) store** — calling `createSession()` inside the factory and returning that session loses the child's checkpointed leaf. Live demo: [`examples/autonomous-coding-loop.ts`](../examples/autonomous-coding-loop.ts) (`childAgent` returns `createAgent(...)`).
58
82
 
59
83
  ## Durable child approvals
60
84
 
61
- With `checkpoints` + `definitionRevision`, every child run is durable with `interruptBeforeTool: true`. A child that suspends on pending decisions throws `AgentDelegationSuspendedError` out of `delegate()`; when the delegation runs inside a root agent's tool, core converts it into a root suspension whose `interruption.pendingDecisions` carry hashed root-visible approval ids (`sub_<sha256(runId:childApprovalId)>`) and `attribution.path` (redacted child ids, root first, at most 8 deep). Root decisions route back through the same CAS rules: pass `supervisor.resumeNestedRun` as `resumeNestedRun` in the root run's `runState` and in every `resumeAgentRun` options object. The supervisor rebuilds the child from a bounded delegation mapping stored in the same checkpoint store (child id, delegation/thread ids, redacted input, version), re-runs the `before` hook so its narrowing applies to the resumed run (hooks must be idempotent), and re-attributes re-suspensions recursively, so grandchild decisions surface with the full path. A delegating child's own `interruptBeforeTool` also gates its delegate tool, so hosts approve delegation and the child's own side effects as separate stages. Root `*_for_run` stickies record the attribution path and only match the same delegation path; child stickies live on the child run and expire with it. A root approval never widens the child: the child's narrowed permission re-runs at dispatch. Unknown or foreign nested run ids fail closed with one non-enumerating error. Child factories must return stable configs and a durable (or rebuild-stable) session store for resume to work.
85
+ With `checkpoints` + `definitionRevision`, every child run is durable with `interruptBeforeTool: true`. A child that suspends on pending decisions throws `AgentDelegationSuspendedError` out of `delegate()`; when the delegation runs inside a root agent's tool, core converts it into a root suspension whose `interruption.pendingDecisions` carry hashed root-visible approval ids (`sub_<sha256(runId:childApprovalId)>`) and `attribution.path` (redacted child ids, root first, at most 8 deep). Root decisions route back through the same CAS rules: pass `supervisor.resumeNestedRun` as `resumeNestedRun` in the root run's `runState` and in every `resumeAgentRun` options object. The supervisor rebuilds the child from a bounded delegation mapping stored in the same checkpoint store (child id, delegation/thread ids, redacted input, version), re-runs the `before` hook so its narrowing applies to the resumed run (hooks must be idempotent), and re-attributes re-suspensions recursively, so grandchild decisions surface with the full path. A delegating child's own `interruptBeforeTool` also gates its delegate tool, so hosts approve delegation and the child's own side effects as separate stages. Root `*_for_run` stickies record the attribution path and only match the same delegation path; child stickies live on the child run and expire with it. A root approval never widens the child: the child's narrowed permission re-runs at dispatch. Unknown or foreign nested run ids fail closed with one non-enumerating error. A resumed attempt is terminal-symmetric with live `delegate()`: it publishes `delegation_finished` (`delegation_rejected` when the re-run `before` hook denies) and runs `hooks.after` once with the original `childId`/`delegationId`, which is what lets an isolated child's worktree be cleaned up. A suspended child stays non-terminal — no finish event, no `after` — and a rebuild that throws before the run starts (stale version, fingerprint drift) publishes nothing and runs no terminal hook, so a duplicate resume attempt can never clean up a live suspended child. Child factories must return stable configs and a durable (or rebuild-stable) session store for resume to work.
62
86
 
63
87
  ## Extension and configuration notes
64
88
 
89
+ Parallel isolated children: wrap one catalog factory with `createWorktreeChildFactory` from `@arnilo/prism-coding-tools/agent` and pass its `after` as the supervisor's terminal hook — the supervisor stays git-agnostic, and the child context gains `cwd` pointing at its own linked worktree. See [Coding workspaces](coding-workspaces.md#spawn-isolation-supervisor-children).
90
+
65
91
  Child factories resolve their own providers/credentials and construct context/memory using the supplied IDs. Parent, child, returned-agent, budget, and hook permission policies are AND-composed. Child/request/hook limits can only lower inherited limits. A nested factory can call the supplied `delegate()`; immutable path state rejects cycles and depth overflow.
66
92
 
67
- Supervisors propagate parent `identity` and `effectStore` to every child agent/run so delegated tool effects stay under the same ownership scope.
93
+ Supervisors propagate parent `identity` and `effectStore` to every child agent/run so delegated tool effects stay under the same ownership scope. Set host-authored `SupervisorChild.scopes` to derive a child identity with `narrowIdentity`; `assertIdentityPropagation` rejects scope widening before its factory runs.
68
94
 
69
95
  ## Security and performance notes
70
96
 
@@ -82,7 +108,9 @@ Supervisors propagate parent `identity` and `effectStore` to every child agent/r
82
108
  - [Agent identity](agent-identity.md): host-verified identity and narrow delegation.
83
109
  - [A2A interoperability](a2a.md): separate remote protocol boundary. `A2ATaskLifecycle` adapts host durable agent/workflow state directly; it does not route A2A execution through local supervisor child planning.
84
110
  - [Workflows](workflows.md): preferred deterministic orchestration.
85
- - Example: [`examples/autonomous-coding-loop.ts`](../examples/autonomous-coding-loop.ts) — per-child models, factory returns `Agent`.
111
+ - [Coding workspaces](coding-workspaces.md): opt-in per-child worktree isolation via `createWorktreeChildFactory`.
112
+ - [Coding agent tools](coding-agent-tools.md): opt-in `observeSupervisorLifecycle` bridges supervisor `delegation_*` events to coding `subagent_started` / `subagent_stopped` for host timelines.
113
+ - Examples: [`examples/autonomous-coding-loop.ts`](../examples/autonomous-coding-loop.ts) — per-child models, factory returns `Agent`; [`examples/spawn-agent-tool.ts`](../examples/spawn-agent-tool.ts) — two model-requested explore children in one tool turn.
86
114
  - [Working and semantic memory](working-and-semantic-memory.md): child scope construction.
87
115
  - [Host security](host-security.md): permission and credential boundaries.
88
116
  - [Obscura browser engine](obscura.md): optional binary-backed generic tools for child agents.
package/docs/testing.md CHANGED
@@ -21,7 +21,7 @@ Documents how the hermetic suite runs, which stage a new suite belongs to, and t
21
21
  | build race | `scripts/phase23-build-race.test.mjs` |
22
22
  | workspace suites | `npm run test --workspaces --if-present` |
23
23
 
24
- Protected-environment legs (Postgres, PTY, NATS, live credentials) are not part of `npm test`; they fail closed with one canonical `BLOCKED GATE <id> requires=<names> evidence=<surface> hint=<how to unblock>` record and a non-zero exit when their infrastructure is absent (registry and audit: `node scripts/blocked-gate.mjs`). Retired phase freeze/release gates live in `scripts/` for audit but are deliberately kept out of the chain.
24
+ Protected-environment legs (Postgres, PTY, NATS, live credentials) are not part of `npm test`; they fail closed with one canonical `BLOCKED GATE <id> requires=<names> evidence=<surface> hint=<how to unblock>` record and a non-zero exit when their infrastructure is absent (registry and audit: `node scripts/blocked-gate.mjs`). Retired phase freeze/release gates live in `scripts/` for audit but are deliberately kept out of the chain. 0.7.0 host-completeness packed proof is `scripts/fixtures/e2e-070-host-completeness-journey.mjs` (same packed consumer as the full-surface journey) plus `scripts/host-completeness-evidence.test.mjs`; live legs stay skip-not-fail. R16/R17 stay blocked until plans 077/074 ship.
25
25
 
26
26
  ## Isolation rules
27
27
 
@@ -93,7 +93,8 @@ Core maps only shapes shared by ≥2 packages (or an explicit no-op). Unique kno
93
93
  | `@arnilo/prism-providers/commandcode` / `@arnilo/prism-providers/opencode-go` | Gateway level tables (`claude-*` → `output_config_effort`, `gpt-5.6*` → `openai_reasoning`, K3/DeepSeek/GLM → `reasoning_effort`, K2.x/MiniMax/Qwen → `thinking_type`) |
94
94
  | `@arnilo/prism-providers/alibaba` | `thinking_type` mapped onto Qwen `enable_thinking` (toggle, no effort levels) |
95
95
  | `@arnilo/prism-providers/ollama` | `reasoning_effort`; `gpt-oss*` declares `low/medium/high`; native `think` field never mixed in |
96
- | `@arnilo/prism-providers/azure` / `.../vertex` / `.../bedrock` | OpenAI-compat sanitized forwarder (`reasoning_effort` / `reasoning` object), snapped to declared levels |
96
+ | `@arnilo/prism-providers/azure` / `.../vertex` / `.../bedrock` (compatible route) | OpenAI-compat sanitized forwarder (`reasoning_effort` / `reasoning` object), snapped to declared levels |
97
+ | `@arnilo/prism-providers/bedrock` (native `converse` route) | Anthropic family: `additionalModelRequestFields.thinking` (`enabled`/`disabled`/`adaptive`, default budget injected); OpenAI family: `reasoning_effort` snapped to declared levels |
97
98
  | `@arnilo/prism-providers/ai-sdk` | `noop` — host `LanguageModelV4` owns reasoning settings |
98
99
 
99
100
  ## Declared levels and snapping
@@ -133,6 +134,7 @@ OpenRouter and Hyper derive their sets from each provider's models API (`support
133
134
  - [Provider request policies](provider-request-policies.md) — `mergeProviderRequestOptions`
134
135
  - [Use-case model selection](use-case-model-selection.md) — session vs worker/summary model binding (workers take `thinkingLevel`)
135
136
  - [Agent/session runtime](agent-session-runtime.md) — prior-reasoning preservation across turns
137
+ - [Attention compiler](attention-compiler.md) — opt-in strip of thinking turns older than `thinkingKeepTurns` once the request crosses a ratio of the input cap
136
138
  - [Provider packages](provider-packages.md) — package boundaries and discovery
137
139
  - Per-provider pages under [docs/providers](providers/) — declared levels, wire field, and snapping per provider
138
140
  - [Thinking coverage evidence matrix](_evidence/thinking-coverage-2026-09-05.md) — per-model legality, source, and test pins
package/docs/tools.md CHANGED
@@ -150,14 +150,14 @@ Configuration can carry allow/deny names, but Prism does not define a policy cla
150
150
 
151
151
  ### Per-run tool scoping
152
152
 
153
- `session.run()` intentionally has no `RunOptions.tools` or `RunOptions.toolFilter`. Scope tools by building the active `ToolRegistry` for the agent/session, by resolving declarative `AgentDefinition.tools`, or by using `PermissionPolicy` / `ToolValidator` to fail closed at dispatch time. Skills do not grant tool access; `toolNames` only validates that host-active tools exist.
153
+ There is still no `RunOptions.tools` or `RunOptions.toolFilter` — those would replace or mutate the registry. `RunOptions.toolNames` is an optional **allow-list of already-registered names**. Omitted → every registered tool (legacy). Empty → no tools this run. Unknown names fail closed. The run snapshots the matching `ToolDefinition`s once; provider schemas, `search_tools`, skill `toolNames` checks, and dispatch all use that snapshot. Resume stores the grant and intersects it with current authority — it cannot widen, even if the live registry grew. Middleware, skills, and nested calls cannot add names outside the grant. Skills do not grant tool access; skill `toolNames` only validates that host-active tools exist.
154
154
 
155
155
  ```ts
156
- const activeTools = createToolRegistry([searchTool]);
157
- const agent = createAgent({ model, provider, tools: activeTools, permission, validator });
156
+ const agent = createAgent({ model, provider, tools: registry, permission, validator });
157
+ await session.run(input, { toolNames: ["web_search"] });
158
158
  ```
159
159
 
160
- Need different tools for one request? Build a short-lived agent/session with a narrower registry, or block extra calls with `PermissionPolicy` / `RunOptions.validate`. No extra per-run tool API exists yet; add one only when host apps need it.
160
+ Scope the active `ToolRegistry` (or declarative `AgentDefinition.tools`) at agent construction. `PermissionPolicy` / `RunOptions.validate` still fail closed at dispatch; `toolNames` only intersects that host-active set.
161
161
 
162
162
  ### Artifact-loop tools
163
163
 
@@ -274,7 +274,7 @@ Limits (mirroring the skill-disclosure DEFAULT/HARD cap pattern):
274
274
 
275
275
  - `search_tools({ query, k? })` returns inert `name: short description [matched: …]` lines — no schemas or tool bodies — and marks returned tools active for the session. Activation is names-only in run persistence (`sessionState.activatedToolNames`, capped at 128 names) and inert for tools absent from the current registry; a host can reset it with `session.clearActivatedTools()`.
276
276
  - Fail closed: any index or scoring error discloses the full input list — never zero tools, never wider than the input list. Exhausting the frozen 1024-tool index cap is surfaced the same way.
277
- - Disclosure never grants access: dispatch re-checks registry membership and allow/deny (`unknown_tool` / `tool_denied`) on every call regardless of what was described. Search results are intersected with the disclosed list structurally — searched tools are only ever selected from that list, never widened.
277
+ - Disclosure never grants access: dispatch re-checks registry membership and allow/deny (`unknown_tool` / `tool_denied`) on every call regardless of what was described. Search results are intersected with the disclosed list structurally — searched tools are only ever selected from that list, never widened. When `RunOptions.toolNames` is set, the search index is built from that snapshot only.
278
278
  - Scoring is BM25-lite lexical (name tokens weigh ×3, IDF from the registry): bounded, dependency-free, deterministic tie-breaks. ponytail ceiling: embedder-backed scoring via `@arnilo/prism-memory/rag` if accuracy fixtures fall short.
279
279
  - Cross-link: skills apply the same discipline to prompt text — see [Context and skills](context-and-skills.md).
280
280
 
@@ -293,6 +293,7 @@ Limits (mirroring the skill-disclosure DEFAULT/HARD cap pattern):
293
293
  - [Middleware hooks](middleware-hooks.md): `tool_call` and `tool_result` middleware used during dispatch.
294
294
  - [Credentials and redaction](credentials-and-redaction.md): redaction helpers used for tool execution errors.
295
295
  - [Observational memory compaction package](compaction-observational-memory.md): optional exact-id recall tool factory.
296
+ - [Memory fabric](memory-fabric.md): optional governed note tools (`memory.view`/`read`/`insert`/`recall`/`forget`) jailed to a host directory.
296
297
  - [Tool execution primitives](tool-execution-primitives.md): JSON Schema adapter, parallelism, MCP bridge, and execution-policy designs.
297
298
  - [MCP client bridge](mcp-tools.md): optional remote tool mapping plus separate bounded resource/prompt facades; non-tool MCP capabilities never bypass tool dispatch by masquerading as `ToolDefinition`.
298
299
  - [Recoverable tool effects](tool-effects.md): optional `tool.effect` + `effectStore` claim/CAS recovery around dispatch.