@arnilo/prism 0.5.6 → 0.7.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (160) hide show
  1. package/CHANGELOG.md +81 -5
  2. package/README.md +10 -10
  3. package/dist/agent-approval.js +7 -6
  4. package/dist/agent-definitions.js +1 -0
  5. package/dist/agent-loops.js +51 -12
  6. package/dist/agent-run-lifecycle.js +11 -0
  7. package/dist/agent-run-state.d.ts +6 -0
  8. package/dist/agent-run-state.js +29 -9
  9. package/dist/agent-session/session/assemble.js +33 -2
  10. package/dist/agent-session/session/persist.js +6 -2
  11. package/dist/agent-session/session/tool-round.js +1 -0
  12. package/dist/agent-session/session/types.d.ts +10 -0
  13. package/dist/agent-session/session.d.ts +15 -0
  14. package/dist/agent-session/session.js +59 -4
  15. package/dist/agent-tool-dispatch.js +5 -4
  16. package/dist/artifacts.d.ts +39 -1
  17. package/dist/artifacts.js +73 -0
  18. package/dist/attention-compiler.d.ts +121 -0
  19. package/dist/attention-compiler.js +479 -0
  20. package/dist/cli-init.js +20 -6
  21. package/dist/content.d.ts +3 -16
  22. package/dist/content.js +9 -99
  23. package/dist/context-budget.d.ts +32 -2
  24. package/dist/context-budget.js +51 -19
  25. package/dist/contracts-core/agent.d.ts +18 -0
  26. package/dist/contracts-core/agent.js +4 -1
  27. package/dist/contracts-core/attention.d.ts +66 -0
  28. package/dist/contracts-core/attention.js +2 -0
  29. package/dist/contracts-core/compaction.d.ts +59 -0
  30. package/dist/contracts-core/compaction.js +77 -1
  31. package/dist/contracts-core/provider.d.ts +4 -0
  32. package/dist/contracts-core.d.ts +1 -0
  33. package/dist/contracts-core.js +1 -0
  34. package/dist/contracts-protocol.d.ts +29 -0
  35. package/dist/contracts-run-state.d.ts +6 -0
  36. package/dist/host-composition.d.ts +78 -0
  37. package/dist/host-composition.js +248 -0
  38. package/dist/index.d.ts +11 -8
  39. package/dist/index.js +6 -5
  40. package/dist/input.d.ts +19 -1
  41. package/dist/input.js +52 -2
  42. package/dist/media-types.d.ts +34 -0
  43. package/dist/media-types.js +158 -0
  44. package/dist/pinned-fetch.d.ts +2 -2
  45. package/dist/pinned-fetch.js +11 -12
  46. package/dist/redaction.js +74 -1
  47. package/dist/secure-agent.d.ts +2 -0
  48. package/dist/secure-agent.js +6 -1
  49. package/dist/session-stores.d.ts +11 -0
  50. package/dist/session-stores.js +23 -8
  51. package/dist/tool-result-fold.d.ts +12 -0
  52. package/dist/tool-result-fold.js +13 -6
  53. package/dist/tools.d.ts +10 -0
  54. package/dist/tools.js +41 -0
  55. package/docs/acp-agent.md +42 -11
  56. package/docs/acp.md +3 -2
  57. package/docs/ag-ui.md +9 -5
  58. package/docs/agent-definitions.md +9 -1
  59. package/docs/agent-events.md +6 -1
  60. package/docs/agent-loops.md +1 -1
  61. package/docs/agent-session-runtime.md +9 -7
  62. package/docs/attention-compiler.md +272 -0
  63. package/docs/browser-automation.md +5 -2
  64. package/docs/cli-rpc.md +4 -2
  65. package/docs/coding-agent-tools.md +1 -1
  66. package/docs/coding-security.md +5 -3
  67. package/docs/coding-tools.md +1 -1
  68. package/docs/coding-workspaces.md +22 -0
  69. package/docs/compaction-and-retry.md +36 -4
  70. package/docs/compaction-observational-memory.md +62 -9
  71. package/docs/context-and-skills.md +4 -2
  72. package/docs/contributing.md +37 -0
  73. package/docs/conversations.md +1 -1
  74. package/docs/core.md +2 -0
  75. package/docs/dev-inspector.md +4 -0
  76. package/docs/device-adapters.md +1 -0
  77. package/docs/document-reader.md +12 -2
  78. package/docs/documents.md +11 -3
  79. package/docs/enterprise-postgres-state.md +2 -2
  80. package/docs/evaluations.md +168 -4
  81. package/docs/execution-timeline.md +180 -0
  82. package/docs/graft.md +3 -1
  83. package/docs/history/0.7.0-primitive-review.md +254 -0
  84. package/docs/history/migration-0.0.md +2 -2
  85. package/docs/history/release-handoffs.md +70 -1
  86. package/docs/host-compositions.md +147 -0
  87. package/docs/host-security.md +2 -2
  88. package/docs/hosted-sandboxes.md +94 -0
  89. package/docs/index.md +73 -41
  90. package/docs/input-and-prompt-assembly.md +5 -4
  91. package/docs/knowledge-sync.md +84 -0
  92. package/docs/language-intelligence.md +2 -2
  93. package/docs/live-testing.md +4 -1
  94. package/docs/mcp-tools.md +2 -1
  95. package/docs/memory-fabric.md +416 -0
  96. package/docs/migrate-to-0.5.md +8 -3
  97. package/docs/migrate-to-0.6.md +90 -0
  98. package/docs/migrate-to-0.7.md +345 -0
  99. package/docs/migration.md +43 -1
  100. package/docs/model-registry.md +1 -1
  101. package/docs/model-routing.md +79 -4
  102. package/docs/multi-agent-patterns.md +20 -6
  103. package/docs/multimodal-content.md +1 -1
  104. package/docs/obscura.md +3 -1
  105. package/docs/observability.md +52 -1
  106. package/docs/operations.md +13 -1
  107. package/docs/options-index.md +298 -0
  108. package/docs/peer-dependencies.md +96 -0
  109. package/docs/performance.md +34 -2
  110. package/docs/ponytail.md +2 -0
  111. package/docs/postgres-persistence.md +3 -1
  112. package/docs/process-sessions.md +3 -1
  113. package/docs/prompt-registry.md +1 -1
  114. package/docs/provider-caching.md +4 -2
  115. package/docs/provider-conformance.md +2 -2
  116. package/docs/provider-packages.md +23 -23
  117. package/docs/provider-primitives.md +2 -1
  118. package/docs/providers/ai-sdk.md +5 -2
  119. package/docs/providers/bedrock.md +71 -7
  120. package/docs/providers/openai.md +1 -1
  121. package/docs/public-contracts.md +2 -2
  122. package/docs/rag.md +24 -8
  123. package/docs/realtime-voice.md +87 -0
  124. package/docs/release-and-install.md +78 -56
  125. package/docs/runs-and-usage.md +3 -2
  126. package/docs/server.md +6 -4
  127. package/docs/session-stores.md +3 -1
  128. package/docs/speech.md +2 -0
  129. package/docs/sqlite-persistence.md +2 -0
  130. package/docs/supervisors.md +33 -5
  131. package/docs/testing.md +38 -0
  132. package/docs/thinking-and-reasoning.md +3 -1
  133. package/docs/tools.md +7 -6
  134. package/docs/web-tools.md +2 -1
  135. package/docs/wiki.md +1 -1
  136. package/docs/work-artifacts-and-review.md +14 -4
  137. package/docs/work-connectors.md +3 -1
  138. package/docs/work-tools.md +14 -4
  139. package/docs/workflows.md +69 -1
  140. package/docs/working-and-semantic-memory.md +25 -14
  141. package/package.json +5 -5
  142. package/templates/README.md +2 -0
  143. package/templates/business-worker/README.md.tmpl +19 -0
  144. package/templates/business-worker/env.example.tmpl +1 -0
  145. package/templates/business-worker/gitignore.tmpl +11 -0
  146. package/templates/business-worker/manifest.json +11 -0
  147. package/templates/business-worker/package.json.tmpl +23 -0
  148. package/templates/business-worker/src/agent.ts.tmpl +92 -0
  149. package/templates/business-worker/src/index.ts.tmpl +13 -0
  150. package/templates/business-worker/src/tests/agent.test.ts.tmpl +77 -0
  151. package/templates/business-worker/tsconfig.json.tmpl +15 -0
  152. package/templates/personal-assistant/README.md.tmpl +18 -0
  153. package/templates/personal-assistant/env.example.tmpl +1 -0
  154. package/templates/personal-assistant/gitignore.tmpl +11 -0
  155. package/templates/personal-assistant/manifest.json +11 -0
  156. package/templates/personal-assistant/package.json.tmpl +23 -0
  157. package/templates/personal-assistant/src/agent.ts.tmpl +65 -0
  158. package/templates/personal-assistant/src/index.ts.tmpl +13 -0
  159. package/templates/personal-assistant/src/tests/agent.test.ts.tmpl +28 -0
  160. package/templates/personal-assistant/tsconfig.json.tmpl +15 -0
