@arnilo/prism 0.8.0 → 0.9.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.
- package/CHANGELOG.md +38 -0
- package/README.md +11 -11
- package/dist/agent-approval.d.ts +11 -2
- package/dist/agent-event-source.d.ts +9 -1
- package/dist/agent-event-source.js +10 -3
- package/dist/agent-loops.js +7 -4
- package/dist/agent-run-lifecycle.d.ts +15 -1
- package/dist/agent-run-lifecycle.js +63 -6
- package/dist/agent-run-state.d.ts +22 -2
- package/dist/agent-run-state.js +57 -5
- package/dist/agent-session/helpers.js +14 -0
- package/dist/agent-session/session/assemble.js +126 -24
- package/dist/agent-session/session/persist.d.ts +11 -0
- package/dist/agent-session/session/persist.js +37 -11
- package/dist/agent-session/session/provider-round.d.ts +14 -4
- package/dist/agent-session/session/provider-round.js +185 -19
- package/dist/agent-session/session/tool-round.js +20 -1
- package/dist/agent-session/session/types.d.ts +25 -2
- package/dist/agent-session/session.d.ts +38 -4
- package/dist/agent-session/session.js +76 -5
- package/dist/attention-compiler.d.ts +51 -2
- package/dist/attention-compiler.js +282 -21
- package/dist/cache-helpers.d.ts +4 -2
- package/dist/cache-helpers.js +8 -6
- package/dist/checkpoint-restore.d.ts +45 -0
- package/dist/checkpoint-restore.js +54 -0
- package/dist/context-budget.d.ts +2 -1
- package/dist/context-budget.js +24 -2
- package/dist/contracts-core/agent.d.ts +30 -0
- package/dist/contracts-core/attention.d.ts +95 -0
- package/dist/contracts-core/content.d.ts +10 -0
- package/dist/contracts-core/guardrail-packs.d.ts +41 -0
- package/dist/contracts-core/guardrail-packs.js +2 -0
- package/dist/contracts-core/provider.d.ts +25 -0
- package/dist/contracts-core/run-limits.d.ts +19 -0
- package/dist/contracts-core/session.d.ts +23 -5
- package/dist/contracts-core/session.js +21 -2
- package/dist/contracts-core/usage.d.ts +40 -0
- package/dist/contracts-core/usage.js +8 -0
- package/dist/contracts-core.d.ts +2 -0
- package/dist/contracts-core.js +2 -0
- package/dist/contracts-protocol.d.ts +76 -2
- package/dist/contracts-run-state.d.ts +56 -1
- package/dist/guardrail-packs/coding-standard.d.ts +3 -0
- package/dist/guardrail-packs/coding-standard.js +63 -0
- package/dist/guardrail-packs/destructive-commands.d.ts +3 -0
- package/dist/guardrail-packs/destructive-commands.js +46 -0
- package/dist/guardrail-packs/errors.d.ts +7 -0
- package/dist/guardrail-packs/errors.js +9 -0
- package/dist/guardrail-packs/index.d.ts +4 -0
- package/dist/guardrail-packs/index.js +15 -0
- package/dist/guardrail-packs/secrets-hygiene.d.ts +3 -0
- package/dist/guardrail-packs/secrets-hygiene.js +23 -0
- package/dist/guardrail-packs/types.d.ts +16 -0
- package/dist/guardrail-packs/types.js +2 -0
- package/dist/guardrail-packs/validation-respect.d.ts +3 -0
- package/dist/guardrail-packs/validation-respect.js +53 -0
- package/dist/guardrails.d.ts +20 -1
- package/dist/guardrails.js +268 -0
- package/dist/index.d.ts +14 -9
- package/dist/index.js +9 -6
- package/dist/input.d.ts +8 -1
- package/dist/input.js +68 -6
- package/dist/middleware.d.ts +37 -2
- package/dist/middleware.js +41 -0
- package/dist/node/session-store-jsonl.js +18 -3
- package/dist/observability.js +6 -0
- package/dist/provider-events.d.ts +8 -2
- package/dist/provider-events.js +60 -2
- package/dist/providers/openai-compatible.js +6 -3
- package/dist/run-bundle.js +2 -1
- package/dist/run-limits.d.ts +11 -1
- package/dist/run-limits.js +46 -0
- package/dist/session-stores.d.ts +12 -1
- package/dist/session-stores.js +21 -4
- package/dist/testing/agent-event-source-conformance.js +41 -2
- package/dist/testing/prefix-stability-conformance.d.ts +30 -0
- package/dist/testing/prefix-stability-conformance.js +104 -0
- package/dist/testing/session-store-conformance.d.ts +3 -2
- package/dist/testing/session-store-conformance.js +48 -0
- package/dist/tools.d.ts +5 -0
- package/dist/tools.js +11 -3
- package/dist/usage-estimation.d.ts +29 -0
- package/dist/usage-estimation.js +79 -0
- package/docs/agent-events.md +68 -1
- package/docs/agent-session-runtime.md +1 -0
- package/docs/attention-compiler.md +89 -8
- package/docs/coding-agent-tools.md +1 -1
- package/docs/compaction-and-retry.md +1 -1
- package/docs/compaction-observational-memory.md +33 -6
- package/docs/durable-runs.md +42 -0
- package/docs/embeddings.md +5 -0
- package/docs/evaluations.md +5 -0
- package/docs/execution-timeline.md +78 -1
- package/docs/guardrails.md +38 -2
- package/docs/index.md +32 -13
- package/docs/input-and-prompt-assembly.md +3 -3
- package/docs/knowledge-sync.md +4 -0
- package/docs/middleware-hooks.md +38 -2
- package/docs/migrate-to-0.9.md +210 -0
- package/docs/migration.md +13 -0
- package/docs/multi-agent-patterns.md +25 -2
- package/docs/node-jsonl-session-store.md +7 -1
- package/docs/observability.md +7 -3
- package/docs/options-index.md +2 -1
- package/docs/policy-and-audit.md +13 -1
- package/docs/prefix-stability-conformance.md +93 -0
- package/docs/provider-caching.md +4 -4
- package/docs/provider-conformance.md +16 -0
- package/docs/provider-packages.md +20 -20
- package/docs/public-contracts.md +2 -2
- package/docs/rag.md +101 -3
- package/docs/release-and-install.md +39 -37
- package/docs/runs-and-usage.md +43 -6
- package/docs/scoped-agent-memory.md +262 -0
- package/docs/session-store-conformance.md +1 -2
- package/docs/session-stores.md +17 -17
- package/docs/supervisors.md +32 -12
- package/docs/tools.md +17 -0
- package/docs/workflows.md +5 -0
- package/package.json +5 -1
|
@@ -87,6 +87,7 @@ Key exports:
|
|
|
87
87
|
| `createFoldedMemoryDetails()` | Create JSON details for compaction `data.memory`. |
|
|
88
88
|
| `renderObservationalMemory()` | Render reflections and observations into a prepared memory summary. |
|
|
89
89
|
| `recallObservationalMemory()` | Recover source evidence for a known observation/reflection id from supplied current-branch entries. `invalidatedIds` withholds content (`reason: "revoked"`) without injecting derived text. |
|
|
90
|
+
| `listInvalidatedIds()` (`@arnilo/prism-memory`) | Read the ids one exact scope currently withholds (`corrected` stays) and pass them as `invalidatedIds`, so blocks that rest on a source revoked mid-turn go stale on the next build. Empty for stores without lineage invalidation. |
|
|
90
91
|
| `recallObservationalMemoryBranchPage()` | Page eligible user/assistant/tool messages around a cursor entry id (`forward`/`backward`, optional `detail: summary|full`). |
|
|
91
92
|
| `createMemoryId()` / `isMemoryId()` | Create/check 12-character ids. |
|
|
92
93
|
| `resolveObservationalMemorySettings()` | Merge `observational-memory` settings with defaults and overrides. |
|
|
@@ -94,7 +95,7 @@ Key exports:
|
|
|
94
95
|
| `createObservationalMemoryRuntime()` | Low-level explicit flush for advanced hosts or tests. |
|
|
95
96
|
| `createObservationalMemoryCompactionStrategy()` | Render existing folded memory as a standard Prism compaction summary with `data.memory`. |
|
|
96
97
|
| `createObservationalMemoryExtension()` | Inert extension helper that registers the strategy contribution unless disabled. |
|
|
97
|
-
| `createRecallMemoryTool()` | Optional `recall` tool factory: exact id lookup or current-branch message paging via host-supplied entries. |
|
|
98
|
+
| `createRecallMemoryTool()` | Optional `recall` tool factory: exact id lookup (optionally merged with granted shared scopes) or current-branch message paging via host-supplied entries. |
|
|
98
99
|
| `createMemoryStatusCommand()` / `createMemoryViewCommand()` | Optional `om:status` and `om:view` command factories. |
|
|
99
100
|
| `createObservationalMemoryCommands()` | Convenience factory returning status and view commands. |
|
|
100
101
|
|
|
@@ -102,14 +103,40 @@ Pure utilities create no events, workers, tools, commands, credentials, or provi
|
|
|
102
103
|
|
|
103
104
|
### Work-scope index (opt-in)
|
|
104
105
|
|
|
105
|
-
`WorkScope` is a host-named, append-only index over one observational-memory ledger. Without `om.scope.*` entries, the map has only its implicit `session` root, context renders the existing active pool, and the dropper keeps its existing behavior.
|
|
106
|
+
`WorkScope` is a host-named, append-only index over one observational-memory ledger. Without `om.scope.*` entries, the map has only its implicit `session` root, context renders the existing active pool, and the dropper keeps its existing behavior. Shared work scopes extend that index across sessions under explicit grants — see "Shared work scopes (opt-in)" below.
|
|
106
107
|
|
|
107
|
-
Use `createWorkScopeController({ session, appendEntry, secrets? })` to `open`, `close`, `enter`, `leave`, `bind`, or `
|
|
108
|
+
Use `createWorkScopeController({ session, appendEntry, secrets? })` to `open`, `close`, `enter`, `leave`, `bind`, `unbind`, `grant`, or `revoke` scopes. Scope ids are host-defined (`[A-Za-z0-9._:/-]{1,128}`, no `..`); there are caps of 256 scopes, depth/stack 8, 4,096 binds and 1,024 principals per scope, and 512 characters for labels or kinds. Invalid ids, missing/closed parents, duplicate scopes, unknown record ids, reserved/closed grant targets, and ownership mismatch fail closed. Labels and kinds receive the same secret redaction as observational-memory text.
|
|
108
109
|
|
|
109
110
|
`projectWorkMemory(ledger, map, { from, include, closed?, kinds? })` returns a filtered observation/reflection view plus outline. `include` is `self`, `self+ancestors`, `self+descendants`, or `lineage`; `closed: "hide"` is the default, except closed ancestors of `from` remain available. Default attached context uses the current leaf with `self+ancestors`, rendering Scope Outline, Reflections, then Observations. The compaction summary — the layer the next run's pack starts from — renders the same projection, so the full ledger never rides into the prefix; the folded payload keeps every observation, so entering another scope can still surface what that summary hid. `recallObservationalMemory()` still reads the complete current branch by exact id.
|
|
110
111
|
|
|
111
112
|
After a flush records new observations or reflections, it binds those ids once to the current leaf scope only. A host promotes relevant memory explicitly by binding it to an ancestor; a reflection whose bind sits on a **closed** scope can also graduate into durable semantic memory through the fabric's `remember({ kind: "fact" | "procedure", reflectionId })`. While any host scope exists, the runtime skips the observation dropper; the folded-payload byte cap remains a storage safety cap, not working-set garbage collection. `withWorkScope(controller, spec, fn)` opens `spec` if needed, enters it, runs `fn`, and leaves in `finally`; it never closes a scope. This index does not provide resource-scoped observational memory or budget-based dropping as a working-set mechanism.
|
|
112
113
|
|
|
114
|
+
### Shared work scopes (opt-in)
|
|
115
|
+
|
|
116
|
+
A shared work scope lets several sessions contribute to and read one scope under explicit owner grants. Declare it per participant in `attach()`; see `examples/shared-work-scope.ts` for a runnable grant → contribute → recall → revoke demo:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
const attached = om.attach(session, {
|
|
120
|
+
appendEntry: (entry, options) => store.append(entry, options),
|
|
121
|
+
sharedScopes: { "build-42": { ownerSessionId: "session-...", entries: (sessionId) => store.list(sessionId) } },
|
|
122
|
+
onScopeAccess: (event) => audit.info("om.scope.access", event),
|
|
123
|
+
});
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Requirements, all fail-closed:
|
|
127
|
+
|
|
128
|
+
- The participant opens the scope in its own branch (`open`/`enter`) and binds its own observations/reflections to it. Only ids bound to that exact scope id are shared; memory bound to an ancestor, descendant, or other scope stays private.
|
|
129
|
+
- The owner branch carries `om.scope.granted` / `om.scope.revoked` records (`controller.grant(scopeId, principalIds)` / `revoke`) and is the only grant authority; grants in any other branch are inert. A grant is symmetric read+write — use separate scopes for asymmetric visibility.
|
|
130
|
+
- The host `entries(sessionId)` callback is the store/tenant boundary: the package checks grants, it cannot verify another branch's tenant. Keep the callback inside one `OwnershipScope`.
|
|
131
|
+
- Absent, unknown, revoked, unreachable, or not-opened-locally scope state denies the read and reports `onScopeAccess({ granted: false, reason })`. `onScopeAccess` fires for every decision, granted or denied.
|
|
132
|
+
- Revocation lands on the next read: each resolve re-reads the owner branch and re-folds the grant map. The local folded payload never contains foreign observations, so revocation also holds across local compaction.
|
|
133
|
+
|
|
134
|
+
`resolveSharedScopes({ scopes, principalId, map, onAccess? })` reads the owner branch for grants, then the owner and every granted branch, folds each branch separately, and unions the id-keyed results (`mergeObservationalMemoryLedgers`). Raw entry lists are never concatenated across branches — coverage cursors and projection boundaries are positional per branch. Bound memory is merged into the context blocks, `recallObservationalMemory`, the `recall` tool (exact-id only; branch paging stays current-branch), and `om:view`; `om:status` counts stay session-local.
|
|
135
|
+
|
|
136
|
+
Rendering still follows the work-scope projection: the reader needs the shared scope in its current leaf lineage (`enter`, or a host that keeps it entered) for the context block to include it. Recall by exact id does not depend on the leaf. The compaction strategy and its folded payload stay local, so a shared observation re-enters context from the provider rather than from the summary.
|
|
137
|
+
|
|
138
|
+
Cost: one branch read and fold per participating branch per context resolve (and per `recall` call that resolves shared scopes) — not per observation. Cache per flush only if profiling demands it.
|
|
139
|
+
|
|
113
140
|
### Compact-when override
|
|
114
141
|
|
|
115
142
|
`createObservationalMemory()` accepts a compact-when gate beside the settings: `trigger` (the same union `CompactionOptions.trigger` uses) or the `shouldCompact(context)` shorthand. When either is set it **replaces** `context.compactAfterTokens`; omitted, the token gate is unchanged.
|
|
@@ -220,7 +247,7 @@ The runtime requires host-supplied `session`, an `appendEntry` callback bound to
|
|
|
220
247
|
|
|
221
248
|
## Cross-session / delegation-tree recall (opt-in pattern)
|
|
222
249
|
|
|
223
|
-
Default is per-session: `attach()` + `appendEntry` bind one store/branch, and `recallObservationalMemory(entries, id)` / `createRecallMemoryTool({ getEntries })` see only the entries the host passes for that session. Supervisor children therefore produce observations the parent cannot recall. That is acceptable for v1 — the parent transcript already contains `delegate()` results, so parent OM covers milestones.
|
|
250
|
+
Default is per-session: `attach()` + `appendEntry` bind one store/branch, and `recallObservationalMemory(entries, id)` / `createRecallMemoryTool({ getEntries })` see only the entries the host passes for that session. Supervisor children therefore produce observations the parent cannot recall. That is acceptable for v1 — the parent transcript already contains `delegate()` results, so parent OM covers milestones. When the host can read the participating branches, use a shared work scope instead (above); the funnel below remains the option when it cannot (a namespaced multi-tenant store key is still out of scope).
|
|
224
251
|
|
|
225
252
|
Hosts that need parent recall of child *source* work compose it themselves: wrap the shared `SessionStore.append` so eligible child messages (`isEligibleObservationSourceEntry`) are copied onto a workspace (or parent) session with a **new entry id** and that session's `sessionId`/`parentId`. Parent OM then observes those copies and mints **new** observation ids. Child OM, if attached, stays on the child session with its own ids.
|
|
226
253
|
|
|
@@ -254,7 +281,7 @@ Wire the wrapped store into both the parent session and each supervisor child fa
|
|
|
254
281
|
|
|
255
282
|
Rules that keep exact-id recall unambiguous:
|
|
256
283
|
|
|
257
|
-
- Recall always takes **one** branch (`session.entries()` / `getEntries(sessionId)`). Never concatenate parent + child lists into one `recallObservationalMemory()` call.
|
|
284
|
+
- Recall always takes **one** branch (`session.entries()` / `getEntries(sessionId)`). Never concatenate parent + child lists into one `recallObservationalMemory()` call. Shared work scopes are the supported exception: they union per-branch folded ledgers (id-keyed), never raw entry lists.
|
|
258
285
|
- Copies mint a new `entry.id`. `createMemorySessionStore` rejects duplicate ids globally; JSONL/DB adapters do too.
|
|
259
286
|
- Do **not** rewrite the child's OM `appendEntry` onto the workspace session. After each memory append the runtime checks the entry is visible at the **child** leaf and fails closed on a session/store mismatch. Funnel messages; let parent OM observe them.
|
|
260
287
|
- Do **not** copy `om.*` custom entries across. Their `sourceEntryIds` point at the origin session and would dangle on the workspace branch.
|
|
@@ -262,7 +289,7 @@ Rules that keep exact-id recall unambiguous:
|
|
|
262
289
|
|
|
263
290
|
Cost: the workspace branch grows with every funneled child message; parent `compactAfterTokens` / observation-pool caps still apply but fire sooner. Keep the per-session default unless parent recall of child sources is required.
|
|
264
291
|
|
|
265
|
-
Ownership: funnel only within the `OwnershipScope` already on the parent agent/store. Child factories receive that ownership from the supervisor; do not share a store across tenants or identities. Observations never leave the store the host scoped.
|
|
292
|
+
Ownership: funnel only within the `OwnershipScope` already on the parent agent/store. Child factories receive that ownership from the supervisor; do not share a store across tenants or identities. Observations never leave the store the host scoped — the same rule applies to shared work-scope grants.
|
|
266
293
|
|
|
267
294
|
## Security and performance notes
|
|
268
295
|
|
package/docs/durable-runs.md
CHANGED
|
@@ -26,6 +26,47 @@ For approval suspension and batch decisions, see [Agent/session runtime § Durab
|
|
|
26
26
|
| `persistSessionState` | Also carries loaded-skill names and the attention sticky frontier into each turn checkpoint. |
|
|
27
27
|
| `includeSkillBodies` | Alongside `persistSessionState`, carries exact skill instructions. |
|
|
28
28
|
| `maxStateBytes` | Save-side byte ceiling (default 256 KB, hard 1 MB). Applies to every turn checkpoint identically. |
|
|
29
|
+
| `checkpointMetadata` | Sidecar map (`Record<string, string>`, ≤ 4 KB, redacted) written with every checkpoint record — never inside the state value, so it costs no `maxStateBytes` budget. A function is resolved at each write, so a host closure can pin state that moves mid-run (git commit, document version). |
|
|
30
|
+
|
|
31
|
+
```ts
|
|
32
|
+
let head = "commit-1";
|
|
33
|
+
await session.run("investigate", {
|
|
34
|
+
runState: {
|
|
35
|
+
checkpoints,
|
|
36
|
+
definitionRevision: "2026-09-19.1",
|
|
37
|
+
checkpointMetadata: () => ({ gitCommit: head, docVersion: "v12" }),
|
|
38
|
+
},
|
|
39
|
+
});
|
|
40
|
+
head = "commit-2"; // the next checkpoint records the new commit
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
`AgentRunLifecycle.status()` and `loadAgentRunState()` return the record's `metadata`; `resume` accepts `checkpointMetadata` to annotate the claim write, and without it the recorded map is preserved byte-for-byte across the claim and every later write. Legacy records without metadata read as `undefined` — an oversize or non-string map reads as absent rather than failing the resume.
|
|
44
|
+
|
|
45
|
+
### Restore hooks (all-or-nothing)
|
|
46
|
+
|
|
47
|
+
`resume` also accepts `restoreHooks`: host code that puts each external layer recorded in `checkpointMetadata` back where the checkpoint says it was. Hooks run sequentially before the claim write, each receiving the checkpoint context (`runId`, `version`, `status`, the redacted `metadata` map, and the raw `checkpoint` record) plus an `AbortSignal` that fires on host abort or the per-hook timeout.
|
|
48
|
+
|
|
49
|
+
```ts
|
|
50
|
+
await lifecycle.resume(ref, { decision: "approve", expectedVersion }, {
|
|
51
|
+
restoreHooks: [
|
|
52
|
+
async function restoreGit(cp) {
|
|
53
|
+
await git.reset(cp.metadata?.gitCommit);
|
|
54
|
+
},
|
|
55
|
+
async function restoreDocs(cp) {
|
|
56
|
+
await docs.restoreVersion(cp.metadata?.docVersion);
|
|
57
|
+
},
|
|
58
|
+
],
|
|
59
|
+
restoreHookTimeoutMs: 10_000, // default, per hook
|
|
60
|
+
});
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
All-or-nothing:
|
|
64
|
+
|
|
65
|
+
- The first hook that throws or overruns `restoreHookTimeoutMs` (default 10 s, `DEFAULT_CHECKPOINT_RESTORE_TIMEOUT_MS`) aborts the resume with `CheckpointRestoreError` — `code: "ERR_PRISM_CHECKPOINT_RESTORE"`, `hook` naming the layer, `cause` the original error. Later hooks do not run.
|
|
66
|
+
- The claim write and the conversation replay happen only after every hook succeeds, so a failed restore leaves the checkpoint byte-for-byte as it was — still resumable — instead of claiming a half-restored world. The server maps the failure to `409`/`ERR_PRISM_CHECKPOINT_RESTORE`.
|
|
67
|
+
- Hooks run on claiming resumes only; `deny` and resuspend paths never call them.
|
|
68
|
+
- The claim's `agent_resumed` event carries the audit: `restore: { hooks: [{ hook, durationMs }], durationMs }`.
|
|
69
|
+
- Register once on the lifecycle (`createAgentRunLifecycle({ restoreHooks })`) or per resume; lifecycle-registered hooks run first. No hooks ⇒ no call, no overhead, no `restore` field.
|
|
29
70
|
|
|
30
71
|
Resume uses `resumeAgentRun` / `resumeAgentRunStream` with `{ expectedVersion, decision: "continue" }`. The checkpoint records its own cadence, so a continued run keeps writing turn checkpoints without the host repeating `checkpointPolicy`.
|
|
31
72
|
|
|
@@ -82,6 +123,7 @@ The complete network-free demo — one tool execution across the crash, resumed
|
|
|
82
123
|
## Security and performance notes
|
|
83
124
|
|
|
84
125
|
- `"continue"` is a host-API action only. Prism's AG-UI interrupt resolution accepts `approve`/`deny` only, channel adapters resume with `deny`, and there is no server route that forwards an untrusted `continue`; adding one would create an approval-bypass path.
|
|
126
|
+
- Restore hooks are trusted host code running outside the sandbox: they see the checkpoint's (already redacted) sidecar map and are bounded only by their timeout. Because they run before the claim write, a timeout cannot leave a claimed checkpoint pointing at un-restored external state.
|
|
85
127
|
- Every gate that protects a suspension protects a continue resume: exact ownership, fencing token, fingerprint, revision, CAS version, and the absence of unresolved work. A running checkpoint is a recovery point, never an authorization.
|
|
86
128
|
- Cost is one bounded checkpoint write per provider turn (same redaction and `maxStateBytes` ceiling as suspension writes). A 40-turn investigation under `"every-turn"` therefore writes 40 checkpoint rows plus the terminal save, while the default `"decision"` policy writes at most one row per approval or suspension. Each row carries the run frontier, counters, run limits, and loop snapshot — not the message history, which stays in the session store and is pointed at by `leafId` — so the store grows with turns, not with turns × transcript; a state that would exceed `maxStateBytes` (default 256 KiB, `DEFAULT_MAX_AGENT_RUN_STATE_BYTES`) fails closed rather than truncating. Pick `"every-turn"` when a worker restart must cost at most one turn of thinking, and leave the default for runs with many cheap turns.
|
|
87
129
|
- Checkpoints never contain provider objects, callbacks, signals, credentials, or raw secrets; the payload is bounded and redacted like any other durable state.
|
package/docs/embeddings.md
CHANGED
|
@@ -87,6 +87,11 @@ await runEmbeddingsConformance({
|
|
|
87
87
|
bridge structurally (`createAlibabaEmbedder` remains assignable to `Embedder`
|
|
88
88
|
without importing it). The contract is a superset: it adds usage and per-item
|
|
89
89
|
error mapping.
|
|
90
|
+
- The same host-seam posture covers local inference in retrieval: the RAG local
|
|
91
|
+
reranker (`resolveReranker({ kind: "local" })` / `createLocalReranker`) runs a
|
|
92
|
+
cross-encoder in the host process through a `LocalRerankRuntime`, so no
|
|
93
|
+
inference dependency name enters any manifest — see
|
|
94
|
+
[RAG local reranker](rag.md#local-reranker).
|
|
90
95
|
- Adapters never auto-chunk: a batch over the provider cap rejects with
|
|
91
96
|
`batch_too_large`, so `embedBatched`-style callers own batching and preserve
|
|
92
97
|
per-item error attribution.
|
package/docs/evaluations.md
CHANGED
|
@@ -31,6 +31,7 @@ Use this package when a host needs offline quality checks or sampled live scorin
|
|
|
31
31
|
| `createSchemaScorer` | Validates final result or named step output against JSON schema |
|
|
32
32
|
| `createErrorClassScorer` | Fails closed if denied error codes or blocked executions appear on timeline |
|
|
33
33
|
| `createApprovalBeforeEffectScorer` | Verifies explicit approval occurred on timeline prior to sensitive tool effect |
|
|
34
|
+
| `createDeterministicTurnScorer` | Requires host-answered (no-model) turns with intact provenance: `minTurns` deterministic steps, optionally from one `middleware`, and no provider request inside those turns |
|
|
34
35
|
| `createCitationIntegrityScorer` | Invariant 0 on missing source, hash/span mismatch, or revoked ACL. Reads `environment.citations[]`. Ignores semantic `support`. |
|
|
35
36
|
| `runComparison` | immutable dataset, 2–8 named candidates by default, pairwise scorers |
|
|
36
37
|
| `assertEvaluationThreshold` / `serializeEvaluationReport` | mean/failure/per-scorer gates, hard invariant enforcement, and bounded redacted JSON |
|
|
@@ -267,6 +268,10 @@ The spawn pack (`@arnilo/prism-core/governance/evals` `spawn-pack.test.ts`) grad
|
|
|
267
268
|
|
|
268
269
|
Negative controls wire deliberately vulnerable host compositions — uncatalogued spawn, skipped reservation, model-supplied scope escalation, leaky child tool list, non-aborting cancel, ungated ship — and assert the matching grader reports `0` naming the violation.
|
|
269
270
|
|
|
271
|
+
## Guardrail-pack trajectory scenarios (plan 092)
|
|
272
|
+
|
|
273
|
+
`guardrail-pack-scenarios.test.ts` gives every built-in [guardrail pack](guardrails.md#guardrail-packs) a violating and a compliant trajectory, graded by `createGuardrailPackScorer()` on the projected timeline: a denying guardrail step scores `0` and names `metadata.guardrail` (`pack:<pack>/<rule>`), a compliant trajectory scores `1` with no pack denial, and a `forbidTools` call that executed fails as an enforcement escape instead of passing vacuously. Each scenario runs `runScenario({ agent, turns, sessionConfig: { guardrailPacks: [...] }, timeline: "metadata" })` against a scripted mock provider; a pack-absent control re-runs the destructive script with no packs to prove the blocked calls were blocked by the pack.
|
|
274
|
+
|
|
270
275
|
## PostgreSQL enterprise state (0.0.23)
|
|
271
276
|
|
|
272
277
|
`createPostgresEnterpriseState({ pool, schema }).evaluations` implements this package's existing `EvaluationStore`. The host creates an `EvaluationRecord` from verified ownership before append; every PostgreSQL query requires tenant scope, uses exact normalized account/user matching, and returns owner-bound opaque cursor pages. It is durable across reopen and supports the existing id/scorer/session/run/trace/dataset/item/experiment/status filters.
|
|
@@ -68,6 +68,8 @@ interface ExecutionTimeline {
|
|
|
68
68
|
readonly sessionId?: string;
|
|
69
69
|
readonly workflowId?: string;
|
|
70
70
|
readonly workflowRevision?: string;
|
|
71
|
+
/** Workflow checkpoint sidecar metadata (`WorkflowCheckpointValue.metadata`); present only when projected with a checkpoint. */
|
|
72
|
+
readonly workflowMetadata?: Readonly<Record<string, unknown>>;
|
|
71
73
|
readonly traceId?: string;
|
|
72
74
|
readonly status: string;
|
|
73
75
|
readonly stopReason?: AgentFinishReason;
|
|
@@ -77,7 +79,10 @@ interface ExecutionTimeline {
|
|
|
77
79
|
readonly input?: unknown;
|
|
78
80
|
readonly result?: unknown;
|
|
79
81
|
readonly usage?: Usage;
|
|
82
|
+
readonly cacheHitRate?: number;
|
|
80
83
|
readonly steps: readonly ExecutionStep[];
|
|
84
|
+
readonly turns?: readonly TimelineTurn[];
|
|
85
|
+
readonly exhaustion?: TimelineExhaustion;
|
|
81
86
|
readonly redacted: boolean;
|
|
82
87
|
readonly content: TimelineContentPolicy;
|
|
83
88
|
}
|
|
@@ -104,10 +109,54 @@ interface ExecutionStep {
|
|
|
104
109
|
}
|
|
105
110
|
```
|
|
106
111
|
|
|
107
|
-
Step kinds: `"run"`, `"turn"`, `"provider"`, `"tool"`, `"guardrail"`, `"delegation"`, `"compaction"`, `"attention"`, `"retry"`, `"hitl"`, `"artifact"`, `"workflow_node"`, `"loop_iteration"`, `"nested_workflow"`.
|
|
112
|
+
Step kinds: `"run"`, `"turn"`, `"deterministic"`, `"provider"`, `"tool"`, `"guardrail"`, `"delegation"`, `"compaction"`, `"attention"`, `"retry"`, `"hitl"`, `"artifact"`, `"workflow_node"`, `"loop_iteration"`, `"nested_workflow"`.
|
|
113
|
+
|
|
114
|
+
### `TimelineTurn` and `TimelineExhaustion`
|
|
115
|
+
|
|
116
|
+
```ts
|
|
117
|
+
interface TimelineTurn {
|
|
118
|
+
readonly turn: number;
|
|
119
|
+
readonly status: ExecutionStepStatus;
|
|
120
|
+
readonly startedAt: string;
|
|
121
|
+
readonly finishedAt?: string;
|
|
122
|
+
readonly durationMs?: number;
|
|
123
|
+
readonly providerAttempts: number;
|
|
124
|
+
readonly cacheHitRate?: number;
|
|
125
|
+
readonly budgets?: TurnBudgets;
|
|
126
|
+
readonly stopReason?: ProviderStopReason;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
interface TimelineExhaustion {
|
|
130
|
+
readonly limit: RunLimitName;
|
|
131
|
+
readonly maximum?: number;
|
|
132
|
+
readonly observed?: number;
|
|
133
|
+
readonly currency?: string;
|
|
134
|
+
readonly consumed?: BudgetConsumedCounters;
|
|
135
|
+
readonly closestOtherAxes: readonly BudgetAxisUsage[];
|
|
136
|
+
readonly recentToolCalls: readonly ToolCallSummary[];
|
|
137
|
+
}
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
`turns` is the per-turn trace, derived in one pass over folded provider steps: turn number, status,
|
|
141
|
+
timing, attempts (retries included), input-token-weighted `cacheHitRate`, the last provider
|
|
142
|
+
attempt's recorded `budgets`, and its stop reason. Cache rate is absent when cache usage is unknown;
|
|
143
|
+
`budgets` is copied verbatim from `provider_turn_finished.metadata.budgets` and is absent on legacy
|
|
144
|
+
events. `ExecutionTimeline.cacheHitRate` is the same input-token-weighted calculation across all
|
|
145
|
+
provider attempts. The stop reason also rides the `provider` step's metadata (`metadata.stopReason`),
|
|
146
|
+
so a flat renderer can badge attempts without walking `turns`. `turns` is absent on timelines with
|
|
147
|
+
no turn steps (workflow timelines).
|
|
148
|
+
|
|
149
|
+
`exhaustion` is the terminal limit attribution, present only when the run died on a run limit. It
|
|
150
|
+
joins `run_limit_exceeded` (`limit`, `maximum`, `observed`, `currency`) with `budget_exhausted`
|
|
151
|
+
(`consumed`, `closestOtherAxes`, `recentToolCalls`); a trace that recorded only the breach carries the
|
|
152
|
+
first group and empty axes. Argument hashes only — `recentToolCalls` never contains raw arguments.
|
|
108
153
|
|
|
109
154
|
`attention_compiled` folds into a one-step `"attention"` entry (status `succeeded`) whose metadata carries the measured counts (`used`, `usedAfter`, `inputCap`, `triggerRatio`, `droppedThinkingTurns`, `stubbedToolResults`, `stubbedBytes`, `truncated`); under-ratio turns emit no event, so they add no step.
|
|
110
155
|
|
|
156
|
+
`deterministic_turn` folds into a `"deterministic"` step whose `name` is the answering middleware id and whose metadata carries `{ turn, middleware }`. A deterministic turn has no provider step, no `usage`, and no `stopReason`, so a host-answered turn can never be read as model output; its `turns` entry carries `providerAttempts: 0`, and `summarizeTimeline()`/`summarizeSession()` split the turn count into `turns: { model, deterministic }`. The same provenance is copied onto the assistant message as `message.metadata.deterministic = { middleware }`, so the persisted transcript alone proves the turn had no model behind it.
|
|
157
|
+
|
|
158
|
+
`guardrail_decision` folds into a `"guardrail"` step whose `name` is the stage (`input`/`output`/`tool_input`/`tool_output`) and whose metadata carries `action`, the rule identity `metadata.guardrail` (compiled packs name it `pack:<pack>/<rule>`, other guardrails their configured name), and `toolName`/`toolCallId` when the decision is tool-scoped. A denying action (`deny`, `block`, `tripwire`) sets status `denied`; the free-text guardrail reason stays on the event, not the step.
|
|
159
|
+
|
|
111
160
|
Step statuses: `"running"`, `"succeeded"`, `"failed"`, `"blocked"`, `"skipped"`, `"suspended"`, `"denied"`, `"aborted"`.
|
|
112
161
|
|
|
113
162
|
Tree structure: steps are a flat ordered array. Tree via `parentId` (run → turn → provider/tool). Scorers iterate the flat array; UIs that need nesting walk `parentId`.
|
|
@@ -138,6 +187,7 @@ const timeline = projectTraceTimeline(trace, {
|
|
|
138
187
|
redactor: createSecretRedactor(secrets),
|
|
139
188
|
});
|
|
140
189
|
// timeline.steps.map(s => [s.order, s.kind, s.name, s.status])
|
|
190
|
+
// timeline.turns.map(t => [t.turn, t.cacheHitRate, t.budgets, t.stopReason])
|
|
141
191
|
```
|
|
142
192
|
|
|
143
193
|
### Workflow fold with checkpoint outputs
|
|
@@ -158,6 +208,33 @@ See runnable host demo in `examples/execution-timeline.ts` for offline workflow
|
|
|
158
208
|
|
|
159
209
|
Run-level `stopReason` mirrors `agent_finished.finishReason` when the loop stopped on a ceiling or a host turn policy (`"host_policy"`); `status` reads `finished:<stopReason>` for those runs and `succeeded` for a natural end. `stopDetail` carries the host's `turnPolicy.stop` reason, bounded to 256 bytes and redacted at the runtime boundary. See [Runs and usage ledger § Clean stops and stop reasons](runs-and-usage.md#clean-stops-and-stop-reasons).
|
|
160
210
|
|
|
211
|
+
Per-turn stop reasons are a separate, closed taxonomy (`ProviderStopReason`: `end_turn`, `tool_calls`,
|
|
212
|
+
`max_output_tokens`, `content_filter`, `abort`, `provider_error`, `unknown`) because they answer a
|
|
213
|
+
different question — why the *provider* returned, not why the loop ended. Each `provider_turn_finished`
|
|
214
|
+
badges its turn (`timeline.turns[i].stopReason`) and its provider step (`metadata.stopReason`). See
|
|
215
|
+
[Agent events](agent-events.md) § Provider turn events.
|
|
216
|
+
|
|
217
|
+
A run that died on a run limit packs its attribution into the timeline and the summary line:
|
|
218
|
+
|
|
219
|
+
```ts
|
|
220
|
+
import { projectTraceTimeline, summarizeTimeline } from "@arnilo/prism-core/governance/observability";
|
|
221
|
+
|
|
222
|
+
const timeline = projectTraceTimeline(trace);
|
|
223
|
+
// timeline.turns.map(t => [t.turn, t.stopReason]);
|
|
224
|
+
// [[1, "tool_calls"], [2, "end_turn"]]
|
|
225
|
+
// timeline.exhaustion;
|
|
226
|
+
// { limit: "maxTurns", maximum: 12, observed: 13,
|
|
227
|
+
// consumed: { turns: 13, inputTokens: 41_200, providerAttempts: 13, requestBytes: 1_048_576 },
|
|
228
|
+
// closestOtherAxes: [{ axis: "maxToolCalls", usedRatio: 0.625 }],
|
|
229
|
+
// recentToolCalls: [{ id: "tc_91", name: "searchCodebase", argHash: "sha256:9f.." }] }
|
|
230
|
+
|
|
231
|
+
summarizeTimeline(timeline).exhaustion;
|
|
232
|
+
// "maxTurns exhausted (13/12); closest: maxToolCalls 0.625"
|
|
233
|
+
```
|
|
234
|
+
|
|
235
|
+
`summarizeTimeline().exhaustion` is one renderable dashboard line; runs that ended any other way omit
|
|
236
|
+
it, and `summarizeSession()` keeps each run's line in its `runs` array.
|
|
237
|
+
|
|
161
238
|
## Bounds
|
|
162
239
|
|
|
163
240
|
| Dimension | Default | Hard cap |
|
package/docs/guardrails.md
CHANGED
|
@@ -26,6 +26,8 @@ const guardrails: Guardrails = { input: [pii], maxConcurrency: 1 };
|
|
|
26
26
|
|
|
27
27
|
Set `AgentConfig.guardrails` for every session run or `RunOptions.guardrails` to append checks for one run. `DispatchToolCallOptions.guardrails`, workflow `RunWorkflowOptions.guardrails`, and MCP server `CreatePrismMcpServerOptions.guardrails` apply tool stages to direct calls. A stage has `Guardrail<"input" | "output" | "tool_input" | "tool_output">`, a name, optional revision, and `evaluate(context)` result.
|
|
28
28
|
|
|
29
|
+
`AgentSessionConfig.guardrailPacks` compiles declarative, restrictive-only rule sets onto the tool stages once per session (see [Guardrail packs](#guardrail-packs)). Session packs merge after `AgentConfig.guardrails` and before `RunOptions.guardrails`. Hosts that dispatch tools directly can compile the same config with `compileGuardrailPacks(refs)` and pass the result as `DispatchToolCallOptions.guardrails`.
|
|
30
|
+
|
|
29
31
|
Decisions are `allow`, `block`, `tripwire`, or `interrupt`. Evaluation defaults to declaration-order sequential. `maxConcurrency` may be 1–16; records are emitted in declaration order. Thrown or malformed decisions become a fail-closed tripwire. A throwing guardrail produces a `guardrail_failed` record whose `metadata.error` carries the underlying error message — redacted and bounded to 4 KiB — so failures stay diagnosable without leaking internals. Decision reasons are capped at 4 KiB and metadata at 16 KiB after JSON normalization and optional redaction.
|
|
30
32
|
|
|
31
33
|
## Outputs / response / events
|
|
@@ -70,6 +72,40 @@ const agent = createAgent({ model, provider, guardrails: { input: [pii], output:
|
|
|
70
72
|
await agent.createSession().run("Draft reply", { guardrails: { toolInput: [commandGuard] } });
|
|
71
73
|
```
|
|
72
74
|
|
|
75
|
+
## Guardrail packs
|
|
76
|
+
|
|
77
|
+
A pack is configuration, not code: rules compile once per session onto the existing `tool_input` / `tool_output` seams. Packs can only deny or tripwire — they never grant permissions, widen arguments, or add a stage. A rule that matches produces the standard refusal-shaped `ToolResult`; `tripwire` additionally rejects the enclosing run.
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
const session = agent.createSession({
|
|
81
|
+
guardrailPacks: ["secrets-hygiene"],
|
|
82
|
+
// or, with options / inline rules:
|
|
83
|
+
guardrailPacks: [
|
|
84
|
+
{ id: "coding-standard", options: { cwd: "/repo", roots: ["/repo"] } },
|
|
85
|
+
{
|
|
86
|
+
id: "my-pack",
|
|
87
|
+
version: 1,
|
|
88
|
+
rules: [{ id: "no-etc", tool: "write", pattern: "^/etc/", reason: "system path" }],
|
|
89
|
+
},
|
|
90
|
+
],
|
|
91
|
+
});
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
Built-in pack ids are public surface and versioned:
|
|
95
|
+
|
|
96
|
+
| Pack | Rules | Notes |
|
|
97
|
+
| --- | --- | --- |
|
|
98
|
+
| `coding-standard` | `no-unrelated-file-edits`, `no-test-rewrites` | Applies to `write`/`edit`/`delete`/`move`. `options.roots` defaults to `[process.cwd()]`; `options.cwd` is the resolution base. Containment is lexical — symlinks are not resolved, so an `ExecutionPolicy` remains the hard boundary. |
|
|
99
|
+
| `destructive-commands` | `no-recursive-force-delete`, `no-long-flag-force-delete`, `no-force-push` (includes `--force-with-lease`), `no-destructive-sql`, `no-device-overwrite` | Matched against the `shell` tool's `command` argument. |
|
|
100
|
+
| `validation-respect` | `no-mutation-after-failed-validation` | Observes `options.validationTools` (default `test`, `run_tests`, `validate`, `validation`, `lint`, `typecheck`, `check`). A result carrying an error or a non-zero `exitCode` marks validation failed; the next successful validation clears it. Opt `shell` in explicitly when validations run through the shell tool. |
|
|
101
|
+
| `secrets-hygiene` | `no-secret-material-in-arguments` | Scans argument strings (bounded depth and count) for credential shapes: `sk-`, `gh[pousr]_`, `AKIA…`, PEM private-key headers, JWTs, `xox[baprs]-`. Prism redaction replaces exact known values only, so these patterns ship with the pack. |
|
|
102
|
+
|
|
103
|
+
Inline rule shape: exactly one of `pattern` (string or `RegExp`, compiled once) or `deny(args, context)` (typed predicate, host-trusted like all host code); optional `tool` (name or names; omitted matches every tool), `argPath` (dot path or paths such as `command` or `["from", "to"]`; omitted scans every argument string), `action` (`deny` default, or `tripwire`), and `reason`. Predicates receive `{ toolName, toolCallId, sessionId, runId, metadata, state }`, where `state` is pack-local and read-only. `action: "ask"` is rejected: the tool stage has no deterministic approval seam.
|
|
104
|
+
|
|
105
|
+
Every evaluated rule emits a `guardrail_decision` event; the denying record's `guardrail` is `pack:<pack>/<rule>` and its `metadata` is `{ pack, rule, version }` — never tool arguments. `describeGuardrailPacks(refs)` returns the same identity rows (`pack:<pack>/<rule>`, stage, `pack@version`) that `snapshotRunBundle()` reports for the session config. Malformed config (unknown id, duplicate pack or rule id, both `pattern` and `deny`, invalid regex, `ask`) throws `GuardrailPackError` at session creation instead of silently dropping a rule.
|
|
106
|
+
|
|
107
|
+
On the observability timeline each guardrail step carries that identity in `metadata.guardrail` (with `status: "denied"` when it denied — the free-text reason stays off the step to keep metadata low-cardinality), so evals can grade enforcement without reading tool arguments: `createGuardrailPackScorer()` from `@arnilo/prism-core/governance/evals` scores a denied `pack:` rule as a failed trajectory and names it. The built-in packs are covered by violating/compliant scenario pairs in `packages/prism-core/src/governance/evals/__tests__/guardrail-pack-scenarios.test.ts` (see [Evaluations](evaluations.md#guardrail-pack-trajectory-scenarios-plan-092)).
|
|
108
|
+
|
|
73
109
|
## Claim grounding
|
|
74
110
|
|
|
75
111
|
`createClaimGroundingGuardrail(options: ClaimGroundingGuardrailOptions)` is a deterministic output guardrail for quantitative claims. It scans assistant text once, then attributes each number to a completed host tool result from **this run** or to a host-governed figure. It never calls a model, store, or network service.
|
|
@@ -105,13 +141,13 @@ With `onViolation: "block"`, the standard `GuardrailError` has `reason: "claim_u
|
|
|
105
141
|
|
|
106
142
|
## Extension and configuration notes
|
|
107
143
|
|
|
108
|
-
Guardrails are callbacks supplied by the host. Prism does not discover, load, retry, or persist callback code. `createSecureAgent()` keeps configured guardrails and only appends run-level checks; it never lets a run remove secure defaults. Custom loops receive guarded `LoopContext.generate()` and `LoopContext.dispatchToolCall()`; host code that directly calls a provider or `ToolDefinition.execute()` is outside the runtime boundary.
|
|
144
|
+
Guardrails are callbacks supplied by the host. Prism does not discover, load, retry, or persist callback code. `createSecureAgent()` keeps configured guardrails and only appends run-level checks; it never lets a run remove secure defaults. Custom loops receive guarded `LoopContext.generate()` and `LoopContext.dispatchToolCall()`; host code that directly calls a provider or `ToolDefinition.execute()` is outside the runtime boundary. Guardrail packs follow the same rule: they are host-supplied config, compiled in memory per session, never discovered from disk or persisted by Prism.
|
|
109
145
|
|
|
110
146
|
## Security and performance notes
|
|
111
147
|
|
|
112
148
|
Optional `@arnilo/prism-core/governance/policy` can record guardrail outcomes via `recordGuardrailDecision` (evidence refs only; see [Policy and audit](policy-and-audit.md)).
|
|
113
149
|
|
|
114
|
-
Output buffering prevents blocked provider content from reaching subscribers, session entries, ledgers, parsers, delegation, or tools. Tool-output checks receive raw results but Prism discards blocked raw output before event, ledger, transcript, or MCP exposure. Redaction replaces exact known values only; it is not general secret detection. Parallel checks receive an abort signal, but callback code must honor it to stop in-flight work. Browser snapshots and page text from the `browser` subpath are untrusted external content: never allow them to modify tools, permissions, credentials, or policy. Browser mutations still require host `ExecutionPolicy`/approval; prompt-injection text in a page cannot grant upload/download release.
|
|
150
|
+
Output buffering prevents blocked provider content from reaching subscribers, session entries, ledgers, parsers, delegation, or tools. Tool-output checks receive raw results but Prism discards blocked raw output before event, ledger, transcript, or MCP exposure. Redaction replaces exact known values only; it is not general secret detection. Guardrail-pack patterns compile once at session creation and argument scans are bounded (depth 8, 64 strings, 16 KiB per string), so rule cost stays off the provider path. Parallel checks receive an abort signal, but callback code must honor it to stop in-flight work. Browser snapshots and page text from the `browser` subpath are untrusted external content: never allow them to modify tools, permissions, credentials, or policy. Browser mutations still require host `ExecutionPolicy`/approval; prompt-injection text in a page cannot grant upload/download release.
|
|
115
151
|
|
|
116
152
|
## Related APIs
|
|
117
153
|
|
package/docs/index.md
CHANGED
|
@@ -2,14 +2,30 @@
|
|
|
2
2
|
|
|
3
3
|
Prism is a TypeScript/Node.js agent harness. Hosts own providers, tools, credentials, storage, and behavior; Prism supplies contracts, registries, events, and replaceable runtime primitives.
|
|
4
4
|
|
|
5
|
-
## Current line (0.
|
|
5
|
+
## Current line (0.9.0)
|
|
6
|
+
|
|
7
|
+
- **Attention budget axes**: `attentionCompiler.trigger` accepts one axis, a predicate, or an any-of array (`input_ratio`, `run_input_ratio`, `token_floor`), and `durable: true` keeps the fold ledger and sticky frontier in the checkpoint across a resume.
|
|
8
|
+
- **Turn traces and exhaustion attribution**: `provider_turn_finished` carries a closed `stopReason`, a `budgets` snapshot, the effective tool menu (`count` / `idsHash`), and provider cache counts; `agent_finished` carries the run outcome, and the execution timeline adds per-turn stop reasons plus `exhaustion`.
|
|
9
|
+
- **Cache-stable disclosure**: late-expanding context (skill bodies, deferred schemas, loaded references) lands at the request tail instead of rewriting the prefix, cache read/write telemetry rides usage records, and `runPrefixStabilityConformance` asserts a shared-prefix floor against a host's own assembly.
|
|
10
|
+
- **Per-turn tool narrowing**: a host callback receives the turn and the run grant and returns the effective subset; out-of-grant names are dropped and reported as `tool_narrowing_clamped`.
|
|
11
|
+
- **Usage estimation and the context meter**: labeled token estimates for providers that report no usage (reported usage always wins, and `usageEstimation: "off"` restores zero-for-no-usage), plus `session.contextMeter()` for cap and spend ratios.
|
|
12
|
+
- **Guardrail packs**: four built-in restrictive packs (`coding-standard`, `destructive-commands`, `validation-respect`, `secrets-hygiene`) compiled onto existing tool stages, each with a trajectory scorer.
|
|
13
|
+
- **Background child agents**: session-lifetime children with milestone or streamed reports, narrowed budget shares, and rate-coalesced child events.
|
|
14
|
+
- **Checkpoint sidecar metadata**: a redacted ≤4 KiB map attached to every checkpoint record without charging `maxStateBytes`, plus restore hooks that revert external layers before a resume claims the run.
|
|
15
|
+
- **Bounded session search**: `store.searchSessions(query)` over workspace/time/provider/label/kind/ownership filters, indexed at append time (SQLite FTS5, Postgres `tsvector`) with a bounded linear matcher for JSONL and memory stores.
|
|
16
|
+
- **Deterministic turns**: the `beforeProviderTurn` middleware hook answers a turn from host data with no provider request, recorded as `deterministic` on the timeline and in usage.
|
|
17
|
+
- **Shared work scopes**: explicitly granted observational-memory scopes shared across sessions, deny-by-default, audited, and revocable at the next read; session-private scopes stay the default.
|
|
18
|
+
- **Retrieval revocation and local reranking**: deletion and revocation propagate through derived vector/wiki artifacts under bounded walks, and an in-process cross-encoder reranker ships with no declared inference dependency.
|
|
19
|
+
- **Live-stream terminal semantics**: one `isTerminalAgentEventType` predicate (`agent_finished` / `agent_denied` / `error`) shared by every source, so a limit death delivers `run_limit_exceeded` → `budget_exhausted` → `error` before a stream or replay ends.
|
|
20
|
+
- **11 publishable packages** at current **0.9.0** lockstep, with the migration guide reachable from the release section below — inventory below.
|
|
21
|
+
|
|
22
|
+
### Carried from the 0.8.0 line
|
|
6
23
|
|
|
7
24
|
- **Messaging channels**: `@arnilo/prism-channels` transport-neutral runtime with deny-by-default authorization, owned bindings, one-use durable approvals, official Telegram (private DMs, opt-in granted groups/topics, drafts, bounded media/voice, opt-in notices) and experimental pinned signal-cli Signal.
|
|
8
25
|
- **Connected apps**: identity-bound MCP server sessions admit host-selected transports and register prefixed tools; Google Workspace and Microsoft 365 HTTP adapters live under `@arnilo/prism-work/connectors`.
|
|
9
26
|
- **Work family**: `@arnilo/prism-work` replaces `@arnilo/prism-office` — connectors, documents, sheets, diagrams, document-reader, sandbox, and vendored office skills. No pre-1.0 shim.
|
|
10
27
|
- **Durable long runs**: turn-boundary checkpoints with host-only `decision: "continue"`, turn-stop policy, frozen run-bundle snapshots, claim-grounding guardrail, and typed provider failure classes.
|
|
11
28
|
- **Honesty surfaces**: Postgres release evidence is this-commit, channel lease release stays held until the store acknowledges, and observational-memory workers ignore non-tool events on purpose.
|
|
12
|
-
- **11 publishable packages** at current **0.8.0** lockstep, with the migration guide reachable from the release section below — inventory below.
|
|
13
29
|
|
|
14
30
|
### Carried from the 0.7.0 line
|
|
15
31
|
|
|
@@ -79,6 +95,7 @@ Prism is a TypeScript/Node.js agent harness. Hosts own providers, tools, credent
|
|
|
79
95
|
- [Observational memory compaction subpath](compaction-observational-memory.md): source-backed observations/reflections, an optional work-scope index for the current working set, and exact-id recall; `invalidatedIds` withhold derived injection.
|
|
80
96
|
- [Working and semantic memory](working-and-semantic-memory.md): working-memory store, semantic recall, pgvector path, consent lifecycle, lineage invalidation, parent-child share grants.
|
|
81
97
|
- [Memory fabric](memory-fabric.md): opt-in typed notes (fact/procedure/file/working/episode) with validity windows over the existing vector and working stores.
|
|
98
|
+
- [Scoped agent memory design concept](scoped-agent-memory.md): workspace-scoped persistent memory — gated writes, promotion ladder, decay-based reads; case study and research basis.
|
|
82
99
|
- [Session stores](session-stores.md): `SessionStore` contract, append options, branches, bounded search — start here for persistence.
|
|
83
100
|
- [Conversations](conversations.md): durable user-scoped threads with versioned metadata and legal-hold-aware deletion.
|
|
84
101
|
- [Work artifacts and review](work-artifacts-and-review.md): artifact attach, revision compare, evidence-bound citations, approve/reject, expiring delivery links.
|
|
@@ -213,6 +230,7 @@ Prism is a TypeScript/Node.js agent harness. Hosts own providers, tools, credent
|
|
|
213
230
|
- [Session store conformance](session-store-conformance.md): assert append/idempotency/conflict/branch invariants for any store.
|
|
214
231
|
- [Run ledger conformance](run-ledger-conformance.md): assert durable run/usage writes and reopen survival.
|
|
215
232
|
- [Compaction conformance](compaction-conformance.md): assert redacted non-empty summaries and abort observation.
|
|
233
|
+
- [Prefix stability conformance](prefix-stability-conformance.md): assert progressive disclosure keeps the provider cache prefix stable.
|
|
216
234
|
- [Tool conformance](tool-conformance.md): assert blocked-reason matrix and success-path dispatch behavior.
|
|
217
235
|
- [Extension conformance](extension-conformance.md): assert inert contributions and redacted setup errors.
|
|
218
236
|
- `examples/`: compile-checked typed examples ([`conversation-durable-replay.ts`](../examples/conversation-durable-replay.ts), [`artifact-review-delivery.ts`](../examples/artifact-review-delivery.ts), [`enterprise-identity.ts`](../examples/enterprise-identity.ts), [`enterprise-policy-audit.ts`](../examples/enterprise-policy-audit.ts), [`enterprise-work-connectors.ts`](../examples/enterprise-work-connectors.ts), [`connected-slack-mcp.ts`](../examples/connected-slack-mcp.ts), [`server-deployment-seams.ts`](../examples/server-deployment-seams.ts), [`neuralwatt-agent-run.ts`](../examples/neuralwatt-agent-run.ts), [`cache-aware-prompt-assembly.ts`](../examples/cache-aware-prompt-assembly.ts), [`ag-ui-server.ts`](../examples/ag-ui-server.ts), [`acp-coding-host.ts`](../examples/acp-coding-host.ts), [`telegram-agent.ts`](../examples/telegram-agent.ts), [`signal-agent.ts`](../examples/signal-agent.ts), [`messaging-agent.ts`](../examples/messaging-agent.ts), and more), plus runnable mock demos.
|
|
@@ -231,6 +249,7 @@ Prism is a TypeScript/Node.js agent harness. Hosts own providers, tools, credent
|
|
|
231
249
|
## Release and install
|
|
232
250
|
|
|
233
251
|
- [Release and install](release-and-install.md): install rules, package graph, and deterministic resumable publication.
|
|
252
|
+
- [Migrate 0.8 → 0.9](migrate-to-0.9.md): the four behavior deltas inside existing surfaces (limit-death stream order, turn-trace metadata, cache-stable disclosure, labeled usage estimates), every new option with its sizing line, and 0.9.0 host migration steps.
|
|
234
253
|
- [Migrate 0.7 → 0.8](migrate-to-0.8.md): work-family import map, messaging channels, connected apps, durable runs, and 0.8.0 host migration steps.
|
|
235
254
|
- [Migrate 0.6 → 0.7](migrate-to-0.7.md): ACP MCP allow-list URL normalization, model router facade fail-closed governance, and 0.7.0 host migration steps.
|
|
236
255
|
- [Migrate 0.5 → 0.6](migrate-to-0.6.md): Node 22 floor, folded 0.5.7 host delta, third-party floors, and upgrade/rollback steps.
|
|
@@ -247,15 +266,15 @@ The generated inventory below derives from [`scripts/package-truth.json`](../scr
|
|
|
247
266
|
|
|
248
267
|
| package | version | notes |
|
|
249
268
|
| --- | --- | --- |
|
|
250
|
-
| `@arnilo/prism` | 0.
|
|
251
|
-
| `@arnilo/prism-channels` | 0.
|
|
252
|
-
| `@arnilo/prism-coding-tools` | 0.
|
|
253
|
-
| `@arnilo/prism-core` | 0.
|
|
254
|
-
| `@arnilo/prism-providers` | 0.
|
|
255
|
-
| `@arnilo/prism-acp-agent` | 0.
|
|
256
|
-
| `@arnilo/prism-ag-ui` | 0.
|
|
257
|
-
| `@arnilo/prism-mcp` | 0.
|
|
258
|
-
| `@arnilo/prism-memory` | 0.
|
|
259
|
-
| `@arnilo/prism-web-tools` | 0.
|
|
260
|
-
| `@arnilo/prism-work` | 0.
|
|
269
|
+
| `@arnilo/prism` | 0.9.0 | core — runtime, CLI/RPC, templates, docs |
|
|
270
|
+
| `@arnilo/prism-channels` | 0.9.0 | family — transport-neutral messaging runtime, durable journal, pairing and one-use approvals; official /telegram (private DMs, opt-in granted groups/topics) and experimental pinned signal-cli /signal |
|
|
271
|
+
| `@arnilo/prism-coding-tools` | 0.9.0 | family — /agent, /security, /openapi, /computer-use-linux, /dev, /caveman, /ponytail, /impeccable subpaths |
|
|
272
|
+
| `@arnilo/prism-core` | 0.9.0 | family — /runtime, /sessions, /governance, /credentials, /enterprise, /validation subpaths |
|
|
273
|
+
| `@arnilo/prism-providers` | 0.9.0 | family — all provider adapters as `/<adapter>` subpaths |
|
|
274
|
+
| `@arnilo/prism-acp-agent` | 0.9.0 | capability — ACP adapter |
|
|
275
|
+
| `@arnilo/prism-ag-ui` | 0.9.0 | capability — AG-UI/A2A/A2UI adapter |
|
|
276
|
+
| `@arnilo/prism-mcp` | 0.9.0 | capability — MCP client/server/OAuth interop |
|
|
277
|
+
| `@arnilo/prism-memory` | 0.9.0 | capability — memory plus /rag, /compaction/*, /fabric, /graft, /wiki subpaths |
|
|
278
|
+
| `@arnilo/prism-web-tools` | 0.9.0 | capability — Brave/Exa/Firecrawl plus peer-gated /browser and /obscura subpaths |
|
|
279
|
+
| `@arnilo/prism-work` | 0.9.0 | capability — /connectors, /documents, /sheets, /diagrams, /document-reader, /sandbox, /skills, /tools subpaths |
|
|
261
280
|
<!-- generated:package-truth:inventory end -->
|
|
@@ -62,7 +62,7 @@ Useful exported types:
|
|
|
62
62
|
- `InputAttachment`: already-loaded text/content blocks (including `audio`, `file`, and `document`) or an explicit URI loaded through a caller-provided `ResourceLoader`.
|
|
63
63
|
- `PromptInstruction`: labeled system instruction text.
|
|
64
64
|
- `DefaultPromptBuilder`: the default `PromptBuilder`; cache-aware by default and legacy-preserving when `inputLayout: "legacy"` is passed in its request.
|
|
65
|
-
- `AssembleProviderInputOptions`: model, input, optional builders, context providers, selected skills, active tools, generic provider options, metadata, signal, and optional `contextBudget` (`maxInputTokens` / `maxInputBytes` / `reportOmissions` / `tokenEstimator`).
|
|
65
|
+
- `AssembleProviderInputOptions`: model, input, optional builders, context providers, selected skills, active tools, generic provider options, metadata, signal, optional session-owned `tailSegments`, and optional `contextBudget` (`maxInputTokens` / `maxInputBytes` / `reportOmissions` / `tokenEstimator`).
|
|
66
66
|
- `applyContextBudget` / `getContextBudgetReport` / `resolveContextBudget`: deterministic eviction + omission report helpers (estimate = UTF-16 code units ÷ 4, or the host's `tokenEstimator`).
|
|
67
67
|
- `PromptTemplateOptions`: missing-variable behavior for `renderPromptTemplate()`.
|
|
68
68
|
|
|
@@ -81,10 +81,10 @@ The builder returns `readonly Message[]`.
|
|
|
81
81
|
|
|
82
82
|
The default prompt builder preserves one composition path while honoring layout:
|
|
83
83
|
|
|
84
|
-
- `cache_aware` (default): leading system messages from input assembly → resolved context blocks → selected/progressively disclosed
|
|
84
|
+
- `cache_aware` (default): leading system messages from input assembly → resolved context blocks → selected/progressively disclosed skill catalogs → text tool declarations for text-only/unknown models → remaining input-builder messages (attachments/resources → summaries → history → tool results → current input) → optional session tail.
|
|
85
85
|
- `legacy`: context blocks → skills → text tool declarations → all input-builder messages (instructions → summaries → history → current input → attachments/resources → tool results).
|
|
86
86
|
|
|
87
|
-
In cache-aware mode, leading system instructions form the stable boundary before dynamic context and
|
|
87
|
+
In cache-aware mode, leading system instructions form the stable boundary before dynamic context and skill catalogs. The provider `tools` field remains the host-supplied schema list; text declarations are only a fallback for models without declared tool support. `RuntimeAgentSession` supplies a run-owned `tailSegments` map: URI resources and loaded skill bodies move to the final tail, while their catalog rows remain in place. First insertion fixes tail order (`resource:<uri>` / `skill:<name>`); re-derivation of an id replaces only that segment's bytes, making changed source content an explicit cache-invalidation boundary. Context-budget eviction can omit a tail segment. Custom prompt builders receive the tail in `messages` plus `tailSkillBodies`; a builder that independently renders `skills` must honor that flag. A stable prefix persists only while those stable inputs stay byte-stable; provider cache hits remain best-effort.
|
|
88
88
|
- History is prepended before current input.
|
|
89
89
|
- Instructions and summaries are system messages; compacted branch summaries from `rebuildSessionContext()` use the same path.
|
|
90
90
|
- Text attachments and explicit text resources are user messages; inline `audio`/`file`/`document` blocks pass through unchanged on attachments with `content`.
|
package/docs/knowledge-sync.md
CHANGED
|
@@ -29,6 +29,10 @@ Use it when a host must keep a RAG corpus aligned with an enterprise file source
|
|
|
29
29
|
|
|
30
30
|
No tools, no watch-channel authorization, no events.
|
|
31
31
|
|
|
32
|
+
## Delete contract
|
|
33
|
+
|
|
34
|
+
A connector `delete` change calls `deleteSource()` for that source only: the connector's own chunk rows and ingestion status go away, under exact tenant/resource/corpus scope. Connector payloads are never authorization, so sync cannot reach beyond the corpus it owns — derived artifacts (summaries, observational-memory entries, compiled wiki pages, host projections) survive sync deletes by design. Removing those is a separate privileged pass through `createDeletionPropagator().propagate(sourceId)` (see [RAG deletion propagation](rag.md#deletion-propagation)), driven by the host, not by the connector.
|
|
35
|
+
|
|
32
36
|
## Request/response example
|
|
33
37
|
|
|
34
38
|
```json
|
package/docs/middleware-hooks.md
CHANGED
|
@@ -14,7 +14,7 @@ APIs:
|
|
|
14
14
|
|
|
15
15
|
Use middleware hooks when a host wants extension/package code to observe or transform a value at a named runtime boundary.
|
|
16
16
|
|
|
17
|
-
Do not use middleware hooks as a provider adapter, prompt builder, retry policy, compaction strategy, tool dispatcher, permission system, or agent/session runtime.
|
|
17
|
+
Do not use middleware hooks as a provider adapter, prompt builder, retry policy, compaction strategy, tool dispatcher, permission system, or agent/session runtime. Per-turn tool menus use `AgentConfig.toolNarrowing` / `RunOptions.toolNarrowing`, not a middleware hook — see [Tools](tools.md).
|
|
18
18
|
|
|
19
19
|
## Inputs / request
|
|
20
20
|
|
|
@@ -24,6 +24,7 @@ createMiddlewareRegistry(options?: MiddlewareRegistryOptions): MiddlewareRegistr
|
|
|
24
24
|
|
|
25
25
|
Built-in hook names:
|
|
26
26
|
|
|
27
|
+
- `beforeProviderTurn`
|
|
27
28
|
- `provider_request`
|
|
28
29
|
- `input_assembly`
|
|
29
30
|
- `prompt_build`
|
|
@@ -47,7 +48,7 @@ Built-in hook names:
|
|
|
47
48
|
|
|
48
49
|
## Outputs / response / events
|
|
49
50
|
|
|
50
|
-
`run()` returns the transformed value. If no middleware is registered for a hook, `run()` returns the original value. `assembleProviderInput()` calls Phase 5 hooks in this order when middleware is supplied: `input_assembly`, then `context`, then `prompt_build`. The `input_assembly` call is unconditional — it runs after whatever `InputBuilder` produced the messages, so host middleware at that hook cannot be skipped by a custom builder. The agent/session runtime applies configured provider request policies, then invokes `provider_request` once with the `ProviderRequest` before `AIProvider.generate()`, invokes `tool_call` and `tool_result` through `dispatchToolCall()` for complete provider tool calls, invokes `compaction` with `{ context, result }` after a compaction strategy returns and before the runtime appends its standard compaction entry, and invokes `retry` with `{ context, decision }` before scheduling a provider-turn retry. There is no `provider_response` hook; observing provider output belongs to the provider adapter or subscriber events.
|
|
51
|
+
`run()` returns the transformed value. If no middleware is registered for a hook, `run()` returns the original value. `assembleProviderInput()` calls Phase 5 hooks in this order when middleware is supplied: `input_assembly`, then `context`, then `prompt_build`. The `input_assembly` call is unconditional — it runs after whatever `InputBuilder` produced the messages, so host middleware at that hook cannot be skipped by a custom builder. The agent/session runtime runs `beforeProviderTurn` once per turn after the request is assembled and before any provider-round work, then applies configured provider request policies, then invokes `provider_request` once with the `ProviderRequest` before `AIProvider.generate()`, invokes `tool_call` and `tool_result` through `dispatchToolCall()` for complete provider tool calls, invokes `compaction` with `{ context, result }` after a compaction strategy returns and before the runtime appends its standard compaction entry, and invokes `retry` with `{ context, decision }` before scheduling a provider-turn retry. There is no `provider_response` hook; observing provider output belongs to the provider adapter or subscriber events.
|
|
51
52
|
|
|
52
53
|
With default `errorPolicy: "event"`, middleware errors become `extension_error` events when `onError` is provided, and later middleware still runs with the current value. With `errorPolicy: "throw"`, `run()` rejects on the first middleware error.
|
|
53
54
|
|
|
@@ -92,11 +93,45 @@ export const extension: Extension = {
|
|
|
92
93
|
};
|
|
93
94
|
```
|
|
94
95
|
|
|
96
|
+
## No-model turns (`beforeProviderTurn`)
|
|
97
|
+
|
|
98
|
+
`beforeProviderTurn` lets the host answer a turn from data it already has — teaching empty states, canned flows, deterministic lookups — without any provider request. The payload is `BeforeProviderTurnPayload` (`sessionId`, `runId`, `turn`, `userText`) and middleware returns it unchanged or with `answer: DeterministicTurnAnswer` set:
|
|
99
|
+
|
|
100
|
+
```ts
|
|
101
|
+
export interface DeterministicTurnAnswer {
|
|
102
|
+
readonly content: readonly ContentBlock[];
|
|
103
|
+
readonly provenance: { readonly middleware: string };
|
|
104
|
+
}
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
```ts
|
|
108
|
+
import { createAgent, createMiddlewareRegistry, type BeforeProviderTurnPayload } from "@arnilo/prism";
|
|
109
|
+
|
|
110
|
+
const DESK_ANSWERS = new Map([["what can you do?", "I answer from local records; ask about an order id."]]);
|
|
111
|
+
const middleware = createMiddlewareRegistry();
|
|
112
|
+
middleware.use<BeforeProviderTurnPayload>("beforeProviderTurn", (payload, next) => {
|
|
113
|
+
const text = DESK_ANSWERS.get(payload.userText);
|
|
114
|
+
return text ? { ...payload, answer: { content: [{ type: "text", text }], provenance: { middleware: "desk" } } } : next(payload);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
const session = createAgent({ model, provider, middleware }).createSession();
|
|
118
|
+
await session.run("what can you do?"); // no provider call; assistant message recorded
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
Contract:
|
|
122
|
+
|
|
123
|
+
- Returning the payload without `answer` (or returning `undefined`) sends the turn to the provider exactly as if the hook were absent.
|
|
124
|
+
- `answer.provenance.middleware` is mandatory and validated as a bounded id (1–64 chars: letters, digits, `.` `_` `:` `-`); a deterministic turn can never masquerade as model output.
|
|
125
|
+
- `answer.content` accepts assistant-visible content blocks (`text`, `image`, `audio`, `file`, `document`, `video`, `thinking`). Tool-call blocks are rejected — no provider ran to authorize a call — and an empty block array throws `DeterministicTurnError` (`ERR_PRISM_DETERMINISTIC_TURN`), failing the run closed instead of falling through to the provider.
|
|
126
|
+
- Content passes the same output guardrails as provider output and is charged against `maxResponseBytes`, but the turn records no usage: usage is absent, never zero, and the run timeline shows a `deterministic` step named after the answering middleware.
|
|
127
|
+
- Provenance persists: the assistant message carries `metadata.deterministic = { middleware }`, so a transcript loaded back from any session store still proves the turn had no model behind it. `summarizeTimeline()`/`summarizeSession()` report `turns: { model, deterministic }`, and `createDeterministicTurnScorer()` (from `@arnilo/prism-core/governance/evals`) grades a trajectory for no-model coverage — failing a turn that both answered deterministically and still issued a provider request.
|
|
128
|
+
|
|
95
129
|
## Extension and configuration notes
|
|
96
130
|
|
|
97
131
|
- Middleware registration is explicit through `createMiddlewareRegistry()` or `ExtensionAPI.use()`.
|
|
98
132
|
- `provider_request` middleware sees generic `ProviderRequest.options` after request policies have run; do not add secrets unless a redactor/policy secret list covers that boundary.
|
|
99
133
|
- Middleware runs only when the host/runtime calls `run()` or passes the registry to a helper that documents a call site.
|
|
134
|
+
- `beforeProviderTurn` runs only for turns that reach the provider boundary; a turn already ended by a run limit, host turn policy, or durable suspension never reaches it, and host middleware is trusted code — it must not use the hook to bypass `RunLimits` or guardrails.
|
|
100
135
|
- `compaction` middleware may adjust the compaction result summary/data, but runtime still owns session store append ordering and branch parent ids.
|
|
101
136
|
- `retry` middleware may stop retrying or adjust delay, but runtime still owns retry event emission, abort-aware waiting, and provider-turn boundaries.
|
|
102
137
|
- The registry does not discover packages, read manifests, load config, call providers, execute tools, read resources, or start sessions.
|
|
@@ -112,6 +147,7 @@ export const extension: Extension = {
|
|
|
112
147
|
|
|
113
148
|
## Related APIs
|
|
114
149
|
|
|
150
|
+
- [Middlewares vs restore hooks](durable-runs.md#restore-hooks-all-or-nothing): middleware transforms payloads at named boundaries; `restoreHooks` restore external state before a durable resume and are not middleware.
|
|
115
151
|
- [Extension kernel and event bus](extensions.md): `ExtensionAPI.use()` and shared error policy.
|
|
116
152
|
- [Contribution registries](contribution-registries.md): direct contribution registration separate from middleware.
|
|
117
153
|
- [Agent/session runtime](agent-session-runtime.md): provider request policy/middleware timing, bounded tool loop call site for `tool_call`/`tool_result` hooks, and runtime call sites for `compaction` and `retry`.
|