@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
@@ -0,0 +1,416 @@
1
+ # Memory fabric
2
+
3
+ ## What it does
4
+
5
+ Typed notes over the memory stores a host already configured. `createMemoryFabric()` does not add a
6
+ store, a database, or a second memory engine: it writes notes through `createMemory()` and reads them
7
+ back with the same consent, redaction, and lineage rules.
8
+
9
+ | Kind | Backing store | Notes |
10
+ | --- | --- | --- |
11
+ | `fact` | vector store | `metadata.fabric` carries the note fields. |
12
+ | `procedure` | vector store | Never returned by an untyped recall; request `kinds: ["procedure"]`. |
13
+ | `file` | vector store | `path` is required; `content` defaults to the path. The fabric does not read the file. |
14
+ | `working` | working store | Labeled block under the `_fabric.blocks` key of the scope's working value. |
15
+ | `episode` | observational memory | A **view** over an existing observation id. Nothing is written. |
16
+
17
+ `recall` returns ranked notes plus a parallel `explain` row for each hit, and `searchConversation`
18
+ lexically searches the current branch and returns the observational-memory page around each match.
19
+ `tools()` builds the five governed tools a host may hand to an agent, and `forget` tombstones one note
20
+ or working block.
21
+
22
+ ### Relationship to observational memory
23
+
24
+ Observational memory stays **episodic**: its ledger is still the source-backed record of what happened
25
+ in a session, its observer/reflector/dropper workers are still the only writers of observations and
26
+ reflections, and nothing here re-observes, re-folds, or re-scores a transcript. The `episode` kind is a
27
+ **view**: it reads an existing 12-hex observation id through the attached session's ledger and stores
28
+ nothing, so an episode note cannot disagree with the observation it names, and a valid id that survives
29
+ folding keeps working without a copy. Semantic (`fact`, `procedure`, `file`) and working notes are the
30
+ fabric's own rows in the stores `createMemory()` already owns; they never become observations. Hosts
31
+ that want a concluded case promoted out of the ledger write the generalizable part as a `procedure` and
32
+ keep the specifics as a `fact` or an `episode` view — promotion is an explicit host decision, and one
33
+ that a case conclusion filed as a `procedure` fails an invariant on (see the eval fixture below). The
34
+ fabric can make that decision cheap without turning it into an implicit one: for `fact`/`procedure`,
35
+ `remember({ reflectionId })` derives the note content from a reflection that carries an explicit
36
+ `om.scope.bound` entry on a **closed** work scope ([work-scope index](compaction-observational-memory.md)).
37
+ An unknown, unbound, or open-scope-only reflection is rejected, the note records the bind it came from
38
+ as `promotedFrom: { reflectionId, scopeId }`, and no other field changes — the fold, consent, and
39
+ worker rules of a normal `fact`/`procedure` write all still apply.
40
+
41
+ An imported fabric starts nothing: no workers, no timers, no tools, no context provider until a host
42
+ calls one of its methods. Calling `tools()` registers nothing anywhere — it returns definitions.
43
+
44
+ ### Write path (consolidation, default on)
45
+
46
+ A `fact`, `procedure`, or `file` write first recalls the nearest same-kind note (one bounded
47
+ `memory.recall` batch using the same oversample factor recall uses) and then folds:
48
+
49
+ | Nearest note | Write |
50
+ | --- | --- |
51
+ | below `consolidate.threshold` (default 0.85) | insert a new row. |
52
+ | the same note — same tokens, or the same `path` for `file` | rewrite that row in place: same id, annotations union, the new note's claims win. |
53
+ | the same subject with changed content | set `validTo` on the old row, then insert a new row carrying `supersedes`. |
54
+
55
+ A `file` note never folds across paths (two paths are two documents), `working` notes are already
56
+ keyed by block label, and `episode` views write nothing. A rewrite keeps the row's `createdAt`,
57
+ consent, importance, and every non-fabric metadata key; an explicit `id` or `supersedes` from the
58
+ caller disables auto-folding for that write.
59
+
60
+ ### Workers (opt-in)
61
+
62
+ Both workers run inline (awaited) after the write, see redacted text only, call no model and no
63
+ tools, and are skipped entirely by `passive: true`:
64
+
65
+ | Worker | Option | Does |
66
+ | --- | --- | --- |
67
+ | Linker | `linker: { enabled, topK }` (off by default, `topK` 3) | stores `related` edges to the closest neighbors, weight = embedding similarity. |
68
+ | Evolution | `evolution: { enabled, maxPatches }` (off by default) | unions the new note's keywords into neighbor notes and fills a missing neighbor `context` — never content, validity, consent, or `sourceEntryIds`. |
69
+
70
+ Workers run only while a session is attached (`attach`), so an unattached fabric enriches nothing even
71
+ when the options ask for it — the plain `remember` path writes notes and stops there.
72
+
73
+ ## When to use it
74
+ Use it when a host wants durable, time-bounded notes (facts, procedures, file references, promoted
75
+ episodes) alongside the plain working/semantic memory it already has, or wants one call that returns
76
+ both the matching notes and why they matched. Skip it when plain `createMemory()` recall is enough —
77
+ the fabric is an opt-in layer, not a replacement.
78
+
79
+ ## Inputs / request
80
+
81
+ **Option surfaces** — `createMemoryFabric` takes `CreateMemoryFabricOptions`; `attach` takes `MemoryFabricAttachOptions`; `tools()` returns definitions configured by `MemoryFabricToolsOptions`; the reflection paths use `MemoryFabricWriteOptions` and `MemoryFabricRecallOptions`; and the opt-in workers are configured by `MemoryFabricConsolidationOptions`, `MemoryFabricLinkerOptions`, `MemoryFabricEvolutionOptions`, `MemoryFabricConversationSearchOptions`, and `CreateFabricFileJailOptions`.
82
+
83
+ `createMemoryFabric(options)`
84
+
85
+ | Field | Type | Required | Meaning |
86
+ | --- | --- | --- | --- |
87
+ | `memory` | `Memory` | yes | An existing `createMemory()` instance; supplies scope, embedder, stores, consent, and limits. |
88
+ | `observational` | `{ session: { entries() } }` | only for `episode` | An attached observational-memory session (`om.attach(session)` result satisfies it). |
89
+ | `consolidate` | `boolean \| { threshold? }` | no | Default `true`; `false` writes unconditionally, `threshold` (default 0.85) is the cosine floor for folding. |
90
+ | `linker` | `boolean \| { enabled?, topK? }` | no | Default off; `topK` defaults to 3 and is capped at 32. |
91
+ | `evolution` | `boolean \| { enabled?, maxPatches? }` | no | Default off; `maxPatches` defaults to the worker per-turn budget and is capped at 32. |
92
+ | `passive` | `boolean` | no | `true` skips both opt-in workers; consolidation is part of the write path and still runs. |
93
+ | `workerLimits` | `MemoryWorkerLimitOptions` | no | Observational-memory worker caps resolved by `resolveMemoryWorkerLimits` (patches per write, patch payload bytes). |
94
+
95
+ `remember(input)` fields:
96
+
97
+ | Field | Type | Required | Meaning |
98
+ | --- | --- | --- | --- |
99
+ | `kind` | `"working" \| "episode" \| "fact" \| "procedure" \| "file"` | yes | Note kind; unknown kinds throw. |
100
+ | `id` | 12-hex string | `episode` yes | Note id (the observation id for `episode`). Derived when omitted for `fact`/`procedure`/`file`; rejected for `working`. |
101
+ | `content` | non-empty string | except `episode`, or `fact`/`procedure` with `reflectionId` | Note text, capped by `limits.maxEntryTextChars`. |
102
+ | `reflectionId` | 12-hex string | no | Derives content from an observational-memory reflection; `fact`/`procedure` only, rejected with `content`, and the reflection must be bound to a closed work scope. |
103
+ | `block` | `[a-z0-9][a-z0-9._-]{0,63}` | `working` yes | Block label; the working note id is derived from it. |
104
+ | `path` | non-empty string | `file` yes | Workspace path; stored as metadata. |
105
+ | `tRef` / `validFrom` / `validTo` | ISO timestamp | no | Reference time and validity window: `validFrom` inclusive, `validTo` exclusive. |
106
+ | `keywords` / `tags` / `sourceEntryIds` | non-empty strings | no | Annotations; `sourceEntryIds` are session entry ids. |
107
+ | `context` | non-empty string | no | Short context line. |
108
+ | `links` | `{ id, relation?, weight? }[]` | no | Edges to other note ids (12-hex). |
109
+ | `supersedes` | 12-hex string | no | Id of the note this one replaces. |
110
+ | `importance` | number in [0,1] | no | Stored recall weight. |
111
+ | `consent` | `MemoryConsentInput` | no | Forwarded to `createMemory()`; the fabric never widens visibility. |
112
+
113
+ `recall(query, options)`
114
+
115
+ | Field | Type | Default | Meaning |
116
+ | --- | --- | --- | --- |
117
+ | `kinds` | `MemoryNoteKind[]` | `["fact", "file"]` | Kinds to return; `procedure` and `working` are opt-in. Empty or unknown kinds throw. |
118
+ | `asOf` | ISO string or `Date` | now | Evaluation point for `validFrom` / `validTo`. |
119
+ | `topK` | number | `memory.limits.topK` | Maximum hits, clamped by the memory hard cap. |
120
+ | `budget` | positive integer | none | Ceiling on the summed `tokenCount` of the returned hits. The best hit is always returned; the ranking stops at the first note that would not fit. |
121
+ | `scoring` | `RecallScoringOptions` | none | Passed through to `memory.recall()`. |
122
+
123
+ `searchConversation(query, options)` — lexical search over the current branch, each hit carrying the
124
+ observational-memory page around it. Requires the `observational` source; without it the call fails
125
+ closed.
126
+
127
+ | Field | Type | Default | Meaning |
128
+ | --- | --- | --- | --- |
129
+ | `limit` | page limit | 20 | Messages per page around a hit; the observational-memory limit (hard cap 100, out of range throws). |
130
+ | `direction` | `"backward" \| "forward"` | `"backward"` | Page direction, same semantics as the branch page. |
131
+ | `detail` | `"summary" \| "full"` | `"summary"` | Page rendering detail. |
132
+ | `topK` | number | 5 | Matching messages returned; hard cap 100. |
133
+ | `signal` | `AbortSignal` | none | Aborts the scan or the page loop. |
134
+
135
+ `tools(options)` — five inert tool definitions for the host to register. `root` is the directory
136
+ the file tools are jailed to; without it those tools fail closed and only block operations work.
137
+
138
+ | Tool | Kind | Arguments | Does |
139
+ | --- | --- | --- | --- |
140
+ | `memory.view` | read | `{ path? }` | lists a directory inside `root` (default: the root), names and byte sizes, at most 200 entries. |
141
+ | `memory.read` | read | `{ path }` | reads one file inside `root`, cut at the byte cap and flagged when truncated. |
142
+ | `memory.insert` | edit | `{ text, block? \| path? }` | appends to a labeled working block, or to a file inside `root` and refreshes that file's note. |
143
+ | `memory.recall` | search | `{ query, kinds?, topK?, asOf?, budget? }` | `recall()` with the same kinds, validity, and budget rules. |
144
+ | `memory.forget` | delete | `{ id? \| block?, hold? }` | `forget()` with the same tombstone rules. |
145
+
146
+ `forget(input)` — `{ id?, block?, hold? }`, exactly one of `id`/`block`:
147
+
148
+ | Target | Effect |
149
+ | --- | --- |
150
+ | `id` | `memory.forget()` in this memory's own thread scope: rows are marked and deleted, or kept as a `legal_hold` when `hold: true`. |
151
+ | `block` | the block's content is cleared and stamped with `forgottenAt`, freeing the label for a later insert. |
152
+
153
+ The result is `{ id?, block?, deleted, held }` — `deleted` counts removed rows (1 for a tombstoned
154
+ block, 0 when the note was held or was not in this scope).
155
+
156
+ `attach(session, options?)` — the one way to authorize a session, and the only way the workers run.
157
+ It returns `{ session, contextProvider, settings, detach() }`; `detach()` (or aborting
158
+ `options.signal`) takes the session back. Attaching fails closed on anything that is not a session
159
+ (no id, or a non-function `entries`) and on a session that is not the fabric's `observational`
160
+ session — an attached session may not read another branch. Nothing happens per turn: no timers, no
161
+ loop, no session proxy; `attach` only flips the gate that `tools()` and the workers read.
162
+
163
+ `createContextProvider(options?)` — the context seam, with the same options as
164
+ `memory.createContextProvider` (`includeWorking`, `includeSemantic`, `query`, `topK`,
165
+ `messageRange`, `name`). Blocks are tagged exactly like `createMemory`'s: working memory as
166
+ `working-memory`, recall as `semantic-memory` — the fabric is semantic memory, so it adds no block
167
+ type and no layer id. With the attention compiler off, these blocks still reach the provider input
168
+ through the normal context assembly.
169
+
170
+ ### Attach and the definition recipe
171
+
172
+ A host registers the provider under any name it likes and lists that name in the definition; core
173
+ `AgentDefinition` has no fabric field and takes on no memory dependency:
174
+
175
+ ```ts
176
+ registries.contextProviders.register("memory-fabric", fabric.createContextProvider());
177
+
178
+ const agent = await resolveAgentDefinition(
179
+ { name: "assistant", model, context: ["memory-fabric"], tools: ["memory.recall"] },
180
+ { registries, providerSource },
181
+ );
182
+
183
+ const session = createAgentSession({ agent });
184
+ fabric.attach(session); // tools run and workers enrich only from here
185
+ ```
186
+
187
+ `context: ["memory-fabric"]` resolves through `registries.contextProviders`, so it works only when
188
+ the host registered that name; omitting it is the default (an agent with no fabric injection).
189
+
190
+ A runnable end-to-end demo — notes, folding, supersession, the attach gate, the provider, and
191
+ `forget` — lives in `examples/memory-fabric.ts` (network-free, `node examples/memory-fabric.ts`).
192
+
193
+ ### Tools and the file jail
194
+
195
+ Every tool call is refused unless the fabric was attached to the calling session. `view`, `read`, and
196
+ the `path` form of `insert` resolve against `root` and fail closed on anything that leaves it — `..`
197
+ segments, absolute paths, and symlinks are all rejected with a `MemoryValidationError`, and no file is
198
+ touched when the check fails. A refused insert leaves the file and the working-memory version exactly
199
+ as they were.
200
+
201
+ `insert` appends: a working block grows to `limits.maxEntryTextChars` (over that it throws and the
202
+ stored version is untouched, with the write versioned against concurrent writers), and a file grows to
203
+ `maxFileBytes` (default 50 KiB, hard cap 1 MiB) with a newline added when the file already has content
204
+ and the text does not start with one. A file insert also refreshes the `kind: "file"` note for that
205
+ path with the file's text, so recall can find what was written; the indexed text is truncated at
206
+ `limits.maxEntryTextChars` and the tool reports `noteTruncated` when it was. Parent directories are
207
+ created inside the jail as needed — `view` never creates anything.
208
+
209
+ ## Outputs / response / events
210
+
211
+ `remember` resolves to one `MemoryNote`: `{ id, kind, content, ingestedAt, tokenCount, ... }` plus the
212
+ optional fields above, `scope`, and (for `working`) `block` or (for `file`) `path`. A reflection-derived
213
+ note also carries `promotedFrom: { reflectionId, scopeId }`. A folded write
214
+ returns the surviving id: the rewritten row for a same-note fold, and a new id carrying `supersedes`
215
+ for a superseded one.
216
+
217
+ `recall` resolves to `{ hits, explain }`: `hits` are notes plus `score` and, when scoring wasrequested, `similarity` and `recency`; `explain[i]` is the parallel provenance row for `hits[i]`
218
+ (`{ id, score, similarity?, recency?, importance?, link, valid }`). A hit whose `explain.link` is
219
+ true arrived through a `links` edge from a seed hit rather than the query ranking. `working` notes are
220
+ not recall hits — they belong to the working value — and a note recorded with `validFrom`/`validTo`
221
+ outside `asOf` is omitted.
222
+
223
+ `searchConversation` resolves to `{ hits, scanned }`: each hit is
224
+ `{ entryId, score, timestamp, page }`, where `score` is the share of query terms the message contains
225
+ and `page` is the `recallObservationalMemoryBranchPage` result for that message (entries, cursors, and
226
+ rendered text). Events are whatever `createMemory()` already emits; the fabric adds none.
227
+
228
+ ## Request/response example
229
+
230
+ ```json
231
+ {
232
+ "kind": "fact",
233
+ "id": "2f9c1a0b7d3e",
234
+ "content": "User prefers metric units",
235
+ "validFrom": "2026-01-01T00:00:00.000Z",
236
+ "tags": ["prefs"],
237
+ "metadata": { "fabric": { "v": 1, "kind": "fact", "validFrom": "2026-01-01T00:00:00.000Z", "tags": ["prefs"] } }
238
+ }
239
+ ```
240
+
241
+ `recall` response shape: `hits[i]` and `explain[i]` describe the same note.
242
+
243
+ ```json
244
+ {
245
+ "hits": [
246
+ { "id": "2f9c1a0b7d3e", "kind": "fact", "content": "User prefers metric units", "tokenCount": 7, "score": 0.91, "similarity": 0.91 },
247
+ { "id": "7c4d2f10ab98", "kind": "fact", "content": "Rollout health lives on the deploy dashboard", "tokenCount": 11, "score": 0.42 }
248
+ ],
249
+ "explain": [
250
+ { "id": "2f9c1a0b7d3e", "score": 0.91, "similarity": 0.91, "link": false, "valid": true },
251
+ { "id": "7c4d2f10ab98", "score": 0.42, "link": true, "valid": true }
252
+ ]
253
+ }
254
+ ```
255
+
256
+ `searchConversation` response shape (one hit, abbreviated page):
257
+
258
+ ```json
259
+ {
260
+ "hits": [
261
+ {
262
+ "entryId": "m4",
263
+ "score": 1,
264
+ "timestamp": "2026-01-01T00:03:00.000Z",
265
+ "page": { "found": true, "cursor": "m4", "direction": "backward", "limit": 2, "entries": [], "nextCursor": "m2", "text": "..." }
266
+ }
267
+ ],
268
+ "scanned": 12
269
+ }
270
+ ```
271
+
272
+ ## Implementation example
273
+
274
+ ```ts
275
+ import { createMemory, createMemoryVectorStore, createHashEmbedder } from "@arnilo/prism-memory";
276
+ import { createMemoryFabric } from "@arnilo/prism-memory/fabric";
277
+
278
+ const memory = createMemory({
279
+ tenantId: "acme",
280
+ resourceId: "user-1",
281
+ threadId: "thread-1",
282
+ embedder: createHashEmbedder(),
283
+ vectorStore: createMemoryVectorStore(),
284
+ });
285
+
286
+ const fabric = createMemoryFabric({ memory, linker: { enabled: true, topK: 3 } });
287
+
288
+ await fabric.remember({ kind: "fact", content: "User prefers metric units", tags: ["prefs"] });
289
+ await fabric.remember({ kind: "procedure", content: "Deploy via canary first" });
290
+ await fabric.remember({ kind: "working", block: "core", content: "Prefers terse output" });
291
+
292
+ // Same note: rewritten in place, same id, annotations unioned.
293
+ const repeated = await fabric.remember({ kind: "fact", content: "User prefers metric units", keywords: ["units"] });
294
+ // Changed fact: the old row gets validTo, this note carries supersedes.
295
+ const changed = await fabric.remember({ kind: "fact", content: "User prefers metric units and Celsius" });
296
+ // changed.supersedes → the id of the note it replaced; changed.links → derived `related` edges.
297
+
298
+ const { hits, explain } = await fabric.recall("preferred units", {
299
+ scoring: { recencyWeight: 0.3, importanceWeight: 0.2, halfLifeMs: 7 * 86_400_000 },
300
+ budget: 400,
301
+ });
302
+ // hits[0].content → "User prefers metric units"; procedures stay out until kinds: ["procedure"].
303
+ // explain[1].link === true → that hit came in through a links edge from a seed hit.
304
+ ```
305
+
306
+ Conversation search over an attached session (`om.attach(session)` result):
307
+
308
+ ```ts
309
+ const fabric = createMemoryFabric({ memory, observational: om });
310
+ const { hits, scanned } = await fabric.searchConversation("canary rollout", { limit: 4, topK: 3 });
311
+ // hits[0].page.entries → the four branch messages ending at the match; hits[0].page.text → rendered window.
312
+ ```
313
+
314
+ Giving the agent the tools, jailed to a notes directory:
315
+
316
+ ```ts
317
+ const tools = fabric.tools({ root: "/srv/agent/memories", maxFileBytes: 50 * 1024 });
318
+ // tools.map((tool) => tool.name) →
319
+ // ["memory.view", "memory.read", "memory.insert", "memory.recall", "memory.forget"]
320
+ ```
321
+
322
+ Attaching a session and handing its provider to a definition:
323
+
324
+ ```ts
325
+ registries.contextProviders.register("memory-fabric", fabric.createContextProvider({ includeWorking: true }));
326
+ const agent = await resolveAgentDefinition(
327
+ { name: "assistant", model, context: ["memory-fabric"], tools: ["memory.recall"] },
328
+ { registries, providerSource },
329
+ );
330
+ const attached = fabric.attach(createAgentSession({ agent }));
331
+ // attached.settings.linker.enabled → whether enrichment is live for this fabric
332
+ // attached.contextProvider → the same provider the registry now holds
333
+ attached.detach();
334
+ ```
335
+
336
+ ## Extension and configuration notes
337
+
338
+ The host owns the embedder, vector store, working store, tenant/resource/thread scope, redactor,
339
+ `requireConsent` mode, and working-memory schema — the fabric only writes through them. `metadata.fabric`
340
+ is the reserved metadata key on vector records, and `_fabric.blocks` is the reserved key inside the
341
+ working value; a host schema must allow both if it validates the working value. A host that validates
342
+ working values still sees every block append, so its schema remains the last word on block content.
343
+ Episode views require an
344
+ observational-memory session passed as `observational`; without it, `kind: "episode"` fails closed as
345
+ does `searchConversation`. `budget` bounds injected note tokens; `topK` bounds both the primary hits
346
+ and the 1-hop expansions.
347
+ Enrichment is opt-in per fabric: `consolidate` (default on, `threshold` in [0,1]), `linker.topK` and
348
+ `evolution.maxPatches` (both capped at 32), `passive` to skip the workers, and `workerLimits` for the
349
+ observational-memory worker caps. Tool schemas are small and fixed, and a host may rename or wrap any
350
+ definition it registers.
351
+
352
+ ## Security and performance notes
353
+
354
+ Everything here is opt-in and inert by default: importing the subpath starts no worker, no timer, and
355
+ no context provider, `tools()` registers nothing, and no note reaches a prompt until a host registers a
356
+ provider and attaches a session. No fabric path calls a model — not on write, not on fold, not in the
357
+ linker or evolution workers, and not in `searchConversation` (lexical coverage, no embedder call) — so
358
+ there is no LLM-on-write step an injected document could steer, and no inferred field to audit.
359
+
360
+ Kinds keep the process/case split: `procedure` is the reusable recipe and never comes back from an
361
+ untyped recall, while case-bound specifics belong in `fact`/`file` or in an `episode` view. A case
362
+ conclusion filed as a `procedure` is caught by the package's invariant fixture
363
+ (`caseConclusionsNotProcedures`, wrapped as `defineScorer({ invariant: true })` by a host harness),
364
+ which scores 0 rather than averaging away.
365
+
366
+ Text and metadata are redacted by `createMemory()` before embedding, and recall hits are redacted
367
+ before they reach the caller; the fabric adds no path around either. Consent, visibility, revocation,
368
+ and `requireConsent` filtering are applied inside `memory.recall()`, so a fabric recall can never see
369
+ more than the underlying memory — including for linked neighbors, which are promoted from the same
370
+ recalled batch rather than read back by id. The lineage plane is the only revocation plane: a note
371
+ whose source was corrected, revoked, forgotten, or put on legal hold is excluded from recall **and**
372
+ from injected context before any background cleanup runs, exactly as `createMemory()` excludes it —
373
+ legal-hold rows are retained but never injected, and the fabric does not invent a second tombstone or a
374
+ parallel derivation table (it stores `sourceEntryIds`/`supersedes` and lets that plane decide).
375
+ A malformed or foreign `metadata.fabric` payload is
376
+ skipped rather than injected, and unknown kinds fail closed at write time. The fabric echoes no query
377
+ text into hits, explain rows, or events; explanations carry scores and provenance flags only.
378
+
379
+ One `remember` of a fact is one bounded `memory.recall` candidate batch plus one
380
+ `memory.remember(..., { wait: true })` call — no extra full scan, no extra round trip — and it waits so
381
+ the note (and any fold) is durable when the call resolves. Recall requests an explicit oversample (up
382
+ to `RECALL_OVERSAMPLE` × `topK`, bounded by the hard top-K cap) because kind and validity filtering
383
+ happen after ranking; working-store byte caps and `maxEntryTextChars` are unchanged.
384
+
385
+ Recall costs exactly one store query: 1-hop expansion is served from the oversampled batch the query
386
+ already returned (up to `topK` extra hits), so a linked neighbor outside that window is not fetched —
387
+ `Memory` has no id lookup. `searchConversation` scans the branch entries the session already returned
388
+ (`scanned` reports how many), scores them by query-term coverage, then calls the observational-memory
389
+ page helper per returned hit; the page limit is validated by that helper (1..100), so the scan and the
390
+ result are both bounded.
391
+
392
+ The workers have no model, no tools, and no network: the linker and evolution read the redacted
393
+ candidate batch the write already fetched. Evolution rewrites a neighbor only when it actually gains
394
+ an annotation, and each patched payload is measured against the resolved worker byte budget, so a
395
+ pathological keyword list cannot grow a row past it. Superseded rows stay in the store (auditable) and
396
+ are filtered out of recall by `validTo` until retention or `forget` removes them.
397
+
398
+ `forget` and the tools add no privilege: a note is deleted in this memory instance's own thread scope,
399
+ working blocks by label in the same scope, and every tool refuses to run for a session the fabric was
400
+ not attached to. An attach is a gate read on each call, not a background process: an unattached fabric
401
+ starts nothing and enriches nothing, and `detach` (or aborting the attach signal) closes the gate
402
+ again. The jail re-checks the real path of the longest existing ancestor before any read or
403
+ append, so a symlink placed inside `root` cannot be used to read or grow a file outside it; reads are
404
+ bounded before they are retained (a 1 GiB file costs one capped buffer), and `insert` refuses before
405
+ writing, so an over-cap append is a no-op. Inserted content is data: it never activates tools, skills,
406
+ or configuration, and it is validated by the host's working-memory schema like any other block value.
407
+
408
+ ## Related APIs
409
+
410
+ - [Observational memory](compaction-observational-memory.md): observations and reflections whose ids `episode` notes reference — still the episodic ledger and its workers; the fabric views, never re-observes.
411
+ - [`createMemory`](working-and-semantic-memory.md): working store, semantic recall, consent, redaction, lineage — the store this subpath writes through.
412
+ - [Attention compiler](attention-compiler.md): measures injected context cost; it does not attach or configure a fabric.
413
+ - [Session stores](session-stores.md): branch entries and bounded session search used for conversation-level queries.
414
+ - [Agent definitions](agent-definitions.md): `resolveAgentDefinition` resolves `context` names against `registries.contextProviders`; no fabric field is added to the contract.
415
+ - [Tools](tools.md): how a host registers and governs tool definitions, and what the agent sees.
416
+ - [Coding security](coding-security.md): the path-containment rules the file jail follows locally.
@@ -120,7 +120,7 @@ Additive surface:
120
120
 
