@arnilo/prism 0.3.0 → 0.3.1

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 (69) hide show
  1. package/CHANGELOG.md +27 -0
  2. package/README.md +3 -1
  3. package/dist/agent-loops.js +45 -8
  4. package/dist/agent-session/helpers.js +2 -2
  5. package/dist/cache-helpers.d.ts +11 -0
  6. package/dist/cache-helpers.js +29 -5
  7. package/dist/cli-provider-add.js +2 -1
  8. package/dist/context-budget.js +9 -6
  9. package/dist/contracts-core/agent.d.ts +2 -0
  10. package/dist/contracts-core/provider.d.ts +2 -0
  11. package/dist/event-multiplexer.js +0 -4
  12. package/dist/index.d.ts +6 -4
  13. package/dist/index.js +5 -3
  14. package/dist/input.js +19 -11
  15. package/dist/node/session-store-jsonl.js +7 -3
  16. package/dist/providers/openai-compatible.js +2 -1
  17. package/dist/providers/openai-primitives.js +2 -1
  18. package/dist/providers/schema.d.ts +7 -0
  19. package/dist/providers/schema.js +25 -0
  20. package/dist/testing/provider-conformance.d.ts +10 -0
  21. package/dist/testing/provider-conformance.js +37 -0
  22. package/dist/trim-trailing-slashes.d.ts +8 -0
  23. package/dist/trim-trailing-slashes.js +14 -0
  24. package/docs/0.1.0-readiness.md +1 -1
  25. package/docs/acp.md +1 -0
  26. package/docs/ag-ui.md +1 -0
  27. package/docs/agent-loops.md +3 -0
  28. package/docs/agent-session-runtime.md +1 -0
  29. package/docs/browser-automation.md +1 -0
  30. package/docs/database-persistence.md +1 -1
  31. package/docs/graft.md +125 -0
  32. package/docs/host-security.md +3 -1
  33. package/docs/index.md +11 -9
  34. package/docs/input-and-prompt-assembly.md +11 -6
  35. package/docs/instruction-injection.md +1 -1
  36. package/docs/mcp-tools.md +1 -0
  37. package/docs/migration.md +10 -0
  38. package/docs/node-jsonl-session-store.md +1 -1
  39. package/docs/obscura.md +175 -0
  40. package/docs/observability.md +21 -1
  41. package/docs/performance.md +58 -4
  42. package/docs/ponytail.md +1 -1
  43. package/docs/provider-caching.md +13 -11
  44. package/docs/provider-conformance.md +6 -0
  45. package/docs/provider-packages.md +1 -1
  46. package/docs/provider-primitives.md +15 -2
  47. package/docs/providers/ai-sdk.md +1 -1
  48. package/docs/providers/anthropic.md +1 -1
  49. package/docs/providers/azure.md +1 -0
  50. package/docs/providers/bedrock.md +1 -0
  51. package/docs/providers/kimi.md +2 -1
  52. package/docs/providers/openai.md +19 -7
  53. package/docs/providers/opencode-go.md +3 -1
  54. package/docs/providers/openrouter.md +4 -3
  55. package/docs/providers/vertex.md +1 -0
  56. package/docs/public-contracts.md +2 -1
  57. package/docs/rag.md +55 -8
  58. package/docs/release-and-install.md +45 -7
  59. package/docs/server.md +1 -0
  60. package/docs/supervisors.md +3 -2
  61. package/docs/system-prompts.md +1 -1
  62. package/docs/tools.md +1 -1
  63. package/docs/web-tools.md +2 -0
  64. package/docs/wiki.md +140 -0
  65. package/docs/workflows.md +4 -3
  66. package/docs/working-and-semantic-memory.md +20 -0
  67. package/package.json +12 -5
  68. package/docs/api-page-template.md +0 -32
  69. package/docs/release-0.2.7-evidence.md +0 -514
