@animalabs/agent-framework 0.32.0 → 0.33.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 (288) hide show
  1. package/README.md +204 -14
  2. package/dist/src/admission/admit.d.ts +18 -0
  3. package/dist/src/admission/admit.d.ts.map +1 -1
  4. package/dist/src/admission/admit.js +16 -3
  5. package/dist/src/admission/admit.js.map +1 -1
  6. package/dist/src/agent.d.ts +185 -78
  7. package/dist/src/agent.d.ts.map +1 -1
  8. package/dist/src/agent.js +279 -102
  9. package/dist/src/agent.js.map +1 -1
  10. package/dist/src/api/mcp-server-core.d.ts.map +1 -1
  11. package/dist/src/api/mcp-server-core.js +50 -12
  12. package/dist/src/api/mcp-server-core.js.map +1 -1
  13. package/dist/src/api/server.d.ts.map +1 -1
  14. package/dist/src/api/server.js +4 -20
  15. package/dist/src/api/server.js.map +1 -1
  16. package/dist/src/code-execution/isolate/scripts.d.ts +12 -8
  17. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
  18. package/dist/src/code-execution/isolate/scripts.js +24 -0
  19. package/dist/src/code-execution/isolate/scripts.js.map +1 -1
  20. package/dist/src/config-checks.d.ts +7 -0
  21. package/dist/src/config-checks.d.ts.map +1 -1
  22. package/dist/src/config-checks.js +14 -0
  23. package/dist/src/config-checks.js.map +1 -1
  24. package/dist/src/context-manager-errors.d.ts +25 -0
  25. package/dist/src/context-manager-errors.d.ts.map +1 -0
  26. package/dist/src/context-manager-errors.js +31 -0
  27. package/dist/src/context-manager-errors.js.map +1 -0
  28. package/dist/src/context-options.d.ts +67 -0
  29. package/dist/src/context-options.d.ts.map +1 -0
  30. package/dist/src/context-options.js +85 -0
  31. package/dist/src/context-options.js.map +1 -0
  32. package/dist/src/deferred-import.d.ts +93 -0
  33. package/dist/src/deferred-import.d.ts.map +1 -0
  34. package/dist/src/deferred-import.js +177 -0
  35. package/dist/src/deferred-import.js.map +1 -0
  36. package/dist/src/dendrite/audience.d.ts +223 -0
  37. package/dist/src/dendrite/audience.d.ts.map +1 -0
  38. package/dist/src/dendrite/audience.js +202 -0
  39. package/dist/src/dendrite/audience.js.map +1 -0
  40. package/dist/src/dendrite/index.d.ts +5 -3
  41. package/dist/src/dendrite/index.d.ts.map +1 -1
  42. package/dist/src/dendrite/index.js +2 -1
  43. package/dist/src/dendrite/index.js.map +1 -1
  44. package/dist/src/dendrite/presets.d.ts +10 -5
  45. package/dist/src/dendrite/presets.d.ts.map +1 -1
  46. package/dist/src/dendrite/presets.js +17 -5
  47. package/dist/src/dendrite/presets.js.map +1 -1
  48. package/dist/src/dendrite/registry.d.ts +62 -7
  49. package/dist/src/dendrite/registry.d.ts.map +1 -1
  50. package/dist/src/dendrite/registry.js +179 -10
  51. package/dist/src/dendrite/registry.js.map +1 -1
  52. package/dist/src/dendrite/types.d.ts +91 -8
  53. package/dist/src/dendrite/types.d.ts.map +1 -1
  54. package/dist/src/foreign-rounds.d.ts +69 -0
  55. package/dist/src/foreign-rounds.d.ts.map +1 -0
  56. package/dist/src/foreign-rounds.js +152 -0
  57. package/dist/src/foreign-rounds.js.map +1 -0
  58. package/dist/src/framework.d.ts +1976 -818
  59. package/dist/src/framework.d.ts.map +1 -1
  60. package/dist/src/framework.js +7566 -3422
  61. package/dist/src/framework.js.map +1 -1
  62. package/dist/src/gate/event-gate.d.ts +85 -15
  63. package/dist/src/gate/event-gate.d.ts.map +1 -1
  64. package/dist/src/gate/event-gate.js +421 -47
  65. package/dist/src/gate/event-gate.js.map +1 -1
  66. package/dist/src/gate/index.d.ts +1 -1
  67. package/dist/src/gate/index.d.ts.map +1 -1
  68. package/dist/src/gate/index.js.map +1 -1
  69. package/dist/src/gate/rule-scope.d.ts +30 -0
  70. package/dist/src/gate/rule-scope.d.ts.map +1 -0
  71. package/dist/src/gate/rule-scope.js +41 -0
  72. package/dist/src/gate/rule-scope.js.map +1 -0
  73. package/dist/src/gate/suppress-script-worker.d.ts +2 -0
  74. package/dist/src/gate/suppress-script-worker.d.ts.map +1 -0
  75. package/dist/src/gate/suppress-script-worker.js +103 -0
  76. package/dist/src/gate/suppress-script-worker.js.map +1 -0
  77. package/dist/src/gate/suppress-scripts.d.ts +105 -0
  78. package/dist/src/gate/suppress-scripts.d.ts.map +1 -0
  79. package/dist/src/gate/suppress-scripts.js +403 -0
  80. package/dist/src/gate/suppress-scripts.js.map +1 -0
  81. package/dist/src/gate/suppression-review.d.ts +143 -0
  82. package/dist/src/gate/suppression-review.d.ts.map +1 -0
  83. package/dist/src/gate/suppression-review.js +463 -0
  84. package/dist/src/gate/suppression-review.js.map +1 -0
  85. package/dist/src/gate/suppression-stats.d.ts +125 -0
  86. package/dist/src/gate/suppression-stats.d.ts.map +1 -0
  87. package/dist/src/gate/suppression-stats.js +214 -0
  88. package/dist/src/gate/suppression-stats.js.map +1 -0
  89. package/dist/src/gate/types.d.ts +135 -7
  90. package/dist/src/gate/types.d.ts.map +1 -1
  91. package/dist/src/gate/wake-onboarding.d.ts +119 -0
  92. package/dist/src/gate/wake-onboarding.d.ts.map +1 -0
  93. package/dist/src/gate/wake-onboarding.js +277 -0
  94. package/dist/src/gate/wake-onboarding.js.map +1 -0
  95. package/dist/src/held-writes.d.ts +31 -23
  96. package/dist/src/held-writes.d.ts.map +1 -1
  97. package/dist/src/held-writes.js +44 -59
  98. package/dist/src/held-writes.js.map +1 -1
  99. package/dist/src/index.d.ts +5 -2
  100. package/dist/src/index.d.ts.map +1 -1
  101. package/dist/src/index.js +3 -0
  102. package/dist/src/index.js.map +1 -1
  103. package/dist/src/jsonrpc-envelope.d.ts +69 -0
  104. package/dist/src/jsonrpc-envelope.d.ts.map +1 -0
  105. package/dist/src/jsonrpc-envelope.js +66 -0
  106. package/dist/src/jsonrpc-envelope.js.map +1 -0
  107. package/dist/src/mcpl/channel-registry.d.ts +131 -15
  108. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  109. package/dist/src/mcpl/channel-registry.js +290 -46
  110. package/dist/src/mcpl/channel-registry.js.map +1 -1
  111. package/dist/src/mcpl/checkpoint-manager.d.ts +2 -4
  112. package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
  113. package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
  114. package/dist/src/mcpl/feature-set-manager.d.ts +2 -4
  115. package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
  116. package/dist/src/mcpl/feature-set-manager.js.map +1 -1
  117. package/dist/src/mcpl/inbound-source.d.ts +2 -1
  118. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  119. package/dist/src/mcpl/inbound-source.js +2 -1
  120. package/dist/src/mcpl/inbound-source.js.map +1 -1
  121. package/dist/src/mcpl/inference-router.d.ts +6 -6
  122. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  123. package/dist/src/mcpl/inference-router.js.map +1 -1
  124. package/dist/src/mcpl/push-coalescer.d.ts +132 -22
  125. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  126. package/dist/src/mcpl/push-coalescer.js +105 -15
  127. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  128. package/dist/src/mcpl/push-handler.d.ts +9 -7
  129. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  130. package/dist/src/mcpl/push-handler.js.map +1 -1
  131. package/dist/src/mcpl/server-connection.d.ts +13 -10
  132. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  133. package/dist/src/mcpl/server-connection.js +134 -56
  134. package/dist/src/mcpl/server-connection.js.map +1 -1
  135. package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
  136. package/dist/src/mcpl/tool-classes.js +3 -0
  137. package/dist/src/mcpl/tool-classes.js.map +1 -1
  138. package/dist/src/mcpl/types.d.ts +8 -2
  139. package/dist/src/mcpl/types.d.ts.map +1 -1
  140. package/dist/src/mcpl/types.js.map +1 -1
  141. package/dist/src/module-registry.d.ts +24 -1
  142. package/dist/src/module-registry.d.ts.map +1 -1
  143. package/dist/src/module-registry.js +68 -18
  144. package/dist/src/module-registry.js.map +1 -1
  145. package/dist/src/modules/discord/index.d.ts +45 -8
  146. package/dist/src/modules/discord/index.d.ts.map +1 -1
  147. package/dist/src/modules/discord/index.js +237 -86
  148. package/dist/src/modules/discord/index.js.map +1 -1
  149. package/dist/src/modules/history/index.d.ts +59 -0
  150. package/dist/src/modules/history/index.d.ts.map +1 -1
  151. package/dist/src/modules/history/index.js +432 -67
  152. package/dist/src/modules/history/index.js.map +1 -1
  153. package/dist/src/modules/history/semantic.d.ts +6 -5
  154. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  155. package/dist/src/modules/history/semantic.js +15 -22
  156. package/dist/src/modules/history/semantic.js.map +1 -1
  157. package/dist/src/modules/workspace/disk-agreement.d.ts +1 -1
  158. package/dist/src/modules/workspace/disk-agreement.js +1 -1
  159. package/dist/src/modules/workspace/index.d.ts +28 -7
  160. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  161. package/dist/src/modules/workspace/index.js +76 -22
  162. package/dist/src/modules/workspace/index.js.map +1 -1
  163. package/dist/src/notice-tags.d.ts +10 -1
  164. package/dist/src/notice-tags.d.ts.map +1 -1
  165. package/dist/src/notice-tags.js +10 -1
  166. package/dist/src/notice-tags.js.map +1 -1
  167. package/dist/src/operator-log.d.ts +36 -3
  168. package/dist/src/operator-log.d.ts.map +1 -1
  169. package/dist/src/operator-log.js +2 -2
  170. package/dist/src/operator-log.js.map +1 -1
  171. package/dist/src/presentation.d.ts +746 -0
  172. package/dist/src/presentation.d.ts.map +1 -0
  173. package/dist/src/presentation.js +1435 -0
  174. package/dist/src/presentation.js.map +1 -0
  175. package/dist/src/prose-drafts.d.ts +15 -11
  176. package/dist/src/prose-drafts.d.ts.map +1 -1
  177. package/dist/src/prose-drafts.js +16 -12
  178. package/dist/src/prose-drafts.js.map +1 -1
  179. package/dist/src/read-marks.d.ts +192 -0
  180. package/dist/src/read-marks.d.ts.map +1 -0
  181. package/dist/src/read-marks.js +242 -0
  182. package/dist/src/read-marks.js.map +1 -0
  183. package/dist/src/recovery/offline-branch.d.ts +91 -6
  184. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  185. package/dist/src/recovery/offline-branch.js +273 -29
  186. package/dist/src/recovery/offline-branch.js.map +1 -1
  187. package/dist/src/recovery/recover-cli.js +22 -3
  188. package/dist/src/recovery/recover-cli.js.map +1 -1
  189. package/dist/src/send-tools.d.ts +5 -9
  190. package/dist/src/send-tools.d.ts.map +1 -1
  191. package/dist/src/send-tools.js +40 -7
  192. package/dist/src/send-tools.js.map +1 -1
  193. package/dist/src/stored-injections.d.ts +2 -2
  194. package/dist/src/stored-injections.js +2 -2
  195. package/dist/src/stored-removal.d.ts +17 -4
  196. package/dist/src/stored-removal.d.ts.map +1 -1
  197. package/dist/src/stored-removal.js +29 -5
  198. package/dist/src/stored-removal.js.map +1 -1
  199. package/dist/src/tool-result-guard.d.ts +12 -1
  200. package/dist/src/tool-result-guard.d.ts.map +1 -1
  201. package/dist/src/tool-result-guard.js +42 -8
  202. package/dist/src/tool-result-guard.js.map +1 -1
  203. package/dist/src/tune-out/coordinator.d.ts +1159 -79
  204. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  205. package/dist/src/tune-out/coordinator.js +3709 -363
  206. package/dist/src/tune-out/coordinator.js.map +1 -1
  207. package/dist/src/tune-out/tools.d.ts +114 -17
  208. package/dist/src/tune-out/tools.d.ts.map +1 -1
  209. package/dist/src/tune-out/tools.js +87 -27
  210. package/dist/src/tune-out/tools.js.map +1 -1
  211. package/dist/src/tune-out/window-anchor.d.ts +143 -0
  212. package/dist/src/tune-out/window-anchor.d.ts.map +1 -0
  213. package/dist/src/tune-out/window-anchor.js +202 -0
  214. package/dist/src/tune-out/window-anchor.js.map +1 -0
  215. package/dist/src/types/agent.d.ts +82 -20
  216. package/dist/src/types/agent.d.ts.map +1 -1
  217. package/dist/src/types/events.d.ts +2 -0
  218. package/dist/src/types/events.d.ts.map +1 -1
  219. package/dist/src/types/framework.d.ts +32 -12
  220. package/dist/src/types/framework.d.ts.map +1 -1
  221. package/dist/src/types/module.d.ts +180 -69
  222. package/dist/src/types/module.d.ts.map +1 -1
  223. package/dist/src/types/trace.d.ts +510 -36
  224. package/dist/src/types/trace.d.ts.map +1 -1
  225. package/package.json +3 -3
  226. package/src/admission/admit.ts +32 -3
  227. package/src/agent.ts +323 -120
  228. package/src/api/mcp-server-core.ts +54 -17
  229. package/src/api/server.ts +4 -23
  230. package/src/code-execution/isolate/scripts.ts +28 -3
  231. package/src/config-checks.ts +14 -0
  232. package/src/context-manager-errors.ts +37 -0
  233. package/src/context-options.ts +96 -0
  234. package/src/deferred-import.ts +239 -0
  235. package/src/dendrite/audience.ts +315 -0
  236. package/src/dendrite/index.ts +14 -2
  237. package/src/dendrite/presets.ts +18 -6
  238. package/src/dendrite/registry.ts +209 -12
  239. package/src/dendrite/types.ts +92 -8
  240. package/src/foreign-rounds.ts +199 -0
  241. package/src/framework.ts +7804 -3546
  242. package/src/gate/event-gate.ts +428 -65
  243. package/src/gate/index.ts +3 -0
  244. package/src/gate/rule-scope.ts +45 -0
  245. package/src/gate/suppress-script-worker.ts +89 -0
  246. package/src/gate/suppress-scripts.ts +414 -0
  247. package/src/gate/suppression-review.ts +517 -0
  248. package/src/gate/suppression-stats.ts +314 -0
  249. package/src/gate/types.ts +141 -7
  250. package/src/gate/wake-onboarding.ts +309 -0
  251. package/src/held-writes.ts +49 -60
  252. package/src/index.ts +9 -2
  253. package/src/jsonrpc-envelope.ts +81 -0
  254. package/src/mcpl/channel-registry.ts +353 -85
  255. package/src/mcpl/checkpoint-manager.ts +3 -2
  256. package/src/mcpl/feature-set-manager.ts +2 -1
  257. package/src/mcpl/inbound-source.ts +2 -1
  258. package/src/mcpl/inference-router.ts +7 -4
  259. package/src/mcpl/push-coalescer.ts +193 -34
  260. package/src/mcpl/push-handler.ts +10 -5
  261. package/src/mcpl/server-connection.ts +141 -60
  262. package/src/mcpl/tool-classes.ts +3 -0
  263. package/src/mcpl/types.ts +8 -2
  264. package/src/module-registry.ts +73 -17
  265. package/src/modules/discord/index.ts +229 -81
  266. package/src/modules/history/index.ts +439 -70
  267. package/src/modules/history/semantic.ts +15 -21
  268. package/src/modules/workspace/disk-agreement.ts +1 -1
  269. package/src/modules/workspace/index.ts +85 -23
  270. package/src/notice-tags.ts +10 -1
  271. package/src/operator-log.ts +36 -3
  272. package/src/presentation.ts +1676 -0
  273. package/src/prose-drafts.ts +16 -12
  274. package/src/read-marks.ts +328 -0
  275. package/src/recovery/offline-branch.ts +346 -32
  276. package/src/recovery/recover-cli.ts +21 -3
  277. package/src/send-tools.ts +40 -7
  278. package/src/stored-injections.ts +2 -2
  279. package/src/stored-removal.ts +31 -7
  280. package/src/tool-result-guard.ts +41 -7
  281. package/src/tune-out/coordinator.ts +4089 -457
  282. package/src/tune-out/tools.ts +160 -43
  283. package/src/tune-out/window-anchor.ts +277 -0
  284. package/src/types/agent.ts +86 -20
  285. package/src/types/events.ts +2 -0
  286. package/src/types/framework.ts +32 -12
  287. package/src/types/module.ts +181 -68
  288. package/src/types/trace.ts +542 -40