121
121
  What to do:
122
122
 
123
- 1. You can drop `createSessionCachePolicy()` from Clay / host agent config if it existed only to inject session correlation.
123
+ 1. You can drop `createSessionCachePolicy()` from a host agent config if it existed only to inject session correlation.
124
124
  2. Prefer `createAgent({ thinkingLevel: "low" })` over hand-merging `providerOptions.compat`.
125
125
  3. Raw `provider.generate()` to OpenCode Go without `options.sessionId` now fails closed with `ProviderRequirementError` instead of HTTP 400.
126
126
  4. Custom generate sites should call `applyDefaultProviderRequestOptions(request, { sessionId, thinkingLevel })`.
@@ -83,6 +83,7 @@ Pin the previous published line: `@arnilo/prism@0.5.6` (exact pins per package).
83
83
  ## Related APIs
84
84
 
85
85
  - [Migration guide](migration.md): the era index of migration cuts with replacement tables and rollback notes.
86
+ - [Migrate Prism 0.6 to 0.7](migrate-to-0.7.md): the next line's guide (extended line: plans 072–075, 077–079).
86
87
  - [Migrate Prism 0.4 to 0.5](migrate-to-0.5.md): the previous line's guide (plans 055–067).
87
88
  - [Release and install](release-and-install.md): packed surfaces, install rules, support matrix, and the offline test budget.
88
89
  - [Peer dependencies](peer-dependencies.md): every third-party peer declaration with range, optionality, subpath, and install line.