@@ -10,22 +10,29 @@ Use a supervisor when a host or agent must choose a child dynamically. Use `@arn
10
10
 
11
11
  ## Inputs / request
12
12
 
13
+ **Option surfaces** — `CreateSupervisorOptions` (ownership, child catalog, hooks, `childEvents`, limits), `SupervisorLimits` / `ResolvedSupervisorLimits` (depth, active children, child events, bytes), `DelegationWaitOptions` (`timeoutMs`, `signal`), `CreateSpawnAgentToolOptions` / `CreateDelegationControlToolOptions` (supervisor, tool name, sync/async mode), `WorktreeChildFactoryOptions` (workspace lifecycle, repository, roots), and `ObserveSupervisorLifecycleOptions` (supervisor, emit, redactor, steps).
14
+
13
15
  | API/field | Meaning |
14
16
  | --- | --- |
15
17
  | `createSupervisor({ ownership, children })` | Creates one ownership-scoped supervisor. |
16
18
  | `SupervisorChild.createAgent(context)` | Child-owned factory; receives derived resource/thread IDs, narrowed permission, abort signal, and nested `delegate`. |
17
19
  | `delegate({ childId, input, threadId?, limits?, signal? })` | Invokes one allow-listed child. Input is text and byte-bounded. |
20
+ | `delegateAsync({ childId, input, threadId?, limits?, signal? })` | Starts one local child and returns `{ delegationId, status: "running" }` without waiting for its result. |
21
+ | `wait(delegationId)` / `cancel(delegationId)` | Joins one local async child (capped at supervisor timeout) or aborts it. Unknown and foreign IDs share one denial. |
22
+ | `createSpawnAgentTool({ supervisor, name? })` | Returns non-exclusive `spawn_agent` tool for a parent model. Its closed schema exposes only host child IDs, input, optional thread ID, and `mode`. |
23
+ | `createWaitAgentTool` / `createCancelAgentTool` | Return `wait_agent` / `cancel_agent` tools for host-owned async handles. |
24
+ | `Supervisor.childIds` | Frozen advertised child-id list the spawn tool's schema enum is built from; model arguments cannot extend it. |
18
25
  | `hooks.before` | May reject, modify redacted input, or narrow limits/policy. |
19
26
  | `hooks.after` | Observes redacted terminal summary; failures cannot alter settled result. |
20
27
  | `limits` | Depth 4/16, active children 4/32, input 64 KiB/1 MiB, steps 8/64, tools 32/256, tokens 20k/1m, timeout 60s/30m, event queue 128/4096, child events/delegation 256/4096, child-event bytes 32 KiB/256 KiB default/hard. Over-cap `delegate()` throws `SupervisorLimitError` before incrementing `activeChildren`. Hook rejection and timeout decrement the count exactly once (no leaked timers). |
21
28
 
22
29
  ## Outputs / response / events
23
30
 
24
- `delegate()` returns the child's `AgentRunResult` or throws its `AgentRunError`/a supervisor denial or limit error. `subscribe()` emits bounded `delegation_started`, `delegation_finished`, `delegation_rejected`, and `delegation_error` metadata events. Graceful close drains already-queued terminal events before the iterator completes (same core multiplexer contract). Hosts may project those events through observability `handleDelegation()` using the parent Prism run ID; no OpenTelemetry dependency enters this package.
31
+ `delegate()` returns the child's `AgentRunResult` or throws its `AgentRunError`/a supervisor denial or limit error. `delegateAsync()` returns a local running handle; `wait()` returns its result (or `{ status: "cancelled" }` after `cancel()`), and stays idempotent while its terminal record is retained (bounded by `limits.maxQueuedEvents`; an evicted or foreign id returns the same non-enumerating error). `subscribe()` emits bounded `delegation_started`, `delegation_finished`, `delegation_rejected`, and `delegation_error` metadata events. Graceful close drains already-queued terminal events before the iterator completes (same core multiplexer contract). Hosts may project those events through observability `handleDelegation()` using the parent Prism run ID; no OpenTelemetry dependency enters this package.
25
32
 
26
33
  ### Child event passthrough (opt-in)
27
34
 
28
- `createSupervisor({ childEvents: true })` projects a redacted, size-capped **milestone** subset of child `AgentEvent`s onto the same stream as `delegation_child_event` (tagged `childId`, `delegationId`, `depth`). v1 covers run start/finish/`suspended`/`denied` and tool-execution started/finished/error/blocked — not per-token `message_delta`. Default off: the stream is byte-identical to today (no subscribe, no allocation). Caps: `limits.maxChildEventsPerDelegation` (256/4096) and `limits.maxChildEventBytes` (32 KiB/256 KiB); exceeding either drops further child events and emits one `delegation_child_events_capped` marker (never throws). Events pass through the supervisor `redactor` before emission. Children never receive supervisor internals or store/subscription access. Resume-path rebuilds (`resumeNestedRun`) do not currently project child events — live passthrough is the initial `delegate()` session only.
35
+ `createSupervisor({ childEvents: true })` projects a redacted, size-capped **milestone** subset of child `AgentEvent`s onto the same stream as `delegation_child_event` (tagged `childId`, `delegationId`, `depth`). v1 covers run start/finish/`suspended`/`denied` and tool-execution started/finished/error/blocked — not per-token `message_delta`. Default off: the stream is byte-identical to today (no subscribe, no allocation). Caps: `limits.maxChildEventsPerDelegation` (256/4096) and `limits.maxChildEventBytes` (32 KiB/256 KiB); exceeding either drops further child events and emits one `delegation_child_events_capped` marker (never throws). Events pass through the supervisor `redactor` before emission. Children never receive supervisor internals or store/subscription access. Resume-path rebuilds (`resumeNestedRun`) attach the same pump to the rebuilt child session, so a delegation that suspended for approval keeps projecting milestones after the root run resumes; counters restart per pump, so each attempt gets the full cap.
29
36
 
30
37
  ## Request/response example
31
38
 
@@ -54,17 +61,36 @@ const supervisor = createSupervisor({
54
61
  const result = await supervisor.delegate({ childId: "research", input: "Check sources" });
55
62
  ```
56
63
 
64
+ ## Model-facing spawn tool
65
+
66
+ `createSpawnAgentTool({ supervisor })` turns the same host-owned child allow-list into non-exclusive `spawn_agent` tool calls, so independent calls use the parent session's `toolConcurrency`. The schema has only `childId`, `input`, optional `threadId`, and `mode: "sync" | "async"` (default `sync`); unknown children fail closed as standard tool errors before delegation. Model arguments cannot supply child tools, identity, scopes, or higher limits. Async returns only a local `{ delegationId, status: "running" }` handle. Install `wait_agent` once per handle for wait-all, or `cancel_agent` to abort it; cancellation is terminally reported by `wait_agent`. Parent-run abort propagates to running children. Handles are in-process, ownership-scoped, and bounded — they do not survive host restart.
67
+
68
+ ```ts
69
+ import { createAgent } from "@arnilo/prism";
70
+ import { createCancelAgentTool, createSpawnAgentTool, createWaitAgentTool } from "@arnilo/prism-core/runtime/supervisor";
71
+
72
+ const parent = createAgent({
73
+ /* parent model/provider */
74
+ tools: [createSpawnAgentTool({ supervisor }), createWaitAgentTool({ supervisor }), createCancelAgentTool({ supervisor })],
75
+ });
76
+ await parent.createSession().run("Research auth and billing", {
77
+ loop: { strategy: "single-shot", toolConcurrency: 2 },
78
+ });
79
+ ```
80
+
57
81
  > **Contract — child factories return `Agent`.** `createAgent` must return an `Agent`, not an `AgentSession` (or a plain object). Wrong type throws `SupervisorError: child "<id>" factory must return an Agent, got <type>` on both initial `delegate()` and nested resume. Nested approvals also need a **stable config** plus a **durable (or rebuild-stable) store** — calling `createSession()` inside the factory and returning that session loses the child's checkpointed leaf. Live demo: [`examples/autonomous-coding-loop.ts`](../examples/autonomous-coding-loop.ts) (`childAgent` returns `createAgent(...)`).
58
82
 
59
83
  ## Durable child approvals
60
84
 
61
- With `checkpoints` + `definitionRevision`, every child run is durable with `interruptBeforeTool: true`. A child that suspends on pending decisions throws `AgentDelegationSuspendedError` out of `delegate()`; when the delegation runs inside a root agent's tool, core converts it into a root suspension whose `interruption.pendingDecisions` carry hashed root-visible approval ids (`sub_<sha256(runId:childApprovalId)>`) and `attribution.path` (redacted child ids, root first, at most 8 deep). Root decisions route back through the same CAS rules: pass `supervisor.resumeNestedRun` as `resumeNestedRun` in the root run's `runState` and in every `resumeAgentRun` options object. The supervisor rebuilds the child from a bounded delegation mapping stored in the same checkpoint store (child id, delegation/thread ids, redacted input, version), re-runs the `before` hook so its narrowing applies to the resumed run (hooks must be idempotent), and re-attributes re-suspensions recursively, so grandchild decisions surface with the full path. A delegating child's own `interruptBeforeTool` also gates its delegate tool, so hosts approve delegation and the child's own side effects as separate stages. Root `*_for_run` stickies record the attribution path and only match the same delegation path; child stickies live on the child run and expire with it. A root approval never widens the child: the child's narrowed permission re-runs at dispatch. Unknown or foreign nested run ids fail closed with one non-enumerating error. Child factories must return stable configs and a durable (or rebuild-stable) session store for resume to work.
85
+ With `checkpoints` + `definitionRevision`, every child run is durable with `interruptBeforeTool: true`. A child that suspends on pending decisions throws `AgentDelegationSuspendedError` out of `delegate()`; when the delegation runs inside a root agent's tool, core converts it into a root suspension whose `interruption.pendingDecisions` carry hashed root-visible approval ids (`sub_<sha256(runId:childApprovalId)>`) and `attribution.path` (redacted child ids, root first, at most 8 deep). Root decisions route back through the same CAS rules: pass `supervisor.resumeNestedRun` as `resumeNestedRun` in the root run's `runState` and in every `resumeAgentRun` options object. The supervisor rebuilds the child from a bounded delegation mapping stored in the same checkpoint store (child id, delegation/thread ids, redacted input, version), re-runs the `before` hook so its narrowing applies to the resumed run (hooks must be idempotent), and re-attributes re-suspensions recursively, so grandchild decisions surface with the full path. A delegating child's own `interruptBeforeTool` also gates its delegate tool, so hosts approve delegation and the child's own side effects as separate stages. Root `*_for_run` stickies record the attribution path and only match the same delegation path; child stickies live on the child run and expire with it. A root approval never widens the child: the child's narrowed permission re-runs at dispatch. Unknown or foreign nested run ids fail closed with one non-enumerating error. A resumed attempt is terminal-symmetric with live `delegate()`: it publishes `delegation_finished` (`delegation_rejected` when the re-run `before` hook denies) and runs `hooks.after` once with the original `childId`/`delegationId`, which is what lets an isolated child's worktree be cleaned up. A suspended child stays non-terminal — no finish event, no `after` — and a rebuild that throws before the run starts (stale version, fingerprint drift) publishes nothing and runs no terminal hook, so a duplicate resume attempt can never clean up a live suspended child. Child factories must return stable configs and a durable (or rebuild-stable) session store for resume to work.
62
86
 
