@animalabs/agent-framework 0.32.0 → 0.34.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 (368) hide show
  1. package/README.md +340 -22
  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 +208 -80
  7. package/dist/src/agent.d.ts.map +1 -1
  8. package/dist/src/agent.js +345 -113
  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/append-all.d.ts +5 -0
  17. package/dist/src/append-all.d.ts.map +1 -0
  18. package/dist/src/append-all.js +8 -0
  19. package/dist/src/append-all.js.map +1 -0
  20. package/dist/src/code-execution/isolate/scripts.d.ts +12 -8
  21. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
  22. package/dist/src/code-execution/isolate/scripts.js +24 -0
  23. package/dist/src/code-execution/isolate/scripts.js.map +1 -1
  24. package/dist/src/config-checks.d.ts +7 -0
  25. package/dist/src/config-checks.d.ts.map +1 -1
  26. package/dist/src/config-checks.js +14 -0
  27. package/dist/src/config-checks.js.map +1 -1
  28. package/dist/src/context-manager-errors.d.ts +25 -0
  29. package/dist/src/context-manager-errors.d.ts.map +1 -0
  30. package/dist/src/context-manager-errors.js +31 -0
  31. package/dist/src/context-manager-errors.js.map +1 -0
  32. package/dist/src/context-options.d.ts +70 -0
  33. package/dist/src/context-options.d.ts.map +1 -0
  34. package/dist/src/context-options.js +90 -0
  35. package/dist/src/context-options.js.map +1 -0
  36. package/dist/src/cut-off-round.js +4 -4
  37. package/dist/src/deferred-import.d.ts +93 -0
  38. package/dist/src/deferred-import.d.ts.map +1 -0
  39. package/dist/src/deferred-import.js +177 -0
  40. package/dist/src/deferred-import.js.map +1 -0
  41. package/dist/src/dendrite/audience.d.ts +223 -0
  42. package/dist/src/dendrite/audience.d.ts.map +1 -0
  43. package/dist/src/dendrite/audience.js +202 -0
  44. package/dist/src/dendrite/audience.js.map +1 -0
  45. package/dist/src/dendrite/index.d.ts +5 -3
  46. package/dist/src/dendrite/index.d.ts.map +1 -1
  47. package/dist/src/dendrite/index.js +2 -1
  48. package/dist/src/dendrite/index.js.map +1 -1
  49. package/dist/src/dendrite/presets.d.ts +10 -5
  50. package/dist/src/dendrite/presets.d.ts.map +1 -1
  51. package/dist/src/dendrite/presets.js +17 -5
  52. package/dist/src/dendrite/presets.js.map +1 -1
  53. package/dist/src/dendrite/registry.d.ts +62 -7
  54. package/dist/src/dendrite/registry.d.ts.map +1 -1
  55. package/dist/src/dendrite/registry.js +183 -12
  56. package/dist/src/dendrite/registry.js.map +1 -1
  57. package/dist/src/dendrite/types.d.ts +91 -8
  58. package/dist/src/dendrite/types.d.ts.map +1 -1
  59. package/dist/src/failure-text.d.ts +36 -0
  60. package/dist/src/failure-text.d.ts.map +1 -0
  61. package/dist/src/failure-text.js +89 -0
  62. package/dist/src/failure-text.js.map +1 -0
  63. package/dist/src/foreign-rounds.d.ts +69 -0
  64. package/dist/src/foreign-rounds.d.ts.map +1 -0
  65. package/dist/src/foreign-rounds.js +152 -0
  66. package/dist/src/foreign-rounds.js.map +1 -0
  67. package/dist/src/framework.d.ts +2064 -842
  68. package/dist/src/framework.d.ts.map +1 -1
  69. package/dist/src/framework.js +8586 -3739
  70. package/dist/src/framework.js.map +1 -1
  71. package/dist/src/gate/event-gate.d.ts +125 -21
  72. package/dist/src/gate/event-gate.d.ts.map +1 -1
  73. package/dist/src/gate/event-gate.js +487 -72
  74. package/dist/src/gate/event-gate.js.map +1 -1
  75. package/dist/src/gate/index.d.ts +1 -1
  76. package/dist/src/gate/index.d.ts.map +1 -1
  77. package/dist/src/gate/index.js.map +1 -1
  78. package/dist/src/gate/reply-judge.d.ts +4 -0
  79. package/dist/src/gate/reply-judge.d.ts.map +1 -1
  80. package/dist/src/gate/reply-judge.js +12 -3
  81. package/dist/src/gate/reply-judge.js.map +1 -1
  82. package/dist/src/gate/rule-scope.d.ts +30 -0
  83. package/dist/src/gate/rule-scope.d.ts.map +1 -0
  84. package/dist/src/gate/rule-scope.js +41 -0
  85. package/dist/src/gate/rule-scope.js.map +1 -0
  86. package/dist/src/gate/suppress-script-worker.d.ts +2 -0
  87. package/dist/src/gate/suppress-script-worker.d.ts.map +1 -0
  88. package/dist/src/gate/suppress-script-worker.js +103 -0
  89. package/dist/src/gate/suppress-script-worker.js.map +1 -0
  90. package/dist/src/gate/suppress-scripts.d.ts +105 -0
  91. package/dist/src/gate/suppress-scripts.d.ts.map +1 -0
  92. package/dist/src/gate/suppress-scripts.js +403 -0
  93. package/dist/src/gate/suppress-scripts.js.map +1 -0
  94. package/dist/src/gate/suppression-review.d.ts +143 -0
  95. package/dist/src/gate/suppression-review.d.ts.map +1 -0
  96. package/dist/src/gate/suppression-review.js +463 -0
  97. package/dist/src/gate/suppression-review.js.map +1 -0
  98. package/dist/src/gate/suppression-stats.d.ts +125 -0
  99. package/dist/src/gate/suppression-stats.d.ts.map +1 -0
  100. package/dist/src/gate/suppression-stats.js +214 -0
  101. package/dist/src/gate/suppression-stats.js.map +1 -0
  102. package/dist/src/gate/types.d.ts +142 -7
  103. package/dist/src/gate/types.d.ts.map +1 -1
  104. package/dist/src/gate/wake-onboarding.d.ts +119 -0
  105. package/dist/src/gate/wake-onboarding.d.ts.map +1 -0
  106. package/dist/src/gate/wake-onboarding.js +277 -0
  107. package/dist/src/gate/wake-onboarding.js.map +1 -0
  108. package/dist/src/heartbeat/heartbeat.js +1 -1
  109. package/dist/src/heartbeat/heartbeat.js.map +1 -1
  110. package/dist/src/held-writes.d.ts +31 -23
  111. package/dist/src/held-writes.d.ts.map +1 -1
  112. package/dist/src/held-writes.js +44 -59
  113. package/dist/src/held-writes.js.map +1 -1
  114. package/dist/src/index.d.ts +6 -2
  115. package/dist/src/index.d.ts.map +1 -1
  116. package/dist/src/index.js +4 -0
  117. package/dist/src/index.js.map +1 -1
  118. package/dist/src/inference-bounds.d.ts +2 -1
  119. package/dist/src/inference-bounds.d.ts.map +1 -1
  120. package/dist/src/inference-bounds.js +9 -6
  121. package/dist/src/inference-bounds.js.map +1 -1
  122. package/dist/src/jsonrpc-envelope.d.ts +69 -0
  123. package/dist/src/jsonrpc-envelope.d.ts.map +1 -0
  124. package/dist/src/jsonrpc-envelope.js +66 -0
  125. package/dist/src/jsonrpc-envelope.js.map +1 -0
  126. package/dist/src/mcpl/channel-registry.d.ts +188 -28
  127. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  128. package/dist/src/mcpl/channel-registry.js +426 -70
  129. package/dist/src/mcpl/channel-registry.js.map +1 -1
  130. package/dist/src/mcpl/checkpoint-manager.d.ts +2 -4
  131. package/dist/src/mcpl/checkpoint-manager.d.ts.map +1 -1
  132. package/dist/src/mcpl/checkpoint-manager.js.map +1 -1
  133. package/dist/src/mcpl/feature-set-manager.d.ts +2 -4
  134. package/dist/src/mcpl/feature-set-manager.d.ts.map +1 -1
  135. package/dist/src/mcpl/feature-set-manager.js.map +1 -1
  136. package/dist/src/mcpl/inbound-source.d.ts +2 -1
  137. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  138. package/dist/src/mcpl/inbound-source.js +2 -1
  139. package/dist/src/mcpl/inbound-source.js.map +1 -1
  140. package/dist/src/mcpl/inference-router.d.ts +6 -6
  141. package/dist/src/mcpl/inference-router.d.ts.map +1 -1
  142. package/dist/src/mcpl/inference-router.js.map +1 -1
  143. package/dist/src/mcpl/modern-connection.d.ts +109 -15
  144. package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
  145. package/dist/src/mcpl/modern-connection.js +593 -86
  146. package/dist/src/mcpl/modern-connection.js.map +1 -1
  147. package/dist/src/mcpl/protocol-family.d.ts +3 -1
  148. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  149. package/dist/src/mcpl/protocol-family.js +15 -9
  150. package/dist/src/mcpl/protocol-family.js.map +1 -1
  151. package/dist/src/mcpl/push-coalescer.d.ts +132 -22
  152. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  153. package/dist/src/mcpl/push-coalescer.js +105 -15
  154. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  155. package/dist/src/mcpl/push-handler.d.ts +9 -7
  156. package/dist/src/mcpl/push-handler.d.ts.map +1 -1
  157. package/dist/src/mcpl/push-handler.js.map +1 -1
  158. package/dist/src/mcpl/server-connection.d.ts +50 -28
  159. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  160. package/dist/src/mcpl/server-connection.js +165 -72
  161. package/dist/src/mcpl/server-connection.js.map +1 -1
  162. package/dist/src/mcpl/tool-classes.d.ts.map +1 -1
  163. package/dist/src/mcpl/tool-classes.js +3 -0
  164. package/dist/src/mcpl/tool-classes.js.map +1 -1
  165. package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
  166. package/dist/src/mcpl/tool-presence.js +10 -6
  167. package/dist/src/mcpl/tool-presence.js.map +1 -1
  168. package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
  169. package/dist/src/mcpl/toolset-host.js +7 -6
  170. package/dist/src/mcpl/toolset-host.js.map +1 -1
  171. package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
  172. package/dist/src/mcpl/toolsets-module.js +2 -1
  173. package/dist/src/mcpl/toolsets-module.js.map +1 -1
  174. package/dist/src/mcpl/types.d.ts +33 -6
  175. package/dist/src/mcpl/types.d.ts.map +1 -1
  176. package/dist/src/mcpl/types.js.map +1 -1
  177. package/dist/src/module-registry.d.ts +24 -1
  178. package/dist/src/module-registry.d.ts.map +1 -1
  179. package/dist/src/module-registry.js +68 -18
  180. package/dist/src/module-registry.js.map +1 -1
  181. package/dist/src/modules/discord/index.d.ts +45 -8
  182. package/dist/src/modules/discord/index.d.ts.map +1 -1
  183. package/dist/src/modules/discord/index.js +238 -87
  184. package/dist/src/modules/discord/index.js.map +1 -1
  185. package/dist/src/modules/health/index.js +2 -2
  186. package/dist/src/modules/health/index.js.map +1 -1
  187. package/dist/src/modules/history/index.d.ts +59 -0
  188. package/dist/src/modules/history/index.d.ts.map +1 -1
  189. package/dist/src/modules/history/index.js +432 -67
  190. package/dist/src/modules/history/index.js.map +1 -1
  191. package/dist/src/modules/history/semantic.d.ts +6 -5
  192. package/dist/src/modules/history/semantic.d.ts.map +1 -1
  193. package/dist/src/modules/history/semantic.js +18 -25
  194. package/dist/src/modules/history/semantic.js.map +1 -1
  195. package/dist/src/modules/workspace/disk-agreement.d.ts +1 -1
  196. package/dist/src/modules/workspace/disk-agreement.js +1 -1
  197. package/dist/src/modules/workspace/index.d.ts +28 -7
  198. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  199. package/dist/src/modules/workspace/index.js +80 -25
  200. package/dist/src/modules/workspace/index.js.map +1 -1
  201. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  202. package/dist/src/modules/workspace/observe.js +2 -1
  203. package/dist/src/modules/workspace/observe.js.map +1 -1
  204. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  205. package/dist/src/modules/workspace/reconcile.js +3 -2
  206. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  207. package/dist/src/notice-tags.d.ts +20 -9
  208. package/dist/src/notice-tags.d.ts.map +1 -1
  209. package/dist/src/notice-tags.js +20 -9
  210. package/dist/src/notice-tags.js.map +1 -1
  211. package/dist/src/operator-log.d.ts +36 -3
  212. package/dist/src/operator-log.d.ts.map +1 -1
  213. package/dist/src/operator-log.js +2 -2
  214. package/dist/src/operator-log.js.map +1 -1
  215. package/dist/src/presentation.d.ts +747 -0
  216. package/dist/src/presentation.d.ts.map +1 -0
  217. package/dist/src/presentation.js +1437 -0
  218. package/dist/src/presentation.js.map +1 -0
  219. package/dist/src/prose-drafts.d.ts +15 -11
  220. package/dist/src/prose-drafts.d.ts.map +1 -1
  221. package/dist/src/prose-drafts.js +16 -12
  222. package/dist/src/prose-drafts.js.map +1 -1
  223. package/dist/src/read-marks.d.ts +192 -0
  224. package/dist/src/read-marks.d.ts.map +1 -0
  225. package/dist/src/read-marks.js +242 -0
  226. package/dist/src/read-marks.js.map +1 -0
  227. package/dist/src/reasoning-loss.d.ts +5 -0
  228. package/dist/src/reasoning-loss.d.ts.map +1 -0
  229. package/dist/src/reasoning-loss.js +8 -0
  230. package/dist/src/reasoning-loss.js.map +1 -0
  231. package/dist/src/recovery/offline-branch.d.ts +91 -6
  232. package/dist/src/recovery/offline-branch.d.ts.map +1 -1
  233. package/dist/src/recovery/offline-branch.js +277 -32
  234. package/dist/src/recovery/offline-branch.js.map +1 -1
  235. package/dist/src/recovery/recover-cli.js +24 -4
  236. package/dist/src/recovery/recover-cli.js.map +1 -1
  237. package/dist/src/send-tools.d.ts +5 -9
  238. package/dist/src/send-tools.d.ts.map +1 -1
  239. package/dist/src/send-tools.js +40 -7
  240. package/dist/src/send-tools.js.map +1 -1
  241. package/dist/src/sent-rows.js +1 -1
  242. package/dist/src/stored-injections.d.ts +2 -2
  243. package/dist/src/stored-injections.js +2 -2
  244. package/dist/src/stored-removal.d.ts +17 -4
  245. package/dist/src/stored-removal.d.ts.map +1 -1
  246. package/dist/src/stored-removal.js +29 -5
  247. package/dist/src/stored-removal.js.map +1 -1
  248. package/dist/src/supplied-text.d.ts +1 -1
  249. package/dist/src/supplied-text.js +3 -3
  250. package/dist/src/supplied-text.js.map +1 -1
  251. package/dist/src/thinking-drops.d.ts +2 -0
  252. package/dist/src/thinking-drops.d.ts.map +1 -1
  253. package/dist/src/thinking-drops.js.map +1 -1
  254. package/dist/src/tool-presentation.d.ts.map +1 -1
  255. package/dist/src/tool-presentation.js +38 -9
  256. package/dist/src/tool-presentation.js.map +1 -1
  257. package/dist/src/tool-result-guard.d.ts +12 -1
  258. package/dist/src/tool-result-guard.d.ts.map +1 -1
  259. package/dist/src/tool-result-guard.js +42 -8
  260. package/dist/src/tool-result-guard.js.map +1 -1
  261. package/dist/src/tune-out/coordinator.d.ts +1170 -81
  262. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  263. package/dist/src/tune-out/coordinator.js +3732 -369
  264. package/dist/src/tune-out/coordinator.js.map +1 -1
  265. package/dist/src/tune-out/tools.d.ts +114 -17
  266. package/dist/src/tune-out/tools.d.ts.map +1 -1
  267. package/dist/src/tune-out/tools.js +89 -28
  268. package/dist/src/tune-out/tools.js.map +1 -1
  269. package/dist/src/tune-out/window-anchor.d.ts +143 -0
  270. package/dist/src/tune-out/window-anchor.d.ts.map +1 -0
  271. package/dist/src/tune-out/window-anchor.js +202 -0
  272. package/dist/src/tune-out/window-anchor.js.map +1 -0
  273. package/dist/src/types/agent.d.ts +121 -25
  274. package/dist/src/types/agent.d.ts.map +1 -1
  275. package/dist/src/types/events.d.ts +2 -0
  276. package/dist/src/types/events.d.ts.map +1 -1
  277. package/dist/src/types/framework.d.ts +72 -17
  278. package/dist/src/types/framework.d.ts.map +1 -1
  279. package/dist/src/types/inference-log.d.ts +1 -0
  280. package/dist/src/types/inference-log.d.ts.map +1 -1
  281. package/dist/src/types/module.d.ts +180 -69
  282. package/dist/src/types/module.d.ts.map +1 -1
  283. package/dist/src/types/trace.d.ts +573 -39
  284. package/dist/src/types/trace.d.ts.map +1 -1
  285. package/package.json +4 -4
  286. package/src/admission/admit.ts +32 -3
  287. package/src/agent.ts +392 -132
  288. package/src/api/mcp-server-core.ts +54 -17
  289. package/src/api/server.ts +4 -23
  290. package/src/append-all.ts +6 -0
  291. package/src/code-execution/isolate/scripts.ts +28 -3
  292. package/src/config-checks.ts +14 -0
  293. package/src/context-manager-errors.ts +37 -0
  294. package/src/context-options.ts +106 -0
  295. package/src/cut-off-round.ts +4 -4
  296. package/src/deferred-import.ts +239 -0
  297. package/src/dendrite/audience.ts +315 -0
  298. package/src/dendrite/index.ts +14 -2
  299. package/src/dendrite/presets.ts +18 -6
  300. package/src/dendrite/registry.ts +213 -14
  301. package/src/dendrite/types.ts +92 -8
  302. package/src/failure-text.ts +88 -0
  303. package/src/foreign-rounds.ts +199 -0
  304. package/src/framework.ts +8796 -3813
  305. package/src/gate/event-gate.ts +518 -91
  306. package/src/gate/index.ts +3 -0
  307. package/src/gate/reply-judge.ts +12 -3
  308. package/src/gate/rule-scope.ts +45 -0
  309. package/src/gate/suppress-script-worker.ts +89 -0
  310. package/src/gate/suppress-scripts.ts +414 -0
  311. package/src/gate/suppression-review.ts +517 -0
  312. package/src/gate/suppression-stats.ts +314 -0
  313. package/src/gate/types.ts +148 -7
  314. package/src/gate/wake-onboarding.ts +309 -0
  315. package/src/heartbeat/heartbeat.ts +1 -1
  316. package/src/held-writes.ts +49 -60
  317. package/src/index.ts +10 -2
  318. package/src/inference-bounds.ts +11 -6
  319. package/src/jsonrpc-envelope.ts +81 -0
  320. package/src/mcpl/channel-registry.ts +513 -116
  321. package/src/mcpl/checkpoint-manager.ts +3 -2
  322. package/src/mcpl/feature-set-manager.ts +2 -1
  323. package/src/mcpl/inbound-source.ts +2 -1
  324. package/src/mcpl/inference-router.ts +7 -4
  325. package/src/mcpl/modern-connection.ts +629 -91
  326. package/src/mcpl/protocol-family.ts +15 -9
  327. package/src/mcpl/push-coalescer.ts +193 -34
  328. package/src/mcpl/push-handler.ts +10 -5
  329. package/src/mcpl/server-connection.ts +169 -75
  330. package/src/mcpl/tool-classes.ts +3 -0
  331. package/src/mcpl/tool-presence.ts +10 -5
  332. package/src/mcpl/toolset-host.ts +7 -6
  333. package/src/mcpl/toolsets-module.ts +2 -1
  334. package/src/mcpl/types.ts +33 -6
  335. package/src/module-registry.ts +73 -17
  336. package/src/modules/discord/index.ts +230 -82
  337. package/src/modules/health/index.ts +2 -2
  338. package/src/modules/history/index.ts +439 -70
  339. package/src/modules/history/semantic.ts +18 -24
  340. package/src/modules/workspace/disk-agreement.ts +1 -1
  341. package/src/modules/workspace/index.ts +89 -26
  342. package/src/modules/workspace/observe.ts +2 -1
  343. package/src/modules/workspace/reconcile.ts +3 -2
  344. package/src/notice-tags.ts +21 -9
  345. package/src/operator-log.ts +36 -3
  346. package/src/presentation.ts +1678 -0
  347. package/src/prose-drafts.ts +16 -12
  348. package/src/read-marks.ts +328 -0
  349. package/src/reasoning-loss.ts +7 -0
  350. package/src/recovery/offline-branch.ts +350 -35
  351. package/src/recovery/recover-cli.ts +23 -4
  352. package/src/send-tools.ts +40 -7
  353. package/src/sent-rows.ts +1 -1
  354. package/src/stored-injections.ts +2 -2
  355. package/src/stored-removal.ts +31 -7
  356. package/src/supplied-text.ts +3 -3
  357. package/src/thinking-drops.ts +2 -0
  358. package/src/tool-presentation.ts +34 -9
  359. package/src/tool-result-guard.ts +41 -7
  360. package/src/tune-out/coordinator.ts +4121 -472
  361. package/src/tune-out/tools.ts +162 -44
  362. package/src/tune-out/window-anchor.ts +277 -0
  363. package/src/types/agent.ts +122 -25
  364. package/src/types/events.ts +2 -0
  365. package/src/types/framework.ts +73 -17
  366. package/src/types/inference-log.ts +1 -0
  367. package/src/types/module.ts +181 -68
  368. package/src/types/trace.ts +607 -43
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,
@@ -32,6 +35,7 @@ import {
32
35
  truncateForHistory,
33
36
  DEFAULT_TOOL_RESULT_INLINE_MAX_CHARS,
34
37
  } from './tool-result-history.js';
