@arnilo/prism 0.6.0 → 0.8.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 (178) hide show
  1. package/CHANGELOG.md +79 -5
  2. package/README.md +12 -11
  3. package/dist/agent-approval.d.ts +4 -0
  4. package/dist/agent-approval.js +5 -1
  5. package/dist/agent-definitions.js +1 -0
  6. package/dist/agent-run-lifecycle.js +39 -4
  7. package/dist/agent-run-state.d.ts +18 -0
  8. package/dist/agent-run-state.js +39 -9
  9. package/dist/agent-session/helpers.js +6 -1
  10. package/dist/agent-session/session/assemble.js +159 -7
  11. package/dist/agent-session/session/persist.d.ts +16 -0
  12. package/dist/agent-session/session/persist.js +64 -4
  13. package/dist/agent-session/session/provider-round.d.ts +3 -3
  14. package/dist/agent-session/session/provider-round.js +12 -6
  15. package/dist/agent-session/session/tool-round.js +5 -1
  16. package/dist/agent-session/session/types.d.ts +22 -1
  17. package/dist/agent-session/session.d.ts +16 -0
  18. package/dist/agent-session/session.js +42 -3
  19. package/dist/artifacts.d.ts +39 -1
  20. package/dist/artifacts.js +73 -0
  21. package/dist/attention-compiler.d.ts +121 -0
  22. package/dist/attention-compiler.js +479 -0
  23. package/dist/checkpoints.js +7 -11
  24. package/dist/cli-init.js +20 -6
  25. package/dist/context-budget.d.ts +20 -1
  26. package/dist/context-budget.js +10 -1
  27. package/dist/contracts-core/agent.d.ts +7 -0
  28. package/dist/contracts-core/attention.d.ts +66 -0
  29. package/dist/contracts-core/attention.js +2 -0
  30. package/dist/contracts-core/compaction.d.ts +59 -0
  31. package/dist/contracts-core/compaction.js +77 -1
  32. package/dist/contracts-core/content.d.ts +5 -0
  33. package/dist/contracts-core/loop.d.ts +42 -0
  34. package/dist/contracts-core/provider.d.ts +4 -0
  35. package/dist/contracts-core/run-limits.d.ts +2 -0
  36. package/dist/contracts-core.d.ts +1 -0
  37. package/dist/contracts-core.js +1 -0
  38. package/dist/contracts-protocol.d.ts +44 -3
  39. package/dist/contracts-run-state.d.ts +32 -5
  40. package/dist/evidence-grounding.d.ts +29 -0
  41. package/dist/evidence-grounding.js +162 -0
  42. package/dist/host-composition.d.ts +91 -0
  43. package/dist/host-composition.js +279 -0
  44. package/dist/index.d.ts +13 -6
  45. package/dist/index.js +7 -4
  46. package/dist/input.d.ts +13 -1
  47. package/dist/input.js +40 -1
  48. package/dist/provider-events.d.ts +3 -1
  49. package/dist/provider-events.js +2 -2
  50. package/dist/providers/transport.d.ts +3 -1
  51. package/dist/providers/transport.js +36 -0
  52. package/dist/redaction.js +18 -2
  53. package/dist/run-bundle.d.ts +89 -0
  54. package/dist/run-bundle.js +149 -0
  55. package/dist/secure-agent.d.ts +2 -0
  56. package/dist/secure-agent.js +6 -1
  57. package/dist/testing/state-concurrency-conformance.js +5 -12
  58. package/dist/tool-result-fold.d.ts +12 -0
  59. package/dist/tool-result-fold.js +13 -6
  60. package/dist/tools.d.ts +10 -0
  61. package/dist/tools.js +41 -0
  62. package/docs/acp-agent.md +42 -11
  63. package/docs/acp.md +2 -1
  64. package/docs/ag-ui.md +10 -3
  65. package/docs/agent-definitions.md +9 -1
  66. package/docs/agent-events.md +4 -1
  67. package/docs/agent-loops.md +33 -0
  68. package/docs/agent-session-runtime.md +8 -7
  69. package/docs/attention-compiler.md +272 -0
  70. package/docs/cli-rpc.md +4 -2
  71. package/docs/coding-agent-tools.md +1 -1
  72. package/docs/coding-security.md +6 -3
  73. package/docs/coding-tools.md +0 -1
  74. package/docs/coding-workspaces.md +22 -0
  75. package/docs/compaction-and-retry.md +36 -4
  76. package/docs/compaction-observational-memory.md +63 -10
  77. package/docs/connected-apps.md +116 -0
  78. package/docs/context-and-skills.md +17 -2
  79. package/docs/conversations.md +1 -1
  80. package/docs/core.md +1 -1
  81. package/docs/dev-inspector.md +4 -0
  82. package/docs/device-adapters.md +1 -0
  83. package/docs/diagrams.md +6 -6
  84. package/docs/document-reader.md +18 -10
  85. package/docs/documents.md +40 -11
  86. package/docs/durable-runs.md +87 -0
  87. package/docs/enterprise-postgres-state.md +6 -2
  88. package/docs/evaluations.md +168 -4
  89. package/docs/execution-timeline.md +186 -0
  90. package/docs/guardrails.md +33 -0
  91. package/docs/history/0.7.0-primitive-review.md +254 -0
  92. package/docs/history/079-messaging-primitive-review.md +391 -0
  93. package/docs/history/080-messaging-followon-primitive-review.md +234 -0
  94. package/docs/history/081-connected-apps-primitive-review.md +74 -0
  95. package/docs/history/083-prism-work-primitive-review.md +84 -0
  96. package/docs/history/084-primitive-review.md +96 -0
  97. package/docs/history/085-honesty-and-cut-primitive-review.md +91 -0
  98. package/docs/history/README.md +5 -0
  99. package/docs/history/migration-0.0.md +2 -2
  100. package/docs/history/release-handoffs.md +75 -1
  101. package/docs/host-compositions.md +149 -0
  102. package/docs/host-security.md +2 -2
  103. package/docs/hosted-sandboxes.md +94 -0
  104. package/docs/index.md +82 -45
  105. package/docs/input-and-prompt-assembly.md +1 -0
  106. package/docs/knowledge-sync.md +84 -0
  107. package/docs/language-intelligence.md +1 -1
  108. package/docs/live-testing.md +8 -3
  109. package/docs/mcp-tools.md +3 -1
  110. package/docs/memory-fabric.md +416 -0
  111. package/docs/messaging-channel-operations.md +166 -0
  112. package/docs/messaging-channels.md +150 -0
  113. package/docs/migrate-to-0.5.md +1 -1
  114. package/docs/migrate-to-0.6.md +1 -0
  115. package/docs/migrate-to-0.7.md +345 -0
  116. package/docs/migrate-to-0.8.md +124 -0
  117. package/docs/migration.md +43 -1
  118. package/docs/model-registry.md +12 -2
  119. package/docs/model-routing.md +79 -4
  120. package/docs/multi-agent-patterns.md +20 -6
  121. package/docs/observability.md +52 -1
  122. package/docs/openapi-tools.md +1 -1
  123. package/docs/operations.md +14 -4
  124. package/docs/options-index.md +47 -3
  125. package/docs/peer-dependencies.md +12 -10
  126. package/docs/postgres-persistence.md +1 -1
  127. package/docs/process-sessions.md +3 -1
  128. package/docs/prompt-registry.md +1 -1
  129. package/docs/provider-caching.md +4 -2
  130. package/docs/provider-conformance.md +1 -1
  131. package/docs/provider-layer.md +2 -2
  132. package/docs/provider-packages.md +22 -22
  133. package/docs/providers/bedrock.md +71 -7
  134. package/docs/providers/neuralwatt.md +5 -1
  135. package/docs/providers/openai.md +1 -1
  136. package/docs/rag.md +24 -8
  137. package/docs/realtime-voice.md +87 -0
  138. package/docs/release-and-install.md +53 -45
  139. package/docs/run-bundle.md +92 -0
  140. package/docs/runs-and-usage.md +17 -2
  141. package/docs/server.md +7 -3
  142. package/docs/sheets.md +9 -9
  143. package/docs/signal-channel.md +112 -0
  144. package/docs/speech.md +7 -1
  145. package/docs/sqlite-persistence.md +1 -1
  146. package/docs/supervisors.md +33 -5
  147. package/docs/telegram-channel.md +157 -0
  148. package/docs/testing.md +2 -2
  149. package/docs/thinking-and-reasoning.md +3 -1
  150. package/docs/tools.md +6 -5
  151. package/docs/web-tools.md +2 -1
  152. package/docs/wiki.md +1 -1
  153. package/docs/work-artifacts-and-review.md +14 -4
  154. package/docs/work-connectors.md +12 -10
  155. package/docs/work-sandbox.md +115 -0
  156. package/docs/work-tools.md +50 -18
  157. package/docs/workflows.md +69 -1
  158. package/docs/working-and-semantic-memory.md +25 -14
  159. package/package.json +5 -3
  160. package/templates/README.md +2 -0
  161. package/templates/business-worker/README.md.tmpl +19 -0
  162. package/templates/business-worker/env.example.tmpl +1 -0
  163. package/templates/business-worker/gitignore.tmpl +11 -0
  164. package/templates/business-worker/manifest.json +12 -0
  165. package/templates/business-worker/package.json.tmpl +23 -0
  166. package/templates/business-worker/src/agent.ts.tmpl +92 -0
  167. package/templates/business-worker/src/index.ts.tmpl +13 -0
  168. package/templates/business-worker/src/tests/agent.test.ts.tmpl +77 -0
  169. package/templates/business-worker/tsconfig.json.tmpl +15 -0
  170. package/templates/personal-assistant/README.md.tmpl +18 -0
  171. package/templates/personal-assistant/env.example.tmpl +1 -0
  172. package/templates/personal-assistant/gitignore.tmpl +11 -0
  173. package/templates/personal-assistant/manifest.json +11 -0
  174. package/templates/personal-assistant/package.json.tmpl +23 -0
  175. package/templates/personal-assistant/src/agent.ts.tmpl +65 -0
  176. package/templates/personal-assistant/src/index.ts.tmpl +13 -0
  177. package/templates/personal-assistant/src/tests/agent.test.ts.tmpl +28 -0
  178. package/templates/personal-assistant/tsconfig.json.tmpl +15 -0