63
87
  ## Extension and configuration notes
64
88
 
89
+ Parallel isolated children: wrap one catalog factory with `createWorktreeChildFactory` from `@arnilo/prism-coding-tools/agent` and pass its `after` as the supervisor's terminal hook — the supervisor stays git-agnostic, and the child context gains `cwd` pointing at its own linked worktree. See [Coding workspaces](coding-workspaces.md#spawn-isolation-supervisor-children).
90
+
65
91
  Child factories resolve their own providers/credentials and construct context/memory using the supplied IDs. Parent, child, returned-agent, budget, and hook permission policies are AND-composed. Child/request/hook limits can only lower inherited limits. A nested factory can call the supplied `delegate()`; immutable path state rejects cycles and depth overflow.
66
92
 
67
- Supervisors propagate parent `identity` and `effectStore` to every child agent/run so delegated tool effects stay under the same ownership scope.
93
+ Supervisors propagate parent `identity` and `effectStore` to every child agent/run so delegated tool effects stay under the same ownership scope. Set host-authored `SupervisorChild.scopes` to derive a child identity with `narrowIdentity`; `assertIdentityPropagation` rejects scope widening before its factory runs.
68
94
 
69
95
  ## Security and performance notes
70
96
 
@@ -82,7 +108,9 @@ Supervisors propagate parent `identity` and `effectStore` to every child agent/r
82
108
  - [Agent identity](agent-identity.md): host-verified identity and narrow delegation.
83
109
  - [A2A interoperability](a2a.md): separate remote protocol boundary. `A2ATaskLifecycle` adapts host durable agent/workflow state directly; it does not route A2A execution through local supervisor child planning.
84
110
  - [Workflows](workflows.md): preferred deterministic orchestration.
85
- - Example: [`examples/autonomous-coding-loop.ts`](../examples/autonomous-coding-loop.ts) — per-child models, factory returns `Agent`.
111
+ - [Coding workspaces](coding-workspaces.md): opt-in per-child worktree isolation via `createWorktreeChildFactory`.
112
+ - [Coding agent tools](coding-agent-tools.md): opt-in `observeSupervisorLifecycle` bridges supervisor `delegation_*` events to coding `subagent_started` / `subagent_stopped` for host timelines.
113
+ - Examples: [`examples/autonomous-coding-loop.ts`](../examples/autonomous-coding-loop.ts) — per-child models, factory returns `Agent`; [`examples/spawn-agent-tool.ts`](../examples/spawn-agent-tool.ts) — two model-requested explore children in one tool turn.
86
114
  - [Working and semantic memory](working-and-semantic-memory.md): child scope construction.
87
115
  - [Host security](host-security.md): permission and credential boundaries.
88
116
  - [Obscura browser engine](obscura.md): optional binary-backed generic tools for child agents.
@@ -0,0 +1,38 @@
1
+ # Test layout and isolation
2
+
3
+ ## What it does
4
+
5
+ Documents how the hermetic suite runs, which stage a new suite belongs to, and the isolation rules that keep tracked fixtures byte-identical between runs. Live and credentialed tiers are separate — see [Live and end-to-end testing](live-testing.md).
6
+
7
+ ## When to use it
8
+
9
+ - Adding or moving a suite: pick its stage and follow the scratch-root rule below.
10
+ - Investigating a report that a test run modified tracked files or scaffolded directories in the repository.
11
+
12
+ ## Running the suite
13
+
14
+ `npm test` delegates to `scripts/run-all-tests.mjs`, which runs five stages and reports every stage even when an earlier one fails:
15
+
16
+ | stage | contents |
17
+ | :--- | :--- |
18
+ | build | `npm run build` (all workspaces) |
19
+ | root suites | `dist/__tests__/*.test.js` |
20
+ | gate suites | `scripts/*.test.mjs` — the protection, truth, benchmark, journey, and conformance gates listed in `GATE_FILES` (`scripts/run-all-tests.mjs`) |
21
+ | build race | `scripts/phase23-build-race.test.mjs` |
22
+ | workspace suites | `npm run test --workspaces --if-present` |
23
+
24
+ Protected-environment legs (Postgres, PTY, NATS, live credentials) are not part of `npm test`; they fail closed with one canonical `BLOCKED GATE <id> requires=<names> evidence=<surface> hint=<how to unblock>` record and a non-zero exit when their infrastructure is absent (registry and audit: `node scripts/blocked-gate.mjs`). Retired phase freeze/release gates live in `scripts/` for audit but are deliberately kept out of the chain. 0.7.0 host-completeness packed proof is `scripts/fixtures/e2e-070-host-completeness-journey.mjs` (same packed consumer as the full-surface journey) plus `scripts/host-completeness-evidence.test.mjs`; live legs stay skip-not-fail. R16/R17 stay blocked until plans 077/074 ship.
25
+
26
+ ## Isolation rules
27
+
28
+ - **Scratch roots come from the OS.** A suite that writes anything creates its root with `mkdtempSync(join(tmpdir(), "prism-…"))` and removes it in `after()`. Never rely on `process.cwd()` for write targets: the same suite runs with different working directories (workspace stage vs. root stage), so a cwd-relative root silently writes into the repository.
29
+ - **Pass explicit roots.** Wiki, memory, and store helpers default `workspaceRoot` to `process.cwd()`; suites pass their scratch root (and a `wikiRoot` relative to it) instead of accepting the default.
30
+ - **Tracked fixtures stay byte-identical.** `packages/memory/.wiki/` is a tracked wiki fixture and `docs/` is a tracked corpus. `scripts/wiki-scratch-isolation.test.mjs` runs the wiki suites from the package and from the repository root and fails if the tracked fixture hashes change, if a new file appears inside the fixture, if `<repo>/.wiki/` is scaffolded, or if the old cwd-relative scratch directories reappear.
31
+ - **Gates never write inside the repository.** A gate asserts against tracked content and spawns suites in temporary directories only. A gate that spawns `node --test` must strip `NODE_TEST_CONTEXT`/`NODE_TEST_WORKER_ID` from the child environment (an inherited value makes the nested runner skip every file and still exit 0) and assert the child reported a non-zero pass count.
32
+ - **Wait by polling, not by sleeping.** Async browser state (download quarantine, idle reaping) is not awaitable from the outside — `manager.ts` settles it on a fire-and-forget listener promise — so a fixed sleep is a race that loses under CPU load and fails the assertion for a reason unrelated to the behavior under test. Suites poll observable state through `waitFor(read, ok, label, { timeoutMs, intervalMs })` in `packages/web-tools/src/browser/__tests__/wait-for.ts`, which returns as soon as the state appears and otherwise throws naming the label and the last observed value. Fixed sleeps remain only where real elapsed time is the subject of the test (idle TTLs).
33
+
34
+ ## Related APIs
35
+
36
+ - [Live and end-to-end testing](live-testing.md): live matrix, credential scoping, skip-not-fail contract.
37
+ - [Coverage gates](release-and-install.md): per-package line thresholds and the functional-surface baseline.
38
+ - `scripts/run-all-tests.mjs` — the stage table, `STAGES` and `effectiveTestChain()` exports.
@@ -93,7 +93,8 @@ Core maps only shapes shared by ≥2 packages (or an explicit no-op). Unique kno
93
93
  | `@arnilo/prism-providers/commandcode` / `@arnilo/prism-providers/opencode-go` | Gateway level tables (`claude-*` → `output_config_effort`, `gpt-5.6*` → `openai_reasoning`, K3/DeepSeek/GLM → `reasoning_effort`, K2.x/MiniMax/Qwen → `thinking_type`) |
94
94
  | `@arnilo/prism-providers/alibaba` | `thinking_type` mapped onto Qwen `enable_thinking` (toggle, no effort levels) |
95
95
  | `@arnilo/prism-providers/ollama` | `reasoning_effort`; `gpt-oss*` declares `low/medium/high`; native `think` field never mixed in |
96
- | `@arnilo/prism-providers/azure` / `.../vertex` / `.../bedrock` | OpenAI-compat sanitized forwarder (`reasoning_effort` / `reasoning` object), snapped to declared levels |
96
+ | `@arnilo/prism-providers/azure` / `.../vertex` / `.../bedrock` (compatible route) | OpenAI-compat sanitized forwarder (`reasoning_effort` / `reasoning` object), snapped to declared levels |
97
+ | `@arnilo/prism-providers/bedrock` (native `converse` route) | Anthropic family: `additionalModelRequestFields.thinking` (`enabled`/`disabled`/`adaptive`, default budget injected); OpenAI family: `reasoning_effort` snapped to declared levels |
97
98
  | `@arnilo/prism-providers/ai-sdk` | `noop` — host `LanguageModelV4` owns reasoning settings |
98
99
 