package/src/agent.ts CHANGED
@@ -5,13 +5,16 @@ import { isAbortedResponse } from '@animalabs/membrane';
5
5
  import { streamBounds, type InferenceBounds } from './inference-bounds.js';
6
6
  import type { CacheWireReceipt, KvUnifiedRequestHooks } from './kv-unified-wire.js';
7
7
  import { ToolResultGuard } from './tool-result-guard.js';
8
+ import { storeOwnRow } from './dendrite/audience.js';
8
9
  import { withoutTokenEstimates } from './thinking-token-stamp.js';
9
10
  import { placeThinkingDrops, type CompiledForRequest, type PlacedDrop } from './thinking-drops.js';
10
11
  import { carriedBlock, type SentRow } from './sent-rows.js';
12
+ import { foreignRowAsSent, transcribeForeignRounds } from './foreign-rounds.js';
11
13
  import { injectionsAsStored } from './text-as-stored.js';
12
14
  import { stableHash } from './stable-hash.js';
13
15
  import { hasRenderedContent, storedInjectionMarkOf } from './stored-injections.js';
14
- import { assertBooleanSetting, holdsSettings, kindOf } from './config-checks.js';
16
+ import { covers, markAtOrAfter, readMarkOf, storeHeadOf, type MarkCm, type MarkedRow, type ReadMark } from './read-marks.js';
17
+ import { assertBooleanSetting, assertOneOfSetting, holdsSettings, kindOf } from './config-checks.js';
15
18
  import {
16
19
  CONTINUE_ROW,
17
20
  continueTurn,
@@ -187,11 +190,13 @@ export function assertThinkingBinding(setting: string, value: AgentConfig['think
187
190
 
188
191
  /**
189
192
  * Refuse an agent config whose `promptCaching`, `toolResultGuard`,
190
- * `toolWrapperProseGuard`, `thinking.enabled`, `thinkingBinding.dropBlock`,
193
+ * `toolWrapperProseGuard`, `turnInputsPin`, `thinking.enabled`, `thinkingBinding.dropBlock`,
191
194
  * `thinkingBinding.binds` or `refusalHandling.autoRewind` holds anything but true or false
192
195
  * (assertBooleanSetting, where null counts as left out), or whose `thinking`, `thinkingBinding` or
193
196
  * `refusalHandling` is anything but an object, null or false (holdsSettings),
194
- * or whose `autoContinue` resolveAutoContinue refuses. The constructor runs it
197
+ * or whose `autoContinue` resolveAutoContinue refuses, or whose `observes`
198
+ * is anything but a list of names, or whose `foreignRounds` is neither of
199
+ * its modes (assertOneOfSetting). The constructor runs it
195
200
  * first, so the rule holds wherever an Agent is built. `create()` also runs it
196
201
  * for each of its agents before anything opens, and `createEphemeralAgent`
197
202
  * before it reserves the name, so a refusal there leaves nothing behind.
@@ -233,6 +238,26 @@ export function assertAgentSwitches(config: AgentConfig): void {
233
238
  'true keeps a response whose whole prose is a tool-call-shaped wrapper from running or entering the agent\'s history, ' +
234
239
  'and false or leaving it out doesn\'t',
235
240
  );
241
+ assertBooleanSetting(
242
+ `${agent} turnInputsPin`,
243
+ config.turnInputsPin,
244
+ 'true or leaving it out pins the rows that open a turn raw until it ends, and false places no pin',
245
+ );
246
+ const observes: unknown = config.observes;
247
+ if (observes !== undefined && observes !== null
248
+ && (!Array.isArray(observes) || !observes.every((name) => typeof name === 'string'))) {
249
+ throw new Error(
250
+ `${agent} observes must be a list of agent names, such as ["nova"], not ${Array.isArray(observes) ? 'a list holding something else' : kindOf(observes)}: ` +
251
+ 'each names another configured agent whose own rows this agent\'s window takes',
252
+ );
253
+ }
254
+ assertOneOfSetting(
255
+ `${agent} foreignRounds`,
256
+ config.foreignRounds,
257
+ ['transcript', 'raw'],
258
+ '\'transcript\' or leaving it out sends the rounds of an agent it observes as a line per call and result, ' +
259
+ 'without their thinking, and \'raw\' sends them as stored',
260
+ );
236
261
  resolveAutoContinue(config);
237
262
  }
238
263
 
@@ -274,6 +299,18 @@ export function resolveAutoContinue(config: AgentConfig): { maxConsecutive: numb
274
299
  return { maxConsecutive: max };
275
300
  }
276
301
 
302
+ /** A stored injection's copy a compile built to send rendered (Agent.renderedInjection). */
303
+ interface RenderedInjectionCopy {
304
+ /** The row's id: the body the compile's provenance names. */
305
+ id: MessageId;
306
+ part: number;
307
+ parts: number;
308
+ /** Whether the compile held the part: every shard of it, however the strategy shaped it. */
309
+ held: boolean;
310
+ sequence: number;
311
+ }
312
+
313
+ /** A branch's key in Agent.unsentSince: its id and when it was created. */
277
314
  export function branchKey(branch: { readonly id: string; readonly created: number }): string {
278
315
  return `${branch.id}\u0000${branch.created}`;
279
316
  }
@@ -311,7 +348,9 @@ export class Agent {
311
348
  readonly refusalHandling: AgentConfig['refusalHandling'];
312
349
  readonly toolResultGuard: ToolResultGuard;
313
350
  /** Prose delivery mode (see AgentConfig.proseRouting). Default 'locus'. */
314
- readonly proseRouting: 'locus' | 'explicit' | 'hybrid' | 'disabled';
351
+ readonly proseRouting: 'locus' | 'explicit' | 'hybrid' | 'disabled' | 'locked';
352
+ /** Locked mode's destination until the agent chooses one (see AgentConfig.proseLock). */
353
+ readonly proseLock: string | undefined;
315
354
  /** Exact whole-response known-tool wrapper containment (default off). */
316
355
  readonly toolWrapperProseGuard: boolean;
317
356
  /** Continuation after a turn the model ends itself (see
@@ -456,11 +495,13 @@ export class Agent {
456
495
  this.refusalHandling = config.refusalHandling;
457
496
  this.toolResultGuard = new ToolResultGuard(config.name, contextManager, config.toolResultGuard === true);
458
497
  this.proseRouting = config.proseRouting ?? 'locus';
498
+ this.proseLock = typeof config.proseLock === 'string' && config.proseLock.trim() ? config.proseLock.trim() : undefined;
459
499
  this.toolWrapperProseGuard = config.toolWrapperProseGuard === true;
460
500
  this.autoContinue = resolveAutoContinue(config);
461
501
  this.clock = resolveClockConfig(config.name, config.clock);
462
502
  this.cacheTtl = config.cacheTtl ?? '1h';
463
503
  this.promptCaching = config.promptCaching !== false;
504
+ this.pinsTurnInputs = config.turnInputsPin !== false;
464
505
  this.prefillUserMessage = config.prefillUserMessage;
465
506
  this.providerParams = config.providerParams;
466
507
  this.sameRoundThinkTextPolicy = config.sameRoundThinkTextPolicy;
@@ -539,6 +580,46 @@ export class Agent {
539
580
  return this.ownParticipants.has(participant);
540
581
  }
541
582
 
583
+ /**
584
+ * How another agent's rounds go out in this agent's requests (#291, answer
585
+ * 13): `'raw'`, as stored, or `'transcript'` (foreign-rounds.ts), a line
586
+ * per call and per result and no thinking, the same in every compile and
587
+ * in what a tool boundary injects. The framework sets it, once the
588
+ * agent's window follows the presentation index, for the persistent
589
+ * subconscious, for a resident that observes another
590
+ * (AgentConfig.observes), as their configurations' `foreignRounds` say
591
+ * (`'transcript'` by default), and for an agent derived or forked from
592
+ * one of them, in that agent's mode (its inheritance holds them); every
593
+ * other agent reads no other agent's
594
+ * rounds, but for a co-resident's rows stored before audience stamps,
595
+ * which a window written down from the record keeps and which go out as
596
+ * stored ('raw'), as earlier releases sent them.
597
+ */
598
+ private foreignRounds: 'raw' | 'transcript' = 'raw';
599
+
600
+ /** Set how another agent's rounds go out in this agent's requests (see foreignRounds). */
601
+ setForeignRounds(mode: 'raw' | 'transcript'): void {
602
+ this.foreignRounds = mode;
603
+ }
604
+
605
+ /**
606
+ * A stored row as this agent's requests carry it, for a row a tool
607
+ * boundary injects: another participant's row as a transcript where this
608
+ * agent is sent other agents' rounds so (setForeignRounds), its own and
609
+ * every row otherwise as stored. An injected row holds no tool block (the
610
+ * boundary cuts at one), so what changes is its thinking.
611
+ */
612
+ injectedAsSent(participant: string, content: ContentBlock[]): ContentBlock[] {
613
+ if (this.foreignRounds !== 'transcript' || this.isOwnParticipant(participant)) return content;
614
+ return foreignRowAsSent(content);
615
+ }
616
+
617
+ /** A compile's messages with other agents' rounds as this agent is sent them (setForeignRounds). */
618
+ private foreignRoundsAsSent(messages: NormalizedMessage[]): NormalizedMessage[] {
619
+ if (this.foreignRounds !== 'transcript') return messages;
620
+ return transcribeForeignRounds(messages, (participant) => this.isOwnParticipant(participant));
621
+ }
622
+
542
623
  /**
543
624
  * Role assignment for a request: every participant that is this agent's
544
625
  * own voice — its name, and for a derived agent the agent it inherited
@@ -1131,45 +1212,60 @@ export class Agent {
1131
1212
  }
1132
1213
 
1133
1214
  /**
1134
- * RFC-006 consumed watermark, per branch: the newest stored message as of
1135
- * the latest of the agent's creation, a committing compile that returns,
1136
- * and a tool boundary that injected messages into a live stream declaring
1137
- * it carries them (`supportsInjectedMessages` true: membrane's native
1138
- * path, from membrane 0.6.0). A compile counts from when it takes its
1139
- * value, before it awaits, so one that began before a boundary's mark
1140
- * doesn't roll the mark back (applyCompileWatermark), and a dry run, such
1141
- * as previewActivation's, doesn't move it. Each move takes the store's
1142
- * head, a scalar, so it also covers stored messages the agent wasn't
1143
- * given, such as another slot's.
1215
+ * RFC-006 consumed watermark, per branch: how far the agent had read as of
1216
+ * the latest of its creation, a committing compile that returns, and a
1217
+ * tool boundary that injected messages into a live stream declaring it
1218
+ * carries them (`supportsInjectedMessages` true: membrane's native path,
1219
+ * from membrane 0.6.0). A compile counts from when it takes its mark,
1220
+ * before it awaits, so one that began before a boundary's mark doesn't
1221
+ * roll the mark back (applyCompileWatermark), and a dry run, such as
1222
+ * previewActivation's, doesn't move it.
1223
+ *
1224
+ * It is a ReadMark (read-marks.ts), read only through `covers`. Once the
1225
+ * agent's context presents rows (#291's presentation index, in presented
1226
+ * mode), it covers exactly the rows in the agent's window as of the mark:
1227
+ * a row stored then and not yet presented to the agent isn't covered.
1228
+ * While the context is in legacy mode, its window is the record, and the
1229
+ * mark covers every row stored on the branch by then, as it always did:
1230
+ * a prefix of the record, so it also covers stored messages the agent
1231
+ * wasn't given, such as another slot's. Once the window is written down
1232
+ * (an agent registered on a branch its window was never written down on
1233
+ * holds such a mark until its first compile), a mark taken in legacy mode
1234
+ * covers the rows its window held by the mark where they still stand, as
1235
+ * a mark taken in presented mode then would: a row stored after it, taken
1236
+ * out of the window since, or presented to the agent again, isn't.
1144
1237
  *
1145
1238
  * A message above it may still be replaced or withdrawn in place by its
1146
1239
  * sender, though it may already have been in one of the agent's requests.
1147
1240
  * A live stream carries each round's own content and results, which it
1148
1241
  * stores at its tool boundaries, into its next request.
1149
1242
  */
1150
- private consumedWatermark: { branch: string; sequence: number } | null = null;
1243
+ private consumedWatermark: ReadMark | null = null;
1151
1244
  /** Counts marks (markContextConsumed's and applyCompileWatermark's), so a
1152
1245
  * compile that finishes can tell whether one was made while it ran. */
1153
1246
  private watermarkMarks = 0;
1154
1247
  /**
1155
- * The newest stored message a compile or a tool boundary may have put in
1156
- * front of the agent, per branch. It moves where the consumed watermark
1157
- * does, and sooner: when a committing compile takes its value, before it
1158
- * awaits, and at every tool boundary that injects into its stream. A message above it may still
1159
- * have been shown, though: for one, a live stream carries each round's own
1160
- * content and results, stored at a boundary that injected nothing, into its
1161
- * next request. A compile that throws assembled nothing, and gives its move
1162
- * back where it can (unshowFailedCompile). A scalar counts a prefix, so a
1163
- * message stored below it without having been shown counts as shown, which
1164
- * errs toward a marker.
1248
+ * How far a compile or a tool boundary may have put the agent's context in
1249
+ * front of it, per branch. It moves where the consumed watermark does,
1250
+ * and sooner: when a committing compile takes its mark, before it awaits,
1251
+ * and at every tool boundary that injects into its stream. A message above
1252
+ * it may still have been shown, though: for one, a live stream carries each
1253
+ * round's own content and results, stored at a boundary that injected
1254
+ * nothing, into its next request. A compile that throws assembled nothing,
1255
+ * and gives its move back where it can (unshowFailedCompile). A mark taken
1256
+ * in legacy mode, read while the window is the record, counts a prefix of
1257
+ * the record, so a message stored below it without having been shown
1258
+ * counts as shown, which errs toward a marker; one taken in presented
1259
+ * mode, and one taken in legacy mode read once the window is written down,
1260
+ * cover the window as it stood.
1165
1261
  */
1166
- private shownWatermark: { branch: string; sequence: number } | null = null;
1262
+ private shownWatermark: ReadMark | null = null;
1167
1263
 
1168
- getConsumedWatermark(): { branch: string; sequence: number } | null {
1264
+ getConsumedWatermark(): ReadMark | null {
1169
1265
  return this.consumedWatermark;
1170
1266
  }
1171
1267
 
1172
- getShownWatermark(): { branch: string; sequence: number } | null {
1268
+ getShownWatermark(): ReadMark | null {
1173
1269
  return this.shownWatermark;
1174
1270
  }
1175
1271
 
@@ -1181,27 +1277,32 @@ export class Agent {
1181
1277
  * request runs, so a request that fails, ending its stream with an error,
1182
1278
  * leaves this as it was.
1183
1279
  */
1184
- private answeredWatermark: { branch: string; sequence: number } | null = null;
1280
+ private answeredWatermark: ReadMark | null = null;
1185
1281
 
1186
- getAnsweredWatermark(): { branch: string; sequence: number } | null {
1282
+ getAnsweredWatermark(): ReadMark | null {
1187
1283
  return this.answeredWatermark;
1188
1284
  }
1189
1285
 
1190
1286
  /** A request that carried `watermark` was answered. Requests are answered
1191
1287
  * in the order they're sent, and the consumed watermark only advances
1192
1288
  * within a branch, so the latest is the newest. */
1193
- markAnswered(watermark: { branch: string; sequence: number } | null): void {
1289
+ markAnswered(watermark: ReadMark | null): void {
1194
1290
  if (watermark) this.answeredWatermark = watermark;
1195
1291
  }
1196
1292
 
1293
+ /** Whether a turn this agent opens pins its inputs (AgentConfig.turnInputsPin, on by default). */
1294
+ readonly pinsTurnInputs: boolean;
1295
+
1197
1296
  /**
1198
- * Keep the stored rows from `first` through `last` raw for the turn now
1199
- * opening, with a raw pin under one name per agent. Any pin of that name
1200
- * already held is removed first, one a crash left included. Under a
1201
- * strategy without pins nothing is pinned. A committing compile that
1202
- * refuses as over budget while a pin of that name is held removes it and
1203
- * compiles once more, and `unpinTurnInputs` removes it. Returns the pin's
1204
- * id, or undefined when nothing was pinned.
1297
+ * Keep the rows of the agent's window from `first` through `last` raw for
1298
+ * the turn now opening, with a raw pin under one name per agent: a
1299
+ * strategy reads a pin by its rows' positions in its message view, which
1300
+ * is the window, in its order (#291; in legacy mode the record). Any pin
1301
+ * of that name already held is removed first, one a crash left included.
1302
+ * Under a strategy without pins nothing is pinned. A committing compile
1303
+ * that refuses as over budget while a pin of that name is held removes it
1304
+ * and compiles once more, and `unpinTurnInputs` removes it. Returns the
1305
+ * pin's id, or undefined when nothing was pinned.
1205
1306
  *
1206
1307
  * @internal For the framework's turn starts; not a host API.
1207
1308
  */
@@ -1258,48 +1359,91 @@ export class Agent {
1258
1359
  * draft resend, or a send tool whose channel the framework records), on
1259
1360
  * the branch it was taken on. A wake that carries a channel, unless it's
1260
1361
  * marked ambient (InferenceRequest.ambient), keeps its turn unless the
1261
- * agent spoke there after reading the write the wake is for. It
1362
+ * agent spoke there after reading the row the wake is for. It
1262
1363
  * lives as long as this Agent: an agent made again starts it empty, and
1263
1364
  * such a wake then stays.
1264
1365
  */
1265
- private publishedThrough = new Map<string, { branch: string; sequence: number }>();
1366
+ private publishedThrough = new Map<string, ReadMark>();
1266
1367
 
1267
1368
  /** Note a publish into `destination` (its key), made having read through
1268
1369
  * `readThrough`: the consumed watermark when its words were written, or
1269
- * its call made. */
1270
- notePublished(destination: string, readThrough: { branch: string; sequence: number } | null): void {
1370
+ * its call made. A later mark on the same branch (markAtOrAfter: of two
1371
+ * at one head, the one whose window reached further) replaces an earlier
1372
+ * one; one on another branch replaces whatever was noted. */
1373
+ notePublished(destination: string, readThrough: ReadMark | null): void {
1271
1374
  if (!readThrough) return;
1272
1375
  const noted = this.publishedThrough.get(destination);
1273
- if (noted && noted.branch === readThrough.branch && noted.sequence >= readThrough.sequence) return;
1376
+ if (noted && markAtOrAfter(noted, readThrough) === true) return;
1274
1377
  this.publishedThrough.set(destination, readThrough);
1275
1378
  }
1276
1379
 
1277
1380
  /** Whether the agent published into `destination` (its key) having read
1278
- * the row stored at `sequence` on `branch`. */
1279
- publishedAfterReading(destination: string, branch: string, sequence: number): boolean {
1381
+ * `row`, as `cm` (the agent's context, where the row's place in its
1382
+ * window is read) says: the mark noted for the destination covers it. A
1383
+ * mark that can't say (one on another branch) says no. */
1384
+ publishedAfterReading(destination: string, row: MarkedRow, cm: MarkCm): boolean {
1280
1385
  const noted = this.publishedThrough.get(destination);
1281
- return !!noted && noted.branch === branch && noted.sequence >= sequence;
1386
+ return !!noted && covers(noted, cm, row) === 'covered';
1282
1387
  }
1283
1388
 
1284
- /** Mark everything currently stored as consumed (the agent's creation, at
1285
- * boot or later, and a tool boundary that injected messages into a live
1286
- * stream declaring it carries them; see consumedWatermark). */
1389
+ /**
1390
+ * Every read mark this agent holds: its consumed, shown and answered
1391
+ * watermarks and the mark noted for each destination it published into,
1392
+ * each mark once (two of them are often the one mark). A redo on the
1393
+ * window (AgentFramework.redo) reads which of them covered the rows it
1394
+ * puts back, and gives those marks again (replaceReadMarks).
1395
+ */
1396
+ heldReadMarks(): ReadMark[] {
1397
+ const held = new Set<ReadMark>();
1398
+ for (const mark of [this.consumedWatermark, this.shownWatermark, this.answeredWatermark, ...this.publishedThrough.values()]) {
1399
+ if (mark) held.add(mark);
1400
+ }
1401
+ return [...held];
1402
+ }
1403
+
1404
+ /**
1405
+ * Put each mark `replacements` maps in its place wherever this agent holds
1406
+ * it (heldReadMarks), so a mark held as two of them stays one: a redo on
1407
+ * the window (#291) puts rows back at new ordinals, past every mark that
1408
+ * covered them before their undo, and gives each such mark again where it
1409
+ * covers them now. It counts as a mark (watermarkMarks), so a compile that
1410
+ * began before it doesn't put its own older mark back over it.
1411
+ */
1412
+ replaceReadMarks(replacements: ReadonlyMap<ReadMark, ReadMark>): void {
1413
+ const swap = (mark: ReadMark | null): ReadMark | null => (mark && replacements.get(mark)) ?? mark;
1414
+ const consumed = swap(this.consumedWatermark);
1415
+ if (consumed !== this.consumedWatermark) {
1416
+ this.consumedWatermark = consumed;
1417
+ this.watermarkMarks++;
1418
+ }
1419
+ this.shownWatermark = swap(this.shownWatermark);
1420
+ this.answeredWatermark = swap(this.answeredWatermark);
1421
+ for (const [destination, mark] of this.publishedThrough) {
1422
+ const next = replacements.get(mark);
1423
+ if (next) this.publishedThrough.set(destination, next);
1424
+ }
1425
+ }
1426
+
1427
+ /** Mark everything in the agent's window (in legacy mode, everything
1428
+ * stored) as consumed: the agent's creation, at boot or later, and a tool
1429
+ * boundary that injected messages into a live stream declaring it carries
1430
+ * them; see consumedWatermark. */
1287
1431
  markContextConsumed(): void {
1288
1432
  // Tolerates partial context-manager doubles (tests build Agents over
1289
1433
  // stubs). Without a watermark, a reader that looks for one takes its own
1290
1434
  // side: the coalescer counts the message as read, and the framework's
1291
- // unreadNoticesForPrimary counts it as unread. A wake for a deferred
1292
- // write or a stored message reads the answered watermark instead
1293
- // (deferredWriteDone, storedMessageDone).
1435
+ // releasedWindow counts it as unread. A wake for a row it reads
1436
+ // (InferenceRequest.readsRow) reads the answered watermark instead
1437
+ // (rowDone).
1294
1438
  this.consumedWatermark = this.pendingWatermark();
1295
1439
  this.watermarkMarks++;
1296
1440
  this.shownWatermark = this.consumedWatermark;
1297
1441
  }
1298
1442
 
1299
- /** Mark everything currently stored as possibly shown (a tool boundary
1300
- * that injected messages into its stream). Without
1301
- * a watermark the coalescer counts a message as shown, as it counts one
1302
- * read without a consumed watermark. */
1443
+ /** Mark everything in the agent's window (in legacy mode, everything
1444
+ * stored) as possibly shown (a tool boundary that injected messages into
1445
+ * its stream). Without a watermark the coalescer counts a message as
1446
+ * shown, as it counts one read without a consumed watermark. */
1303
1447
  markContextShown(): void {
1304
1448
  this.shownWatermark = this.pendingWatermark();
1305
1449
  }
@@ -1313,11 +1457,10 @@ export class Agent {
1313
1457
  * the second then throws, it restores the first's failed claim. Messages
1314
1458
  * up to that claim then count as shown, although no request held those
1315
1459
  * above the watermark the first compile found, which errs toward a marker
1316
- * until the next compile or boundary moves the watermark. */
1317
- private unshowFailedCompile(
1318
- watermark: { branch: string; sequence: number } | null,
1319
- before: { branch: string; sequence: number } | null,
1320
- ): void {
1460
+ * until the next compile or boundary moves the watermark. Each mark is a
1461
+ * fresh object (readMarkOf), so the compare by identity is this compile's
1462
+ * own claim. */
1463
+ private unshowFailedCompile(watermark: ReadMark | null, before: ReadMark | null): void {
1321
1464
  if (watermark && this.shownWatermark === watermark) this.shownWatermark = before;
1322
1465
  }
1323
1466
 
@@ -1328,49 +1471,41 @@ export class Agent {
1328
1471
  * mark's branch, so a boundary's mark is never rolled back by a compile
1329
1472
  * that started before it (a committing compile, such as compileContext's,
1330
1473
  * run during a live turn; a dry run, such as previewActivation's, never
1331
- * reaches here).
1474
+ * reaches here). Marks are ordered by markAtOrAfter: by the window's
1475
+ * index length where both keep one, else by the store's head, each of
1476
+ * which only grows on a branch.
1332
1477
  */
1333
- private applyCompileWatermark(watermark: { branch: string; sequence: number }, marksBefore: number): void {
1478
+ private applyCompileWatermark(watermark: ReadMark, marksBefore: number): void {
1334
1479
  const current = this.consumedWatermark;
1335
- if (this.watermarkMarks !== marksBefore && current
1336
- && (current.branch !== watermark.branch || current.sequence >= watermark.sequence)) return;
1480
+ if (this.watermarkMarks !== marksBefore && current && markAtOrAfter(current, watermark) !== false) return;
1337
1481
  this.consumedWatermark = watermark;
1338
1482
  this.watermarkMarks++;
1339
1483
  }
1340
1484
 
1341
1485
  /**
1342
- * The branch now (`branchKey`) and its head, as `pendingWatermark` reads it: a message stored on that branch later
1343
- * has a larger sequence. Sequences are numbered per branch, so one compares with another only on the same branch.
1486
+ * The branch now (`branchKey`) and the store's head on it (storeHeadOf, the
1487
+ * head a mark pendingWatermark takes keeps): a message stored on that
1488
+ * branch later has a larger sequence. Sequences are numbered per branch, so
1489
+ * one compares with another only on the same branch. A record sequence, not
1490
+ * a place in the agent's window (#291).
1344
1491
  */
1345
1492
  branchHead(): { branch: string; sequence: number } | undefined {
1346
- const head = this.pendingWatermark();
1493
+ const sequence = storeHeadOf(this.contextManager);
1347
1494
  const cm = this.contextManager as Partial<ContextManager>;
1348
- if (!head || typeof cm.currentBranchRef !== 'function') return undefined;
1495
+ if (sequence === null || typeof cm.currentBranchRef !== 'function') return undefined;
1349
1496
  try {
1350
- return { branch: branchKey(cm.currentBranchRef()), sequence: head.sequence };
1497
+ return { branch: branchKey(cm.currentBranchRef()), sequence };
1351
1498
  } catch {
1352
1499
  return undefined;
1353
1500
  }
1354
1501
  }
1355
1502
 
1356
- /** The watermark a successful compile will establish (taken before, applied after). */
1357
- private pendingWatermark(): { branch: string; sequence: number } | null {
1358
- const cm = this.contextManager as Partial<ContextManager>;
1359
- try {
1360
- if (typeof cm.currentBranch !== 'function' || typeof cm.getAllMessages !== 'function') return null;
1361
- // The chronicle head, not this slot's last message: a compile reads
1362
- // everything stored on the branch so far, including messages merged in
1363
- // from another slot (the subconscious reads the residents' shared slot).
1364
- const store = typeof cm.getStore === 'function' ? cm.getStore() as { currentSequence?: () => number } : undefined;
1365
- let sequence = typeof store?.currentSequence === 'function' ? store.currentSequence() : undefined;
1366
- if (sequence === undefined) {
1367
- const count = typeof cm.getMessageCount === 'function' ? cm.getMessageCount() : cm.getAllMessages().length;
1368
- sequence = count > 0 ? cm.getAllMessages().at(-1)?.sequence ?? 0 : 0;
1369
- }
1370
- return { branch: cm.currentBranch().name, sequence };
1371
- } catch {
1372
- return null;
1373
- }
1503
+ /** The watermark a successful compile will establish (taken before, applied
1504
+ * after): where the agent's context stands now (readMarkOf), its window's
1505
+ * index length with the store's head once it presents rows. Null for a
1506
+ * context double that can't say. */
1507
+ private pendingWatermark(): ReadMark | null {
1508
+ return readMarkOf(this.contextManager);
1374
1509
  }
1375
1510
 
1376
1511
  /** A committing compile, as for a request that is sent: the context
@@ -1413,9 +1548,10 @@ export class Agent {
1413
1548
  */
1414
1549
  private renderedInjections?: {
1415
1550
  branch: { readonly id: string; readonly created: number };
1416
- /** Per namespace, per content hash: the parts any copy of that content
1417
- * holds, how many it has, and its newest copy's sequence. */
1418
- byNamespace: Map<string, Map<string, { parts: number; held: Set<number>; sequence: number }>>;
1551
+ /** Per namespace, per content hash: each copy of that content the
1552
+ * compile rendered, by its row's id, with the part it is, how many
1553
+ * parts the content has, whether the compile held it, and its sequence. */
1554
+ byNamespace: Map<string, Map<string, RenderedInjectionCopy[]>>;
1419
1555
  };
1420
1556
 
1421
1557
  /**
@@ -1446,9 +1582,8 @@ export class Agent {
1446
1582
  /**
1447
1583
  * A host's hook, called with each committing compile's `thinkingStrip` (never a dry run's) and the key of the
1448
1584
  * branch it read (`branchKey`), as the compile returns, whether or not its request is then sent, since the
1449
- * context manager's binding record takes the strip either way. One that throws is logged. The framework sets it
1450
- * at the agent's first stream unless a host has set one, so a host's own observer stands in for the framework's,
1451
- * whose `[reasoning]` line for the agent then isn't told.
1585
+ * context manager's binding record takes the strip either way. One that throws is logged. The framework sets none
1586
+ * of its own, and tells the agent nothing of a strip.
1452
1587
  */
1453
1588
  onThinkingStrip?: (strip: ThinkingStrip, branch: string) => void;
1454
1589
 
@@ -1462,8 +1597,16 @@ export class Agent {
1462
1597
  * start and the next (a host's own committing compile, or the compression
1463
1598
  * an over-budget retry waits for) can summarize them all the same: the
1464
1599
  * next request then has the summary's word for them, and the start after
1465
- * it stores them again, as after any fold. The window is read back 64
1466
- * rows at a time from the newest, stopping at that point.
1600
+ * it stores them again, as after any fold. The slot's record is read
1601
+ * back 64 rows at a time from the newest, stopping at that point.
1602
+ *
1603
+ * It reads the record, by sequence, not the agent's window, and that
1604
+ * holds once the context presents rows (#291): the rows it counts are
1605
+ * this agent's own stored injections, its own rows, which enter its
1606
+ * window as they are written, so one stored after the point is in no
1607
+ * request yet whatever else has been stored or presented since. Another
1608
+ * agent's rows, stored into a shared slot at once rather than queued, are
1609
+ * passed over, whatever they are.
1467
1610
  */
1468
1611
  unsentInjection(namespace: string): { contentHash: string; whole: boolean } | undefined {
1469
1612
  let newest: { contentHash: string; parts: number; held: Set<number> } | undefined;
@@ -1484,6 +1627,9 @@ export class Agent {
1484
1627
  if (row.sequence <= since) break scan;
1485
1628
  const mark = storedInjectionMarkOf(row.metadata);
1486
1629
  if (mark?.agent !== this.name || mark.namespace !== namespace) continue;
1630
+ // A copy an undo took out of the window (#291) is in no window
1631
+ // now, so it isn't the copy the agent will read.
1632
+ if (this.contextManager.windowMode?.() === 'presented' && this.contextManager.windowStatusOf(row.id) === 'undone') continue;
1487
1633
  // Read from the newest back, the first copy met is the newest
1488
1634
  // content; its other parts pool with it.
1489
1635
  newest ??= { contentHash: mark.contentHash, parts: mark.parts, held: new Set() };
@@ -1534,7 +1680,7 @@ export class Agent {
1534
1680
  this.renderedInjections = undefined;
1535
1681
  return;
1536
1682
  }
1537
- const byNamespace = new Map<string, Map<string, { parts: number; held: Set<number>; sequence: number }>>();
1683
+ const byNamespace = new Map<string, Map<string, RenderedInjectionCopy[]>>();
1538
1684
  for (const [i, source] of provenance.messages.entries()) {
1539
1685
  // Only a message rendered as an injection's participant can be one, so
1540
1686
  // the store is read for those alone.
@@ -1548,14 +1694,11 @@ export class Agent {
1548
1694
  // would only add copies, every start. A part missing shards still
1549
1695
  // says which content is newest.
1550
1696
  const held = body.complete || (body.missing !== undefined && !body.missing.includes('shards'));
1551
- const contents = byNamespace.get(mark.namespace) ?? new Map();
1697
+ const contents = byNamespace.get(mark.namespace) ?? new Map<string, RenderedInjectionCopy[]>();
1552
1698
  byNamespace.set(mark.namespace, contents);
1553
- // Copies of the same content in view pool what they hold: the agent
1554
- // has each part that one of them holds.
1555
- const known = contents.get(mark.contentHash) ?? { parts: mark.parts, held: new Set<number>(), sequence: body.sequence };
1556
- if (held) known.held.add(mark.part);
1557
- known.sequence = Math.max(known.sequence, body.sequence);
1558
- contents.set(mark.contentHash, known);
1699
+ const copies = contents.get(mark.contentHash) ?? [];
1700
+ copies.push({ id: body.messageId, part: mark.part, parts: mark.parts, held, sequence: body.sequence });
1701
+ contents.set(mark.contentHash, copies);
1559
1702
  }
1560
1703
  }
1561
1704
  this.renderedInjections = { branch: provenance.branch, byNamespace };
@@ -1565,19 +1708,48 @@ export class Agent {
1565
1708
  * The newest content of `namespace` among this agent's stored injections
1566
1709
  * that the last compile built to send rendered, by its newest copy: its
1567
1710
  * content hash, and whether every part of that content was there in some
1568
- * copy, every shard, however the strategy shaped it. Undefined when none
1569
- * was, when this agent hasn't built a request since it started, or when
1570
- * the window's branch has changed since.
1711
+ * copy, every shard, however the strategy shaped it. Copies of the same
1712
+ * content pool what they hold: the agent has each part one of them holds.
1713
+ * A copy the agent's next request won't hold is passed over: one an undo
1714
+ * took out of its window since (#291: `'undone'`, which no presentation
1715
+ * point presents again until a redo), and one no longer stored. Undefined
1716
+ * when no copy is left, when this agent hasn't built a request since it
1717
+ * started, or when the window's branch has changed since.
1571
1718
  */
1572
1719
  renderedInjection(namespace: string): { contentHash: string; whole: boolean } | undefined {
1573
1720
  const rendered = this.renderedInjections;
1574
1721
  const branch = this.contextManager.currentBranchRef();
1575
1722
  if (!rendered || rendered.branch.id !== branch.id || rendered.branch.created !== branch.created) return undefined;
1576
- let newest: [string, { parts: number; held: Set<number>; sequence: number }] | undefined;
1577
- for (const entry of rendered.byNamespace.get(namespace) ?? []) {
1578
- if (!newest || entry[1].sequence > newest[1].sequence) newest = entry;
1723
+ let newest: { contentHash: string; parts: number; held: Set<number>; sequence: number } | undefined;
1724
+ for (const [contentHash, copies] of rendered.byNamespace.get(namespace) ?? []) {
1725
+ let pooled: { parts: number; held: Set<number>; sequence: number } | undefined;
1726
+ for (const copy of copies) {
1727
+ if (!this.renderedCopyStands(copy.id)) continue;
1728
+ pooled ??= { parts: copy.parts, held: new Set(), sequence: copy.sequence };
1729
+ if (copy.held) pooled.held.add(copy.part);
1730
+ pooled.sequence = Math.max(pooled.sequence, copy.sequence);
1731
+ }
1732
+ if (pooled && (!newest || pooled.sequence > newest.sequence)) newest = { contentHash, ...pooled };
1733
+ }
1734
+ return newest ? { contentHash: newest.contentHash, whole: newest.held.size === newest.parts } : undefined;
1735
+ }
1736
+
1737
+ /**
1738
+ * Whether a stored injection's copy the last compile rendered is still
1739
+ * where the agent's next request reads it: stored, and not taken out of
1740
+ * its window by an undo (#291). A copy out of the window otherwise
1741
+ * (`'out'`, as a redo leaves one presented since its undo) is presented
1742
+ * again at the agent's next point, its own rows' sweep, so it counts. A
1743
+ * context that can't say (a double without windowStatusOf, a read that
1744
+ * throws) counts it, as before the window was presented.
1745
+ */
1746
+ private renderedCopyStands(id: MessageId): boolean {
1747
+ try {
1748
+ const status = this.contextManager.windowStatusOf?.(id);
1749
+ return status !== 'undone' && status !== 'absent';
1750
+ } catch {
1751
+ return true;
1579
1752
  }
1580
- return newest ? { contentHash: newest[0], whole: newest[1].held.size === newest[1].parts } : undefined;
1581
1753
  }
1582
1754
 
1583
1755
  /**
@@ -1747,7 +1919,7 @@ export class Agent {
1747
1919
  const { systemInjections } = compiled;
1748
1920
  // The framework's own pricing stamp never goes out (thinking-token-stamp.ts);
1749
1921
  // the agent's own turns are then presented under its name (presentOwnTurns).
1750
- const messages = this.presentOwnTurns(withoutTokenEstimates(compiled.messages));
1922
+ const messages = this.presentOwnTurns(this.foreignRoundsAsSent(withoutTokenEstimates(compiled.messages)));
1751
1923
  // Read once, after the first compile, since results may have become
1752
1924
  // ready while it ran; the refusal retry, assembled while the agent is
1753
1925
  // inferring, sends the same ones.
@@ -1989,6 +2161,8 @@ export class Agent {
1989
2161
  // compression path.)
1990
2162
  // The same pass a tool boundary applies to what it notes as sent
1991
2163
  // (sent-rows.ts), so the two can't drift.
2164
+ // Another agent's rounds go out as this agent is sent them (setForeignRounds).
2165
+ messages = this.foreignRoundsAsSent(messages);
1992
2166
  messages = messages
1993
2167
  .map((m) => ({ ...m, content: m.content.filter(carriedBlock) }))
1994
2168
  .filter((m) => m.content.length > 0);
@@ -2157,17 +2331,23 @@ export class Agent {
2157
2331
  return (await this.assembleActivationRequest(availableTools, injections, budget, compressionTools, options)).request;
2158
2332
  }
2159
2333
 
2160
- /** Stored rows that follow a request-only turn, memoized on the store's
2161
- * cached message array (ContextManager returns the same array until the
2162
- * store changes). */
2334
+ /** Stored rows that follow a request-only turn, memoized on the window's
2335
+ * cached row array (ContextManager returns the same array until the
2336
+ * window changes): the rows a compile sends are the window's (#291), and
2337
+ * a write another agent stores, which the window doesn't take, leaves the
2338
+ * memo as it was. A context without a window reads the record, the same
2339
+ * array as the window's for a single slot that is still the record. */
2163
2340
  private requestOnlyIndex: { source: readonly unknown[]; index: RequestOnlyRowIndex } | null = null;
2164
2341
 
2165
2342
  private indexRequestOnlyRows(): RequestOnlyRowIndex {
2166
2343
  const empty: RequestOnlyRowIndex = { ticks: new Map(), prompts: new Map() };
2167
2344
  const cm = this.contextManager as Partial<ContextManager>;
2168
- if (typeof cm.getAllMessages !== 'function') return empty;
2345
+ const read = typeof cm.getPresentedMessages === 'function' ? () => cm.getPresentedMessages!()
2346
+ : typeof cm.getAllMessages === 'function' ? () => cm.getAllMessages!()
2347
+ : undefined;
2348
+ if (!read) return empty;
2169
2349
  try {
2170
- const stored = cm.getAllMessages() as unknown as readonly StoredRowLike[];
2350
+ const stored = read() as unknown as readonly StoredRowLike[];
2171
2351
  if (this.requestOnlyIndex?.source === stored) return this.requestOnlyIndex.index;
2172
2352
  const index = indexRequestOnlyRows(stored, this.ownParticipants);
2173
2353
  this.requestOnlyIndex = { source: stored, index };
@@ -2382,7 +2562,9 @@ export class Agent {
2382
2562
  );
2383
2563
  return undefined;
2384
2564
  }
2385
- return this.contextManager.addMessage(this.name, content, metadata);
2565
+ // The agent's own round, stamped as its own (#291's audience stamp); its
2566
+ // id is the stream's last reply for the thinking binding (noteStreamEnd).
2567
+ return storeOwnRow(this, this.name, content, metadata);
2386
2568
  }
2387
2569
 
2388
2570
  /**
@@ -2492,6 +2674,27 @@ export class Agent {
2492
2674
  return this.contextManager;
2493
2675
  }
2494
2676
 
2677
+ /** Where this agent tells of its own rows once they are stored (setOwnRowSink). */
2678
+ private ownRowSink?: (ids: readonly MessageId[]) => void;
2679
+
2680
+ /**
2681
+ * Install where this agent tells of its own rows once they are stored
2682
+ * (its rounds and the rows the framework writes for it through
2683
+ * storeOwnRow, and its tool results through its guard), or take it off
2684
+ * with undefined. The presentation index (#291) installs one when it takes
2685
+ * the agent on, and presents each row in the agent's window as it is
2686
+ * told. With none, as for an agent nothing took on, nothing is told.
2687
+ */
2688
+ setOwnRowSink(sink: ((ids: readonly MessageId[]) => void) | undefined): void {
2689
+ this.ownRowSink = sink;
2690
+ this.toolResultGuard.setOwnRowSink(sink);
2691
+ }
2692
+
2693
+ /** Tell the sink of own rows just stored (storeOwnRow calls it, once each add has returned). */
2694
+ ownRowsStored(ids: readonly MessageId[]): void {
2695
+ this.ownRowSink?.(ids);
2696
+ }
2697
+
2495
2698
  /**
2496
2699
  * Build the effective system prompt, appending any system-position
2497
2700
  * injections, their blocks joined by line breaks. The system prompt is
@@ -2583,8 +2786,8 @@ export class Agent {
2583
2786
  const content = guardedRefusal ? [] : response.content;
2584
2787
  const { toolCalls, speechContent } = this.extractToolCallsAndSpeech(content);
2585
2788
 
2586
- // Add assistant response to context
2587
- if (!guardedRefusal) this.contextManager.addMessage(this.name, content);
2789
+ // Add assistant response to context, as the agent's own row
2790
+ if (!guardedRefusal) storeOwnRow(this, this.name, content);
2588
2791
 
2589
2792
  return {
2590
2793
  toolCalls,