@@ -0,0 +1,272 @@
1
+ # Attention compiler
2
+
3
+ ## What it does
4
+
5
+ `createAttentionCompiler(options?, context?)` returns a validated, frozen configuration for the opt-in attention compiler: a per-turn gate that **measures** the assembled input against a host ratio of the model input cap and **rewrites nothing** until that ratio is reached.
6
+
7
+ The compiler is a gate, not a mixer. Once over the ratio it mutates a **history clone** monotonically — oldest `thinking` blocks first, then oldest fold-eligible tool results — so prompt-cache prefixes survive and the session store, observational-memory ledger, and input history array are never touched. Still over after every eligible row → `AttentionBudgetError` instead of silently dropping constitution.
8
+
9
+ The whole request (instruction groups, summaries, history, input, attachments, in-flight tool results, context blocks, skill catalog, tool declarations) is measured **once per turn**; each mutation then subtracts its own delta instead of re-measuring, so a turn costs one extra pass over the assembled input.
10
+
11
+ Current status: `createAttentionCompiler` and the two stages ship today, wired through `AgentConfig` / `AgentDefinition` and overlayed per run by `RunOptions`, and also available directly on `assembleProviderInput`. The field is opt-in everywhere: omitted, the request bytes are unchanged and no frontier is allocated.
12
+
13
+ Context blocks are measured, never repacked: the compiler sees whatever providers returned, tagged
14
+ however they tagged it. A memory fabric's provider is one such source — it contributes the same
15
+ `working-memory` / `semantic-memory` blocks `createMemory` resolves (see
16
+ [Memory fabric](memory-fabric.md)), and turning the compiler on or off changes their cost, never
17
+ their identity. The observational-memory ledger it protects stays the session's episodic record,
18
+ written (if at all) by that subpath's own workers; the compiler only ever mutates its history clone,
19
+ so neither OM nor a typed-notes layer is rewritten here. There is no layer id, handle, or per-source
20
+ quota in this seam.
21
+
22
+ ## When to use it
23
+
24
+ Use it when a host runs long sessions with prompt caching and wants a deterministic, dependency-free gate that keeps a request inside the model input cap **without** reordering or deleting history:
25
+
26
+ - thinking-heavy agent loops where old reasoning blocks are pure attention waste;
27
+ - long tool loops where old grep/dump results crowd out recent context;
28
+ - hosts that want an explicit `AttentionBudgetError` signal (compact now) rather than silent eviction.
29
+
30
+ Do not use it as a replacement for compaction or for `applyContextBudget` eviction: compaction is the boundary operation that writes a summary, the compiler only rewrites what this turn sends.
31
+
32
+ ## Inputs / request
33
+
34
+ ### Enabling it
35
+
36
+ | Surface | Value | Meaning |
37
+ | --- | --- | --- |
38
+ | `AgentConfig.attentionCompiler` | `true \| AttentionCompilerOptions` | `true` uses the defaults below. Omitted (or `false`) keeps today's request bytes. |
39
+ | `AgentDefinition.attentionCompiler` | same | Copied onto the resolved config by `resolveAgentDefinition`; it changes nothing else about the definition. |
40
+ | `RunOptions.attentionCompiler` | `false \| true \| AttentionCompilerOptions` | `false` disables the compiler for that run, `true` is a no-op, and an object is a **narrowing overlay** on the agent setting. |
41
+
42
+ ```ts
43
+ const agent = createAgent({ model, provider, attentionCompiler: true });
44
+ await agent.createSession({ id: "s" }).run("long task");
45
+ await session.run("cheap run", { attentionCompiler: { triggerRatio: 0.95, compactRatio: 0.99 } });
46
+ ```
47
+
48
+ The agent setting is resolved with the run's model at run start, before any provider turn, so a malformed setting or a widening overlay fails the run immediately instead of on the turn that crosses the ratio:
49
+
50
+ - **Allowed in the overlay:** `triggerRatio` / `compactRatio` at or above the agent setting, `keepLast` / `thinkingKeepTurns` at or below it, and extra `excludeTools` (unioned with the agent list, never removed).
51
+ - **Rejected:** a lower gate ratio, more protected rows, and `maxInputTokens` / `reserveTokens` — cap inputs are agent-config only, because moving the cap moves the gate itself. Raising `triggerRatio` at or above the agent's `compactRatio` needs `compactRatio` raised in the same overlay.
52
+ - **Enabling from a run is rejected:** a run may disable or relax the compiler, never switch it on where the agent config left it off.
53
+
54
+ The **sticky frontier is session-owned and created lazily** the first time an enabled run assembles a request: one `{ thinking, toolCallIds }` set pair per session, shared across runs, provider rounds, and branches, so a stub or strip made once stays applied even on a later under-ratio turn. It lives in memory only — a resumed process simply re-decides from the ratio it sees.
55
+
56
+ `AttentionCompilerOptions` (all optional):
57
+
58
+ | Field | Type | Default | Meaning |
59
+ | --- | --- | --- | --- |
60
+ | `triggerRatio` | `number` | `0.75` | Fraction of `inputCap` that enables mutation; must be in `(0, 1)` (exclusive). |
61
+ | `compactRatio` | `number` | `0.9` | Where compaction should fire relative to the compiler; must exceed `triggerRatio`. |
62
+ | `thinkingKeepTurns` | `number` | `1` | Newest thinking-bearing assistant turns kept intact. |
63
+ | `keepLast` | `number` | `3` | Newest tool results kept full. |
64
+ | `excludeTools` | `readonly string[]` | `[]` | Tool names whose results are never stubbed, whatever the ratio. |
65
+ | `maxInputTokens` | `number` | — | Host cap; when set it wins over `model.limits.contextWindow`. |
66
+ | `reserveTokens` | `number` | `1024` | Output + next-turn headroom subtracted from the window. |
67
+
68
+ `AttentionCompilerContext`:
69
+
70
+ | Field | Type | Meaning |
71
+ | --- | --- | --- |
72
+ | `model` | `{ limits?: ModelLimits }` | Source of `contextWindow` / `maxOutputTokens` when `maxInputTokens` is absent. |
73
+ | `compactionTrigger` | `CompactionTrigger` | Optional: validated here so an unknown trigger `type` fails at create time, not on the first turn. An `input_ratio` trigger must exceed `triggerRatio`. |
74
+
75
+ **Public surface.** `createAttentionCompiler(options?: AttentionCompilerOptions, context?)` is the factory; `AttentionCompilerOptions` carries the gate ratios, sticky-stage tuning (`thinkingKeepTurns`, `keepLast`), `excludeTools`, and `reserveTokens`. `resolveInputCap(options?: AttentionInputCapOptions, model?)` is the cap resolver, `compileAttention(options: AttentionCompileOptions)` is the per-turn call `assembleProviderInput` makes (`AttentionCompileOptions` also carries `fold`, `frontier`, `redactor`, `signal`, and the `turn`/`sessionId`/`runId` telemetry ids), and `createAttentionTruncationTrigger(options?: AttentionTruncationTriggerOptions)` builds the host-programmable compaction trigger.
76
+
77
+ Input cap resolution: `maxInputTokens` when set, otherwise `contextWindow - (maxOutputTokens ?? 0) - reserveTokens`. Both `resolveInputCap(options?, model?)` and the compiler fail closed with a `TypeError` when neither source is present, when a declared limit is malformed, or when the computed cap is not positive.
78
+
79
+ Turn options, passed to `assembleProviderInput`:
80
+
81
+ | Field | Type | Meaning |
82
+ | --- | --- | --- |
83
+ | `attentionCompiler` | `AttentionCompilerOptions \| AttentionCompiler` | Raw options are validated for that call; a resolved handle reuses one validation. The session passes the run's resolved handle so a tuning typo fails before the first provider turn. |
84
+ | `attentionSticky` | `AttentionStickyFrontier` | `{ thinking, toolCallIds }` sets from `createAttentionStickyFrontier()`. The session supplies its own; a direct `assembleProviderInput` caller owns it, and omitting it makes each call mutate for its turn only. |
85
+ | `onAttentionReport` | `(report: AttentionReport) => void` | Called once per **mutated** turn, before `input_assembly` middleware; silent under the ratio. The session uses it to emit `attention_compiled`. |
86
+
87
+ `attentionCompiler` and `contextBudget` are **mutually exclusive** — a compiler-on turn that is still over throws `AttentionBudgetError` rather than evicting through the budget, so passing both fails closed with a `TypeError`.
88
+
89
+ ## Outputs / response / events
90
+
91
+ `createAttentionCompiler` returns an `AttentionCompiler`: `inputCap`, `reserveTokens`, `triggerRatio`, `compactRatio`, `thinkingKeepTurns`, `keepLast`, and a frozen, de-duplicated `excludeTools`. It performs no I/O and calls no provider.
92
+
93
+ `compileAttention` always returns an `AttentionReport` beside `mutated`; under the ratio `mutated` is `false`, the same groups object comes back, and nothing is emitted:
94
+
95
+ | Field | Type | Meaning |
96
+ | --- | --- | --- |
97
+ | `used` | `number` | Estimated tokens measured before this turn's mutation. |
98
+ | `usedAfter` | `number` | Estimated tokens of the same request after the mutation, so `used` → `usedAfter` is the per-turn cost curve. |
99
+ | `inputCap` | `number` | Resolved cap the ratio was compared against. |
100
+ | `triggerRatio` | `number` | Configured ratio. |
101
+ | `droppedThinkingTurns` | `number` | Thinking turns absent from this request — rows re-applied from the sticky frontier count again. |
102
+ | `stubbedToolResults` | `number` | Tool results stubbed in this request — re-applied rows count again. |
103
+ | `stubbedBytes` | `number` | Payload bytes those stubs took out of the request (message bytes minus the stub header). |
104
+ | `truncated` | `boolean` | `true` when the gate stopped with eligible rows left, so the sticky frontier is partial. |
105
+ | `runId` / `sessionId` | `string?` | Owning run/session when known. |
106
+
107
+ Telemetry: a session emits **one `attention_compiled` per mutated turn** and nothing on an under-ratio turn or when the compiler is off. The payload is counts only — `sessionId`, `runId`, `used`, `usedAfter`, `inputCap`, `triggerRatio`, `droppedThinkingTurns`, `stubbedToolResults`, `stubbedBytes`, `truncated` — never message text or stub bodies, so it is safe in a redacted ledger and folds into an `attention` step of the [execution timeline](execution-timeline.md). A run that fails closed raises `AttentionBudgetError` instead, which surfaces as the run's `error` event. Direct `assembleProviderInput` callers get the same data through `onAttentionReport`.
108
+
109
+ Stage order once the gate opens (C4):
110
+
111
+ 1. **Thinking** — strip every `thinking` block from assistant turns except the newest `thinkingKeepTurns`, oldest turn first.
112
+ 2. **Tool results** — stub the oldest tool-result rows beyond the newest `keepLast`, oldest first, across history and the in-flight results of the current turn.
113
+
114
+ Each stage stops as soon as the estimate is back under `triggerRatio`; rows left eligible make `AttentionReport.truncated` `true`. A stub keeps the call identity and drops the payload:
115
+
116
+ ```text
117
+ Tool result read_file [call_1]: omitted 41_982 bytes (sha256 3f9a1c2b4d5e6f70a1b2c3d4e5f6a7b8)
118
+ ```
119
+
120
+ Never stubbed: rows named in `excludeTools`, tool **errors**, results stamped as a decision/approval payload (`approval`, `approvalId`, `prismApproval`, `decision`, `decisions`, `pendingDecisions`, `elicitation` metadata), rows the host fold's own age/byte gates exclude, and any row whose stub would cost more than the payload it replaces. When `toolResultFold.summarize` is configured, that function produces the stub body for the rows the compiler picked (capped by its `maxSummaryBytes`); otherwise the deterministic digest above is used.
121
+
122
+ `compileAttention({ compiler, groups, context?, skills?, tools?, fold?, frontier?, redactor?, signal?, turn?, sessionId?, runId? })` is what `assembleProviderInput` calls; it returns `{ groups, mutated, report }`. Under the ratio it returns the **same groups object** it was given; when it mutates it returns new `history` / `toolResults` arrays and never writes into the caller's arrays.
123
+
124
+ Errors:
125
+
126
+ | Error | Code | Raised when |
127
+ | --- | --- | --- |
128
+ | `AttentionBudgetError` | `attention_budget_exceeded` | Still over `triggerRatio` after every eligible stage — host should compact, not delete. |
129
+ | `TypeError` | — | Invalid option, unknown compaction trigger `type`, or an unresolvable input cap. Use `isAttentionBudgetError` to narrow. |
130
+
131
+ ## Request/response example
132
+
133
+ ```json
134
+ {
135
+ "triggerRatio": 0.75,
136
+ "thinkingKeepTurns": 1,
137
+ "keepLast": 3,
138
+ "excludeTools": ["submit_payment"],
139
+ "reserveTokens": 1024,
140
+ "compaction": {
141
+ "trigger": {
142
+ "type": "custom",
143
+ "shouldCompact": "host function — sees sessionId, entryCount, estimatedInputTokens, inputCapTokens"
144
+ }
145
+ }
146
+ }
147
+ ```
148
+
149
+ ## Implementation example
150
+
151
+ ```ts
152
+ import { createAttentionCompiler, resolveInputCap } from "@arnilo/prism";
153
+
154
+ const compiler = createAttentionCompiler(
155
+ {
156
+ triggerRatio: 0.75,
157
+ thinkingKeepTurns: 1,
158
+ keepLast: 3,
159
+ excludeTools: ["submit_payment"],
160
+ reserveTokens: 1024,
161
+ },
162
+ { model: { limits: { contextWindow: 200_000, maxOutputTokens: 8_192 } } },
163
+ );
164
+
165
+ compiler.inputCap; // 200000 - 8192 - 1024
166
+
167
+ // Host-programmable compact-when, validated at create:
168
+ createAttentionCompiler(
169
+ { triggerRatio: 0.75 },
170
+ { model: { limits: { contextWindow: 200_000 } }, compactionTrigger: { type: "input_ratio", ratio: 0.9 } },
171
+ );
172
+
173
+ // Shared cap helper for hosts that only need the number:
174
+ resolveInputCap({ reserveTokens: 1024 }, { limits: { contextWindow: 200_000, maxOutputTokens: 8_192 } });
175
+ ```
176
+
177
+ Run it through assembly — this is the raw seam; sessions do it for you. Pass the same frontier on every turn so an over-ratio turn stays shrunk afterwards (the session keeps one per session).
178
+
179
+ ```ts
180
+ import { assembleProviderInput, createAttentionStickyFrontier } from "@arnilo/prism";
181
+
182
+ const attentionSticky = createAttentionStickyFrontier();
183
+
184
+ const request = await assembleProviderInput({
185
+ model,
186
+ input: "continue",
187
+ history, // session history snapshot; never mutated
188
+ tools,
189
+ turn,
190
+ attentionSticky,
191
+ attentionCompiler: {
192
+ triggerRatio: 0.75,
193
+ thinkingKeepTurns: 1,
194
+ keepLast: 3,
195
+ excludeTools: ["submit_payment"],
196
+ },
197
+ });
198
+
199
+ // Under the ratio: identical to the same call without `attentionCompiler`.
200
+ // Over the ratio: old `thinking` blocks are gone, old tool bodies are stubs,
201
+ // and the session store still holds every original payload.
202
+ ```
203
+
204
+ ## Compact-when: where the compiler hands off
205
+
206
+ The compiler never compacts. It mutates a clone of the request for one turn, and `session.compact()` keeps its task-boundary rule (it throws while a run is in flight). Compaction stays where it was: `CompactionOptions.trigger`, `session.compact()`, or the observational-memory attach loop.
207
+
208
+ What the compiler does contribute is the *number*. `session.autoCompact()` decides an `input_ratio` trigger with `resolveInputCap` — the same helper that resolves the compiler's `inputCap` — and `AttentionCompilerOptions.compactRatio` / `createAttentionCompiler(options, { compactionTrigger })` exist so a host can express "shrink at 0.75, compact at 0.9" with one validated pair: a compaction `input_ratio` at or below the compiler's `triggerRatio` is rejected, because the compiler has already tried the cheap stages at that point.
209
+
210
+ ```ts
211
+ const agent = createAgent({
212
+ model,
213
+ provider,
214
+ attentionCompiler: { triggerRatio: 0.75, keepLast: 3 },
215
+ compaction: { trigger: { type: "input_ratio", ratio: 0.9 } },
216
+ });
217
+ ```
218
+
219
+ Ordering per run: auto-compaction is evaluated once, after the run input is appended and before provider input assembly; the compiler then runs inside assembly on whatever survived. After a compaction the next request is the frozen prefix plus the fresh summary plus the recent tail, and the compiler treats that summary and prefix as untouchable — it may still strip thinking or stub tool results in the **kept tail** when the estimate is over the ratio again. When the observational-memory strategy wrote that summary while work scopes were open, the summary is already the **projected** working set (leaf scope + ancestors), not the full ledger.
220
+
221
+ ### Acting on `truncated`
222
+
223
+ `truncated: true` says the gate ran out of *eligible* rows: stubs cannot hold the request under the ratio, so the honest answer is a new prefix at the next task boundary rather than a silent eviction. `createAttentionTruncationTrigger` turns that signal into a drop-in trigger — feed it every `attention_compiled` event and hand `trigger` to the same `CompactionOptions.trigger` seam:
224
+
225
+ ```ts
226
+ const truncation = createAttentionTruncationTrigger({ threshold: 2 }); // consecutive truncated turns
227
+ const agent = createAgent({
228
+ model,
229
+ provider,
230
+ attentionCompiler: true,
231
+ compaction: { trigger: truncation.trigger },
232
+ });
233
+ session.subscribe((event) => {
234
+ if (event.type === "attention_compiled") truncation.observe(event);
235
+ });
236
+ ```
237
+
238
+ It fires **once per armed streak** at the next compaction decision (auto-compact before the next `run()`, or a host attach loop's post-run gate), and a mutated turn that was *not* truncated clears the streak because the pressure was relieved. `streak()` reads the current count and `reset()` clears an armed streak (for instance after a host-initiated `session.compact()`). The threshold is validated at create, so a typo fails at config time.
239
+
240
+ See [Compaction and retry policies](compaction-and-retry.md) for the trigger union and its fail-closed rules.
241
+
242
+ ## Extension and configuration notes
243
+
244
+ - `excludeTools` is fail closed: entries are validated as non-empty bounded strings, de-duplicated, and frozen; a named tool is never stubbed even when the request stays over the ratio.
245
+ - The compiler never orchestrates other levers: `toolResultFold.summarize` still wins for fold-eligible rows when a host supplies it, `applyContextBudget` keeps working unchanged for compiler-off agents, and compaction stays a task-boundary operation (`session.compact()` still throws while a run is in flight).
246
+ - Sticky means sticky: a stripped thinking turn is never restored and a stubbed call id is never un-stubbed, even on a later under-ratio turn — restoring either would rewrite the cached prefix. Pass no `attentionSticky` for one-shot assemblies.
247
+ - The frontier is bounded (256 thinking keys, 256 tool-call ids, newest kept) and lives on the session, so it survives turns and runs. A durable run with `persistSessionState: true` also writes it into the checkpoint (`sessionState.attentionSticky`) and restores it on resume, so a resumed run keeps its stubs instead of re-deciding its first turn from the ratio; a malformed or hand-edited frontier is dropped entry by entry, never fatal to a resume.
248
+ - A compiler-on turn assembles from the default message groups (instructions, summaries, history, input, attachments, tool results) exactly like a `contextBudget` turn, so a custom `inputBuilder` is not consulted while the compiler is on.
249
+ - Compaction timing is programmable per agent through `CompactionOptions.trigger` (`threshold_entries` | `input_ratio` | `custom`); omitting it keeps today's `thresholdEntries` gate. `assertCompactionTrigger(trigger)` validates a trigger independently of the compiler.
250
+ - The gate is opt-in per agent/run; omit the option for current assembly bytes.
251
+
252
+ ## Security and performance notes
253
+
254
+ - Validation is synchronous with no provider I/O, and the returned handle plus `excludeTools` are frozen.
255
+ - Reports and trigger contexts carry ids, counts, and token estimates only — never message text, tool payloads, or secrets.
256
+ - Stub text is `name` + `toolCallId` + byte count + a SHA-256 digest of the already-redacted payload: deterministic, model-free, and impossible to invert back into the payload. The digest is taken over redactor output when a redactor is configured, and the assembled request is redacted again at the provider edge.
257
+ - Projection-only, and **a stub is not a delete**: the session store, observational-memory ledger, and semantic stores are never rewritten, so recall, branching, and post-hoc audit still see every original payload. Observational-memory context blocks are never dropped, stubbed, or reordered.
258
+ - Compaction stays a task boundary: the compiler never writes the store and never triggers compaction mid-run (`session.compact()` still refuses while a run is active), and it never rewrites observational memory.
259
+ - Telemetry stays payload-free: the `attention_compiled` event carries `used`, `usedAfter`, `inputCap`, `triggerRatio`, `droppedThinkingTurns`, `stubbedToolResults`, `stubbedBytes`, and `truncated` only.
260
+ - `applyContextBudget` is not the compiler's last resort — overflowing after all eligible stages throws `AttentionBudgetError` so the frozen prefix (system instructions, `AGENTS.md`, skill catalog, tool declarations) cannot be silently evicted.
261
+ - Measured on the hermetic fixture in [`docs/_evidence/phase74-attention-measurements.md`](_evidence/phase74-attention-measurements.md): 63.7 % fewer input tokens, one cache bust on the turn the gate trips (compiler-off is append-only), and a volatile provider block that re-sends everything behind it every turn — which is why pinning a block stays a host recipe (`resolve` once per session) rather than compiler behavior. Regenerate with `node scripts/benchmark.mjs --scenario attention-compiler`.
262
+
263
+ ## Related APIs
264
+
265
+ - [`assembleProviderInput`](input-and-prompt-assembly.md): the compose path the compiler pre-passes when enabled.
266
+ - [`toolResultFold`](input-and-prompt-assembly.md): host summarizer that wins over the deterministic stub for eligible rows.
267
+ - [`CompactionOptions`](compaction-and-retry.md): `trigger` is the host compact-when seam; `thresholdEntries` remains the default gate.
268
+ - [`observational-memory`](compaction-observational-memory.md): host `shouldCompact` / trigger overrides `compactAfterTokens` for post-run compaction.
269
+ - [`provider caching`](provider-caching.md): why mutations are monotonic and in-place.
270
+ - [`AttentionReport` measurements](_evidence/phase74-attention-measurements.md): the hermetic fixture behind the savings, cache, resume, and truncation numbers.
271
+ - [Memory fabric](memory-fabric.md): a context source whose blocks are measured like any other (`working-memory` / `semantic-memory` tags, no layer id).
272
+ - [`thinking and reasoning`](thinking-and-reasoning.md): the `thinking` blocks the first stage strips.
package/docs/cli-rpc.md CHANGED
@@ -40,7 +40,7 @@ prism init <dir> [--template <name>] [--list-templates] [--provider <name>] [--w
40
40
  | Flag / arg | Purpose |
41
41
  | --- | --- |
42
42
  | `<dir>` | Destination directory (created if missing). Required unless `--list-templates` is specified. |
43
- | `--template <name>` | Template starter name (`init` [default], `deep-research`). |
43
+ | `--template <name>` | Template starter name (`init` [default], `deep-research`, `personal-assistant`, `business-worker`). |
44
44
  | `--list-templates` | List available starter templates from the templates gallery. |
45
45
  | `--provider <name>` | `mock` (default), `openai`, `openrouter`, `kimi`, `zai`, `opencode-go`, or `neuralwatt`. |
46
46
  | `--with-workflows` | Add `@arnilo/prism-core/runtime/workflows` and `src/workflows-example.ts`. |
@@ -48,7 +48,7 @@ prism init <dir> [--template <name>] [--list-templates] [--provider <name>] [--w
48
48
  | `--force` | Overwrite generated files when the destination already exists. |
49
49
  | `-h`, `--help` | Print init usage. |
50
50
 
51
- Default generation (`init` template) installs only `@arnilo/prism` (mock provider). Selecting a real provider adds exactly one dependency: the `@arnilo/prism-providers` family package (the selected adapter imports from `@arnilo/prism-providers/<id>`). Specifying `--template deep-research` scaffolds a flagship deep research agent pipeline (`@arnilo/prism`, `@arnilo/prism-web-tools`, `@arnilo/prism-memory/rag`, `@arnilo/prism-core/runtime/workflows`) with planning, attributable citations, bounded refine loops, and HITL decision clarification. Rerunning without `--force` refuses non-empty destinations and existing generated files. `.env.example` contains placeholders only; `.gitignore` excludes `.env` and local stores.
51
+ Default generation (`init` template) installs only `@arnilo/prism` (mock provider). Selecting a real provider adds exactly one dependency: the `@arnilo/prism-providers` family package (the selected adapter imports from `@arnilo/prism-providers/<id>`). Specifying `--template deep-research` scaffolds a flagship deep research agent pipeline (`@arnilo/prism`, `@arnilo/prism-web-tools`, `@arnilo/prism-memory/rag`, `@arnilo/prism-core/runtime/workflows`) with planning, attributable citations, bounded refine loops, and HITL decision clarification. Specifying `--template personal-assistant` scaffolds a single-user personal assistant host composition with local status tools and secret redaction. Specifying `--template business-worker` scaffolds a multi-tenant enterprise worker host composition with verified tenant identity, durable storage contracts, and strict tenant boundaries. Rerunning without `--force` refuses non-empty destinations and existing generated files. `.env.example` contains placeholders only; `.gitignore` excludes `.env` and local stores.
52
52
 
53
53
 
54
54
  ### `prism providers add` (0.1.7)
@@ -224,6 +224,8 @@ prism init my-agent
224
224
  prism init my-agent --provider openai
225
225
  prism init my-agent --provider openrouter --with-workflows --with-evals
226
226
  prism init my-research --template deep-research
227
+ prism init my-assistant --template personal-assistant
228
+ prism init my-worker --template business-worker
227
229
  prism init --list-templates
228
230
  cd my-agent && npm install && npm test
229
231
 
@@ -614,5 +614,5 @@ Every configurable value is a positive safe integer (context may be zero); Prism
614
614
  - [Public contracts](public-contracts.md): `ToolDefinition`, `ToolResult`, `ToolExecutionContext`, `ContentBlock`, and `JsonObject` shapes.
615
615
  - [Host security guide](host-security.md): fail-closed checklist for permission policies, tool validation, and trust boundaries that must gate these tools.
616
616
  - [Tool conformance](tool-conformance.md): assertions for the tool-dispatch blocked-reason matrix these tools participate in.
617
- - [ACP coding-host interop](acp.md): host editors drive these tools through stable ACP v1 — client fs/terminal adapters, `CodingLifecycleEvent` emission (`file_changed` etc. via the `onEvent` options; `plan_changed` also fires from `writeCodingPlanFile`'s `onEvent`, F5), and permission/elicitation through the shared four-outcome decision model.
617
+ - [ACP coding-host interop](acp.md): host editors drive these tools through stable ACP v1 — client fs/terminal adapters, `CodingLifecycleEvent` emission (`file_changed` etc. via the `onEvent` options; `plan_changed` also fires from `writeCodingPlanFile`'s `onEvent`, F5), redacted supervisor `subagent_started` / `subagent_stopped` via `observeSupervisorLifecycle`, and permission/elicitation through the shared four-outcome decision model.
618
618
  - [LLM compaction package](compaction-llm.md): optional `createCodingCompactionStrategy()` retains bounded paths, patch intent, checks, plan/todo state, blockers, and next verification—not complete diffs or raw command output.
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## What it does
4
4
 
5
- `@arnilo/prism-coding-tools/security` is an optional package that supplies structured execution policy for `@arnilo/prism-coding-tools/agent` tools and one disposable Docker/OCI sandbox reference. It complements name-based `PermissionPolicy` at dispatch time with path/command context checked **inside** each tool before side effects, and optionally contains untrusted coding work in a host-invoked container.
5
+ `@arnilo/prism-coding-tools/security` is an optional package that supplies structured execution policy for `@arnilo/prism-coding-tools/agent` tools, one disposable Docker/OCI sandbox reference, and an optional E2B hosted sandbox adapter. It complements name-based `PermissionPolicy` at dispatch time with path/command context checked **inside** each tool before side effects, and optionally contains untrusted coding work in a host-invoked container.
6
6
 
7
7
  | Export | Purpose |
8
8
  | --- | --- |
@@ -13,6 +13,7 @@
13
13
  | `createSandboxCodingTools` / `createSandboxReadOnlyTools` | Thin wrappers that return `tools` only (compat); still require `workspaceMode`. |
14
14
  | `createSandboxFilesystemOperations` / `createSandboxRepositoryOperations` | Optional execFile-backed FS/list/search backends for a disposable sandbox tree. |
15
15
  | `createDockerSandbox(options)` | Creates one disposable non-root Docker container with read-only root/source, bounded tmpfs workspace, typed `execFile`, import/export, and stop/kill/cleanup. |
16
+ | `createE2BSandbox(options)` / `connectE2BSandbox(options)` | Hosted E2B adapter on the same `DisposableSandbox` contract: `execFile`, `startProcess`/`attachProcess`, `pause`/`resume`, explicit `kill`. Optional peer `e2b@2.49.1` or a host `client`. Does not claim Docker network/egress parity. |
16
17
  | `createNativeSandbox(options)` | Linux-only network-free backend: every command runs in a fresh network namespace (`unshare`), POSIX `ulimit` hard caps, cwd-in-root containment; fails closed at creation on platforms/privileges that cannot deny egress. Reports truthful capability metadata (`networkIsolated`/`egressRestricted` true, `filesystemIsolated`/`processIsolated`/`privilegeIsolated` false). Docker remains the stronger, documented reference backend. |
17
18
  | `SandboxProcessHandle` | Optional long-running process handle (`write`/`signal`/`kill`/`release`/`wait`) returned by `DisposableSandbox.startProcess?`. |
18
19
  | `createEgressPolicy(options)` | Deny-all allow-list policy: exact host/port/protocol rules plus frozen `npm-registry` / `github` presets; SHA-256 fingerprint. |
@@ -32,7 +33,7 @@ import type { ExecutionAction, ExecutionPolicy, ExecutionDecision } from "@arnil
32
33
 
33
34
  Use this package when coding tools need path scoping, human approval, command rules, or a pluggable sandbox backend. Wire the returned policy through `createCodingTools(cwd, { executionPolicy })` or per-tool `executionPolicy` options.
34
35
 
35
- Use `createDockerSandbox()` when the host wants a production-reference containment boundary. Prism does **not** claim OS-level isolation unless the host constructs this adapter (or supplies an equivalent custom `DisposableSandbox`). Default policy denies shell/write/edit/delete/move without an `approve` callback and rejects paths outside configured roots. Coding shell definitions are marked `exclusive: true`, matching the approval policy's shell decision, so a single-shot turn containing shell work runs sequentially even when `toolConcurrency > 1`. Non-shell turns retain configured parallelism.
36
+ Use `createDockerSandbox()` when the host wants a production-reference containment boundary. Use `createE2BSandbox()` when the host wants a vendor-hosted VM; see [Hosted sandboxes](hosted-sandboxes.md). Prism does **not** claim OS-level isolation unless the host constructs this adapter (or supplies an equivalent custom `DisposableSandbox`). Default policy denies shell/write/edit/delete/move without an `approve` callback and rejects paths outside configured roots. Coding shell definitions are marked `exclusive: true`, matching the approval policy's shell decision, so a single-shot turn containing shell work runs sequentially even when `toolConcurrency > 1`. Non-shell turns retain configured parallelism.
36
37
 
37
38
  Use `createNativeSandbox()` when the host has no container runtime and needs network-free containment (0.1.6, plan 018 closeout `native-sandbox`). Linux only; creation fails closed with a documented error on other platforms or when the OS cannot create a network namespace (no root/CAP_SYS_ADMIN and no unprivileged user namespaces). Every command runs in a fresh netns — **loopback is down**, so even localhost connections fail; hosts that need loopback keep the Docker backend. Containment is egress denial + `ulimit` hard caps (address space from `memoryBytes`, CPU-time wall backstop, fd count from `maxFds`) + cwd-inside-root (`assertPathInsideRoots`, symlink-aware). The native backend does **not** isolate the filesystem: commands run as the invoking OS user with full host-tree access, so pair it with `createSandboxCodingComposition`/`createSandboxFilesystemOperations` (per-op `assertSandboxPath`) and the approval policy, exactly as with any custom `DisposableSandbox`. Its `capabilities` report `networkIsolated: true` and `egressRestricted: true` but `filesystemIsolated`/`processIsolated`/`privilegeIsolated: false` — the native backend is never a containment boundary for untrusted code (see [Sandbox capabilities](#sandbox-capabilities-020-plan-020-task-4)). Host env is never inherited; `env` is an exact allow-list (PATH only by default). No `startProcess` (ProcessSessions fails closed with `ERR_PRISM_PROCESS_UNSUPPORTED`), no CPU-rate/pids/fs-size caps (cgroup-only). Secrets passed as `secrets` are redacted from surfaced errors. See `docs/_evidence/phase18-primitive-review.md` for the full threat model.
38
39
 
@@ -126,7 +127,7 @@ Rules:
126
127
  - **`containmentClaim` is deprecated (0.2.0).** Retained for 0.1.7 compatibility as the conservative projection `workspaceCoherent && filesystemIsolated && networkIsolated && processIsolated` (privilege isolation excluded). It can only be `true` when every required capability is true — never authorize a security-sensitive action from this boolean alone; use `composition.capabilities`.
127
128
  - **Capability construction is O(1)** — one small frozen object per sandbox/composition; no command, filesystem, Docker, DNS, or network operation.
128
129
 
129
- `createDockerSandbox()` returns a `DisposableSandbox`: typed `execFile(file, args)`, shell-compatible `exec`, `status`, cooperative `stop`, forced `kill`, and idempotent `close`. Import may surface `importIdentity`; successful export updates `lastExportIdentity`. `close({ export })` can stream a bounded workspace tar plus SHA-256/entry/byte metadata through a host callback; checkpoints should retain only host artifact references/hashes, never whole workspaces. Optional `startProcess?(SandboxExecFileRequest)` returns a `SandboxProcessHandle` for long-running work consumed by coding-agent `createProcessSessions({ sandbox })`; absence means one-shot-only — ProcessSessions fails closed with `ERR_PRISM_PROCESS_UNSUPPORTED` (no native fallback). The Docker reference adapter does not implement `startProcess` yet; capability is detected, never assumed. See [Process sessions](process-sessions.md).
130
+ `createDockerSandbox()` returns a `DisposableSandbox`: typed `execFile(file, args)`, shell-compatible `exec`, `status`, cooperative `stop`, forced `kill`, and idempotent `close`. Import may surface `importIdentity`; successful export updates `lastExportIdentity`. `close({ export })` can stream a bounded workspace tar plus SHA-256/entry/byte metadata through a host callback; checkpoints should retain only host artifact references/hashes, never whole workspaces. `startProcess(SandboxExecFileRequest)` returns a `SandboxProcessHandle` for long-running work consumed by coding-agent `createProcessSessions({ sandbox })`; an opaque `ref` (`prism-docker-proc:<base64url>`) enables attested reconnect via `attachProcess(ref)` — container ID, workspace, and command fingerprint are validated fail-closed (no host fallback, no host PID probing). `sandbox.stop()` and `sandbox.kill()` terminate all active child processes before stopping the container. `createDockerProcessRecoveryBackend(sandbox, options?)` provides a ready-made `ProcessRecoveryBackend` with optional `expectedContainerId`, `expectedWorkspace`, and `expectedLabels` assertions; when a sandbox with `attachProcess` is passed to `createProcessSessions`, the recovery backend is wired automatically. See [Process sessions](process-sessions.md).
130
131
 
131
132
  ## Request/response example
132
133
 
@@ -218,7 +219,9 @@ A native Windows backend (Job objects / AppContainer) is tracked, not scheduled.
218
219
 
219
220
  ## Related APIs
220
221
 
222
+ - [Work sandbox](work-sandbox.md): work image + `createWorkComposition`; host injects `createDockerSandbox` (prism-work does not fork it)
221
223
  - [Coding agent tools](coding-agent-tools.md): durable plan/todo Markdown helpers and `state.coding` checkpoint metadata for restart/resume without a second runtime
224
+ - [Hosted sandboxes](hosted-sandboxes.md): E2B pause/resume adapter, filesystem-only snapshots, reconnect by sandbox id
222
225
  - [Workflows](workflows.md): `runWorkflow` / `resumeWorkflow` / `startWorkflowBackground` composition for coding tasks
223
226
  - [Host security guide](host-security.md)
224
227
  - [Performance limits](performance.md)
@@ -24,7 +24,6 @@ npm install @dietrichgebert/ponytail
24
24
  |---|---|---|
25
25
  | `@arnilo/prism-coding-tools/agent` | Core coding tools (read, write, edit, search, bash, git, diagnostics, check, ast-grep, lsp) | — |
26
26
  | `@arnilo/prism-coding-tools/security` | Sandbox execution adapters (Docker/OCI, native disposable sandbox, approval policies, egress proxy) | — |
27
- | `@arnilo/prism-coding-tools/document-reader` | Bounded PDF/DOCX literal-text extraction adapter with fail-closed loading | `pdf-parse`, `mammoth` |
28
27
  | `@arnilo/prism-coding-tools/openapi` | OpenAPI 3.x tool generator and executor with SSRF protection and parameter validation | — |
29
28
  | `@arnilo/prism-coding-tools/computer-use-linux` | Linux desktop observation and targeting tool bridge | — |
30
29
  | `@arnilo/prism-coding-tools/dev` | Loopback-only developer inspector, event timeline visualizer, and local replay server | — |
@@ -56,6 +56,28 @@ Cleanup refuses, unless the host policy explicitly allows the documented action:
56
56
 
57
57
  Partial failure persists state `unknown` with per-repository `unknown`/`removed` legs and remains reconcilable: retrying cleanup converges to `closed`.
58
58
 
59
+ ## Spawn isolation (supervisor children)
60
+
61
+ Parallel model-requested children share the host cwd by default. Wrap the catalog factory that needs isolation with `createWorktreeChildFactory(factory, { workspaces, repositoryId, branch? })` from `@arnilo/prism-coding-tools/agent`:
62
+
63
+ ```ts
64
+ import { createWorktreeChildFactory } from "@arnilo/prism-coding-tools/agent";
65
+
66
+ const isolated = createWorktreeChildFactory((ctx) => createExploreAgent(ctx, ctx.cwd), {
67
+ workspaces,
68
+ repositoryId: "app",
69
+ });
70
+ createSupervisor({
71
+ children: { explore: { createAgent: isolated.createAgent } },
72
+ hooks: { after: isolated.after },
73
+ });
74
+ ```
75
+
76
+ - `createAgent` runs `workspaces.create({ taskId: delegationId, branch: branch ?? `agent/${delegationId}` })` before the child exists, so missing `worktreeRoots` or an unknown repository fails closed with no spawn, and the child context gains `cwd` = the record's `worktreePath`. Coding tools built from that `cwd` cannot reach the main checkout; keep `worktreeRoots` host-approved.
77
+ - `after` is the supervisor terminal hook: success, failure, abort, and pre-spawn rejection clean up once. A suspended child is deliberately **not** cleaned while it is non-terminal — the hook runs when the resume attempt reaches a terminal outcome instead — and a resumed child re-creates the identical workspace because the task id and default branch derive from `delegationId`.
78
+ - Unwrapped children keep the shared cwd: one `git worktree add` per isolated child, no clone, no second sandbox type. Write-heavy parallel children still need isolation or exclusive tools for the shared-cwd case.
79
+ - Dirty isolated worktrees refuse removal by default; extract artifacts first, allow `policy.allowDirtyCleanup` for forced removal, or reconcile with `list`/`cleanup` — a host restart loses in-process ownership of workspaces it created.
80
+
59
81
  ## Ownership and fencing
60
82
 
61
83
  Ownership scopes are part of the trust boundary: records are read and written under the configured `tenantId`/`accountId`/`userId`, and lease acquisition under another scope fails closed as `ERR_PRISM_WORKSPACE_OWNERSHIP`. Every mutation runs under a `LeaseStore` lease (`tryAcquireLease`/`releaseLease`, TTL 30 s default / 300 s hard); the lease fencing token is stored in the record and each `CheckpointStore` save is a version CAS plus a monotonic fencing-token check, so a worker whose lease lapsed or was fenced out cannot overwrite newer state. Stale workers reject deterministically with `ERR_PRISM_WORKSPACE_FENCE`.
@@ -21,7 +21,7 @@ Current APIs:
21
21
 
22
22
  ## When to use it
23
23
 
24
- Use compaction when a host wants provider input rebuilt from a summary plus recent messages while preserving the full branch in the session store. Use `session.compact()` for explicit compaction or `thresholdEntries` for opt-in auto-compaction before provider input.
24
+ Use compaction when a host wants provider input rebuilt from a summary plus recent messages while preserving the full branch in the session store. Use `session.compact()` for explicit compaction, `thresholdEntries` for an entry-count auto-compaction gate before provider input, or `trigger` when the decision should follow estimated input size or host code.
25
25
 
26
26
  Do not use it as vector memory, semantic search, provider-backed summarization, a store rewrite, a database migration, CLI/RPC command, provider-specific HTTP adapter, or whole-run retry loop.
27
27
 
@@ -45,11 +45,12 @@ createDefaultCompactionStrategy(options?: DefaultCompactionStrategyOptions): Com
45
45
  | Field | Purpose |
46
46
  | --- | --- |
47
47
  | `strategy` | Optional `CompactionStrategy`; defaults to `createDefaultCompactionStrategy()`. |
48
- | `thresholdEntries` | Enables auto-compaction when current branch entries exceed this count. Omit it for no auto-compaction. |
48
+ | `thresholdEntries` | Enables auto-compaction when current branch entries exceed this count. Omit it for no auto-compaction. Ignored when `trigger` is set. |
49
+ | `trigger` | Replaces `thresholdEntries`: `{ type: "threshold_entries", entries }`, `{ type: "input_ratio", ratio }` (compact when the estimated input is at least `ratio` of the compiler's resolved input cap), or `{ type: "custom", shouldCompact(context) }`. |
49
50
  | `keepRecentEntries` | Number of recent message entries kept in provider context. |
50
51
  | `maxSummaryChars` | Maximum default summary length. |
51
52
  | `secrets` | Exact known secret strings to redact from summaries/events/store text. |
52
- | `metadata` | Explicit host metadata passed to the compaction strategy only. |
53
+ | `metadata` | Explicit host metadata passed to the compaction strategy, and to a `custom` trigger context. |
53
54
  | `signal` | Optional manual compaction abort signal. |
54
55
 
55
56
  `RunOptions.compaction: false` disables configured auto-compaction for that run. `CompactionContext` also accepts optional `keepRecentEntries`, `trigger`, and `secrets`. Context values override or add to strategy defaults for that compaction call.
@@ -95,7 +96,37 @@ createDefaultRetryPolicy(options?: DefaultRetryPolicyOptions): RetryPolicy
95
96
 
96
97
  > **Contract — compact at the task boundary.** `session.compact()` throws `Error("Agent session already has an active run")` while `run()`/`stream()` is in flight. Intended model: one `run()` per task, then compact. Do not design mid-run compaction. Auto-compaction (when `thresholdEntries` is set) already runs **before** provider input, not during the turn. Live demo: [`examples/autonomous-coding-loop.ts`](../examples/autonomous-coding-loop.ts) (`compact` node after execute/validate/gate).
97
98
 
98
- Auto-compaction checks at most once per `run()`, after input/model-change entries are appended and before provider input assembly. It runs only when `AgentConfig.compaction` or `RunOptions.compaction` supplies `thresholdEntries`, and it is skipped by `RunOptions.compaction: false`.
99
+ Auto-compaction checks at most once per `run()`, after input/model-change entries are appended and before provider input assembly. It runs only when `AgentConfig.compaction` or `RunOptions.compaction` supplies `thresholdEntries` or `trigger`, and it is skipped by `RunOptions.compaction: false`.
100
+
101
+ `trigger` replaces the legacy gates and is asked once per run, with the would-be input already appended. All three forms are resolved by one helper (`resolveShouldCompact`), so `session`, observational memory, and host code share the same decision:
102
+
103
+ | Trigger | Decides with | Notes |
104
+ | --- | --- | --- |
105
+ | `threshold_entries` | `entryCount > entries` | Pure count, no token estimate. |
106
+ | `input_ratio` | `estimatedInputTokens >= ratio * inputCapTokens` | The cap comes from `resolveInputCap` — the same helper `attentionCompiler` uses, which needs `maxInputTokens` or `model.limits.contextWindow`. An unresolvable cap is a config error and fails the run loudly. |
107
+ | `custom` | `shouldCompact(context)` | Async-ok. `context` carries `sessionId`, `entryCount`, `estimatedInputTokens`, `inputCapTokens`, `metadata`, and `signal`; the two token numbers are resolved lazily, so a callback that only reads counts never needs a model cap. A callback that throws — including one that reads a cap that cannot resolve — decides **false** and never compacts on a guess. |
108
+
109
+ An unknown trigger `type` throws at first use (`assertCompactionTrigger`), so a typo never silently disables compaction. A branch whose last entry is already `kind: "compaction"` is skipped, so a fresh summary is never compacted again.
110
+
111
+ `custom` also has a ready-made builder for the [attention compiler](attention-compiler.md)'s `truncated` signal. `createAttentionTruncationTrigger({ threshold })` counts consecutive truncated turns from `attention_compiled` events and fires **once per armed streak** at the next compaction decision, which is exactly "compact at the next task boundary because stubs could no longer hold the request":
112
+
113
+ ```ts
114
+ const truncation = createAttentionTruncationTrigger();
115
+ session.subscribe((event) => {
116
+ if (event.type === "attention_compiled") truncation.observe(event);
117
+ });
118
+ const agent = createAgent({ model, provider, attentionCompiler: true, compaction: { trigger: truncation.trigger } });
119
+ ```
120
+
121
+ Example — compact when the assembled input passes 90% of the model window:
122
+
123
+ ```ts
124
+ const agent = createAgent({
125
+ model,
126
+ provider,
127
+ compaction: { trigger: { type: "input_ratio", ratio: 0.9 }, keepRecentEntries: 8 },
128
+ });
129
+ ```
99
130
 
100
131
  `rebuildSessionContext()` detects the latest compaction entry on a branch. Its returned `entries` still contains the raw full branch, while `messages` contains only messages after the compaction boundary plus `keepEntryIds`, and `summaries` contains the compaction summary plus later summary entries.
101
132
 
@@ -172,6 +203,7 @@ The default strategy does not call a provider. Hosts that need model-generated s
172
203
  - [Session stores and branching](session-stores-and-branching.md): branch entries, compaction entries, and `rebuildSessionContext()` behavior.
173
204
  - [Input and prompt assembly](input-and-prompt-assembly.md): compacted summaries become default summary messages for provider input.
174
205
  - [Agent/session runtime](agent-session-runtime.md): `session.compact()`, opt-in auto-compaction, `RunOptions.retry`, and `retry_scheduled` runtime behavior.
206
+ - [Attention compiler](attention-compiler.md): resolves the same input cap and shrinks an over-ratio request before compaction is considered.
175
207
  - Example: [`examples/autonomous-coding-loop.ts`](../examples/autonomous-coding-loop.ts) — task-boundary compact after each iteration.
176
208
  - [Middleware hooks](middleware-hooks.md): `compaction` and `retry` middleware payload timing.
177
209
  - [Contribution registries](contribution-registries.md): compaction strategy and retry policy contributions.