38
+ import { appendAll } from './append-all.js';
35
39
 
36
40
  export interface ActivationRequestOptions {
37
41
  /** The silent heartbeat tick this request answers, if any. */
@@ -109,6 +113,11 @@ export interface StartStreamResult {
109
113
  * far in sent order, with this request's head (context-manager's `noteRequestSent`). Present only where the
110
114
  * model binds its thinking and the context manager keeps a record it can be told of. */
111
115
  noteRequestSent?: (rows: readonly SentRow[]) => boolean;
116
+ /** Tells the context manager which stored bodies' thinking the provider dropped from a call of this stream, by the
117
+ * ids `placeDrops` gives for a `reply` drop, with the compile the stream was built from (context-manager's
118
+ * `noteThinkingDropped`), so, once the context manager takes the note, its record leaves them without their
119
+ * thinking from the next compile on. Present only where `noteRequestSent` is. */
120
+ noteThinkingDropped?: (messageIds: readonly MessageId[]) => boolean;
112
121
  /** The stored bodies the request's compile carried, by each body's first message id (its provenance's raw bodies).
113
122
  * Absent when the compile reports no provenance. */
114
123
  carriedBodies?: ReadonlySet<MessageId>;
@@ -187,11 +196,13 @@ export function assertThinkingBinding(setting: string, value: AgentConfig['think
187
196
 
188
197
  /**
189
198
  * Refuse an agent config whose `promptCaching`, `toolResultGuard`,
190
- * `toolWrapperProseGuard`, `thinking.enabled`, `thinkingBinding.dropBlock`,
199
+ * `toolWrapperProseGuard`, `turnInputsPin`, `thinking.enabled`, `thinkingBinding.dropBlock`,
191
200
  * `thinkingBinding.binds` or `refusalHandling.autoRewind` holds anything but true or false
192
201
  * (assertBooleanSetting, where null counts as left out), or whose `thinking`, `thinkingBinding` or
193
202
  * `refusalHandling` is anything but an object, null or false (holdsSettings),
194
- * or whose `autoContinue` resolveAutoContinue refuses. The constructor runs it
203
+ * or whose `autoContinue` resolveAutoContinue refuses, or whose `observes`
204
+ * is anything but a list of names, or whose `foreignRounds` is neither of
205
+ * its modes (assertOneOfSetting). The constructor runs it
195
206
  * first, so the rule holds wherever an Agent is built. `create()` also runs it
196
207
  * for each of its agents before anything opens, and `createEphemeralAgent`
197
208
  * before it reserves the name, so a refusal there leaves nothing behind.
@@ -233,6 +244,26 @@ export function assertAgentSwitches(config: AgentConfig): void {
233
244
  'true keeps a response whose whole prose is a tool-call-shaped wrapper from running or entering the agent\'s history, ' +
234
245
  'and false or leaving it out doesn\'t',
235
246
  );
247
+ assertBooleanSetting(
248
+ `${agent} turnInputsPin`,
249
+ config.turnInputsPin,
250
+ 'true or leaving it out pins the rows that open a turn raw until it ends, and false places no pin',
251
+ );
252
+ const observes: unknown = config.observes;
253
+ if (observes !== undefined && observes !== null
254
+ && (!Array.isArray(observes) || !observes.every((name) => typeof name === 'string'))) {
255
+ throw new Error(
256
+ `${agent} observes must be a list of agent names, such as ["nova"], not ${Array.isArray(observes) ? 'a list holding something else' : kindOf(observes)}: ` +
257
+ 'each names another configured agent whose own rows this agent\'s window takes',
258
+ );
259
+ }
260
+ assertOneOfSetting(
261
+ `${agent} foreignRounds`,
262
+ config.foreignRounds,
263
+ ['transcript', 'raw'],
264
+ '\'transcript\' or leaving it out sends the rounds of an agent it observes as a line per call and result, ' +
265
+ 'without their thinking, and \'raw\' sends them as stored',
266
+ );
236
267
  resolveAutoContinue(config);
237
268
  }
238
269
 
@@ -274,6 +305,18 @@ export function resolveAutoContinue(config: AgentConfig): { maxConsecutive: numb
274
305
  return { maxConsecutive: max };
275
306
  }
276
307
 
308
+ /** A stored injection's copy a compile built to send rendered (Agent.renderedInjection). */
309
+ interface RenderedInjectionCopy {
310
+ /** The row's id: the body the compile's provenance names. */
311
+ id: MessageId;
312
+ part: number;
313
+ parts: number;
314
+ /** Whether the compile held the part: every shard of it, however the strategy shaped it. */
315
+ held: boolean;
316
+ sequence: number;
317
+ }
318
+
319
+ /** A branch's key in Agent.unsentSince: its id and when it was created. */
277
320
  export function branchKey(branch: { readonly id: string; readonly created: number }): string {
278
321
  return `${branch.id}\u0000${branch.created}`;
279
322
  }
@@ -311,7 +354,9 @@ export class Agent {
311
354
  readonly refusalHandling: AgentConfig['refusalHandling'];
312
355
  readonly toolResultGuard: ToolResultGuard;
313
356
  /** Prose delivery mode (see AgentConfig.proseRouting). Default 'locus'. */
314
- readonly proseRouting: 'locus' | 'explicit' | 'hybrid' | 'disabled';
357
+ readonly proseRouting: 'locus' | 'explicit' | 'hybrid' | 'disabled' | 'locked';
358
+ /** Locked mode's destination until the agent chooses one (see AgentConfig.proseLock). */
359
+ readonly proseLock: string | undefined;
315
360
  /** Exact whole-response known-tool wrapper containment (default off). */
316
361
  readonly toolWrapperProseGuard: boolean;
317
362
  /** Continuation after a turn the model ends itself (see
@@ -396,6 +441,7 @@ export class Agent {
396
441
  * the next change.
397
442
  */
398
443
  private liveBudgetEpoch = 0;
444
+ private contextSettingsChangeCount = 0;
399
445
  /**
400
446
  * Counts changes of the descent (each arm of one, the same target included,
401
447
  * a reset, a cancel, a settle). A settle proves its target only to a
@@ -456,11 +502,13 @@ export class Agent {
456
502
  this.refusalHandling = config.refusalHandling;
457
503
  this.toolResultGuard = new ToolResultGuard(config.name, contextManager, config.toolResultGuard === true);
458
504
  this.proseRouting = config.proseRouting ?? 'locus';
505
+ this.proseLock = typeof config.proseLock === 'string' && config.proseLock.trim() ? config.proseLock.trim() : undefined;
459
506
  this.toolWrapperProseGuard = config.toolWrapperProseGuard === true;
460
507
  this.autoContinue = resolveAutoContinue(config);
461
508
  this.clock = resolveClockConfig(config.name, config.clock);
462
509
  this.cacheTtl = config.cacheTtl ?? '1h';
463
510
  this.promptCaching = config.promptCaching !== false;
511
+ this.pinsTurnInputs = config.turnInputsPin !== false;
464
512
  this.prefillUserMessage = config.prefillUserMessage;
465
513
  this.providerParams = config.providerParams;
466
514
  this.sameRoundThinkTextPolicy = config.sameRoundThinkTextPolicy;
@@ -539,6 +587,46 @@ export class Agent {
539
587
  return this.ownParticipants.has(participant);
540
588
  }
541
589
 
590
+ /**
591
+ * How another agent's rounds go out in this agent's requests (#291, answer
592
+ * 13): `'raw'`, as stored, or `'transcript'` (foreign-rounds.ts), a line
593
+ * per call and per result and no thinking, the same in every compile and
594
+ * in what a tool boundary injects. The framework sets it, once the
595
+ * agent's window follows the presentation index, for the persistent
596
+ * subconscious, for a resident that observes another
597
+ * (AgentConfig.observes), as their configurations' `foreignRounds` say
598
+ * (`'transcript'` by default), and for an agent derived or forked from
599
+ * one of them, in that agent's mode (its inheritance holds them); every
600
+ * other agent reads no other agent's
601
+ * rounds, but for a co-resident's rows stored before audience stamps,
602
+ * which a window written down from the record keeps and which go out as
603
+ * stored ('raw'), as earlier releases sent them.
604
+ */
605
+ private foreignRounds: 'raw' | 'transcript' = 'raw';
606
+
607
+ /** Set how another agent's rounds go out in this agent's requests (see foreignRounds). */
608
+ setForeignRounds(mode: 'raw' | 'transcript'): void {
609
+ this.foreignRounds = mode;
610
+ }
611
+
612
+ /**
613
+ * A stored row as this agent's requests carry it, for a row a tool
614
+ * boundary injects: another participant's row as a transcript where this
615
+ * agent is sent other agents' rounds so (setForeignRounds), its own and
616
+ * every row otherwise as stored. An injected row holds no tool block (the
617
+ * boundary cuts at one), so what changes is its thinking.
618
+ */
619
+ injectedAsSent(participant: string, content: ContentBlock[]): ContentBlock[] {
620
+ if (this.foreignRounds !== 'transcript' || this.isOwnParticipant(participant)) return content;
621
+ return foreignRowAsSent(content);
622
+ }
623
+
624
+ /** A compile's messages with other agents' rounds as this agent is sent them (setForeignRounds). */
625
+ private foreignRoundsAsSent(messages: NormalizedMessage[]): NormalizedMessage[] {
626
+ if (this.foreignRounds !== 'transcript') return messages;
627
+ return transcribeForeignRounds(messages, (participant) => this.isOwnParticipant(participant));
628
+ }
629
+
542
630
  /**
543
631
  * Role assignment for a request: every participant that is this agent's
544
632
  * own voice — its name, and for a derived agent the agent it inherited
@@ -615,7 +703,19 @@ export class Agent {
615
703
  if (typeof manager.updateHotContextSettings !== 'function') {
616
704
  throw new Error('The active context strategy does not support live context settings');
617
705
  }
618
- return manager.updateHotContextSettings(update);
706
+ const settings = (s: HotContextSettingsStatus | null) =>
707
+ JSON.stringify([s?.tailTokens, s?.transitionPaceTokens, s?.preparedWindowTokens]);
708
+ const before = settings(this.getHotContextSettings());
709
+ const status = manager.updateHotContextSettings(update);
710
+ if (settings(status) !== before) this.contextSettingsChangeCount++;
711
+ return status;
712
+ }
713
+
714
+ /** How many times the live budget, its descent target or the hot context
715
+ * settings have changed.
716
+ * @internal For the framework's budget holds; not a host API. */
717
+ contextSettingsChanges(): number {
718
+ return this.contextSettingsChangeCount;
619
719
  }
620
720
 
621
721
  getRuntimeSettings(): AgentRuntimeSettingsSnapshot {
@@ -682,6 +782,7 @@ export class Agent {
682
782
  private setContextBudget(budget: number | undefined, target: number | undefined, armed = false): void {
683
783
  if (budget !== this.contextBudgetTokens) this.liveBudgetEpoch++;
684
784
  if (armed || target !== this.contextBudgetTargetTokens) this.descentEpoch++;
785
+ if (budget !== this.contextBudgetTokens || target !== this.contextBudgetTargetTokens) this.contextSettingsChangeCount++;
685
786
  this.contextBudgetTokens = budget;
686
787
  this.contextBudgetTargetTokens = target;
687
788
  }
@@ -721,7 +822,7 @@ export class Agent {
721
822
  return await this.compileAtLiveBudgetNoting(compile, started);
722
823
  } finally {
723
824
  this.compilesRunning.delete(started);
724
- const earliest = Math.min(...this.compilesRunning);
825
+ const earliest = [...this.compilesRunning].reduce((min, start) => Math.min(min, start), Infinity);
725
826
  for (const [budget, start] of this.latestEndedStart) {
726
827
  if (start < earliest) this.latestEndedStart.delete(budget);
727
828
  }
@@ -1131,45 +1232,60 @@ export class Agent {
1131
1232
  }
1132
1233
 
1133
1234
  /**
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.
1235
+ * RFC-006 consumed watermark, per branch: how far the agent had read as of
1236
+ * the latest of its creation, a committing compile that returns, and a
1237
+ * tool boundary that injected messages into a live stream declaring it
1238
+ * carries them (`supportsInjectedMessages` true: membrane's native path,
1239
+ * from membrane 0.6.0). A compile counts from when it takes its mark,
1240
+ * before it awaits, so one that began before a boundary's mark doesn't
1241
+ * roll the mark back (applyCompileWatermark), and a dry run, such as
1242
+ * previewActivation's, doesn't move it.
1243
+ *
1244
+ * It is a ReadMark (read-marks.ts), read only through `covers`. Once the
1245
+ * agent's context presents rows (#291's presentation index, in presented
1246
+ * mode), it covers exactly the rows in the agent's window as of the mark:
1247
+ * a row stored then and not yet presented to the agent isn't covered.
1248
+ * While the context is in legacy mode, its window is the record, and the
1249
+ * mark covers every row stored on the branch by then, as it always did:
1250
+ * a prefix of the record, so it also covers stored messages the agent
1251
+ * wasn't given, such as another slot's. Once the window is written down
1252
+ * (an agent registered on a branch its window was never written down on
1253
+ * holds such a mark until its first compile), a mark taken in legacy mode
1254
+ * covers the rows its window held by the mark where they still stand, as
1255
+ * a mark taken in presented mode then would: a row stored after it, taken
1256
+ * out of the window since, or presented to the agent again, isn't.
1144
1257
  *
1145
1258
  * A message above it may still be replaced or withdrawn in place by its
1146
1259
  * sender, though it may already have been in one of the agent's requests.
1147
1260
  * A live stream carries each round's own content and results, which it
1148
1261
  * stores at its tool boundaries, into its next request.
1149
1262
  */
1150
- private consumedWatermark: { branch: string; sequence: number } | null = null;
1263
+ private consumedWatermark: ReadMark | null = null;
1151
1264
  /** Counts marks (markContextConsumed's and applyCompileWatermark's), so a
1152
1265
  * compile that finishes can tell whether one was made while it ran. */
1153
1266
  private watermarkMarks = 0;
1154
1267
  /**
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.
1268
+ * How far a compile or a tool boundary may have put the agent's context in
1269
+ * front of it, per branch. It moves where the consumed watermark does,
1270
+ * and sooner: when a committing compile takes its mark, before it awaits,
1271
+ * and at every tool boundary that injects into its stream. A message above
1272
+ * it may still have been shown, though: for one, a live stream carries each
1273
+ * round's own content and results, stored at a boundary that injected
1274
+ * nothing, into its next request. A compile that throws assembled nothing,
1275
+ * and gives its move back where it can (unshowFailedCompile). A mark taken
1276
+ * in legacy mode, read while the window is the record, counts a prefix of
1277
+ * the record, so a message stored below it without having been shown
1278
+ * counts as shown, which errs toward a marker; one taken in presented
1279
+ * mode, and one taken in legacy mode read once the window is written down,
1280
+ * cover the window as it stood.
1165
1281
  */
1166
- private shownWatermark: { branch: string; sequence: number } | null = null;
1282
+ private shownWatermark: ReadMark | null = null;
1167
1283
 
1168
- getConsumedWatermark(): { branch: string; sequence: number } | null {
1284
+ getConsumedWatermark(): ReadMark | null {
1169
1285
  return this.consumedWatermark;
1170
1286
  }
1171
1287
 
1172
- getShownWatermark(): { branch: string; sequence: number } | null {
1288
+ getShownWatermark(): ReadMark | null {
1173
1289
  return this.shownWatermark;
1174
1290
  }
1175
1291
 
@@ -1181,27 +1297,32 @@ export class Agent {
1181
1297
  * request runs, so a request that fails, ending its stream with an error,
1182
1298
  * leaves this as it was.
1183
1299
  */
1184
- private answeredWatermark: { branch: string; sequence: number } | null = null;
1300
+ private answeredWatermark: ReadMark | null = null;
1185
1301
 
1186
- getAnsweredWatermark(): { branch: string; sequence: number } | null {
1302
+ getAnsweredWatermark(): ReadMark | null {
1187
1303
  return this.answeredWatermark;
1188
1304
  }
1189
1305
 
1190
1306
  /** A request that carried `watermark` was answered. Requests are answered
1191
1307
  * in the order they're sent, and the consumed watermark only advances
1192
1308
  * within a branch, so the latest is the newest. */
1193
- markAnswered(watermark: { branch: string; sequence: number } | null): void {
1309
+ markAnswered(watermark: ReadMark | null): void {
1194
1310
  if (watermark) this.answeredWatermark = watermark;
1195
1311
  }
1196
1312
 
1313
+ /** Whether a turn this agent opens pins its inputs (AgentConfig.turnInputsPin, on by default). */
1314
+ readonly pinsTurnInputs: boolean;
1315
+
1197
1316
  /**
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.
1317
+ * Keep the rows of the agent's window from `first` through `last` raw for
1318
+ * the turn now opening, with a raw pin under one name per agent: a
1319
+ * strategy reads a pin by its rows' positions in its message view, which
1320
+ * is the window, in its order (#291; in legacy mode the record). Any pin
1321
+ * of that name already held is removed first, one a crash left included.
1322
+ * Under a strategy without pins nothing is pinned. A committing compile
1323
+ * that refuses as over budget while a pin of that name is held removes it
1324
+ * and compiles once more, and `unpinTurnInputs` removes it. Returns the
1325
+ * pin's id, or undefined when nothing was pinned.
1205
1326
  *
1206
1327
  * @internal For the framework's turn starts; not a host API.
1207
1328
  */
@@ -1258,48 +1379,91 @@ export class Agent {
1258
1379
  * draft resend, or a send tool whose channel the framework records), on
1259
1380
  * the branch it was taken on. A wake that carries a channel, unless it's
1260
1381
  * marked ambient (InferenceRequest.ambient), keeps its turn unless the
1261
- * agent spoke there after reading the write the wake is for. It
1382
+ * agent spoke there after reading the row the wake is for. It
1262
1383
  * lives as long as this Agent: an agent made again starts it empty, and
1263
1384
  * such a wake then stays.
1264
1385
  */
1265
- private publishedThrough = new Map<string, { branch: string; sequence: number }>();
1386
+ private publishedThrough = new Map<string, ReadMark>();
1266
1387
 
1267
1388
  /** Note a publish into `destination` (its key), made having read through
1268
1389
  * `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 {
1390
+ * its call made. A later mark on the same branch (markAtOrAfter: of two
1391
+ * at one head, the one whose window reached further) replaces an earlier
1392
+ * one; one on another branch replaces whatever was noted. */
1393
+ notePublished(destination: string, readThrough: ReadMark | null): void {
1271
1394
  if (!readThrough) return;
1272
1395
  const noted = this.publishedThrough.get(destination);
1273
- if (noted && noted.branch === readThrough.branch && noted.sequence >= readThrough.sequence) return;
1396
+ if (noted && markAtOrAfter(noted, readThrough) === true) return;
1274
1397
  this.publishedThrough.set(destination, readThrough);
1275
1398
  }
1276
1399
 
1277
1400
  /** 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 {
1401
+ * `row`, as `cm` (the agent's context, where the row's place in its
1402
+ * window is read) says: the mark noted for the destination covers it. A
1403
+ * mark that can't say (one on another branch) says no. */
1404
+ publishedAfterReading(destination: string, row: MarkedRow, cm: MarkCm): boolean {
1280
1405
  const noted = this.publishedThrough.get(destination);
1281
- return !!noted && noted.branch === branch && noted.sequence >= sequence;
1406
+ return !!noted && covers(noted, cm, row) === 'covered';
1407
+ }
1408
+
1409
+ /**
1410
+ * Every read mark this agent holds: its consumed, shown and answered
1411
+ * watermarks and the mark noted for each destination it published into,
1412
+ * each mark once (two of them are often the one mark). A redo on the
1413
+ * window (AgentFramework.redo) reads which of them covered the rows it
1414
+ * puts back, and gives those marks again (replaceReadMarks).
1415
+ */
1416
+ heldReadMarks(): ReadMark[] {
1417
+ const held = new Set<ReadMark>();
1418
+ for (const mark of [this.consumedWatermark, this.shownWatermark, this.answeredWatermark, ...this.publishedThrough.values()]) {
1419
+ if (mark) held.add(mark);
1420
+ }
1421
+ return [...held];
1282
1422
  }
1283
1423
 
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). */
1424
+ /**
1425
+ * Put each mark `replacements` maps in its place wherever this agent holds
1426
+ * it (heldReadMarks), so a mark held as two of them stays one: a redo on
1427
+ * the window (#291) puts rows back at new ordinals, past every mark that
1428
+ * covered them before their undo, and gives each such mark again where it
1429
+ * covers them now. It counts as a mark (watermarkMarks), so a compile that
1430
+ * began before it doesn't put its own older mark back over it.
1431
+ */
1432
+ replaceReadMarks(replacements: ReadonlyMap<ReadMark, ReadMark>): void {
1433
+ const swap = (mark: ReadMark | null): ReadMark | null => (mark && replacements.get(mark)) ?? mark;
1434
+ const consumed = swap(this.consumedWatermark);
1435
+ if (consumed !== this.consumedWatermark) {
1436
+ this.consumedWatermark = consumed;
1437
+ this.watermarkMarks++;
1438
+ }
1439
+ this.shownWatermark = swap(this.shownWatermark);
1440
+ this.answeredWatermark = swap(this.answeredWatermark);
1441
+ for (const [destination, mark] of this.publishedThrough) {
1442
+ const next = replacements.get(mark);
1443
+ if (next) this.publishedThrough.set(destination, next);
1444
+ }
1445
+ }
1446
+
1447
+ /** Mark everything in the agent's window (in legacy mode, everything
1448
+ * stored) as consumed: the agent's creation, at boot or later, and a tool
1449
+ * boundary that injected messages into a live stream declaring it carries
1450
+ * them; see consumedWatermark. */
1287
1451
  markContextConsumed(): void {
1288
1452
  // Tolerates partial context-manager doubles (tests build Agents over
1289
1453
  // stubs). Without a watermark, a reader that looks for one takes its own
1290
1454
  // 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).
1455
+ // releasedWindow counts it as unread. A wake for a row it reads
1456
+ // (InferenceRequest.readsRow) reads the answered watermark instead
1457
+ // (rowDone).
1294
1458
  this.consumedWatermark = this.pendingWatermark();
1295
1459
  this.watermarkMarks++;
1296
1460
  this.shownWatermark = this.consumedWatermark;
1297
1461
  }
1298
1462
 
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. */
1463
+ /** Mark everything in the agent's window (in legacy mode, everything
1464
+ * stored) as possibly shown (a tool boundary that injected messages into
1465
+ * its stream). Without a watermark the coalescer counts a message as
1466
+ * shown, as it counts one read without a consumed watermark. */
1303
1467
  markContextShown(): void {
1304
1468
  this.shownWatermark = this.pendingWatermark();
1305
1469
  }
@@ -1313,11 +1477,10 @@ export class Agent {
1313
1477
  * the second then throws, it restores the first's failed claim. Messages
1314
1478
  * up to that claim then count as shown, although no request held those
1315
1479
  * 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 {
1480
+ * until the next compile or boundary moves the watermark. Each mark is a
1481
+ * fresh object (readMarkOf), so the compare by identity is this compile's
1482
+ * own claim. */
1483
+ private unshowFailedCompile(watermark: ReadMark | null, before: ReadMark | null): void {
1321
1484
  if (watermark && this.shownWatermark === watermark) this.shownWatermark = before;
1322
1485
  }
1323
1486
 
@@ -1328,49 +1491,41 @@ export class Agent {
1328
1491
  * mark's branch, so a boundary's mark is never rolled back by a compile
1329
1492
  * that started before it (a committing compile, such as compileContext's,
1330
1493
  * run during a live turn; a dry run, such as previewActivation's, never
1331
- * reaches here).
1494
+ * reaches here). Marks are ordered by markAtOrAfter: by the window's
1495
+ * index length where both keep one, else by the store's head, each of
1496
+ * which only grows on a branch.
1332
1497
  */
1333
- private applyCompileWatermark(watermark: { branch: string; sequence: number }, marksBefore: number): void {
1498
+ private applyCompileWatermark(watermark: ReadMark, marksBefore: number): void {
1334
1499
  const current = this.consumedWatermark;
1335
- if (this.watermarkMarks !== marksBefore && current
1336
- && (current.branch !== watermark.branch || current.sequence >= watermark.sequence)) return;
1500
+ if (this.watermarkMarks !== marksBefore && current && markAtOrAfter(current, watermark) !== false) return;
1337
1501
  this.consumedWatermark = watermark;
1338
1502
  this.watermarkMarks++;
1339
1503
  }
1340
1504
 
1341
1505
  /**
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.
1506
+ * The branch now (`branchKey`) and the store's head on it (storeHeadOf, the
1507
+ * head a mark pendingWatermark takes keeps): a message stored on that
1508
+ * branch later has a larger sequence. Sequences are numbered per branch, so
1509
+ * one compares with another only on the same branch. A record sequence, not
1510
+ * a place in the agent's window (#291).
1344
1511
  */
1345
1512
  branchHead(): { branch: string; sequence: number } | undefined {
1346
- const head = this.pendingWatermark();
1513
+ const sequence = storeHeadOf(this.contextManager);
1347
1514
  const cm = this.contextManager as Partial<ContextManager>;
1348
- if (!head || typeof cm.currentBranchRef !== 'function') return undefined;
1515
+ if (sequence === null || typeof cm.currentBranchRef !== 'function') return undefined;
1349
1516
  try {
1350
- return { branch: branchKey(cm.currentBranchRef()), sequence: head.sequence };
1517
+ return { branch: branchKey(cm.currentBranchRef()), sequence };
1351
1518
  } catch {
1352
1519
  return undefined;
1353
1520
  }
1354
1521
  }
1355
1522
 
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
- }
1523
+ /** The watermark a successful compile will establish (taken before, applied
1524
+ * after): where the agent's context stands now (readMarkOf), its window's
1525
+ * index length with the store's head once it presents rows. Null for a
1526
+ * context double that can't say. */
1527
+ private pendingWatermark(): ReadMark | null {
1528
+ return readMarkOf(this.contextManager);
1374
1529
  }
1375
1530
 
1376
1531
  /** A committing compile, as for a request that is sent: the context
@@ -1413,9 +1568,10 @@ export class Agent {
1413
1568
  */
1414
1569
  private renderedInjections?: {
1415
1570
  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 }>>;
1571
+ /** Per namespace, per content hash: each copy of that content the
1572
+ * compile rendered, by its row's id, with the part it is, how many
1573
+ * parts the content has, whether the compile held it, and its sequence. */
1574
+ byNamespace: Map<string, Map<string, RenderedInjectionCopy[]>>;
1419
1575
  };
1420
1576
 
1421
1577
  /**
@@ -1446,9 +1602,8 @@ export class Agent {
1446
1602
  /**
1447
1603
  * A host's hook, called with each committing compile's `thinkingStrip` (never a dry run's) and the key of the
1448
1604
  * 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.
1605
+ * context manager's binding record takes the strip either way. One that throws is logged. It is observational only;
1606
+ * the framework does not write agent-facing reasoning-loss notices.
1452
1607
  */
1453
1608
  onThinkingStrip?: (strip: ThinkingStrip, branch: string) => void;
1454
1609
 
@@ -1462,18 +1617,23 @@ export class Agent {
1462
1617
  * start and the next (a host's own committing compile, or the compression
1463
1618
  * an over-budget retry waits for) can summarize them all the same: the
1464
1619
  * 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.
1620
+ * it stores them again, as after any fold. The slot's record is read
1621
+ * back 64 rows at a time from the newest, stopping at that point.
1622
+ *
1623
+ * It reads the record, by sequence, not the agent's window, and that
1624
+ * holds once the context presents rows (#291): the rows it counts are
1625
+ * this agent's own stored injections, its own rows, which enter its
1626
+ * window as they are written, so one stored after the point is in no
1627
+ * request yet whatever else has been stored or presented since. Another
1628
+ * agent's rows, stored into a shared slot at once rather than queued, are
1629
+ * passed over, whatever they are.
1467
1630
  */
1468
1631
  unsentInjection(namespace: string): { contentHash: string; whole: boolean } | undefined {
1469
1632
  let newest: { contentHash: string; parts: number; held: Set<number> } | undefined;
1470
1633
  try {
1471
- const branch = branchKey(this.contextManager.currentBranchRef());
1472
- const since = this.unsentSince.get(branch);
1634
+ const since = this.unsentSince.get(branchKey(this.contextManager.currentBranchRef()));
1473
1635
  if (since === undefined) {
1474
- const count = this.contextManager.getMessageCount();
1475
- const top = count > 0 ? this.contextManager.getMessageWindow(count - 1, 1, { resolveBlobs: false }).messages[0]?.sequence ?? 0 : 0;
1476
- this.unsentSince.set(branch, top);
1636
+ this.markUnsentPoint();
1477
1637
  return undefined;
1478
1638
  }
1479
1639
  scan: for (let end = this.contextManager.getMessageCount(); end > 0;) {
@@ -1484,6 +1644,9 @@ export class Agent {
1484
1644
  if (row.sequence <= since) break scan;
1485
1645
  const mark = storedInjectionMarkOf(row.metadata);
1486
1646
  if (mark?.agent !== this.name || mark.namespace !== namespace) continue;
1647
+ // A copy an undo took out of the window (#291) is in no window
1648
+ // now, so it isn't the copy the agent will read.
1649
+ if (this.contextManager.windowMode?.() === 'presented' && this.contextManager.windowStatusOf(row.id) === 'undone') continue;
1487
1650
  // Read from the newest back, the first copy met is the newest
1488
1651
  // content; its other parts pool with it.
1489
1652
  newest ??= { contentHash: mark.contentHash, parts: mark.parts, held: new Set() };
@@ -1506,6 +1669,42 @@ export class Agent {
1506
1669
  return newest && { contentHash: newest.contentHash, whole: newest.held.size === newest.parts };
1507
1670
  }
1508
1671
 
1672
+ /** Set the unsent point where it isn't set, as a start's plan does: a row
1673
+ * stored after it counts as in no request until a compile built to send.
1674
+ * @internal For the framework's budget holds; not a host API. */
1675
+ markUnsentPoint(): void {
1676
+ const branch = branchKey(this.contextManager.currentBranchRef());
1677
+ if (this.unsentSince.has(branch)) return;
1678
+ const count = this.contextManager.getMessageCount();
1679
+ const top = count > 0 ? this.contextManager.getMessageWindow(count - 1, 1, { resolveBlobs: false }).messages[0]?.sequence ?? 0 : 0;
1680
+ this.unsentSince.set(branch, top);
1681
+ }
1682
+
1683
+ /** The ids of this agent's stored injections, of every namespace, stored
1684
+ * on its branch since its last compile built to send and in its window
1685
+ * where it's presented, in the record's order, a sharded body's first
1686
+ * shard first: the rows unsentInjection counts, which no request has
1687
+ * carried. Reads only, so an unset point gives none. */
1688
+ unsentInjectionRows(): MessageId[] {
1689
+ const ids: MessageId[] = [];
1690
+ const since = this.unsentSince.get(branchKey(this.contextManager.currentBranchRef()));
1691
+ if (since === undefined) return ids;
1692
+ const presented = this.contextManager.windowMode?.() === 'presented';
1693
+ scan: for (let end = this.contextManager.getMessageCount(); end > 0;) {
1694
+ const span = Math.min(end, 64);
1695
+ const { messages } = this.contextManager.getMessageWindow(end - span, span, { resolveBlobs: false });
1696
+ for (let i = messages.length - 1; i >= 0; i--) {
1697
+ const row = messages[i]!;
1698
+ if (row.sequence <= since) break scan;
1699
+ if (storedInjectionMarkOf(row.metadata)?.agent !== this.name) continue;
1700
+ if (presented && this.contextManager.windowStatusOf(row.id) !== 'in') continue;
1701
+ ids.push(row.id);
1702
+ }
1703
+ end -= span;
1704
+ }
1705
+ return ids.reverse();
1706
+ }
1707
+
1509
1708
  /** Record which of this agent's stored injections a compile built to send
1510
1709
  * rendered. Another agent's stored rows never reach this agent's compile,
1511
1710
  * since every compile of a shared store leaves them out by its view
@@ -1534,7 +1733,7 @@ export class Agent {
1534
1733
  this.renderedInjections = undefined;
1535
1734
  return;
1536
1735
  }
1537
- const byNamespace = new Map<string, Map<string, { parts: number; held: Set<number>; sequence: number }>>();
1736
+ const byNamespace = new Map<string, Map<string, RenderedInjectionCopy[]>>();
1538
1737
  for (const [i, source] of provenance.messages.entries()) {
1539
1738
  // Only a message rendered as an injection's participant can be one, so
1540
1739
  // the store is read for those alone.
@@ -1548,14 +1747,11 @@ export class Agent {
1548
1747
  // would only add copies, every start. A part missing shards still
1549
1748
  // says which content is newest.
1550
1749
  const held = body.complete || (body.missing !== undefined && !body.missing.includes('shards'));
1551
- const contents = byNamespace.get(mark.namespace) ?? new Map();
1750
+ const contents = byNamespace.get(mark.namespace) ?? new Map<string, RenderedInjectionCopy[]>();
1552
1751
  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);
1752
+ const copies = contents.get(mark.contentHash) ?? [];
1753
+ copies.push({ id: body.messageId, part: mark.part, parts: mark.parts, held, sequence: body.sequence });
1754
+ contents.set(mark.contentHash, copies);
1559
1755
  }
1560
1756
  }
1561
1757
  this.renderedInjections = { branch: provenance.branch, byNamespace };
@@ -1565,19 +1761,48 @@ export class Agent {
1565
1761
  * The newest content of `namespace` among this agent's stored injections
1566
1762
  * that the last compile built to send rendered, by its newest copy: its
1567
1763
  * 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.
1764
+ * copy, every shard, however the strategy shaped it. Copies of the same
1765
+ * content pool what they hold: the agent has each part one of them holds.
1766
+ * A copy the agent's next request won't hold is passed over: one an undo
1767
+ * took out of its window since (#291: `'undone'`, which no presentation
1768
+ * point presents again until a redo), and one no longer stored. Undefined
1769
+ * when no copy is left, when this agent hasn't built a request since it
1770
+ * started, or when the window's branch has changed since.
1571
1771
  */
1572
1772
  renderedInjection(namespace: string): { contentHash: string; whole: boolean } | undefined {
1573
1773
  const rendered = this.renderedInjections;
1574
1774
  const branch = this.contextManager.currentBranchRef();
1575
1775
  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;
1776
+ let newest: { contentHash: string; parts: number; held: Set<number>; sequence: number } | undefined;
1777
+ for (const [contentHash, copies] of rendered.byNamespace.get(namespace) ?? []) {
1778
+ let pooled: { parts: number; held: Set<number>; sequence: number } | undefined;
1779
+ for (const copy of copies) {
1780
+ if (!this.renderedCopyStands(copy.id)) continue;
1781
+ pooled ??= { parts: copy.parts, held: new Set(), sequence: copy.sequence };
1782
+ if (copy.held) pooled.held.add(copy.part);
1783
+ pooled.sequence = Math.max(pooled.sequence, copy.sequence);
1784
+ }
1785
+ if (pooled && (!newest || pooled.sequence > newest.sequence)) newest = { contentHash, ...pooled };
1786
+ }
1787
+ return newest ? { contentHash: newest.contentHash, whole: newest.held.size === newest.parts } : undefined;
1788
+ }
1789
+
1790
+ /**
1791
+ * Whether a stored injection's copy the last compile rendered is still
1792
+ * where the agent's next request reads it: stored, and not taken out of
1793
+ * its window by an undo (#291). A copy out of the window otherwise
1794
+ * (`'out'`, as a redo leaves one presented since its undo) is presented
1795
+ * again at the agent's next point, its own rows' sweep, so it counts. A
1796
+ * context that can't say (a double without windowStatusOf, a read that
1797
+ * throws) counts it, as before the window was presented.
1798
+ */
1799
+ private renderedCopyStands(id: MessageId): boolean {
1800
+ try {
1801
+ const status = this.contextManager.windowStatusOf?.(id);
1802
+ return status !== 'undone' && status !== 'absent';
1803
+ } catch {
1804
+ return true;
1579
1805
  }
1580
- return newest ? { contentHash: newest[0], whole: newest[1].held.size === newest[1].parts } : undefined;
1581
1806
  }
1582
1807
 
1583
1808
  /**
@@ -1747,13 +1972,13 @@ export class Agent {
1747
1972
  const { systemInjections } = compiled;
1748
1973
  // The framework's own pricing stamp never goes out (thinking-token-stamp.ts);
1749
1974
  // the agent's own turns are then presented under its name (presentOwnTurns).
1750
- const messages = this.presentOwnTurns(withoutTokenEstimates(compiled.messages));
1975
+ const messages = this.presentOwnTurns(this.foreignRoundsAsSent(withoutTokenEstimates(compiled.messages)));
1751
1976
  // Read once, after the first compile, since results may have become
1752
1977
  // ready while it ran; the refusal retry, assembled while the agent is
1753
1978
  // inferring, sends the same ones.
1754
1979
  unguardedResults ??= this._state.status === 'ready' && !guardedResults
1755
1980
  ? this.buildToolResultMessages(this._state.toolResults) : [];
1756
- messages.push(...unguardedResults);
1981
+ appendAll(messages, unguardedResults);
1757
1982
  return {
1758
1983
  messages: this.toolResultGuard.prepareRequest(messages, true),
1759
1984
  system: this.buildSystemPrompt(systemInjections),
@@ -1989,6 +2214,8 @@ export class Agent {
1989
2214
  // compression path.)
1990
2215
  // The same pass a tool boundary applies to what it notes as sent
1991
2216
  // (sent-rows.ts), so the two can't drift.
2217
+ // Another agent's rounds go out as this agent is sent them (setForeignRounds).
2218
+ messages = this.foreignRoundsAsSent(messages);
1992
2219
  messages = messages
1993
2220
  .map((m) => ({ ...m, content: m.content.filter(carriedBlock) }))
1994
2221
  .filter((m) => m.content.length > 0);
@@ -2094,9 +2321,10 @@ export class Agent {
2094
2321
  return { ...own, blockBinding: { prefixMismatchBehavior: 'drop_block' } };
2095
2322
  }
2096
2323
 
2097
- /** The stream's way to tell the context manager what its later rounds sent, with the head its compile was given:
2098
- * where the agent's model binds its thinking (context-manager's `noteRequestSent`). */
2099
- private requestNotes(binding: BindingCompileOptions, compiled: CompiledForRequest): Pick<StartStreamResult, 'noteRequestSent'> {
2324
+ /** The stream's way to tell the context manager what its later rounds sent, with the head its compile was given,
2325
+ * and which stored bodies' thinking the provider dropped: where the agent's model binds its thinking
2326
+ * (context-manager's `noteRequestSent` and `noteThinkingDropped`). */
2327
+ private requestNotes(binding: BindingCompileOptions, compiled: CompiledForRequest): Pick<StartStreamResult, 'noteRequestSent' | 'noteThinkingDropped'> {
2100
2328
  const identity = binding.identity;
2101
2329
  // Each note names the compile the stream was built from, so a note can't vouch for this stream under a record a
2102
2330
  // later committing compile with binds wrote. A compile that reports no provenance gives the stream nothing to name, and the
@@ -2104,7 +2332,10 @@ export class Agent {
2104
2332
  const compileId = compiled.provenance?.compileId;
2105
2333
  if (!identity || compileId === undefined) return {};
2106
2334
  const cm = this.contextManager;
2107
- return { noteRequestSent: (rows) => cm.noteRequestSent(rows, identity, compileId) };
2335
+ return {
2336
+ noteRequestSent: (rows) => cm.noteRequestSent(rows, identity, compileId),
2337
+ noteThinkingDropped: (messageIds) => cm.noteThinkingDropped(messageIds, compileId),
2338
+ };
2108
2339
  }
2109
2340
 
2110
2341
  /**
@@ -2157,17 +2388,23 @@ export class Agent {
2157
2388
  return (await this.assembleActivationRequest(availableTools, injections, budget, compressionTools, options)).request;
2158
2389
  }
2159
2390
 
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). */
2391
+ /** Stored rows that follow a request-only turn, memoized on the window's
2392
+ * cached row array (ContextManager returns the same array until the
2393
+ * window changes): the rows a compile sends are the window's (#291), and
2394
+ * a write another agent stores, which the window doesn't take, leaves the
2395
+ * memo as it was. A context without a window reads the record, the same
2396
+ * array as the window's for a single slot that is still the record. */
2163
2397
  private requestOnlyIndex: { source: readonly unknown[]; index: RequestOnlyRowIndex } | null = null;
2164
2398
 
2165
2399
  private indexRequestOnlyRows(): RequestOnlyRowIndex {
2166
2400
  const empty: RequestOnlyRowIndex = { ticks: new Map(), prompts: new Map() };
2167
2401
  const cm = this.contextManager as Partial<ContextManager>;
2168
- if (typeof cm.getAllMessages !== 'function') return empty;
2402
+ const read = typeof cm.getPresentedMessages === 'function' ? () => cm.getPresentedMessages!()
2403
+ : typeof cm.getAllMessages === 'function' ? () => cm.getAllMessages!()
2404
+ : undefined;
2405
+ if (!read) return empty;
2169
2406
  try {
2170
- const stored = cm.getAllMessages() as unknown as readonly StoredRowLike[];
2407
+ const stored = read() as unknown as readonly StoredRowLike[];
2171
2408
  if (this.requestOnlyIndex?.source === stored) return this.requestOnlyIndex.index;
2172
2409
  const index = indexRequestOnlyRows(stored, this.ownParticipants);
2173
2410
  this.requestOnlyIndex = { source: stored, index };
@@ -2382,7 +2619,9 @@ export class Agent {
2382
2619
  );
2383
2620
  return undefined;
2384
2621
  }
2385
- return this.contextManager.addMessage(this.name, content, metadata);
2622
+ // The agent's own round, stamped as its own (#291's audience stamp); its
2623
+ // id is the stream's last reply for the thinking binding (noteStreamEnd).
2624
+ return storeOwnRow(this, this.name, content, metadata);
2386
2625
  }
2387
2626
 
2388
2627
  /**
@@ -2492,6 +2731,27 @@ export class Agent {
2492
2731
  return this.contextManager;
2493
2732
  }
2494
2733
 
2734
+ /** Where this agent tells of its own rows once they are stored (setOwnRowSink). */
2735
+ private ownRowSink?: (ids: readonly MessageId[]) => void;
2736
+
2737
+ /**
2738
+ * Install where this agent tells of its own rows once they are stored
2739
+ * (its rounds and the rows the framework writes for it through
2740
+ * storeOwnRow, and its tool results through its guard), or take it off
2741
+ * with undefined. The presentation index (#291) installs one when it takes
2742
+ * the agent on, and presents each row in the agent's window as it is
2743
+ * told. With none, as for an agent nothing took on, nothing is told.
2744
+ */
2745
+ setOwnRowSink(sink: ((ids: readonly MessageId[]) => void) | undefined): void {
2746
+ this.ownRowSink = sink;
2747
+ this.toolResultGuard.setOwnRowSink(sink);
2748
+ }
2749
+
2750
+ /** Tell the sink of own rows just stored (storeOwnRow calls it, once each add has returned). */
2751
+ ownRowsStored(ids: readonly MessageId[]): void {
2752
+ this.ownRowSink?.(ids);
2753
+ }
2754
+
2495
2755
  /**
2496
2756
  * Build the effective system prompt, appending any system-position
2497
2757
  * injections, their blocks joined by line breaks. The system prompt is
@@ -2583,8 +2843,8 @@ export class Agent {
2583
2843
  const content = guardedRefusal ? [] : response.content;
2584
2844
  const { toolCalls, speechContent } = this.extractToolCallsAndSpeech(content);
2585
2845
 
2586
- // Add assistant response to context
2587
- if (!guardedRefusal) this.contextManager.addMessage(this.name, content);
2846
+ // Add assistant response to context, as the agent's own row
2847
+ if (!guardedRefusal) storeOwnRow(this, this.name, content);
2588
2848
 
2589
2849
  return {
2590
2850
  toolCalls,