99
100
  ## Declared levels and snapping
@@ -133,6 +134,7 @@ OpenRouter and Hyper derive their sets from each provider's models API (`support
133
134
  - [Provider request policies](provider-request-policies.md) — `mergeProviderRequestOptions`
134
135
  - [Use-case model selection](use-case-model-selection.md) — session vs worker/summary model binding (workers take `thinkingLevel`)
135
136
  - [Agent/session runtime](agent-session-runtime.md) — prior-reasoning preservation across turns
137
+ - [Attention compiler](attention-compiler.md) — opt-in strip of thinking turns older than `thinkingKeepTurns` once the request crosses a ratio of the input cap
136
138
  - [Provider packages](provider-packages.md) — package boundaries and discovery
137
139
  - Per-provider pages under [docs/providers](providers/) — declared levels, wire field, and snapping per provider
138
140
  - [Thinking coverage evidence matrix](_evidence/thinking-coverage-2026-09-05.md) — per-model legality, source, and test pins
package/docs/tools.md CHANGED
@@ -150,14 +150,14 @@ Configuration can carry allow/deny names, but Prism does not define a policy cla
150
150
 
151
151
  ### Per-run tool scoping
152
152
 
153
- `session.run()` intentionally has no `RunOptions.tools` or `RunOptions.toolFilter`. Scope tools by building the active `ToolRegistry` for the agent/session, by resolving declarative `AgentDefinition.tools`, or by using `PermissionPolicy` / `ToolValidator` to fail closed at dispatch time. Skills do not grant tool access; `toolNames` only validates that host-active tools exist.
153
+ There is still no `RunOptions.tools` or `RunOptions.toolFilter` — those would replace or mutate the registry. `RunOptions.toolNames` is an optional **allow-list of already-registered names**. Omitted → every registered tool (legacy). Empty → no tools this run. Unknown names fail closed. The run snapshots the matching `ToolDefinition`s once; provider schemas, `search_tools`, skill `toolNames` checks, and dispatch all use that snapshot. Resume stores the grant and intersects it with current authority — it cannot widen, even if the live registry grew. Middleware, skills, and nested calls cannot add names outside the grant. Skills do not grant tool access; skill `toolNames` only validates that host-active tools exist.
154
154
 
155
155
  ```ts
156
- const activeTools = createToolRegistry([searchTool]);
157
- const agent = createAgent({ model, provider, tools: activeTools, permission, validator });
156
+ const agent = createAgent({ model, provider, tools: registry, permission, validator });
157
+ await session.run(input, { toolNames: ["web_search"] });
158
158
  ```
159
159
 
160
- Need different tools for one request? Build a short-lived agent/session with a narrower registry, or block extra calls with `PermissionPolicy` / `RunOptions.validate`. No extra per-run tool API exists yet; add one only when host apps need it.
160
+ Scope the active `ToolRegistry` (or declarative `AgentDefinition.tools`) at agent construction. `PermissionPolicy` / `RunOptions.validate` still fail closed at dispatch; `toolNames` only intersects that host-active set.
161
161
 
162
162
  ### Artifact-loop tools
163
163
 