@@ -159,14 +159,26 @@ Official: [Prompt caching](https://developers.openai.com/api/docs/guides/prompt-
159
159
  model declares `ModelConfig.cache.longRetention === true`; models without that
160
160
  metadata omit the field. Featured `gpt-5.1` declares
161
161
  `cache: { kind: "openai_key", longRetention: true, maxKeyLength: 64 }`.
162
- - GPT-5.6+ official docs prefer `prompt_cache_options` / explicit breakpoints;
163
- `listOpenAIModels` sets `longRetention: false` for those ids so Prism does not
164
- emit deprecated `prompt_cache_retention` for them. Breakpoint helpers are not
165
- shipped in this package yet — hosts may pass `prompt_cache_options` through
166
- `compat` / `extra` when needed.
162
+ - GPT-5.6+ models use current `prompt_cache_options` instead of retention:
163
+ `listOpenAIModels` / `mapOpenAIModel` set `cache.explicitBreakpoints: true` and
164
+ `longRetention: false` for those ids, so Prism never emits
165
+ `prompt_cache_retention` for them. When the host supplies
166
+ `cache.breakpoints` (or forces `cache.mode: "on"`), Prism emits
167
+ `prompt_cache_options: { mode: "explicit" }` and stamps
168
+ `prompt_cache_breakpoint: { mode: "explicit" }` on the last text block of each
169
+ selected message anchor (shared breakpoint selection with
170
+ `applyCacheControl`, capped at the official 4 cache writes per request;
171
+ `tools` breakpoints are skipped — tool definitions are not markable blocks).
172
+ The only supported TTL is `"30m"` (also the default), so no ttl field is ever
173
+ emitted. `cache.mode: "off"` suppresses explicit options and markers.
174
+ - Resolved cache fields win over caller `extra`: `prompt_cache_key`,
175
+ `prompt_cache_retention`, and `prompt_cache_options` are re-applied after the
176
+ `extra` spread, so invalid caller values cannot replace the resolved policy.
167
177
  - Cache accounting is preserved in normalized `Usage`: OpenAI
168
- `input_tokens_details.cached_tokens` maps to `Usage.cacheReadTokens`. OpenAI
169
- Responses does not report a cache-write token field on older models.
178
+ `input_tokens_details.cached_tokens` maps to `Usage.cacheReadTokens` and
179
+ `input_tokens_details.cache_write_tokens` maps to `Usage.cacheWriteTokens`
180
+ (GPT-5.6+ report cache writes; older models omit the field, leaving
181
+ `Usage.cacheWriteTokens` undefined).
170
182
  - Provider-owned headers (`content-type`, `authorization`, `x-client-request-id`)
171
183
  are applied after caller `ProviderRequestOptions.headers` so caller config
172
184
  cannot replace credentials, content type, or the session request id; non-owned
@@ -219,7 +219,9 @@ Owned compat keys (`route`, `thinking`, `reasoning`, `reasoning_effort`,
219
219
  shared `applyCacheControl()` helper) on the last content block of each selected
220
220
  message — not to every block. Caching is enabled unless disabled
221
221
  (`cacheRetention: "none"` / `cache.mode: "off"`) and the model opts in via
222
- `ModelConfig.cache.kind: "cache_control"` (or `cache.mode: "on"`).
222
+ `ModelConfig.cache.kind: "cache_control"` (or `cache.mode: "on"`). A
223
+ `system_prompt` breakpoint serializes `system` as native text blocks with the
224
+ marker (plain string otherwise).
223
225
  - `cacheRetention: "long"` emits `cache_control: { type: "ephemeral", ttl: "1h" }`
224
226
  markers when the model allows long retention
225
227
  (`ModelConfig.cache.longRetention !== false`); otherwise the default ephemeral
@@ -136,9 +136,10 @@ await kernel.load([
136
136
  ### Cache and session behavior
137
137
 
138
138
  - `session_id` (request body) and the `X-Session-Id` header are derived from
139
- `ProviderRequestOptions.cacheKey` (falling back to `sessionId`) and sanitized
140
- + clamped to 256 characters via the shared `sanitizeCacheKey()` helper.
141
- OpenRouter uses this for provider sticky routing to maximize cache hits.
139
+ `ProviderRequestOptions.cache.key` (falling back to legacy `cacheKey`, then
140
+ `sessionId`) and sanitized + clamped to 256 characters via the shared
141
+ `sanitizeCacheKey()` helper. OpenRouter uses this for provider sticky routing
142
+ to maximize cache hits.
142
143
  - **Automatic caching** (no breakpoints): when caching is enabled for an
143
144
  explicit `cache_control` model (or `compat.openRouterCache` /
144
145
  `cache.mode: "on"`), Prism emits a top-level
@@ -60,6 +60,7 @@ const provider = createVertexProvider({
60
60
  - No Google Cloud SDK dependency in the package.
61
61
  - Custom/private endpoint hosts are preserved.
62
62
  - Tokens redacted from errors; no import-time credential prefetch — the credential is resolved exactly once per request (a rotating `CredentialValueSource` is never consumed twice; the same resolved token drives the wrapper check and the inner auth header).
63
+ - Conformance-proven (Task 6): package `setup()` performs zero fetch and zero credential resolution; an already-aborted signal fails fast; a truncated SSE stream (no `data: [DONE]`) ends in an `error` event; native Vertex cached-content lifecycle is intentionally unsupported on the OpenAI-compatible route — no cache wire fields are emitted even when the request carries Prism cache hints (use `@arnilo/prism-provider-google`'s `extra.cachedContent` on that package, or manage cache resources host-side).
63
64
  - Pair with model-router residency allow-lists on `location`.
64
65
 
65
66
  ## Related APIs
@@ -124,6 +124,7 @@ Important request shapes:
124
124
  | `ContextResolutionContext` | Context provider input: messages plus optional session/run ids, metadata, and signal. |
125
125
  | `InputAssemblyLayout` | Default input layout selector: `"cache_aware"` (default) or opt-in `"legacy"`. |
126
126
  | `DefaultInputBuildContext` | Optional default input assembly context: input layout, instructions, history, summaries, attachments, explicit resources, tool results, middleware, ids, metadata, and signal. |
127
+ | `PromptBuildRequest` | Prompt-builder input: messages, context, selected skills, active tools, model, metadata, signal, and optional `inputLayout`; the default builder uses cache-aware ordering unless `legacy` is explicit. |
127
128
  | `ResolveContextOptions` | Ordered context resolution input: selected providers, messages, ids, metadata, signal, and optional middleware. |
128
129
  | `AssembleProviderInputOptions` | Provider input assembly input: model, input, optional builders, selected context providers/skills, active tools, metadata, and signal. |
129
130
  | `PromptTemplateOptions` | Missing-variable behavior for tiny `renderPromptTemplate()` substitutions. |
@@ -148,7 +149,7 @@ Important request shapes:
148
149
  | `CheckpointStore` | Generic versioned checkpoint capability: save/load/bounded-list/delete by namespace and key, with ownership, exact-version CAS, and lease fencing. `createMemoryCheckpointStore()` is the reference implementation; it is bounded — `maxRecords` (default 10,000, evicts least-recently-saved) and `maxValueBytes` (default 1 MiB per JSON value). |
149
150
  | `LeaseStore` | Atomic acquire/renew/release/get by namespace and key, with opaque claim tokens, expiry, ownership scope, and monotonically increasing takeover fences. `createMemoryLeaseStore()` is the reference implementation. |
150
151
  | `RunFeedbackStore` | Immutable append, bounded owned query, and owned deletion for ratings/comments/tags linked to existing run/trace/evaluation IDs. `createMemoryRunFeedbackStore()` is the reference implementation. |
151
- | `EventMultiplexer<T>` | Generic bounded fan-in from async sources. `createEventMultiplexer()` owns queue limits, overflow policy, abort, source teardown, and close behavior. Single-consumer contract: a second concurrent `subscribe()` throws `EventMultiplexerError` (`ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER`); the slot frees when the active consumer completes/is `return()`ed at a yield or the multiplexer closes. `observe` fan-in is unchanged (broadcast happens at the source). |
152
+ | `EventMultiplexer<T>` | Generic bounded fan-in from async sources. `createEventMultiplexer()` owns queue limits, overflow policy, abort, source teardown, and close behavior. Graceful `close()` stops publishes/sources and drains already-queued events before the subscriber completes; overflow `close` still emits one notice and terminates. Single-consumer contract: a second concurrent `subscribe()` throws `EventMultiplexerError` (`ERR_PRISM_EVENT_MULTIPLEXER_SINGLE_CONSUMER`); the slot frees when the active consumer completes or is `return()`ed at a yield. `observe` fan-in is unchanged (broadcast happens at the source). |
152
153
  | `PersistencePage<T>` | Cursor-paginated result page: `items`, optional `nextCursor`, optional `total`. |
153
154
  | `PersistenceQuery` | Common pagination controls: `cursor?`, `limit?`, `order?: "asc" \| "desc"`. |
154
155
  | `OwnershipScope` | Multi-tenant scope: `tenantId?`, `accountId?`, `userId?`. Included in records and queries. |
package/docs/rag.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- `@arnilo/prism-rag` is an optional package for deterministic text/Markdown chunking, bounded embedding/vector indexing, atomic scoped source replacement/deletion, focused text/Markdown/HTML/PDF parsing, bounded reranking, ingestion status, attributable citations, content-trust metadata, and explicit `ContextProvider` injection. It reuses `Embedder` and `VectorStore` from `@arnilo/prism-memory`; Prism core input assembly is unchanged.
5
+ `@arnilo/prism-rag` is an optional package 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 `@arnilo/prism-memory`; Prism core input assembly is unchanged.
6
6
 
7
7
  ## When to use it
8
8
 
@@ -18,7 +18,7 @@ Chunking:
18
18
  | `chunkMarkdown(markdown, options)` | Same engine, preferring heading/paragraph boundaries |
19
19
  | `sourceId` | Required stable, non-secret source identifier |
20
20
  | `size` / `overlap` | Character ceiling and repeated context |
21
- | `metadata` | JSON metadata copied to every chunk |
21
+ | `metadata` | JSON metadata copied to every chunk; Markdown chunking additionally stamps `heading` (ordered parent-first heading stack, e.g. `["Policy", "3.2 Leave"]`) unless the caller supplies one.
22
22
 
23
23
  Document lifecycle:
24
24
 
@@ -35,13 +35,18 @@ Index/retrieve:
35
35
  | Field | Required | Meaning |
36
36
  | --- | --- | --- |
37
37
  | `embedder` / `store` | yes | Phase 7 `Embedder` and `VectorStore` |
38
- | `scope` | yes | `{ tenantId, resourceId, corpusId }`; corpus maps to vector thread isolation |
38
+ | `scope` / `scopes` | one or the other | Exact `{ tenantId, resourceId, corpusId }` (corpus → vector thread). `scope` is the single-corpus path; `scopes` is 0..`HARD_RETRIEVE_SCOPE_CAP` (8) exact scopes. Empty `scopes` returns no hits and does not embed/search/rerank. Passing both or neither throws. |
39
39
  | `chunks` | indexing | `RagChunk[]` from package chunkers or compatible host parser |
40
- | `topK` / `queryCandidates` | retrieval | Returned result count and bounded pre-filter candidates |
40
+ | `topK` / `queryCandidates` | retrieval | Returned result count and bounded pre-filter candidates (`queryCandidates` is **per scope**) |
41
+ | `lexical` | no | `"fts"` \| `"bm25"` \| `"off"` (default `"off"`); enables the lexical retrieval leg when the store advertises it |
42
+ | `fusion` / `rrfK` | no | `"rrf"` fusion of vector+lexical legs (default `"rrf"` when `lexical` is on; `rrfK` default 60, hard cap 1,000) |
41
43
  | `filter` | no | Shallow JSON metadata equality filter |
42
44
  | `reranker` | no | Host-owned `Reranker` receives redacted bounded `RagHit[]` and must return the same IDs once each, in preferred order. |
43
45
  | `maxRerankBytes` / `maxRerankMs` / `rerankConcurrency` | no | Reranker caps; defaults/hard limits are 64/256 KiB, 2/10 s, and 2/8 active calls per reranker object. |
44
46
  | `statusStore` | no | `IngestionStatusStore` records per-source pending/indexed/failed/partial byte/chunk progress; use `listIngestionStatus()` for capped exact-scope pages. |
47
+ | `contentHash` | no | Host-computed document digest; stamped on records and enables unchanged-source skip in `replaceSource` (`skipIfUnchanged`, default true when present) |
48
+ | `reuseEmbeddings` | no | `ReadonlyMap<string, ReusableEmbedding>` — chunk id → `{ text, embedding }`; embeddings reused (no embed call) when texts match |
49
+ | `telemetry` / `telemetryParent` | no | `RagTelemetry` seam (e.g. `createRagTelemetry()` from `@arnilo/prism-observability-opentelemetry`); spans nest under `telemetryParent` |
45
50
  | `redactor` / `secrets` | no | Redact before embedding, persistence, reranking, and injection |
46
51
  | `signal` | no | Abort embedding, vector operations, reranking, and batch progression |
47
52
 
@@ -51,7 +56,8 @@ Index/retrieve:
51
56
  - `indexChunks()` returns `{ indexed, sourceIds }` after bounded batch upserts.
52
57
  - `replaceSource()` / `deleteSource()` return `{ sourceId, deleted, indexed }`.
53
58
  - `replaceDocument()` carries loader parser metadata into chunk metadata; the web loader preserves web-tools citation ID and `untrusted: true`.
54
- - `retrieveContext()` returns `{ query, trust, text, hits, citations, truncated }`. Every hit/citation carries `{ provenance: { sourceId, chunkId, citationId, provider, retrieval: "vector", retrievedAt }, trust: { untrusted: true, inert: true, injectionCapable: true } }`; `retrievalRank` preserves pre-rerank order. Rendered text uses `[citation-id] text` blocks.
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.
60
+ - `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.
55
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.
56
62
  - `createRagContextProvider()` returns one ordinary context provider. Empty queries/results contribute no block.
57
63
  - No events, tools, permissions, provider calls, loaders, or network requests are added.
@@ -94,7 +100,7 @@ await indexChunks({ chunks, embedder, store, scope, statusStore });
94
100
  const found = await retrieveContext("approval policy", {
95
101
  embedder,
96
102
  store,
97
- scope,
103
+ scopes: [scope], // or `scope` for one corpus
98
104
  topK: 4,
99
105
  filter: { category: "security" },
100
106
  reranker: { rerank: async ({ hits }) => [...hits].sort((a, b) => b.score - a.score) },
@@ -109,11 +115,47 @@ const agent = createAgent({
109
115
  console.log(found.text, await agent.createSession().run("How do approvals work?"));
110
116
  ```
111
117
 
118
+ Content-hash skip and hash validation:
119
+
120
+ ```ts
121
+ import { isValidContentHash } from "@arnilo/prism-rag";
122
+
123
+ const digest = "ab12..."; // host-computed SHA-256 hex of the document
124
+ if (!isValidContentHash(digest)) throw new Error("invalid digest");
125
+ await replaceSource({ sourceId: "doc", chunks, embedder, store, scope, contentHash: digest }); // unchanged → skipped, zero embeds
126
+ ```
127
+
128
+ Hybrid retrieval, TEI reranking, and telemetry:
129
+
130
+ ```ts
131
+ import { createRagTelemetry } from "@arnilo/prism-observability-opentelemetry";
132
+ import { createTeiReranker } from "@arnilo/prism-rag";
133
+
134
+ const telemetry = createRagTelemetry({ tracer, meter }); // @opentelemetry/api instruments
135
+ const org = { tenantId: "t1", resourceId: "docs", corpusId: "org" };
136
+ const user = { tenantId: "t1", resourceId: "docs", corpusId: "user" };
137
+ const session = { tenantId: "t1", resourceId: "docs", corpusId: "session" };
138
+ const found = await retrieveContext("leave balance", {
139
+ embedder,
140
+ store, // a store that advertises lexicalModes: ["fts"]
141
+ scopes: [org, user, session], // one embed, per-scope legs, one RRF, one rerank
142
+ lexical: "fts",
143
+ topK: 8,
144
+ reranker: createTeiReranker({ baseUrl: "https://tei.svc:8080" }),
145
+ telemetry, // roots a rag_request span tree; attachSession/handleAgentEvent NOT required
146
+ });
147
+ ```
148
+
112
149
  ## Extension and configuration notes
113
150
 
114
151
  - Supply any Phase 7-conforming embedder/vector store, including the in-memory reference or PostgreSQL/pgvector adapter.
115
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.
116
153
  - `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
+ - `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
+ - Hybrid retrieval: pass `lexical: "fts"` (or `"bm25"` when the store supports it) to `retrieveContext()`; the two legs are fused with reciprocal-rank fusion (`fusion: "rrf"`, `rrfK` 60 default; the pure helper `fuseReciprocalRank()` returns `FusedCandidate[]` for custom orchestration). Stores advertise support via `lexicalModes?: readonly LexicalMode[]` and `tokenizeLexical()` is the shared tokenizer. Each hit's provenance `retrieval` field reports `vector`/`lexical`/`hybrid`; fusion internals expose `RetrievalLeg`.
156
+ - Multi-scope retrieve: `scopes: RagScope[]` searches each exact scope against that scope's current generation, then runs **one** RRF over the union and **one** rerank. The query is embedded once. `queryCandidates` is per scope. Duplicate scopes are dropped. `HARD_RETRIEVE_SCOPE_CAP` is 8.
157
+ - Embedder identity/drift guard: `Embedder.id` (memory contract) is stamped onto every vector record as `embedderId`. `retrieveContext()` fails closed with `ERR_PRISM_RAG_EMBEDDER_MISMATCH` when a stored record's `embedderId` or dimensions differ from the active embedder (for example after a model change) — re-index the source before retrieving. Legacy records without an `embedderId` also fail closed, naming the re-index path.
158
+ - Generations: `replaceSource()` stamps a scope-level generation (auto-incremented per replacement) on staged records and the vector store filters retrieval to the current generation. `setCurrentGeneration()` supports rollback; stores without generation tracking keep legacy behavior (everything visible).
117
159
  - `IngestionStatusStore` is optional observability storage. It is keyed by exact scope and source ID; use `listIngestionStatus()` rather than an unbounded corpus scan. The reference memory store is process-local; implement the same capped scope behavior for durable status.
118
160
  - `createRagContextProvider()` derives its query from latest user text by default; pass a fixed string or callback for host-controlled query generation.
119
161
  - `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.
@@ -123,14 +165,19 @@ console.log(found.text, await agent.createSession().run("How do approvals work?"
123
165
 
124
166
  ## Security and performance notes
125
167
 
126
- - Every index/query includes exact tenant/resource/corpus scope; returned records are rechecked and malformed/foreign records fail closed.
168
+ - 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.
169
+ - 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.
127
170
  - 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.
171
+ - Heading metadata is document text only — it passes through the existing `maxMetadataBytes` cap as chunk metadata; no new content path is introduced.
172
+ - `contentHash` skip and `reuseEmbeddings` never leak embeddings: reused embeddings are keyed by chunk id within one replacement and only accepted when the stored text matches exactly.
128
173
  - Retrieved documents are untrusted inert context. Prompt-injection text cannot activate tools, skills, credentials, permissions, or extensions.
129
174
  - Remote sources must pass existing resource/media trust, SSRF, MIME, and byte policies before their decoded text reaches this package.
130
175
  - `replaceSource()` stages every bounded embedding before opening the store transaction. It requires a source-aware transactional store and fails closed rather than pretending generic upserts are atomic. `createMemoryVectorStore()` supplies the reference `getBySource()` / transaction capability; durable stores must implement equivalent exact-scope behavior.
131
176
  - `deleteSource()` rechecks every returned record's tenant/resource/corpus and source metadata before delete. Same source IDs in another corpus remain untouched.
132
177
  - Parsers enforce byte/page/time caps, abort before and after parsing, decode UTF-8 strictly, and strip HTML script/style content. Parsed and retrieved text remains untrusted inert context; it never gains tool authority.
133
- - Rerankers receive redacted input under byte/time/concurrency caps. Timeout, abort, unknown/duplicate/missing IDs, oversized input, and reranker failures fail closed; returned objects cannot overwrite Prism provenance/trust fields.
178
+ - Rerankers receive redacted input under byte/time/concurrency caps. Timeout, abort, unknown/duplicate/missing IDs, oversized input, and reranker failures fail closed; returned objects cannot overwrite Prism provenance/trust fields. The TEI adapter adds fail-closed response parsing (permutation completeness, finite scores) and honors the 65,536-byte response ceiling; SSRF/URL policy is host-side (see Extension notes).
179
+ - Telemetry is a host-owned seam: `RagTelemetry` adapter (`createRagTelemetry()`) drops anything outside a fixed span-name set and `rag.*`-shaped attribute keys, so raw chunk text never reaches the tracer unless the host's own `attributeFilter` opts it in; when the seam is absent, instrumentation costs nothing.
180
+ - Durable vector stores (PostgreSQL/pgvector path via `@arnilo/prism-memory`) run their DDL against the host's knowledge database — tables are created in a schema/table the host names (default `prism_memory.semantic_memory`), and hosts must own backup/retention of that database. See [Working and semantic memory](working-and-semantic-memory.md).
134
181
  - Ingestion failure errors are redacted before status storage. Status reads reject foreign scope entries and page-limit violations; status itself creates no permission or tool authority.
135
182
  - Filtering scans at most `queryCandidates` hits; rendering stops at top-K, UTF-8 result bytes, or estimated context-token ceiling.
136
183
 
@@ -2,11 +2,11 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- Prism's current **0.3.0** line has **57 publishable manifests**: the root `@arnilo/prism` core package plus **56 workspace packages** — 17 provider adapters, 10 `prism-*` family/profile packages, and 29 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.) This final lockstep cut adds the host-owned `@arnilo/prism-computer-use-linux` wrapper and `@arnilo/prism-antigravity-agent`; after the cut, Decision B publishes changed packages independently inside `^0.3.0`. This page describes how they are packed, what each tarball contains, how to install them, the required `@arnilo/prism` 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`](./0.1.0-readiness.md).
5
+ Prism's current **0.3.1** line has **60 publishable manifests**: the root `@arnilo/prism` core package plus **59 workspace packages** — 17 provider adapters, 10 `prism-*` family/profile packages, and 32 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** (obscura joined at its reviewed initial 0.3.0), and independent publication continues inside `^0.3.0` ranges (which satisfy 0.3.1). This page describes how they are packed, what each tarball contains, how to install them, the required `@arnilo/prism` 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`](./0.1.0-readiness.md).
6
6
 
7
- Core `@arnilo/prism` ships runtime, CLI, templates, and docs. Every code package has a required `@arnilo/prism@^0.3.0` peer; profiles are pure manifests. Installation activates no provider, listener, database, browser, credential, or tool capability.
7
+ Core `@arnilo/prism` ships runtime, CLI, templates, and docs. Every code package has a required `@arnilo/prism` peer inside the Decision B window — packages republishing in the plan 039 cut carry `^0.3.1`; unchanged packages keep their `^0.3.0` peer (both satisfy `@arnilo/prism@0.3.1`); profiles are pure manifests. The plan 039 republished set declares the required `@arnilo/prism@^0.3.1` peer; unchanged packages keep `^0.3.0`. Installation activates no provider, listener, database, browser, credential, or tool capability.
8
8
 
9
- Current **57** publishable manifests (root + 56 workspace packages):
9
+ Current **58** publishable manifests (root + 57 workspace packages):
10
10
 
11
11
  `@arnilo/prism`, `@arnilo/prism-ag-ui`, `@arnilo/prism-browser`, `@arnilo/prism-coding-agent`, `@arnilo/prism-coding-security`, `@arnilo/prism-compaction-llm`
12
12
  `@arnilo/prism-compaction-observational-memory`, `@arnilo/prism-credentials-node`, `@arnilo/prism-enterprise-postgres`, `@arnilo/prism-evals`, `@arnilo/prism-mcp`, `@arnilo/prism-memory`
@@ -15,7 +15,7 @@ Current **57** publishable manifests (root + 56 workspace packages):
15
15
  `@arnilo/prism-provider-alibaba`, `@arnilo/prism-provider-anthropic`, `@arnilo/prism-provider-azure`, `@arnilo/prism-provider-bedrock`, `@arnilo/prism-provider-google`, `@arnilo/prism-provider-kimi`
16
16
  `@arnilo/prism-provider-neuralwatt`, `@arnilo/prism-provider-ollama`, `@arnilo/prism-provider-openai`, `@arnilo/prism-provider-opencode-go`, `@arnilo/prism-provider-openrouter`, `@arnilo/prism-provider-vertex`
17
17
  `@arnilo/prism-provider-clinepass`, `@arnilo/prism-provider-deepseek`, `@arnilo/prism-provider-xai`, `@arnilo/prism-provider-zai`, `@arnilo/prism-rag`, `@arnilo/prism-server`, `@arnilo/prism-session-store-codecs`, `@arnilo/prism-session-store-nats`, `@arnilo/prism-session-store-postgres`, `@arnilo/prism-session-store-sqlite`
18
- `@arnilo/prism-openapi-tools`, `@arnilo/prism-supervisor`, `@arnilo/prism-tool-validator-json-schema`, `@arnilo/prism-web-tools`, `@arnilo/prism-work-tools`, `@arnilo/prism-workflows`, `@arnilo/prism-document-reader`, `@arnilo/prism-computer-use-linux`, `@arnilo/prism-antigravity-agent`
18
+ `@arnilo/prism-openapi-tools`, `@arnilo/prism-supervisor`, `@arnilo/prism-tool-validator-json-schema`, `@arnilo/prism-web-tools`, `@arnilo/prism-wiki`, `@arnilo/prism-work-tools`, `@arnilo/prism-workflows`, `@arnilo/prism-document-reader`, `@arnilo/prism-computer-use-linux`, `@arnilo/prism-antigravity-agent`, `@arnilo/prism-graft`, `@arnilo/prism-obscura`
19
19
 
20
20
  Core ships `dist`, docs, templates, and `CHANGELOG.md`; code packages ship compiled output, README, license, and changelog. Family/profile packages ship manifest, README, and changelog. `@arnilo/prism-providers` includes all fourteen `@arnilo/prism-provider-*` packages in its family (Azure/Bedrock/Vertex stay on `@arnilo/prism-all`).
21
21
 
@@ -41,6 +41,7 @@ Consumers install the core package for the runtime and add first-party packages
41
41
  | 0.0.12 AG-UI (after release) | `npm install @arnilo/prism@0.0.12 @arnilo/prism-ag-ui@0.0.12` |
42
42
  | Install bounded web research tools | `npm install @arnilo/prism @arnilo/prism-web-tools @arnilo/prism-tool-validator-json-schema` |
43
43
  | Install browser automation tools | `npm install @arnilo/prism @arnilo/prism-browser playwright-core@1.61.0` |
44
+ | Install Obscura browser-engine tools (host supplies the binary) | `npm install @arnilo/prism @arnilo/prism-obscura` |
44
45
  | Build everything (core + workspaces) | `npm run build` |
45
46
  | Delete all build output (explicit one-shot, see build notes) | `npm run clean` |
46
47
  | Run the default (network-free) test suite | `npm test` |
@@ -63,6 +64,7 @@ Run `npm run clean` explicitly after deleting source files or switching branches
63
64
  | `@arnilo/prism/providers/openai-compatible` | `dist/providers/openai-compatible.{js,d.ts}` |
64
65
  | `@arnilo/prism/providers/transport` | `dist/providers/transport.{js,d.ts}` |
65
66
  | `@arnilo/prism/providers/openai` | `dist/providers/openai-primitives.{js,d.ts}` |
67
+ | `@arnilo/prism/providers/schema` | `dist/providers/schema.{js,d.ts}` |
66
68
  | `@arnilo/prism/providers/media` | `dist/providers/media.{js,d.ts}` |
67
69
  | `@arnilo/prism/testing/provider-conformance` | `dist/testing/provider-conformance.{js,d.ts}` |
68
70
  | `@arnilo/prism/testing/agent-event-source-conformance` | `dist/testing/agent-event-source-conformance.{js,d.ts}` |
@@ -92,7 +94,7 @@ A packed tarball contains only public compiled output and release files:
92
94
  - Code packages ship `README.md`, `LICENSE`, and `CHANGELOG.md`; family/profile packages ship `README.md` and `CHANGELOG.md`.
93
95
  - The core tarball additionally ships the full `docs/` directory (the docs hub) and `templates/init/` used by `prism init`.
94
96
  - `dist/cli.js` and the `bin` link in core.
95
- - **Tarball filenames.** npm strips the `@scope/` prefix, so the core package `@arnilo/prism` produces a tarball named `arnilo-prism-0.3.0.tgz`; first-party packages produce `arnilo-prism-provider-<name>-0.3.0.tgz` / `arnilo-prism-compaction-<name>-0.3.0.tgz` / `arnilo-prism-coding-agent-0.3.0.tgz`; family/profile packages produce `arnilo-prism-{providers,compaction,base,code,sdk,all}-0.3.0.tgz`. Later independent package tags carry their own package 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).
97
+ - **Tarball filenames.** npm strips the `@scope/` prefix, so the core package `@arnilo/prism` produces a tarball named `arnilo-prism-0.3.1.tgz`; first-party packages produce `arnilo-prism-provider-<name>-0.3.1.tgz` / `arnilo-prism-compaction-<name>-0.3.1.tgz` / `arnilo-prism-coding-agent-0.3.1.tgz`; family/profile packages produce `arnilo-prism-{providers,compaction,base,code,sdk,all}-0.3.1.tgz`; independent Decision B tags (e.g. `@arnilo/prism-obscura@0.3.0`, the 0.3.1 RAG engine patch) carry their own package version. Later independent package tags carry their own package 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).
96
98
 
97
99
  Excluded from every tarball by `files` negation:
98
100
 
@@ -365,6 +367,41 @@ For a later coding-agent-only patch, bump its manifest with `bump --package @arn
365
367
 
366
368
  **Rollback notes.** Before publication, restore the 0.2.9 manifests/tag. After publication, roll forward with an additive 0.3.x package patch; npm unpublish is not a rollback strategy.
367
369
 
370
+ ### 0.3.1 independent RAG engine patch (plan 034 Task 12)
371
+
372
+ **Decision: GO when the operator prerequisites below are recorded.** Release **0.3.1** is the first Decision B independent patch: only `@arnilo/prism-memory`, `@arnilo/prism-rag`, and `@arnilo/prism-observability-opentelemetry` move `0.3.0 → 0.3.1`. Internal `^0.3.0` ranges stay. Root current line remains **0.3.0**.
373
+
374
+ ```bash
375
+ node scripts/release.mjs bump --package @arnilo/prism-memory --type patch
376
+ node scripts/release.mjs bump --package @arnilo/prism-rag --type patch
377
+ node scripts/release.mjs bump --package @arnilo/prism-observability-opentelemetry --type patch
378
+ node scripts/release.mjs gate --update-baseline --skip-tarball # review Embedder.id; scanner is additive-only
379
+ node scripts/release.mjs check --allow-dirty --allow-untagged
380
+ # publish tags (operator handoff; not this task):
381
+ # git tag @arnilo/prism-memory@0.3.1 && git tag @arnilo/prism-rag@0.3.1
382
+ # git tag @arnilo/prism-observability-opentelemetry@0.3.1 && git push --tags
383
+ ```
384
+
385
+ **Compat.** Baselines regenerated with `--update-baseline`. Expected deltas are additive exports (`createPostgresVectorStore`, `createTeiReranker`, `createRagTelemetry`, multi-scope `scopes`, `HARD_RETRIEVE_SCOPE_CAP`, fusion/hash/generation helpers). `Embedder.id` is a TypeScript implementer break documented in `docs/migration.md` `0.3.0 → 0.3.1`; the name-level scanner does not see interface members, so `--allow-break` is not required. `RagProvenance` gained `tenantId`/`resourceId`/`corpusId` (additive interface members, invisible to scanner). **Store:** additive Postgres DDL; 0.3.0 rows remain readable. **Rollback:** restore the 0.3.0 package versions. Publication remains the operator handoff — this task does not publish.
386
+
387
+ ### 0.3.1 changed-package cut (plan 039 Task 8)
388
+
389
+ **Decision: GO when the operator prerequisites below are recorded.** The plan 039 cut is the first Decision B **root** patch: the baseline is the plan 035 completion parent `c600eaa`; 30 packages publish in dependency order — root `@arnilo/prism` plus 27 changed workspace packages move to **0.3.1** (`@arnilo/prism-rag` moves 0.3.1 → 0.3.2), and `@arnilo/prism-obscura` publishes new at its reviewed initial **0.3.0**. Every unchanged package stays byte-identical (peers keep the `^0.3.0` window; republished packages carry `^0.3.1` root peers — both satisfy the root). Additive-only compat (version literal + plan 036/037 additive exports + the obscura CDP/browser surface; baselines regenerated with `--update-baseline`, no `--allow-break`, no migration).
390
+
391
+ ```bash
392
+ node scripts/release.mjs changed --baseline c600eaa18f65b56764ec2fb408ec813536eff6f7 # 30 packages
393
+ # per-package: node scripts/release.mjs bump --package <name> --type patch (applied by plan 039 task 8)
394
+ node scripts/release.mjs gate --update-baseline --skip-tarball
395
+ node scripts/release.mjs check --independent --baseline c600eaa18f65b56764ec2fb408ec813536eff6f7
396
+ node scripts/release.mjs publish --independent --baseline c600eaa18f65b56764ec2fb408ec813536eff6f7 --dry-run
397
+ # publish tags (operator handoff; not this task): push the 30 annotated
398
+ # `<name>@<version>` package tags (e.g. @arnilo/prism@0.3.1,
399
+ # @arnilo/prism-obscura@0.3.0, @arnilo/prism-rag@0.3.2) — release.yml's publish
400
+ # job runs deterministic release:publish in dependency order with OIDC provenance.
401
+ ```
402
+
403
+ **Compat.** Baselines regenerated (`--update-baseline`): version literal, obscura `connectObscuraCdp`/`createObscuraWebTools` types, plan 036/037 additive exports. **Rollback:** restore the pre-cut manifests/tags. Publication remains the operator handoff — this task does not publish.
404
+
368
405
  ### 0.2.9 publish handoff (plan 029 Task 10)
369
406
 
370
407
  **Decision: GO when the operator prerequisites below are recorded.** Release **0.2.9** (plan 029) is the provider-adoption and behavior-packages cut on the 0.2.x review-remediation line. API surface **additive-only** (plain reviewed compat gate at 0.2.9: expected deltas are the version literal plus the new provider/OAuth/impeccable exports and the form-urlencoded `pollDeviceCodeToken` options; zero removals; baselines regenerated with `--update-baseline`, no `--allow-break`). Ships `@arnilo/prism-provider-deepseek`, `@arnilo/prism-provider-xai` (API key + SuperGrok RFC 8628), `@arnilo/prism-provider-clinepass`, and `@arnilo/prism-impeccable`. Ponytail peer `^4.9.0` (bare `/ponytail` reports status). Caveman registers extra `SKILL.md`. SuperGrok is host-invoked; Cline WorkOS, DeepSeek `/anthropic`, grok-cli file scan, harness/Cordis/Muse, Caveman 2 engine, and Impeccable live detector stay out. Release graph is **55** publishable manifests at exact **0.2.9** (root + 54 workspace). Store compatibility with 0.2.8: **compatible, no migration**.
@@ -876,8 +913,9 @@ Audit fixes, dependency updates, and security patches land only for the supporte
876
913
 
877
914
  ## Extension and configuration notes
878
915
 
879
- - **Required `@arnilo/prism` peer.** Every first-party code package declares a non-optional **caret** `@arnilo/prism@^0.3.0` peer (`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.
916
+ - **Required `@arnilo/prism` peer.** Every first-party code package declares a non-optional **caret** `@arnilo/prism@^0.3.1` peer (plan 039 republished set; unchanged packages keep the prior `^0.3.0` window peer — both 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.
880
917
  - **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.
918
+ - **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.
881
919
  - **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).
882
920
  - **Release workflow.** `.github/workflows/release.yml` has six jobs. `verify` runs network-free SDK readiness on Node 24; `node20-compat` builds/imports every public root `exports` default target on Node 20 for declared `engines.node >=20` (docs examples need Node >=22.6 native TypeScript stripping); `postgres-integration` uses `pgvector/pgvector:pg16`; `supply-chain` runs high-severity audit, SPDX/license policy, and tracked-source secret scanning; and tag-only `codeql-release` runs SAST. `publish` runs on `v0.3.0` for the one lockstep cut and on `@arnilo/*@*` package tags afterward; it needs all five gates, preserves clean tagged/version/topological publication, and alone receives `NPM_TOKEN`, `id-token: write`, and `attestations: write`. Before npm publish it packs all current tarballs, generates checksums plus SPDX, scans unpacked public artifacts, creates GitHub attestations for tarballs and SBOM, then retains artifacts for 30 days. Registry state remains the resumable journal. Local `npm run release:dry-run` remains network-free SDK readiness; local PostgreSQL coverage is `PRISM_TEST_POSTGRES_URL=... npm run test:postgres`.
883
921
  - **Adding a package.** New workspace packages are picked up automatically by `npm run build --workspaces`, `npm test --workspaces`, `npm run pack:dry-run`, the packaging guard (`src/__tests__/packaging.test.ts`), and the install-smoke test (`src/__tests__/install-smoke.test.ts`) via the workspace glob; add the package to both tests' config arrays for explicit per-package assertions.
@@ -1051,7 +1089,7 @@ Every release gate maps to an exact enforcement test or command, so the checklis
1051
1089
  | Root SDK export surface freeze | `public-export-contract.test.ts` `root export surface is frozen` snapshots every value and type export of `src/index.ts` (107 value + 69 type) so any add/remove is a deliberate test update; `every frozen value export resolves at runtime` rebuilds `dist/index.js` and asserts each value export is present (catches build drift), and `every frozen type export appears in the built type declarations` asserts each type export is in `dist/index.d.ts`. |
1052
1090
  | Examples compile and are listed; runnable demos execute | `npm run typecheck` runs `tsc -p examples --noEmit`; `docs.test.ts` checks every `examples/*.ts` file is listed in `examples/README.md`, then runs demos offline and scans output for secrets. |
1053
1091
  | Examples run to completion with no secret leakage | `docs.test.ts` `examples_demos_run_to_completion_and_emit_no_secret` runs each demo (Node strips TypeScript types natively) with exit-0 and real-secret scans; `external_app_example_*` pins the DB-backed adapter reference exercising the `RunLedger`, branch-handle checkout, fork, and prior-run resume. |
1054
- | Tarball excludes built tests, source maps, and source | `packaging.test.ts` rejects `dist/__tests__/`, `*.map`, `src/`, `plans/`, and internal files; confirms every package ships README/changelog (and code packages ship LICENSE), core ships docs + CLI, and every export target exists. `prism-all` reaches 47 of the 56 workspace packages (21 direct + 26 transitive); the deliberate Caveman/Ponytail/Impeccable/computer-use-linux/antigravity-agent opt-outs and the other non-closure packages (document-reader, OpenAPI tools, NATS) are not in its install set. |
1092
+ | Tarball excludes built tests, source maps, and source | `packaging.test.ts` rejects `dist/__tests__/`, `*.map`, `src/`, `plans/`, and internal files; confirms every package ships README/changelog (and code packages ship LICENSE), core ships docs + CLI, and every export target exists. `prism-all` reaches 47 of the 59 workspace packages (21 direct + 26 transitive); the deliberate Caveman/Ponytail/Impeccable/computer-use-linux/antigravity-agent/Graft/Obscura opt-outs and the other non-closure packages (document-reader, OpenAPI tools, NATS, wiki) are not in its install set. |
1055
1093
  | NeuralWatt package/docs/examples release gate | `packaging.test.ts` pins `@arnilo/prism-provider-neuralwatt` package exports/type declarations and `@arnilo/prism-providers`/`@arnilo/prism-all` membership; `docs.test.ts` asserts `docs/index.md` links `providers/neuralwatt.md` and `provider-caching.md`, and that `examples/cache-aware-prompt-assembly.ts` plus `examples/neuralwatt-agent-run.ts` exist and are listed. |
1056
1094
  | Enterprise PostgreSQL package/docs/example gate | Packaging/install/public-contract tests include `@arnilo/prism-enterprise-postgres`; `docs.test.ts` pins its API page, four-store migration/ownership/unknown-outcome/async-router guidance, and `examples/enterprise-postgres-state.ts`; `npm run test:postgres` exercises migration, restart, contention, and cleanup with an explicit database URL. |
1057
1095
  | Version graph and resumable publication | `release.test.ts` covers exact package/lock/range validation, topological order, registry collisions, dry-run, interrupted reports/resume, clean tagged git state, provenance/public/tag arguments, and token-safe errors. `release:check` and `release:publish` derive the workspace graph without a manual package list. |
package/docs/server.md CHANGED
@@ -173,6 +173,7 @@ A2A routes are not added to `createPrismHandler()`. Install `@arnilo/prism-super
173
173
  - [MCP client and server exposure](mcp-tools.md): selected MCP capabilities and web-standard MCP transport.
174
174
  - [Host security guide](host-security.md): remote-boundary checklist.
175
175
  - [A2A interoperability](a2a.md): separately mounted A2A 1.0 handler/client.
176
+ - [Obscura browser engine](obscura.md): optional binary-backed generic tools for hosted agents.
176
177
  - [Conversations](conversations.md): durable user-scoped conversation service, replay, branches, export, deletion.
177
178
  - [Work artifacts and review](work-artifacts-and-review.md): durable artifact review service, revisions, approvals, authorized expiring delivery links.
178
179
  - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): separately installed authorized AG-UI Web handler.
@@ -17,11 +17,11 @@ Use a supervisor when a host or agent must choose a child dynamically. Use `@arn
17
17
  | `delegate({ childId, input, threadId?, limits?, signal? })` | Invokes one allow-listed child. Input is text and byte-bounded. |
18
18
  | `hooks.before` | May reject, modify redacted input, or narrow limits/policy. |
19
19
  | `hooks.after` | Observes redacted terminal summary; failures cannot alter settled result. |
20
- | `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 default/hard. |
20
+ | `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 default/hard. Over-cap `delegate()` throws `SupervisorLimitError` before incrementing `activeChildren`. Hook rejection and timeout decrement the count exactly once (no leaked timers). |
21
21
 
22
22
  ## Outputs / response / events
23
23
 
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. Hosts may project those events through observability `handleDelegation()` using the parent Prism run ID; no OpenTelemetry dependency enters this package.
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.
25
25
 
26
26
  ## Request/response example
27
27
 
@@ -77,3 +77,4 @@ Supervisors propagate parent `identity` and `effectStore` to every child agent/r
77
77
  - [Workflows](workflows.md): preferred deterministic orchestration.
78
78
  - [Working and semantic memory](working-and-semantic-memory.md): child scope construction.
79
79
  - [Host security](host-security.md): permission and credential boundaries.
80
+ - [Obscura browser engine](obscura.md): optional binary-backed generic tools for child agents.
@@ -45,7 +45,7 @@ Known `source` order is deterministic: `user`, `package`, `app`, then `run`. Unk
45
45
 
46
46
  ## Outputs / response / events
47
47
 
48
- `composeSystemPrompt()` returns the composed prompt string or `undefined` when no prompt text remains. The agent/session runtime passes that string to `assembleProviderInput()` as `systemInstructions`; it does not emit a separate event or store prompt layers.
48
+ `composeSystemPrompt()` returns the composed prompt string or `undefined` when no prompt text remains. The agent/session runtime passes that string to `assembleProviderInput()` as `systemInstructions`; it does not emit a separate event or store prompt layers. With the default `cache_aware` layout, this composed system message is emitted before dynamic context, skills, history, tool results, and current input; set `inputLayout: "legacy"` to retain the prior whole-prompt order.
49
49
 
50
50
  ## Request/response example
51
51
 
package/docs/tools.md CHANGED
@@ -216,7 +216,7 @@ By default tools without `parameters` skip schema validation (`missingSchema: "a
216
216
 
217
217
  ### Parallel tool execution (single-shot loop)
218
218
 
219
- Opt in through `loop.toolConcurrency` on `AgentConfig` / `RunOptions` (single-shot strategy only). Default is `1` (sequential). Independent calls from one provider turn run concurrently up to the limit; transcript rows and `appendMessage` stay in original call order. Each call still uses `dispatchToolCall` (permission, validation, abort signal). See [Agent loops](agent-loops.md).
219
+ Opt in through `loop.toolConcurrency` on `AgentConfig` / `RunOptions` (single-shot strategy only). Default is `1` (sequential). Independent calls from one provider turn run concurrently up to the limit; transcript rows and `appendMessage` stay in original call order. Each call still uses `dispatchToolCall` (permission, validation, abort signal). If a worker throws or the run aborts, workers stop claiming new calls, already-claimed calls settle, buffered tool-result rows are not appended, and the first failure is rethrown. Already-claimed side effects are not rolled back; the shared abort signal is still passed to each dispatch. The round-level `chargeToolRound` approval gate runs before any worker starts. See [Agent loops](agent-loops.md).
220
220
 
221
221
  ```ts
222
222
  await session.run(input, {
package/docs/web-tools.md CHANGED
@@ -8,6 +8,8 @@
8
8
 
9
9
  Use when agent needs explicit public-web discovery or host-approved document retrieval/extraction. Keep search separate from fetch/extract so model cannot select provider, credential, API origin, extraction schema, or cost path.
10
10
 
11
+ **Obscura-backed alternative**: the optional [`@arnilo/prism-obscura`](obscura.md) package provides `web_search`/`web_fetch` behavior backed by a host-installed Obscura headless browser through its CLI (one replaceable HTML search profile instead of an API key), plus explicit native `obscura_fetch`/`obscura_scrape` batch tools. It reuses this package's normalized citation/untrusted shapes (`provider: "obscura"`) but does not require credentials; the API-backed Brave/Exa/Firecrawl adapters here remain the preferred path when an API key is available.
12
+
11
13
  ## Inputs / request
12
14
 
13
15
  | Tool | Model-visible input | Host-only construction input |
package/docs/wiki.md ADDED
@@ -0,0 +1,140 @@
1
+ # LLM Wiki (@arnilo/prism-wiki)
2
+
3
+ ## What it does
4
+
5
+ `@arnilo/prism-wiki` implements Andrej Karpathy's **LLM Wiki Pattern** for the Prism agent ecosystem. It acts as a knowledge compiler that transforms raw, immutable sources (source code, AST symbols, notes, markdown clips, transcripts, journal entries) into a persistent, compounding, cross-linked Markdown knowledge base (`.wiki/`).
6
+
7
+ It integrates Tobias Lütke's [`qmd`](https://github.com/tobi/qmd) on-device hybrid search engine (BM25, vector search, and LLM reranking) and hydrates search results with Context7-inspired hierarchical breadcrumbs (`# Category > ## Topic`) and live clickable source line anchors (`file:///path/to/file#Lxx-Lyy` format), enabling agents and humans to navigate code and notes directly without blind regex loops (`grep`/`rg`).
8
+
9
+ ## When to use it
10
+
11
+ - **Codebase Knowledge Compilation**: Ingesting modules, architecture patterns, and decision records (ADRs) with exact AST and line anchors that track code drift.
12
+ - **Personal Knowledge Management (PKM)**: Ingesting research papers, meeting notes, book summaries, and journal entries into an interlinked knowledge graph.
13
+ - **Context7-Style Navigation**: Allowing agents to query concepts and immediately jump to exact file and line locations without broad repository scans.
14
+ - **Compounding Q&A**: Persisting valuable answers, analyses, and architectural comparisons back into the wiki for future sessions.
15
+
16
+ ## Architecture
17
+
18
+ The Karpathy LLM Wiki pattern is structured into 3 distinct tiers:
19
+
20
+ 1. **Raw Sources (Immutable)**: Source code files, design docs, transcripts, journals, and Markdown notes. Raw sources are strictly read-only and never mutated.
21
+ 2. **Compiled Wiki (`.wiki/`)**: Persistent, cross-linked Markdown documents containing synthesized architecture models, entity descriptions, decision records, and line-anchored claims.
22
+ 3. **Schema & Protocols (`SCHEMA.md`)**: Operational guidelines governing entity categorization, link formatting (`[[wikilink]]`), citation rules (`file:///path#Lxx-Lyy`), catalog indexing (`index.md`), and chronological change logging (`log.md`).
23
+
24
+ ## Inputs / request
25
+
26
+ ### `createWikiExtension(options)`
27
+
28
+ | Field | Type | Required | Default | Description |
29
+ | :--- | :--- | :--- | :--- | :--- |
30
+ | `wikiRoot` | `string` | No | `".wiki"` | Path to the compiled wiki directory. |
31
+ | `rawRoots` | `readonly string[]` | No | `["."]` | Directories containing raw source files (code, notes, docs). |
32
+ | `profile` | `"codebase" \| "pkm" \| "hybrid" \| "auto"` | No | `"auto"` | Operating strategy for parsing and symbol indexing. |
33
+ | `qmdPath` | `string` | No | `"qmd"` | Path or executable name for the `qmd` CLI binary. |
34
+ | `workspaceRoot` | `string` | No | `process.cwd()` | Workspace root for resolving relative paths and `.agents/skills/`. |
35
+ | `autoDeploySkills` | `boolean` | No | `true` | Auto-deploys `wiki-maintainer` and `wiki-searcher` skills to `.agents/skills/` on init. |
36
+
37
+ ### Tools
38
+
39
+ - `wiki_search`: `{ query: string, mode?: "search" | "vsearch" | "query", maxResults?: number }`
40
+ - `wiki_read_page`: `{ pagePath: string }` — `pagePath` must resolve inside the wiki root (lexical + `fs.realpath` containment). Traversal (sibling-prefix, `..`, absolute paths) and symlinks pointing outside the wiki throw an access-denied error; a missing contained page returns `found: false`.
41
+ - `wiki_record_insight`: `{ title: string, content: string, category?: "decision" | "concept" | "entity" }` — title and content must be non-empty; titles are capped at 200 characters, content at 65,536 bytes, and control characters/newlines in titles are collapsed to spaces so titles cannot inject Markdown headings, index entries, or log entries.
42
+
43
+ ### Slash Commands
44
+
45
+ - `/wiki-init`: Scaffolds `.wiki/`, instantiates `SCHEMA.md`, `index.md`, and `log.md`, deploys skills, and adds the `qmd` collection.
46
+ - `/wiki-refresh`: Detects modified source files via SHA-256 Merkle diffing, compiles updates to affected entity pages, reconciles contradictions in `log.md`, and runs `qmd update`.
47
+ - `/wiki-lint`: Checks for broken `[[wikilinks]]`, dead line anchors, orphan pages, and unindexed symbols.
48
+
49
+ ### Standalone CLI Commands
50
+
51
+ ```bash
52
+ # Initialize wiki in project
53
+ npx prism-wiki init --profile codebase
54
+
55
+ # Refresh wiki after code edits
56
+ npx prism-wiki refresh
57
+
58
+ # Check wiki health and dead anchors
59
+ npx prism-wiki lint
60
+
61
+ # Search wiki from terminal
62
+ npx prism-wiki search "How does authentication work?" --mode query
63
+ ```
64
+
65
+ ## Outputs / response / events
66
+
67
+ - `wiki_search` returns a structured markdown payload containing section breadcrumbs, conceptual summaries, and clickable source line links (`file:///path#Lxx-Lyy`).
68
+ - Lifecycle commands return status objects (`{ status: "initialized" | "refreshed" | "clean", ok: boolean }`).
69
+
70
+ ## Request/response example
71
+
72
+ ### `wiki_search` Query:
73
+ ```json
74
+ {
75
+ "query": "How is authentication handled?",
76
+ "mode": "query",
77
+ "maxResults": 2
78
+ }
79
+ ```
80
+
81
+ ### Response Content:
82
+ ```markdown
83
+ ### Match 1: Authentication Architecture > Token Verification
84
+ - **Wiki Page:** [[entities/authentication.md]]
85
+ - **Category:** Core Module
86
+ - **Freshness:** Current (Source hash matches manifest)
87
+
88
+ **Synthesized Summary:**
89
+ The authentication layer uses asymmetric Ed25519 JWT verification in middleware, backed by a persistent token-revocation denylist stored in PostgreSQL.
90
+
91
+ **Code & Source Anchors (Clickable):**
92
+ - Token verification: `verifyToken()` (`file:///src/auth/jwt.ts#L45-L89`)
93
+ - Revocation check: `assertNotRevoked()` (`file:///src/auth/session-store.ts#L112-L138`)
94
+ - Architecture Decision: [[decisions/ADR-004-ed25519-migration.md]]
95
+ ```
96
+
97
+ ## Implementation example
98
+
99
+ ```ts
100
+ import { createExtensionKernel } from "@arnilo/prism";
101
+ import { createWikiExtension, initWiki, refreshWiki, lintWiki } from "@arnilo/prism-wiki";
102
+
103
+ const kernel = createExtensionKernel();
104
+
105
+ const wiki = createWikiExtension({
106
+ wikiRoot: ".wiki",
107
+ profile: "codebase",
108
+ });
109
+
110
+ await kernel.load([wiki]);
111
+ ```
112
+
113
+ ## Skills and Auto-Deployment
114
+
115
+ `@arnilo/prism-wiki` includes two specialized skills formatted according to `.agents/skills/skill-creator`:
116
+
117
+ 1. **`wiki-maintainer`**: Ingestion, compilation, line-anchor validation, and contradiction reconciliation rules.
118
+ 2. **`wiki-searcher`**: Context7 hierarchical breadcrumb query resolution, zero-grep instructions, and compounding insight recording.
119
+
120
+ When initialized (`wiki-init` or `createWikiExtension`), these skills are automatically deployed to the host workspace's `.agents/skills/` folder so any compatible agent can leverage them immediately.
121
+
122
+ ## Extension and configuration notes
123
+
124
+ - `@arnilo/prism-wiki` registers tools (`wiki_search`, `wiki_read_page`, `wiki_record_insight`), commands (`wiki-init`, `wiki-refresh`, `wiki-lint`), skills (`wiki-maintainer`, `wiki-searcher`), and instruction injectors (`wiki-guidance`) into Prism registries.
125
+ - It operates with zero core modifications and can be used with any `@arnilo/prism` agent.
126
+ - `qmd` is optional but recommended. When `@tobilu/qmd` is not installed, the search engine falls back to catalog matching against `index.md`.
127
+
128
+ ## Security and performance notes
129
+
130
+ - **Source Immutability**: Raw source files are read-only and never modified by wiki operations.
131
+ - **Subprocess Safety**: All `qmd` subprocess calls use argument arrays (`execFile`) to prevent shell injection.
132
+ - **Path Containment**: Wiki and raw source paths are confined to the workspace root; directory traversal (`../`) is rejected.
133
+ - **Bounded Token Consumption**: Incremental Merkle hashing ensures only modified files and 1-hop dependent wiki pages are processed during refresh passes.
134
+
135
+ ## Related APIs
136
+
137
+ - [`@arnilo/prism-rag`](rag.md): Bounded document chunking and vector context injection.
138
+ - [`@arnilo/prism-memory`](working-and-semantic-memory.md): Embedder and VectorStore primitives.
139
+ - [`@arnilo/prism-coding-agent`](coding-agent-tools.md): Code manipulation and reading tools.
140
+ - [`Contribution registries`](contribution-registries.md): Extension contribution model.