@@ -216,7 +216,7 @@ By default tools without `parameters` skip schema validation (`missingSchema: "a
216
216
 
217
217
  ### Parallel tool execution (single-shot loop)
218
218
 
219
- Opt in through `loop.toolConcurrency` on `AgentConfig` / `RunOptions` (single-shot strategy only). Default is `1` (sequential). Independent calls from one provider turn run concurrently up to the limit; transcript rows and `appendMessage` stay in original call order. Each call still uses `dispatchToolCall` (permission, validation, abort signal). If a worker throws or the run aborts, workers stop claiming new calls, already-claimed calls settle, buffered tool-result rows are not appended, and the first failure is rethrown. Already-claimed side effects are not rolled back; the shared abort signal is still passed to each dispatch. The round-level `chargeToolRound` approval gate runs before any worker starts. See [Agent loops](agent-loops.md).
219
+ Opt in through `loop.toolConcurrency` on `AgentConfig` / `RunOptions` (single-shot strategy only). Default is `1` (sequential). Independent calls from one provider turn run concurrently up to the limit; transcript rows and `appendMessage` stay in original call order. Each call still uses `dispatchToolCall` (permission, validation, abort signal). If a worker throws or the run aborts, workers stop claiming new calls, already-claimed calls settle, and rows are then persisted in call order before the first failure is rethrown: finished calls keep their real results, the call that threw gets an error row carrying that failure, and calls the batch never started get a `tool_call_not_dispatched` error row — a stopped batch never leaves `tool_call` ids without a `tool_result` (run-level suspension errors are exempt: their resume machinery appends the real result). Already-claimed side effects are not rolled back; the shared abort signal is still passed to each dispatch. The round-level `chargeToolRound` approval gate runs before any worker starts. See [Agent loops](agent-loops.md).
220
220
 
221
221
  ```ts
222
222
  await session.run(input, {
@@ -274,7 +274,7 @@ Limits (mirroring the skill-disclosure DEFAULT/HARD cap pattern):
274
274
 
275
275
  - `search_tools({ query, k? })` returns inert `name: short description [matched: …]` lines — no schemas or tool bodies — and marks returned tools active for the session. Activation is names-only in run persistence (`sessionState.activatedToolNames`, capped at 128 names) and inert for tools absent from the current registry; a host can reset it with `session.clearActivatedTools()`.
276
276
  - Fail closed: any index or scoring error discloses the full input list — never zero tools, never wider than the input list. Exhausting the frozen 1024-tool index cap is surfaced the same way.
277
- - Disclosure never grants access: dispatch re-checks registry membership and allow/deny (`unknown_tool` / `tool_denied`) on every call regardless of what was described. Search results are intersected with the disclosed list structurally — searched tools are only ever selected from that list, never widened.
277
+ - Disclosure never grants access: dispatch re-checks registry membership and allow/deny (`unknown_tool` / `tool_denied`) on every call regardless of what was described. Search results are intersected with the disclosed list structurally — searched tools are only ever selected from that list, never widened. When `RunOptions.toolNames` is set, the search index is built from that snapshot only.
278
278
  - Scoring is BM25-lite lexical (name tokens weigh ×3, IDF from the registry): bounded, dependency-free, deterministic tie-breaks. ponytail ceiling: embedder-backed scoring via `@arnilo/prism-memory/rag` if accuracy fixtures fall short.
279
279
  - Cross-link: skills apply the same discipline to prompt text — see [Context and skills](context-and-skills.md).
280
280
 
@@ -293,6 +293,7 @@ Limits (mirroring the skill-disclosure DEFAULT/HARD cap pattern):
293
293
  - [Middleware hooks](middleware-hooks.md): `tool_call` and `tool_result` middleware used during dispatch.
294
294
  - [Credentials and redaction](credentials-and-redaction.md): redaction helpers used for tool execution errors.
295
295
  - [Observational memory compaction package](compaction-observational-memory.md): optional exact-id recall tool factory.
296
+ - [Memory fabric](memory-fabric.md): optional governed note tools (`memory.view`/`read`/`insert`/`recall`/`forget`) jailed to a host directory.
296
297
  - [Tool execution primitives](tool-execution-primitives.md): JSON Schema adapter, parallelism, MCP bridge, and execution-policy designs.
297
298
  - [MCP client bridge](mcp-tools.md): optional remote tool mapping plus separate bounded resource/prompt facades; non-tool MCP capabilities never bypass tool dispatch by masquerading as `ToolDefinition`.
298
299
  - [Recoverable tool effects](tool-effects.md): optional `tool.effect` + `effectStore` claim/CAS recovery around dispatch.
package/docs/web-tools.md CHANGED
@@ -67,7 +67,7 @@ Default/hard limits: query 4/16 KiB; results 10/20; URLs 5/20; request 256 KiB/1
67
67
 
68
68
  ## Security and performance notes
69
69
 
70
- Provider credentials never enter tool schemas/results, prompts, telemetry, URLs, or errors. Error text excludes remote bodies. Search snippets, Markdown, and extracted JSON are prompt-injection-capable data: never concatenate them into system instructions or use them to modify tools, permissions, credentials, trust, routing, or schemas. Firecrawl fetches target URLs remotely; Prism cannot claim target DNS pinning after handoff. Use controlled host fetch when that guarantee is required.
70
+ Provider credentials never enter tool schemas/results, prompts, telemetry, URLs, or errors. Error text excludes remote bodies. Search snippets, Markdown, and extracted JSON are prompt-injection-capable data: never concatenate them into system instructions or use them to modify tools, permissions, credentials, trust, routing, or schemas. `snapshotWebEvidence({ url, body, provider })` hashes an already-fetched body into the shared `ArtifactCitation` evidence shape; it does not refetch and does not store credentials. Firecrawl fetches target URLs remotely; Prism cannot claim target DNS pinning after handoff. Use controlled host fetch when that guarantee is required.
71
71
 
72
72
  Default tests use injected fake fetch and make no public request. Restricted smoke: `PRISM_LIVE_WEB=1 npm run test:live -w @arnilo/prism-web-tools` plus least-privilege provider environment credential. Prefer the [`browser`](browser-automation.md) subpath over ordinary public retrieval; use browser automation only for interactive/authenticated/JavaScript-heavy work behind a host egress proxy. Arbitrary HTML execution, model-selected providers, automatic OAuth forwarding, and generic web/MCP passthrough are unsupported.
73
73
 
@@ -77,4 +77,5 @@ Default tests use injected fake fetch and make no public request. Restricted smo
77
77
  - [Credential storage](credential-storage.md): explicit resolver composition and environment mapping.
78
78
  - [Host security](host-security.md): SSRF, untrusted-content, and secret boundaries.
79
79
  - [MCP tools](mcp-tools.md): hardened prototype path for official vendor MCP servers.
80
+ - [Work artifacts and review](work-artifacts-and-review.md): `snapshotWebEvidence` produces shared citation evidence from an already-fetched body.
80
81
  - [Performance and resource limits](performance.md): operational ceilings and benchmark evidence.
package/docs/wiki.md CHANGED
@@ -27,7 +27,7 @@ The Karpathy LLM Wiki pattern is structured into 3 distinct tiers:
27
27
 
28
28
  | Field | Type | Required | Default | Description |
29
29
  | :--- | :--- | :--- | :--- | :--- |
30
- | `wikiRoot` | `string` | No | `".wiki"` | Path to the compiled wiki directory. |
30
+ | `wikiRoot` | `string` | No | `".wiki"` | Path to the compiled wiki directory; a relative path is resolved against `workspaceRoot`, an absolute path is used as-is. |
31
31
  | `rawRoots` | `readonly string[]` | No | `["."]` | Directories containing raw source files (code, notes, docs). |
32
32
  | `profile` | `"codebase" \| "pkm" \| "hybrid" \| "auto"` | No | `"auto"` | Operating strategy for parsing and symbol indexing. |
33
33
  | `qmdPath` | `string` | No | `"qmd"` | Path or executable name for the `qmd` CLI binary. |
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- `@arnilo/prism-core/runtime/server` ships a durable artifact co-work review service (Phase 9 / 0.0.14): authorized attach of source/output references with MIME/hash/version, producer-run attribution, citations/data sources, and preview metadata; revision comparison; reviewer approve/reject (request-changes) with last-validated recovery; and authorized, expiring delivery links. Core (`@arnilo/prism`) exports artifact **types only** (`ArtifactRecord`, `ArtifactRevision`, `ArtifactApproval`, `ArtifactDeliveryToken`, approval state `pending | approved | rejected`). Prism persists bounded metadata, revisions, approvals, and delivery references over the existing versioned checkpoint store — **never file bodies**; hosts own blob storage and rendering.
5
+ `@arnilo/prism-core/runtime/server` ships a durable artifact co-work review service: authorized attach of source/output references with MIME/hash/version, producer-run attribution, citations/data sources, and preview metadata; revision comparison; reviewer approve/reject (request-changes) with last-validated recovery; and authorized, expiring delivery links. Citations may carry shared evidence fields (`sourceId`, `revision`, `contentHash`, `retrievedAt`, `excerpt`, `span`, `tenantId`, `support`). Approve stamps `evidenceDigest` over those identity tuples. Core (`@arnilo/prism`) exports artifact types plus `checkCitationIntegrity` / `citationBindingDigest` / `approvalEvidenceIntact`. Prism persists bounded metadata, revisions, approvals, and delivery references over the existing versioned checkpoint store — **never file bodies**; hosts own blob storage and rendering. Integrity is existence/hash/span/ACL only; `support` is an optional host verdict, not proof.
6
6
 
7
7
  ## When to use it
8
8
 
@@ -34,8 +34,8 @@ Every operation input carries `ownership` (from host `authorize`, never request
34
34
  | `list` | Ownership/thread-scoped `PersistencePage<ArtifactRecord>` |
35
35
  | `get` | `ArtifactRecord` |
36
36
  | `revise` | `ArtifactRecord` with an appended revision (new revision resets state to pending) |
37
- | `compare` | `{ artifactId, from, to, changed: { hash, mime, uri, citations } }` — hash+metadata only |
38
- | `approve` / `reject` | `ArtifactRecord`; approve advances `lastValidatedVersion`, reject never clears it |
37
+ | `compare` | `{ artifactId, from, to, changed: { hash, mime, uri, citations } }` — hash+metadata only; structural Office diffs use `diffDocument` |
38
+ | `approve` / `reject` | `ArtifactRecord`; approve advances `lastValidatedVersion` and stamps `evidenceDigest`; reject never clears last-validated |
39
39
  | `lastValidated` | The last approved `ArtifactRevision` (fails closed before any approval) |
40
40
  | `deliveryLink` | `{ link, token }` — signed expiring `ArtifactDeliveryToken` |
41
41
 
@@ -87,7 +87,7 @@ export const handler = createArtifactHandler({ service: artifacts, authorize: ho
87
87
 
88
88
  - Every operation requires authenticated identity + thread ownership derived from host `authorize`; cross-ownership access fails closed as `not_found` (never leaks existence).
89
89
  - Concurrent reviewer conflicts resolve via checkpoint CAS (`expectedVersion`); the loser gets a retryable `conflict` and no approval is lost or duplicated. A throw before commit persists nothing, so failed updates roll back.
90
- - Local filesystem paths are rejected in `uri`/citations (`file:`, absolute, or drive paths); records are redacted before persist and on response, so paths/secrets/document-private data never enter records, events, or exports.
90
+ - Local filesystem paths are rejected in `uri`/citations (`file:`, absolute, or drive paths); records are redacted before persist and on response, so paths/secrets/document-private data never enter records, events, or exports. Citation evidence is untrusted/inert: hosts pass already-retrieved snapshots into `checkCitationIntegrity` (no URL refetch, no persisted presigned credentials). A live source hash/revision/ACL change fails integrity even when a semantic judge scores the prose 1.0.
91
91
  - Frozen caps (default / hard): artifacts per thread 64/256; revisions per artifact 32/128; record 8/64 KiB; preview 16/64 KiB; citations 32/128 and 2/8 KiB each; MIME 128/512 B; hash 256/1 KiB; compare exactly 2 revisions; delivery TTL 5 min/24 h; delivery token 4/16 KiB. Raising the revision cap may require raising `recordBytes` (aggregate backstop).
92
92
  - Compare is hash+metadata-bounded (hosts render content); no file bodies are persisted or transferred. With a wired body store, bodies live in the host's object store and are streamed through the adapter (bounded by `maxBodyBytes` 64 MiB/512 MiB, concurrent transfers 4/16, presign TTL 10 min/24 h); object-store outages surface typed `ERR_PRISM_S3_*` / `ERR_PRISM_ARTIFACT_BODY_*` errors, never silent success.
93
93
 
@@ -95,6 +95,14 @@ export const handler = createArtifactHandler({ service: artifacts, authorize: ho
95
95
 
96
96
  `@arnilo/prism-coding-tools/agent` composes over this service for the coding patch review workflow: `createCodingPatchReviewManifest` builds a bounded manifest (repository/worktree identity, base/head, patch digest, changed paths, diffstat, check and diagnostic summaries) and returns a structural `ArtifactAttachInput` whose `preview.review` embeds the manifest and whose `hash` is the patch SHA-256; `assertCodingPatchAccepted` derives `pending|accepted|rejected|superseded` from the returned `ArtifactRecord` by binding to the exact artifact revision, digest, and identity — any patch/repository/worktree/base/head change supersedes a prior acceptance (a newer revision attached after approval makes the old acceptance stale and refused). Decisions never apply/commit/push/merge; the manifest never embeds a raw patch body. Full contract: [Coding review and diagnostics](coding-review-and-diagnostics.md).
97
97
 
98
+ ## Business action drafts and editable approvals (0.7.0)
99
+
100
+ Business tools (e.g. mail, calendar, documents in `@arnilo/prism-core/integrations/work`) record mutations through durable `WorkDraftStore` drafts before execution. Human reviewers can approve, deny, or edit draft payloads directly:
101
+ - AG-UI clients advertise and send `approveWithEdits` with revised arguments (`editedArgs`/`modifiedArguments`).
102
+ - The server resume endpoint accepts `{ decision: "approve", modifiedArguments: { ... } }` under CAS `expectedVersion`.
103
+ - If arguments are modified, a new draft revision is created with bumped revision number and payload digest. The previous revision's approval is invalidated and the mutation requires approval for the revised content.
104
+ - Untyped/malformed edits, recipient escalation, schema violations, or stale CAS versions fail closed.
105
+
98
106
  ## Live probe (plans/064 Task 9)
99
107
 
100
108
  The S3 artifact-body store has an operator-gated live probe against a real S3-compatible endpoint (use a throwaway bucket):
@@ -115,3 +123,5 @@ Probes: put → get (hash + size verified), presigned delivery URL with `X-Amz-S
115
123
  - [Policy and audit](policy-and-audit.md): `onDecision` events bridge here for an auditable review ledger.
116
124
  - [Host security](host-security.md): identity/ownership, redaction, and expiring-link boundaries.
117
125
  - [Frontend interoperability (AG-UI and ACP)](ag-ui.md): projects artifact progress/approval/download-link as redacted co-work events over the durable-resume stream.
126
+ - [Documents, spreadsheets, and presentations](documents.md): `diffDocument` for structural paragraph/table/cell/slide review.
127
+ - [Evaluations](evaluations.md): `createCitationIntegrityScorer` invariant over `environment.citations`.
@@ -6,7 +6,7 @@ Least-privilege Microsoft 365 and Google Workspace connectors live in `@arnilo/p
6
6
 
7
7
  1. **Host-pinned binary** — Prism never downloads or shells an untrusted CLI path.
8
8
  2. **Hard-coded argv templates** — models choose typed tool args; they never supply command strings.
9
- 3. **Draft-then-approve** — mutations create a draft; side effects run only after host approval.
9
+ 3. **Draft-then-approve & durable resumption** — mutations create a draft with tracked revisions and payload digests; side effects run only after host approval binds to that exact revision; durable checkpoint persistence survives process restart.
10
10
  4. **Idempotent retries** — `IdempotencyStore` keyed by identity + operation key.
11
11
  5. **Isolated config** — per-identity `configDir` (CLI `HOME`); no credential argv.
12
12
  6. **Shared result shapes** — mail/calendar/file/task list/get tools normalize onto `WorkMailMessage` / `WorkCalendarEvent` / `WorkFileItem` / `WorkTaskItem` without hiding provider-specific ops.
@@ -23,6 +23,8 @@ See [Work tools](work-tools.md). Adapter: `createGoogleWorkspaceCliAdapter` / su
23
23
 
24
24
  Uses [`@googleworkspace/cli` (`gws`)](https://github.com/googleworkspace/cli): `gmail users messages list|get`, `gmail +send`, `calendar events list|insert`, `drive files list|create`, `drive permissions create`, `tasks tasks *`. Docs/Sheets/Slides create remain capability-gated. Discovery `schema` and `auth`/`login`/`setup` are forbidden from Prism argv.
25
25
 
26
+ Drive **knowledge synchronization** (RAG import of file text + host-mapped ACL via `changes.list`) is not this CLI adapter. Use `createGoogleDriveConnector` / `syncKnowledge` from `@arnilo/prism-memory/rag` — see [Knowledge synchronization](knowledge-sync.md).
27
+
26
28
  ## Scoped OAuth establishment (0.0.14)
27
29
 
28
30
  Hosts establish, refresh, and revoke scoped OAuth credentials for these workloads through the existing `OAuthProvider` / credential-store seams (`@arnilo/prism-core/credentials/node`): `createMicrosoft365OAuthProvider` / `createGoogleWorkspaceOAuthProvider` (PKCE + device code), least-privilege scope bundles per capability (`resolveMicrosoft365Scopes` / `resolveGoogleWorkspaceScopes`, read vs mutation). Connectors consume a per-identity token via a late-bound `tokenProvider` injected as an env var — never argv, never model context; revocation fails closed. See [Credential storage](credential-storage.md) and [Work tools](work-tools.md).
@@ -1,6 +1,6 @@
1
1
  # Work tools
2
2
 
3
- Optional `@arnilo/prism-core/integrations/work` package: identity-scoped Microsoft 365 and Google Workspace connectors. Host-pinned CLI binaries only; hard-coded `execFile` argv templates; draft-then-approve mutations; side-effect idempotency; shared mail/calendar/file/task result shapes.
3
+ Optional `@arnilo/prism-core/integrations/work` subpath: identity-scoped Microsoft 365 and Google Workspace connectors. Host-pinned CLI binaries only; hard-coded `execFile` argv templates; draft-then-approve mutations; side-effect idempotency; shared mail/calendar/file/task result shapes.
4
4
 
5
5
  ## When to use
6
6
 
@@ -9,7 +9,7 @@ Use when agents must read or mutate tenant mail/calendar/files/tasks through the
9
9
  ## Install
10
10
 
11
11
  ```bash
12
- npm install @arnilo/prism-core/integrations/work
12
+ npm install @arnilo/prism-core
13
13
  # host separately:
14
14
  # npm i -g @pnp/cli-microsoft365
15
15
  # npm i -g @googleworkspace/cli
@@ -87,9 +87,19 @@ Verified against [`@googleworkspace/cli` / `gws`](https://github.com/googleworks
87
87
 
88
88
  Startup: M365 `version --output json`; GWS `--version`. Forbidden: `login`, `setup`, `auth`, `schema`, `doctor`, `--debug`, `--verbose`, credentials in argv, anonymous share, model-supplied command strings / free-form Discovery.
89
89
 
90
- ### Draft → approve → execute
90
+ ### Draft → approve → execute (0.7.0, R02)
91
91
 
92
- Mutation tools (`*_mail_draft_send`, `*_draft_*`) create an in-adapter draft and return `{ status: "pending_approval", draftId }` until `approval.isApproved` is true.
92
+ Mutation tools (`*_mail_draft_send`, `*_draft_*`) create an in-adapter draft and return `{ status: "pending_approval", draftId, revision, payloadDigest }` until the host approval gate grants permission.
93
+
94
+ In Prism 0.7.0, draft lifecycles are durably managed:
95
+
96
+ - **Exact revision binding**: Every draft carries an integer `revision` (starts at 1) and a deterministic canonical `payloadDigest` (`sha256:<hex>`). Approvals bind strictly to `{ draftId, revision, payloadDigest, identityKey, approvedAt, expiresAt, policyRevision }`.
97
+ - **Durable persistence across restarts**: When adapters are configured with `checkpoints: CheckpointStore` (e.g. `createPostgresEnterpriseState({ pool }).checkpoints` or `createMemoryCheckpointStore()`), drafts are stored under namespace `prism.work.draft`. Drafts survive process restarts; a worker process can resume an exact draft revision approved in a prior process or via a delayed human-in-the-loop review.
98
+ - **Edits invalidate approval**: Any mutation or update to a draft increments `revision`, recalculates `payloadDigest`, clears any previous `approval`, and resets `status` to `pending_approval`. Prior approvals cannot execute a modified draft.
99
+ - **Resuming approved drafts**: Mutation tools accept `{ draftId, revision }` without requiring callers to re-supply the full payload. The tool loads the stored draft, validates approval status and digest, reauthorizes immediately before execution, and executes the effect.
100
+ - **Idempotent duplicate approvals**: Re-approving an approved draft with the same approval object is idempotent. Submitting an approval with a mismatched revision or payload digest is rejected with `ERR_PRISM_WORK_DRAFT_STALE` or `ERR_PRISM_WORK_DRAFT_DIGEST`.
101
+ - **Ambiguous failure handling**: If a connector call fails ambiguously after dispatch, both the idempotency record and the draft are marked `unknown`. Re-running with that draft ID or idempotency key fails closed (`ERR_PRISM_WORK_IDEMPOTENCY_UNKNOWN`) and never auto-replays without explicit operator reconciliation.
102
+ - **Optional body offloading**: Supplying `bodies: ArtifactBodyStore` automatically stores large draft message/file bodies in the object store with an `ArtifactBodyRef` recorded on the draft metadata.
93
103
 
94
104
  ### Durable idempotency (0.0.23)
95
105
 
package/docs/workflows.md CHANGED
@@ -20,6 +20,9 @@ Primary exports:
20
20
  | `defineSaga` / `runSaga` / `resumeSaga` | Bounded linear durable forward steps, reverse compensation, unknown-outcome reconciliation, lease fencing, and manual resolution over existing checkpoint/lease stores |
21
21
  | `createWorkflowSchedules` | Explicit ownership-scoped one-time/interval/host-calculated schedules over existing checkpoint/lease stores |
22
22
  | `createProactiveScheduleCapabilities` | Scoped, expiring, revocable capability tokens that enable proactive schedules; revocation stops firing fail-closed |
23
+ | `serializeWorkflowGraph` / `collectWorkflowGraphs` | Pure JSON serialization of workflow DAGs (`WorkflowGraphView`) without functions/closures; collect nested graphs |
24
+ | `workflowGraphToMermaid` / `workflowGraphToDot` | Deterministic Mermaid flowchart and Graphviz DOT exporters with node shapes by kind and label escaping |
25
+ | `projectWorkflowGraphRun` / `createWorkflowGraphRunFolder` | Run overlay view (`WorkflowGraphRunView`) from checkpoints, timelines, or live event stream |
23
26
 
24
27
  Included through the `@arnilo/prism` / `@arnilo/prism-core` family packages; installing them does not start workflows. Interactive TUI is out of scope (C-012 deferred).
25
28
 
@@ -106,7 +109,7 @@ Every node receives bounded `ctx.state`, `ctx.stateVersion`, and async `ctx.upda
106
109
 
107
110
  `replayWorkflow(workflow, { sourceRunId, fromNodeId, runId? }, options)` requires a succeeded source/node, creates a new checkpoint, copies terminal evidence outside the selected node's downstream closure, restores selected-node pre-state, and records `{ sourceRunId, fromNodeId, rootRunId, depth }`. Source evidence is untouched. Copying any prior nested/tool approval is rejected; replay from that approval node or earlier so Phase 8 approval executes again.
108
111
 
109
- `createWorkflowCoordinator({ coordinatorId, workflows, checkpoints, leases, ... })` polls queued/running checkpoints with bounded pages, atomically claims each run, renews its lease, and aborts/fences work after lease loss. Key controls: `leaseTtlMs` (default 30s), `renewalIntervalMs` (default TTL/3), `pollIntervalMs` (default 1s), `maxConcurrentRuns` (default 4), and `pageSize` (default 100, maximum 500).
112
+ `createWorkflowCoordinator({ coordinatorId, workflows, checkpoints, leases, ... })` polls queued/running checkpoints with bounded pages, atomically claims each run, renews its lease, and aborts/fences work after lease loss. Key controls: `leaseTtlMs` (default 30s), `renewalIntervalMs` (default TTL/3), `pollIntervalMs` (default 1s), `maxConcurrentRuns` (default 4), and `pageSize` (default 100, maximum 500). Optional `admission` wraps claims: cursor wrap across pages (default 4 pages/poll, hard 16) so a noisy first page cannot starve later tenants; `perTenant` / `perClass` cap concurrent claims on that worker; `deadlineMs` skips stale `createdAt`; `drain` stops new claims while draining and aborts in-flight after `snapshot().expired`. Workload class is `metadata.workloadClass` (`^[a-z][a-z0-9_-]{0,31}$`, else `default`). `onMetric` labels are `outcome` + `class` only — never tenant or run ids. This is not a second scheduler.
110
113
 
111
114
  `defineSaga({ id, revision, steps })` validates a bounded linear definition. Each step supplies `run`, `compensate`, and `reconcile`; handlers receive a stable tenant-scoped `operationId`, redacted bounded input/output, prior outputs, and an abort signal. `runSaga(definition, { checkpoints, leases, ownerId, tenantId, runId?, input?, maxAttempts?, leaseTtlMs?, redactor?, onEvent? })` stores a surrogate workflow checkpoint through `WorkflowCheckpointAdapter`, acquires a fenced `LeaseStore` lease, and advances one cursor at a time. `resumeSaga` takes over an expired run; it never replays durably succeeded steps. Forward or compensation handlers mark ambiguous failures with `unknown: true` (or `ERR_PRISM_SAGA_UNKNOWN`), and `reconcile` must return `succeeded`, `failed`, or `unknown` before retry.
112
115
 
@@ -404,6 +407,70 @@ For a single bounded refinement, prefer `loopNode`. Keep this host-loop pattern
404
407
  - Agent exclusivity is per session: one active `run()` at a time, same as core.
405
408
  - Saga definitions remain host code and only their revision, ordered step IDs, bounded JSON snapshots, cursors, attempt counters, and redacted error/provenance metadata are persisted. The surrogate workflow checkpoint namespace is private to the package.
406
409
 
410
+ ## Graph serialization, Mermaid, DOT, and run overlay
411
+
412
+ Workflows can be converted to JSON view-models and visualization formats without executing them or shipping JS functions:
413
+
414
+ ```ts
415
+ import {
416
+ collectWorkflowGraphs,
417
+ createWorkflowGraphRunFolder,
418
+ projectWorkflowGraphRun,
419
+ serializeWorkflowGraph,
420
+ workflowGraphToDot,
421
+ workflowGraphToMermaid,
422
+ } from "@arnilo/prism-core/runtime/workflows";
423
+
424
+ // 1. Pure static graph view (JSON-serializable)
425
+ const view = serializeWorkflowGraph(workflow);
426
+ // view.nodes: [{ id: "stepA", kind: "function", label: "stepA" }, ...]
427
+ // view.edges: [{ from: "stepA", to: "stepB", kind: "always" }, ...]
428
+
429
+ // 2. Export to Mermaid flowchart
430
+ const mermaid = workflowGraphToMermaid(view);
431
+ // flowchart TD
432
+ // stepA["stepA"]
433
+ // stepB["stepB"]
434
+ // stepA --> stepB
435
+
436
+ // 3. Export to Graphviz DOT
437
+ const dot = workflowGraphToDot(view);
438
+
439
+ // 4. Project run state overlay from checkpoint or timeline
440
+ const overlay = projectWorkflowGraphRun(view, checkpoint);
441
+ // overlay.nodes[0].run?.status -> "succeeded" | "failed" | "running" | ...
442
+
443
+ // 5. Incremental live folder for WebSocket/SSE cockpits
444
+ const folder = createWorkflowGraphRunFolder(view);
445
+ for await (const event of eventBus.subscribe()) {
446
+ folder.push(event);
447
+ const live = folder.snapshot();
448
+ }
449
+
450
+ // 6. Collect nested workflow graphs by ID
451
+ const graphs = collectWorkflowGraphs(hierarchicalWorkflow);
452
+ // Map with parent and child workflow views
453
+ ```
454
+
455
+ ### Graph view-model (`WorkflowGraphView`)
456
+
457
+ - `schemaVersion`: 1
458
+ - `workflowId`, `revision`, `definitionHash`: matches definition
459
+ - `nodes`: deterministically sorted array of `WorkflowGraphNode` with `id`, `kind`, `label`, `metadata`, `nestedWorkflowId`, `loop`
460
+ - `edges`: deterministically sorted array of `WorkflowGraphEdge` with `from`, `to`, `kind` (`"always" | "then" | "else"`)
461
+ - Closures and functions (`when`, `execute`, `map`, `reduce`) are **omitted** by design so the view is safe for JSON wire transfer and audit logging.
462
+
463
+ ### Visualization & escaping
464
+
465
+ - `workflowGraphToMermaid()` assigns distinct shapes by node kind: diamond for `conditional`, hexagon for `loop`, stadium for `agent`, subroutine for `workflow`, rounded for `tool`, box for `function`.
466
+ - Mermaid and DOT exporters automatically escape double quotes, HTML characters (`<`, `>`), and literal arrows (`-->`) to prevent script injection (XSS) and parser corruption.
467
+ - Output is byte-identical across runs regardless of dictionary key insertion order.
468
+
469
+ ### Run overlay (`WorkflowGraphRunView`)
470
+
471
+ - Paints per-node runtime status (`status`, `durationMs`, `attempt`, `errorCode`, `skippedReason`) onto the static DAG structure.
472
+ - Does **not** include node outputs — outputs remain on the execution timeline under content-capture policy to protect credentials and manage payload size.
473
+
407
474
  ## Security and performance notes
408
475
 
409
476
  - Definitions require a non-empty host-authored `revision` and fail closed on cycles, unknown edges, self-edges, invalid limits, and `maxNodes` overflow. Revision and every nested revision enter the deterministic definition hash; hosts must bump revision when function/tool behavior changes. Loop `maxIterations` is required and capped at 64.
@@ -440,6 +507,7 @@ Use workflows for known, durable, replayable graphs. Use optional supervisor del
440
507
  - [A2A interoperability](a2a.md): hosts may adapt existing exact-owner workflow status/list/cancel/checkpoint/event surfaces to `A2ATaskLifecycle`; A2A package adds no workflow worker, queue, or schema.
441
508
  - [Agent events](agent-events.md): core `AgentEvent` wrapped by `agent_event`
442
509
  - [Session stores and branching](session-stores-and-branching.md): session `leafId` reuse on resume
510
+ - [Observational memory compaction](compaction-observational-memory.md): hosts may pass `ctx.nodeId` to `withWorkScope`; the workflow runner stays scope-unaware.
443
511
  - [CLI/RPC](cli-rpc.md): host control seam; wire `createWorkflowCommands()` into `runRpcServer`
444
512
  - [Database persistence](database-persistence.md): generic `CheckpointStore` and `LeaseStore` capabilities
445
513
  - [SQLite persistence](sqlite-persistence.md): durable `persistence.checkpoints`
@@ -4,6 +4,8 @@
4
4
 
5
5
  `@arnilo/prism-memory` is an optional package for schema/template-backed working memory and embedding-based semantic recall. It owns narrow `Embedder` and `VectorStore` contracts reused by the `@arnilo/prism-memory/rag` subpath, plus an in-memory reference path and one PostgreSQL/pgvector production adapter.
6
6
 
7
+ The [memory fabric](memory-fabric.md) subpath is a typed-notes layer on top of these same stores: notes are ordinary rows here, so this page's consent, redaction, lineage, and scope rules are the whole rulebook. [Observational memory](compaction-observational-memory.md) is a separate, **episodic** layer — a source-backed ledger for the current session — and is not a store this package owns or replaces.
8
+
7
9
  ## When to use it
8
10
 
9
11
  Use it when a host needs durable per-tenant profile/state (working memory) or top-K semantic retrieval over prior thread entries. Do not use it as a replacement for observational memory compaction: observational memory compresses source-backed observations; semantic memory retrieves embeddings; working memory stores the current structured profile.
@@ -27,6 +29,7 @@ Ordinary Prism sessions do not require this package or any vector backend.
27
29
  | `redactor` / `secrets` | no | Redact text/metadata before persist/inject |
28
30
  | `requireConsent` | no | Strict mode: recall/injection excludes entries lacking explicit consent |
29
31
  | `importanceFrom` | no | Host-owned hook deriving importance from a redacted reflection payload (write time only; no default, no LLM) |
32
+ | `onInvalidate` | no | After lineage rows land, before body delete (observational drop / RAG delete wiring) |
30
33
 
31
34
  Semantic indexing (entries carry `MemoryConsent` source/visibility; unset defaults to `{ source: "user", scope: "thread", visible: true }`):
32
35
 
@@ -38,13 +41,13 @@ Semantic indexing (entries carry `MemoryConsent` source/visibility; unset defaul
38
41
  | `grantedAt` / `revokedAt` | Optional host/audit timestamps; a revocation excludes the record. |
39
42
 
40
43
  ```ts
41
- await memory.remember({ entries: [{ id, text, metadata?, consent?, sequence?, importance?, reflection? }] }, { wait?: boolean })
44
+ await memory.remember({ entries: [{ id, text, metadata?, consent?, sequence?, importance?, reflection?, lineage?: { sourceIds, reason? } }] }, { wait?: boolean })
42
45
  ```
43
46
 
44
47
  Semantic recall (honors consent/visibility at assembly time):
45
48
 
46
49
  ```ts
47
- await memory.recall(query, { topK?, messageRange?, requireConsent?, scoring?, signal? })
50
+ await memory.recall(query, { topK?, messageRange?, requireConsent?, scoring?, explain?, shareFromParentThreadId?, signal? })
48
51
  ```
49
52
 
50
53
  #### Composite recall scoring (opt-in)
@@ -99,8 +102,10 @@ Consent + lifecycle (real grant/correct/delete/retention on stored entries):
99
102
  ```ts
100
103
  await memory.setConsent(entryId, { visible?: boolean, source?, scope? }) // grant/revoke; no re-embed
101
104
  await memory.correct(entryId, text) // re-embeds, preserves consent
102
- await memory.forget({ ids? }) // real delete (whole thread if no ids)
103
- await memory.applyRetention({ maxAgeDays?, maxEntries?, batchSize? }) // bounded real-delete sweep
105
+ await memory.forget({ ids?, hold? }) // real delete; hold:true = legal_hold, no body delete
106
+ await memory.shareWith(childThreadId, sourceIds, { expiresAt? }) // parent→child allow-list; empty ids revoke
107
+ await memory.revokeShare(childThreadId)
108
+ await memory.applyRetention({ maxAgeDays?, maxEntries?, batchSize? }) // bounded real-delete sweep; skips legal_hold
104
109
 
105
110
  const page = await memory.exportMemory({
106
111
  identity: { tenantId, resourceId, threadId }, // exact host-verified owner
@@ -117,9 +122,10 @@ const rebuilt = await memory.rebuildIndex({ cursor?, batchSize?, maxMs?, signal?
117
122
  | --- | --- |
118
123
  | `updateWorking` / `getWorking` | Versioned `WorkingMemoryRecord` |
119
124
  | `remember` | `{ accepted, pending, done }` — default `wait: false` indexes asynchronously |
120
- | `recall` | `{ hits, adjacent }` tenant/thread scoped; invisible/revoked entries excluded |
121
- | `setConsent` / `correct` | Updated `MemoryVectorRecord` with stamped grant/revoke times |
122
- | `forget` | Removed count (real delete) |
125
+ | `recall` | `{ hits, adjacent, explanations? }` tenant/thread scoped; invisible/revoked/invalidated entries excluded |
126
+ | `setConsent` / `correct` | Updated `MemoryVectorRecord`; revoke/correct marks lineage before dependents can inject |
127
+ | `forget` | Removed count (real delete); `0` when `hold: true` |
128
+ | `shareWith` / `revokeShare` | Parent-child grant; sibling threads cannot use it |
123
129
  | `applyRetention` | `{ deleted, scanned }` bounded real-delete sweep |
124
130
  | `exportMemory` | `{ entries, bytes, nextCursor? }` redacted, explicitly consented, identity-bound page |
125
131
  | `rebuildIndex` | `{ rebuilt, nextCursor? }` re-embedded bounded page; caller owns resume scheduling |
@@ -215,8 +221,10 @@ const store = await createPostgresVectorStore({
215
221
  dimension: 32, // optional; pins the embedding column width (HNSW + drift guard)
216
222
  }); // PostgresVectorStoreOptions; dimension must match the embedder's dimensions
217
223
  // store implements rag's VectorStore/TransactionalVectorStore contract: upsert,
218
- // query, getBySource, transaction, lexicalQuery (fts, when available), and
219
- // getCurrentGeneration/setCurrentGeneration. close() ends adapter-owned pools.
224
+ // query, getBySource, transaction, lexicalQuery (fts, when available),
225
+ // getCurrentGeneration/setCurrentGeneration, and document ACL
226
+ // (`authorization: "acl"`, setSourceAccess, checkSourceAccess).
227
+ // close() ends adapter-owned pools.
220
228
  ```
221
229
 
222
230
  `createPostgresVectorStore()` is the production counterpart to `createMemoryVectorStore()` used by the `rag` subpath; `createPostgresMemoryStores()` reuses the same vector implementation internally.
@@ -226,9 +234,9 @@ const store = await createPostgresVectorStore({
226
234
  - Hosts wire the context provider into `AgentConfig.context` or `resolveContextProviders()`.
227
235
  - The working-memory processor is opt-in and host-invoked; middleware is not required.
228
236
  - `createHashEmbedder()` is for tests/demos only; production hosts supply a real `Embedder`.
229
- - Observational memory (`/compaction/observational-memory`) remains unchanged and composable.
230
- - Consent is enforced at the single `recall()` gate, so both direct recall and `createContextProvider()` injection honor it; `visible: false` (or a revoked grant) keeps an entry out of prompts, events, exports, and telemetry. `setConsent`/`correct` re-upsert in place (consent change does not re-embed); `forget`/`applyRetention` are real deletes, not tombstones. Retention uses indexed oldest-first pages plus a scoped count, deleting one default-500/hard-5000 batch without reading a corpus into memory. The PostgreSQL adapter persists consent in a `consent JSONB` column added by `buildMemoryDdl`.
231
- - The PostgreSQL vector path owns its DDL in Prism (`buildMemoryDdl`/`buildVectorSearchDdl` exported): the `<table>_rag_scope_generations` per-scope generation pointer table, `text_tsv` tsvector column + GIN index for the lexical RAG leg, and an HNSW index when the embedding dimension is pinned. DDL runs against the host's **knowledge database** — the host names `schema`/`table` (defaults `prism_memory`/`semantic_memory`), owns backup/retention of that database, and can run migrations manually with `skipMigrations: true`. Identifiers are validated/quoted; values stay parameterized.
237
+ - Observational memory (`/compaction/observational-memory`) is composable. Stamp `lineage.sourceIds` on semantic writes; pass the same ids as `invalidatedIds` into observational projection/recall, or append `om.observations.dropped` from `onInvalidate`. Multi-source facts stay injectable only if none of their sources are invalidated (regenerate from remaining evidence).
238
+ - Consent is enforced at the single `recall()` gate, so both direct recall and `createContextProvider()` injection honor it; `visible: false` (or a revoked grant) keeps an entry out of prompts, events, exports, and telemetry. `setConsent`/`correct` re-upsert in place (consent change does not re-embed) and write invalidation rows first. `forget`/`applyRetention` are real deletes after those rows land; `forget({ hold: true })` keeps the body for legal hold but still excludes injection/export. A revoked grant is not a legal hold: it blocks injection/export but `forget` still purges the body. Legacy records without `_lineage` are self-only: only their own id is excluded. Caps: walk depth 8, 256 edges, 32 source ids, 64-row delete batches — over-cap throws rather than leak. No claim to retract prior disclosures.
239
+ - The PostgreSQL vector path owns its DDL in Prism (`buildMemoryDdl`/`buildVectorSearchDdl` exported): the `<table>_rag_scope_generations` per-scope generation pointer table, `<table>_rag_source_acl` principal/group grants (query-time EXISTS, indexed by principal and group), `<table>_invalidation` tombstones (query-time NOT EXISTS; `corrected` keeps the source), `<table>_share_grant` parent-child allow-lists, GIN on `metadata._lineage.sourceIds`, `text_tsv` tsvector column + GIN index for the lexical RAG leg, and an HNSW index when the embedding dimension is pinned. DDL runs against the host's **knowledge database** — the host names `schema`/`table` (defaults `prism_memory`/`semantic_memory`), owns backup/retention of that database, and can run migrations manually with `skipMigrations: true`. Identifiers are validated/quoted; values stay parameterized.
232
240
  - `createPostgresVectorStore({ dimension })` pins the embedding column width before building indexes: pgvector can only build HNSW over `vector(N)` columns, and dimension mismatch fails closed instead of drifting.
233
241
  - `exportMemory()` requires an exact `{ tenantId, resourceId, threadId }` identity equal to its `createMemory()` scope. It excludes legacy consent-less, invisible, and revoked records even when normal recall allows legacy entries. It returns a stable sequence cursor page, redacted before response, with defaults/hard caps of 100/200 entries, 4/32 MiB, and 10/60 seconds. `rebuildIndex()` uses the same stable cursor shape to re-embed one 32/128-record page under a 10/60-second cap; save the cursor durably to resume. Both APIs require a store implementing bounded `listByThread()`; retention also requires `countByThread()`. PostgreSQL/pgvector and the in-memory reference adapter conform; SQLite persistence stores sessions, not semantic vectors.
234
242
  - Profile bundles do not include this package yet.
@@ -249,7 +257,9 @@ await runMemoryConformance(() => ({
249
257
 
250
258
  - Every write/query/delete requires `tenantId` + `resourceId`; semantic paths also require `threadId`.
251
259
  - Cross-tenant and cross-thread access is denied.
252
- - Revoked/invisible/non-consented memories never enter prompts, events, exports, or telemetry; `requireConsent: true` additionally drops consent-less (legacy) entries. Consent checks are O(hits) at recall, within the existing injected-token cap.
260
+ - RAG document ACL (`RagAccessConstraint`) is host-verified and applied inside `query`/`lexicalQuery` before ranking when `authorization` is passed. Missing grants and unresolved access versions deny. `filter` is not ACL.
261
+ - Revoked/invisible/non-consented/invalidated memories never enter prompts, events, exports, or telemetry; `requireConsent: true` additionally drops consent-less (legacy) entries. Query-time invalidation is an indexed NOT EXISTS (no full-corpus scan on recall). Parent-child shares are explicit, tenant-bound, expiring, and fail closed when missing. Cross-tenant lineage/grants reject.
262
+ - `revokedIdsAbsent(environment, deniedIds)` is the 072 invariant body (`metadata.invariant: true`, score 0 cannot be averaged away). Hosts wrap it with `defineScorer`.
253
263
  - Configure `secrets` / `redactor` so memory text and metadata cannot persist or inject raw canaries.
254
264
  - Injected context is inert text — it cannot grant tools or permissions.
255
265
  - Hard caps: top-K ≤ 32, messageRange ≤ 4, embed batch ≤ 128, injected tokens ≤ 8000, payload/working-memory byte limits enforced.
@@ -266,6 +276,7 @@ Supervisor child factories receive unique derived `resourceId` and `threadId` va
266
276
  - [Supervisor delegation](supervisors.md): package-derived child resource/thread scope.
267
277
  - [Retrieval-augmented generation](rag.md): bounded document chunks reuse this package's embed/vector contracts.
268
278
  - [Context and skills](context-and-skills.md): `ContextProvider` injection seam.
269
- - [Observational memory compaction package](compaction-observational-memory.md): source-backed observation/reflection memory distinction.
279
+ - [Observational memory compaction package](compaction-observational-memory.md): source-backed observation/reflection memory distinction; still episodic, owned by that subpath, not by these stores.
270
280
  - [PostgreSQL persistence](postgres-persistence.md): session/run persistence; memory vectors live in this optional package instead.
271
281
  - [Middleware hooks](middleware-hooks.md): reuse existing `context` hook if hosts transform injected blocks.
282
+ - [Memory fabric](memory-fabric.md): typed notes over these stores, with recall explanations and conversation search.