@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
@@ -16,10 +16,14 @@ import type {
16
16
  AgentRecord,
17
17
  AgentRef,
18
18
  AgentSpec,
19
+ ContextInheritance,
19
20
  EndOutcome,
20
21
  HeldMail,
22
+ MessageSlotRef,
23
+ ObserveEdge,
21
24
  RegistrySnapshot,
22
25
  } from './types.js';
26
+ import { appendAll } from '../append-all.js';
23
27
 
24
28
  /** Thrown for a spec the registry refuses to load. */
25
29
  export class AgentSpecError extends Error {
@@ -76,6 +80,123 @@ function clone<T>(value: T): T {
76
80
  return value === undefined ? value : (JSON.parse(JSON.stringify(value)) as T);
77
81
  }
78
82
 
83
+ /** AgentRegistry.register's options (see there). */
84
+ export interface RegisterOptions {
85
+ resume?: boolean;
86
+ after?: number;
87
+ declaredWith?: readonly AgentName[];
88
+ }
89
+
90
+ /** The residents' shared un-namespaced slot, on the host's current branch. */
91
+ export const RESIDENTS_SLOT: Readonly<MessageSlotRef> = Object.freeze({ namespace: null });
92
+
93
+ /** Whether two refs name one slot: the same namespace on the same branch. */
94
+ export function sameSlot(a: MessageSlotRef | undefined, b: MessageSlotRef | undefined): boolean {
95
+ return !!a && !!b && a.namespace === b.namespace && a.branch === b.branch;
96
+ }
97
+
98
+ /**
99
+ * The slot an observe edge reads. An edge that names a slot reads that
100
+ * namespace on the host's current branch. One that names an agent and no
101
+ * slot reads that agent's own slot, wherever it is (a shared fork's is on its
102
+ * own branch), as `ownSlotOf` looks it up: undefined where the lookup doesn't
103
+ * know the agent, or none is given. The registry's `observedSlotOf` passes its
104
+ * records. An edge that names neither reads the residents' shared slot.
105
+ */
106
+ export function observedSlot(
107
+ edge: ObserveEdge,
108
+ ownSlotOf?: (agent: AgentName) => MessageSlotRef | undefined,
109
+ ): MessageSlotRef | undefined {
110
+ if (edge.slot !== undefined) return { namespace: edge.slot };
111
+ if (edge.agent === undefined) return { namespace: null };
112
+ const own = ownSlotOf?.(edge.agent);
113
+ return own ? { namespace: own.namespace, ...(own.branch !== undefined ? { branch: own.branch } : {}) } : undefined;
114
+ }
115
+
116
+ /**
117
+ * The slot a shared derivation's own rows are stored in: the parent's
118
+ * message slot, as context-manager's derivation record names it, on the
119
+ * child's own branch. Undefined for any other inheritance, or one whose
120
+ * record doesn't say.
121
+ */
122
+ function derivedSlot(inherit: ContextInheritance | undefined): MessageSlotRef | undefined {
123
+ if (inherit?.mode !== 'shared' || typeof inherit.ownBranch !== 'string') return undefined;
124
+ const slots = (inherit.derivation as { slots?: { messageNamespace?: unknown } } | undefined)?.slots;
125
+ const namespace = slots?.messageNamespace;
126
+ if (namespace !== null && typeof namespace !== 'string') return undefined;
127
+ return { namespace, branch: inherit.ownBranch };
128
+ }
129
+
130
+ /**
131
+ * The own slot a spec or record declares, or, where it declares none (one
132
+ * written before `ownSlot` existed), the slot it meant: the shared slot for
133
+ * one that reads that slot with no observe edge on it, which is how a
134
+ * resident was declared; a shared derivation's slot; else the isolated slot
135
+ * of its namespace. A subconscious declared its reading of the shared slot
136
+ * as an observe edge from the start (subconsciousSpec), so it reads as
137
+ * isolated, as it was opened. An edge on the shared slot here is one with
138
+ * no `slot`, whatever agent it names: when the flag was all there was, such
139
+ * an edge read the shared slot, and that is how withOwnSlot restores it, so
140
+ * a spec reads as the same record does when it is restored.
141
+ */
142
+ function resolveOwnSlot(declared: {
143
+ ownSlot?: MessageSlotRef;
144
+ readsSharedSlot?: boolean;
145
+ observes?: ObserveEdge[];
146
+ inherit?: ContextInheritance;
147
+ namespace?: string;
148
+ }): MessageSlotRef | undefined {
149
+ const own = declared.ownSlot;
150
+ if (own) return { namespace: own.namespace, ...(own.branch !== undefined ? { branch: own.branch } : {}) };
151
+ const derived = derivedSlot(declared.inherit);
152
+ if (derived) return derived;
153
+ if (declared.readsSharedSlot === true && !(declared.observes ?? []).some((edge) => edge.slot === undefined)) {
154
+ return { namespace: null };
155
+ }
156
+ return typeof declared.namespace === 'string' ? { namespace: declared.namespace } : undefined;
157
+ }
158
+
159
+ /** What a record's deprecated `readsSharedSlot` says: it owns the residents' slot or observes it. */
160
+ function readsResidentsSlot(
161
+ ownSlot: MessageSlotRef | undefined,
162
+ observes: ObserveEdge[],
163
+ ownSlotOf: (agent: AgentName) => MessageSlotRef | undefined,
164
+ ): boolean {
165
+ return sameSlot(ownSlot, RESIDENTS_SLOT) || observes.some((edge) => sameSlot(observedSlot(edge, ownSlotOf), RESIDENTS_SLOT));
166
+ }
167
+
168
+ /**
169
+ * A restored record with its own slot: one written before `ownSlot` existed
170
+ * gets the slot its `readsSharedSlot`, observe edges, inheritance and
171
+ * namespace meant (resolveOwnSlot). Its `readsSharedSlot` is kept as written.
172
+ *
173
+ * Its observe edges are read as that code read them too: an edge with no
174
+ * `slot` read the residents' shared slot, whatever agent it named, so one
175
+ * naming an agent is restored without the name, an edge on the shared slot
176
+ * (which names neither), rather than one on that agent's own slot, which is
177
+ * what such an edge reads since own slots. A host that wants the agent's
178
+ * slot read declares the edge again (`{ agent }` in a spec now reads it).
179
+ */
180
+ function withOwnSlot(record: AgentRecord): AgentRecord {
181
+ if (record.ownSlot !== undefined) return record;
182
+ const observes = record.relationships?.observes;
183
+ if (Array.isArray(observes)) {
184
+ record.relationships.observes = observes.map((edge) => {
185
+ if (edge.slot !== undefined || edge.agent === undefined) return edge;
186
+ const { agent: _named, ...onShared } = edge;
187
+ return onShared;
188
+ });
189
+ }
190
+ const ownSlot = resolveOwnSlot({
191
+ readsSharedSlot: record.readsSharedSlot,
192
+ observes: record.relationships?.observes,
193
+ inherit: record.inherit,
194
+ namespace: record.namespace,
195
+ });
196
+ if (ownSlot) record.ownSlot = ownSlot;
197
+ return record;
198
+ }
199
+
79
200
  export class AgentRegistry {
80
201
  private readonly live = new Map<AgentName, AgentRecord>();
81
202
  /** Ended records, oldest first. */
@@ -128,19 +249,25 @@ export class AgentRegistry {
128
249
  * spawner's orphan, as if it had been live when the spawner ended.
129
250
  * `after` is the incarnation a resumption continues from: the new one is
130
251
  * past it, since the counter of a name that aged out of memory isn't kept.
252
+ * `declaredWith` names the agents declared with this one, such as a
253
+ * configuration's residents, which register one after another: an observe
254
+ * edge may name one of them before it is registered (two residents may
255
+ * observe each other). The edge reads that agent's slot once it is.
131
256
  */
132
- register(spec: AgentSpec, options: { resume?: boolean; after?: number } = {}): AgentRecord {
257
+ register(spec: AgentSpec, options: RegisterOptions = {}): AgentRecord {
133
258
  return this.requested(() => this.registerUnguarded(spec, options));
134
259
  }
135
260
 
136
- private registerUnguarded(spec: AgentSpec, options: { resume?: boolean; after?: number } = {}): AgentRecord {
137
- this.validate(spec, options.resume === true);
261
+ private registerUnguarded(spec: AgentSpec, options: RegisterOptions = {}): AgentRecord {
262
+ this.validate(spec, options.resume === true, options.declaredWith ?? []);
138
263
  const previous = this.awaiting.get(spec.name);
139
264
  this.awaiting.delete(spec.name);
140
265
 
141
266
  const incarnation = Math.max(this.incarnations.get(spec.name) ?? 0, options.after ?? 0) + 1;
142
267
  this.incarnations.set(spec.name, incarnation);
143
268
 
269
+ const observes = (spec.observes ?? []).map((edge) => ({ ...edge }));
270
+ const ownSlot = resolveOwnSlot({ ...spec, observes });
144
271
  const record: AgentRecord = {
145
272
  name: spec.name,
146
273
  kind: spec.kind,
@@ -152,7 +279,7 @@ export class AgentRegistry {
152
279
  onParentEnd: spec.onParentEnd ?? 'orphan',
153
280
  relationships: {
154
281
  ...(spec.spawnedBy ? { spawnedBy: spec.spawnedBy } : {}),
155
- observes: (spec.observes ?? []).map((edge) => ({ ...edge })),
282
+ observes,
156
283
  // Explicit message paths accumulate over an identity's life.
157
284
  messagePeers: previous ? [...previous.relationships.messagePeers] : [],
158
285
  ...(spec.resultTo ? { resultTo: { ...spec.resultTo } } : {}),
@@ -161,7 +288,10 @@ export class AgentRegistry {
161
288
  ...(spec.selfParticipants?.length ? { selfParticipants: [...spec.selfParticipants] } : {}),
162
289
  ...(spec.presentAs ? { presentAs: spec.presentAs } : {}),
163
290
  ...(spec.homeChannel ? { homeChannel: { serverId: spec.homeChannel.serverId, channelId: spec.homeChannel.channelId } } : {}),
164
- readsSharedSlot: spec.readsSharedSlot ?? false,
291
+ ...(ownSlot ? { ownSlot } : {}),
292
+ // Kept for readers of the record from before the split: derived from
293
+ // it, and again as agents register (refreshReadsSharedSlot).
294
+ readsSharedSlot: readsResidentsSlot(ownSlot, observes, (name) => this.recordedSlotOf(name)),
165
295
  ...(spec.namespace ? { namespace: spec.namespace } : {}),
166
296
  ...(spec.metadata ? { metadata: clone(spec.metadata) } : {}),
167
297
  createdAt: previous?.createdAt ?? this.now(),
@@ -181,11 +311,22 @@ export class AgentRegistry {
181
311
  };
182
312
  }
183
313
  this.live.set(spec.name, record);
314
+ // The deprecated flag of each live record, the new one's included, as
315
+ // the slots it names read now: an edge on an agent declared with it
316
+ // (declaredWith) reads that agent's slot only once it is registered.
317
+ this.refreshReadsSharedSlot();
184
318
  this.persist();
185
319
  return record;
186
320
  }
187
321
 
188
- private validate(spec: AgentSpec, resume = false): void {
322
+ /** Each live record's deprecated `readsSharedSlot`, derived again from its own slot and edges (readsResidentsSlot). */
323
+ private refreshReadsSharedSlot(): void {
324
+ for (const record of this.live.values()) {
325
+ record.readsSharedSlot = readsResidentsSlot(record.ownSlot, record.relationships.observes, (name) => this.recordedSlotOf(name));
326
+ }
327
+ }
328
+
329
+ private validate(spec: AgentSpec, resume = false, declaredWith: readonly AgentName[] = []): void {
189
330
  const fail = (problem: string): never => {
190
331
  throw new AgentSpecError(spec.name || '(unnamed)', problem);
191
332
  };
@@ -233,7 +374,7 @@ export class AgentRegistry {
233
374
  fail(`context source "${spec.inherit.from}" is not a registered agent`);
234
375
  }
235
376
  for (const edge of spec.observes ?? []) {
236
- if (edge.agent !== undefined && !this.live.has(edge.agent)) {
377
+ if (edge.agent !== undefined && !this.live.has(edge.agent) && !declaredWith.includes(edge.agent)) {
237
378
  fail(`observed agent "${edge.agent}" is not a registered agent`);
238
379
  }
239
380
  }
@@ -265,7 +406,9 @@ export class AgentRegistry {
265
406
  for (const { ended } of this.interruptedPending.values()) {
266
407
  if (ended.name === name && (!newest || ended.incarnation > newest.incarnation)) newest = ended;
267
408
  }
268
- return newest ?? this.findEnded?.(name);
409
+ if (newest) return newest;
410
+ const logged = this.findEnded?.(name);
411
+ return logged && withOwnSlot(clone(logged));
269
412
  }
270
413
 
271
414
  /** Flat list of agents — the discovery surface. Ended ones on request. */
@@ -322,15 +465,65 @@ export class AgentRegistry {
322
465
  return this.live.get(name)?.roles.ownsProviderScheduling === true;
323
466
  }
324
467
 
325
- /** Agents reading the residents' shared message slot. */
468
+ /**
469
+ * Agents reading the residents' shared message slot.
470
+ *
471
+ * @deprecated Use `readersOf(RESIDENTS_SLOT)`, or `slotOwners` and
472
+ * `observersOf` where owning and observing differ.
473
+ */
326
474
  sharedSlotReaders(): AgentName[] {
327
475
  return this.namesWhere((r) => r.readsSharedSlot);
328
476
  }
329
477
 
478
+ /** @deprecated Use `ownSlotOf` and `observersOf`. */
330
479
  readsSharedSlot(name: AgentName): boolean {
331
480
  return this.live.get(name)?.readsSharedSlot === true;
332
481
  }
333
482
 
483
+ /** The slot a live agent's own rows are stored in, if it names one. */
484
+ ownSlotOf(name: AgentName): MessageSlotRef | undefined {
485
+ const slot = this.live.get(name)?.ownSlot;
486
+ return slot ? { ...slot } : undefined;
487
+ }
488
+
489
+ /** Live agents whose own rows are stored in `slot`, in registration order. */
490
+ slotOwners(slot: MessageSlotRef): AgentName[] {
491
+ return this.namesWhere((r) => sameSlot(r.ownSlot, slot));
492
+ }
493
+
494
+ /**
495
+ * The slot an observe edge reads (observedSlot): an edge that names an
496
+ * agent and no slot reads that agent's own slot, as its record names it,
497
+ * the live one, else the one awaiting re-declaration, else its newest
498
+ * ended one, so an edge on an agent that ended still reads the slot it
499
+ * read. Undefined for an agent the registry doesn't know.
500
+ */
501
+ observedSlotOf(edge: ObserveEdge): MessageSlotRef | undefined {
502
+ return observedSlot(edge, (name) => this.recordedSlotOf(name));
503
+ }
504
+
505
+ /**
506
+ * Live agents that read `slot` through an observe edge, in registration
507
+ * order. With `held`, only those whose edge shows held (policy-diverted)
508
+ * traffic.
509
+ */
510
+ observersOf(slot: MessageSlotRef, options: { held?: boolean } = {}): AgentName[] {
511
+ return this.namesWhere((r) => r.relationships.observes.some(
512
+ (edge) => sameSlot(this.observedSlotOf(edge), slot) && (!options.held || edge.includeHeld === true),
513
+ ));
514
+ }
515
+
516
+ /** Every live agent that reads `slot`, as its own or through an observe edge, in registration order. */
517
+ readersOf(slot: MessageSlotRef): AgentName[] {
518
+ return this.namesWhere((r) => sameSlot(r.ownSlot, slot)
519
+ || r.relationships.observes.some((edge) => sameSlot(this.observedSlotOf(edge), slot)));
520
+ }
521
+
522
+ /** The own slot an agent's record names: live, else awaiting re-declaration, else its newest ended record. */
523
+ private recordedSlotOf(name: AgentName): MessageSlotRef | undefined {
524
+ return (this.live.get(name) ?? this.awaiting.get(name) ?? this.inspect(name))?.ownSlot;
525
+ }
526
+
334
527
  /** The channel an agent is bound to, if it has one. */
335
528
  homeChannel(name: AgentName): AgentRecord['homeChannel'] {
336
529
  return this.live.get(name)?.homeChannel;
@@ -427,8 +620,9 @@ export class AgentRegistry {
427
620
  if (!this.live.has(child.name)) continue; // ended by an earlier cascade
428
621
  if (child.onParentEnd === 'end') {
429
622
  const inner = this.endRecursive(child, 'parent-ended', record.name);
430
- cascaded.push(...inner.cascaded, inner.ended);
431
- orphaned.push(...inner.orphaned);
623
+ appendAll(cascaded, inner.cascaded);
624
+ cascaded.push(inner.ended);
625
+ appendAll(orphaned, inner.orphaned);
432
626
  } else {
433
627
  const candidates = adopters.filter((candidate) => candidate !== child.name);
434
628
  child.orphaned = { since: this.now(), formerParent, reason, candidates };
@@ -598,7 +792,7 @@ export class AgentRegistry {
598
792
  restore(snapshot: RegistrySnapshot | null | undefined, endedHistory: AgentRecord[] = []): RestoreOutcome {
599
793
  const interrupted: AgentRecord[] = [];
600
794
  for (const raw of endedHistory.slice(-this.endedRetention)) {
601
- if (raw && typeof raw === 'object' && raw.ended) this.ended.push(clone(raw));
795
+ if (raw && typeof raw === 'object' && raw.ended) this.ended.push(withOwnSlot(clone(raw)));
602
796
  }
603
797
  if (!snapshot) return { interrupted };
604
798
  if (snapshot.version !== 1) {
@@ -623,7 +817,7 @@ export class AgentRegistry {
623
817
  const endedKeys = new Set(this.ended.map((record) => `${record.name}#${record.incarnation}`));
624
818
  let dropped = false;
625
819
  for (const raw of snapshot.records) {
626
- const record = clone(raw);
820
+ const record = withOwnSlot(clone(raw));
627
821
  if (record.ended) continue; // history lives in the ended log
628
822
  if (record.lifetime.kind === 'persistent') {
629
823
  this.awaiting.set(record.name, record);
@@ -649,12 +843,17 @@ export class AgentRegistry {
649
843
  * the newest `endedRetention`. An end whose write fails is reported and
650
844
  * stays pending, so the snapshot still lists it and a later settlement (the
651
845
  * next boot's) writes it; a failed snapshot write is reported too.
846
+ * `unsettled` names jobs the host couldn't tell of yet, or whose notices
847
+ * for the primary it couldn't pass on (by name and incarnation): they stay
848
+ * pending, as such a failed end does.
652
849
  * @internal For the framework's boot, once the interrupted jobs'
653
850
  * recipients are told; not a host API.
654
851
  */
655
- settleInterrupted(): void {
852
+ settleInterrupted(unsettled: ReadonlyArray<Pick<AgentRecord, 'name' | 'incarnation'>> = []): void {
656
853
  if (this.interruptedPending.size === 0) return;
854
+ const kept = new Set(unsettled.map((record) => `${record.name}#${record.incarnation}`));
657
855
  for (const [key, { ended }] of [...this.interruptedPending]) {
856
+ if (kept.has(key)) continue;
658
857
  try {
659
858
  this.appendEndedFn?.(clone(ended));
660
859
  } catch (error) {
@@ -122,14 +122,57 @@ export interface ResultRoute {
122
122
  as: 'message' | 'tool-result';
123
123
  }
124
124
 
125
- /** Read access to another slot, merged into this agent's view. */
125
+ /**
126
+ * A message slot: where an agent's rows are stored, or a slot another agent
127
+ * reads. `namespace` null is the residents' shared un-namespaced slot; a
128
+ * string is the isolated slot of that namespace. `branch` is set for a
129
+ * derived agent, whose slot is its parent's slot on a Chronicle branch of
130
+ * its own; without it the slot is the one on the host's current branch,
131
+ * where residents and the agents beside them write. Two refs name one slot
132
+ * when both fields are equal (sameSlot).
133
+ */
134
+ export interface MessageSlotRef {
135
+ namespace: string | null;
136
+ branch?: string;
137
+ }
138
+
139
+ /**
140
+ * Read access to a slot that isn't the agent's own, merged into its view:
141
+ * the relationship a watcher has with the slot it watches (the subconscious
142
+ * with the residents' slot). The slot read is the one `slot` names, else the
143
+ * own slot of the agent `agent` names, else the residents' shared slot (the
144
+ * registry's `observedSlotOf`).
145
+ */
126
146
  export interface ObserveEdge {
127
- /** The agent whose slot is read, when the slot belongs to one. */
147
+ /**
148
+ * The agent whose slot is read. With no `slot`, the edge reads that
149
+ * agent's own slot (its `ownSlot`), on whatever branch that slot is, not
150
+ * the residents' shared slot. Where that slot is the observer's own too (a
151
+ * resident observing another, AgentConfig.observes), the edge shows the
152
+ * observer that agent's own rows there, and no other agent's. It must be
153
+ * registered when the edge is, or declared with it
154
+ * (AgentRegistry.register's `declaredWith`).
155
+ */
128
156
  agent?: AgentName;
129
- /** Message-slot namespace; undefined = the shared un-namespaced slot. */
157
+ /**
158
+ * Message-slot namespace, on the host's current branch. Undefined: the
159
+ * own slot of the agent `agent` names, or the shared un-namespaced slot
160
+ * when it names none.
161
+ */
130
162
  slot?: string;
131
- /** Only messages at or after this sequence. */
163
+ /**
164
+ * Only messages at or after this sequence.
165
+ *
166
+ * @deprecated A sequence is a coordinate of one branch's record, not of
167
+ * what an agent was shown; use `fromMessageId`. Nothing reads either yet.
168
+ */
132
169
  fromSequence?: number;
170
+ /**
171
+ * Only messages from this one on, by message id, which names one row on
172
+ * every branch and in every agent's presentation order (#291). Not read
173
+ * yet: an edge that sets it shows the rows one without it shows.
174
+ */
175
+ fromMessageId?: string;
133
176
  /** Whether held (policy-diverted) traffic in that slot is visible. */
134
177
  includeHeld?: boolean;
135
178
  }
@@ -163,7 +206,15 @@ export interface ContextInheritance {
163
206
  * (requires store support; refused when unavailable).
164
207
  */
165
208
  mode: 'copy' | 'shared';
166
- /** The parent checkpoint (sequence on the parent's branch). Filled at creation. */
209
+ /**
210
+ * The parent checkpoint: a sequence on the parent's branch. Filled at
211
+ * creation, from the derived context (its derivation's checkpoint). One a
212
+ * host sets, for a resume or a derive from an earlier state, is a
213
+ * presentation boundary (#291), such as `currentSequence()` read after a
214
+ * presentation into the parent's window, not a row's own sequence: a row
215
+ * enters the window at a write after its own, so a cut at its own
216
+ * sequence holds the row in the child's record and not in its window.
217
+ */
167
218
  atSequence?: number;
168
219
  /** The parent branch the checkpoint is on. Filled at creation. */
169
220
  branch?: string;
@@ -213,9 +264,10 @@ export interface AgentSpec {
213
264
  kind: AgentKind;
214
265
  /**
215
266
  * The model the agent runs on — "whose weights", next to `inherit.from`
216
- * for "whose context". Together they are what a consent decision about a
217
- * derived agent is made on. Filled by the framework from the agent's
218
- * configuration.
267
+ * for "whose context". Both are on the `dendrite:agent-created` trace and
268
+ * in the spec a host's `dendrite.beforeDerive` decides on, which the
269
+ * framework gives no meaning of its own. Filled by the framework from the
270
+ * agent's configuration.
219
271
  */
220
272
  model?: string;
221
273
  roles: AgentRoles;
@@ -242,9 +294,27 @@ export interface AgentSpec {
242
294
  * registry's `ChannelRef`).
243
295
  */
244
296
  homeChannel?: { serverId: string; channelId: string };
297
+ /**
298
+ * The slot this agent's own rows are stored in: the residents' shared
299
+ * slot for a resident, its own namespace's slot for an isolated agent, the
300
+ * parent's slot on its own branch for a shared fork. The resident and
301
+ * subconscious presets set it (the others leave it to the reading below),
302
+ * and the framework fills it for the agents it derives or creates from
303
+ * how it opens their context. Absent, the registry reads it as an older
304
+ * spec meant it: a shared derivation's slot; the shared slot when
305
+ * `readsSharedSlot` is set and no observe edge without a `slot` explains
306
+ * it (one naming an agent reads as on the shared slot here);
307
+ * else the isolated slot of `namespace`. A slot it reads without owning
308
+ * it is an observe edge (`observes`).
309
+ */
310
+ ownSlot?: MessageSlotRef;
245
311
  /**
246
312
  * Whether the agent reads the residents' shared un-namespaced message
247
313
  * slot (as its own slot or through an observe edge).
314
+ *
315
+ * @deprecated One flag for two relationships: owning the shared slot and
316
+ * observing it. Use `ownSlot` and `observes`. Still read for a spec that
317
+ * sets no `ownSlot` (see there).
248
318
  */
249
319
  readsSharedSlot?: boolean;
250
320
  /** Chronicle namespace owning this agent's context. */
@@ -315,6 +385,20 @@ export interface AgentRecord {
315
385
  * as a wake that names a channel without its server is.
316
386
  */
317
387
  homeChannel?: { serverId: string; channelId: string } | string;
388
+ /**
389
+ * The slot the agent's own rows are stored in (see AgentSpec.ownSlot). A
390
+ * record written before the field existed has it filled at restore from
391
+ * what the record says. Absent only for an agent whose spec named neither
392
+ * a slot nor a namespace.
393
+ */
394
+ ownSlot?: MessageSlotRef;
395
+ /**
396
+ * Whether the agent reads the residents' shared slot, as its own slot or
397
+ * through an observe edge: derived from `ownSlot` and `relationships.observes`
398
+ * at each registration of the record (a restored one included), not once.
399
+ *
400
+ * @deprecated Use `ownSlot` and `relationships.observes`.
401
+ */
318
402
  readsSharedSlot: boolean;
319
403
  namespace?: string;
320
404
  metadata?: Record<string, unknown>;
@@ -0,0 +1,88 @@
1
+ import { MembraneError } from '@animalabs/membrane';
2
+ import { OverBudgetError, UncoveredDropError } from '@animalabs/context-manager';
3
+ import { isDeadlineCut } from './inference-bounds.js';
4
+
5
+ /**
6
+ * Longest failure text the inference-failure pipeline writes to one sink: the
7
+ * reason in the exhaustion stderr line, a field of a logs/failures.log record,
8
+ * an ops alert's message, the inference log's `error`, and a trace event's
9
+ * `error` and `stack`. A failure's text is whatever its source put in
10
+ * `Error.message`, and a provider can echo the entire rejected request there
11
+ * (one OpenAI-compatible 400 echoed ~1.7 MB). The sinks with shorter bounds
12
+ * name their own, and every cut goes through boundFailureText. This bound
13
+ * doesn't depend on membrane bounding its own errors.
14
+ */
15
+ export const FAILURE_TEXT_MAX_CHARS = 2_000;
16
+
17
+ /** Longest provider error code recorded verbatim; real codes are short tokens.
18
+ * A failure's name and type are bounded the same way. */
19
+ export const PROVIDER_ERROR_CODE_MAX_CHARS = 128;
20
+
21
+ const isHighSurrogate = (text: string, index: number): boolean => {
22
+ const code = text.charCodeAt(index);
23
+ return code >= 0xd800 && code <= 0xdbff;
24
+ };
25
+ const isLowSurrogate = (text: string, index: number): boolean => {
26
+ const code = text.charCodeAt(index);
27
+ return code >= 0xdc00 && code <= 0xdfff;
28
+ };
29
+ /** Whether a cut before `index` would separate the halves of a surrogate pair. */
30
+ const splitsPair = (text: string, index: number): boolean =>
31
+ index > 0 && index < text.length && isHighSurrogate(text, index - 1) && isLowSurrogate(text, index);
32
+
33
+ /**
34
+ * `text` when it fits within `max` characters; otherwise its head and tail
35
+ * around a marker stating how many of how many characters were omitted, the
36
+ * whole within `max`. A cut that would separate a surrogate pair moves toward
37
+ * omission, so the result never holds half of a pair the text held whole, and
38
+ * bounding bounded text returns it as is. Each sink cuts the whole text, so
39
+ * its marker states the whole length.
40
+ */
41
+ export function boundFailureText(text: string, max: number = FAILURE_TEXT_MAX_CHARS): string {
42
+ if (text.length <= max) return text;
43
+ const markerLength = ` …[${text.length} of ${text.length} characters omitted]… `.length;
44
+ const budget = Math.max(0, max - markerLength);
45
+ const tailLength = Math.floor(budget / 4);
46
+ let headEnd = budget - tailLength;
47
+ if (splitsPair(text, headEnd)) headEnd--;
48
+ let tailStart = text.length - tailLength;
49
+ if (splitsPair(text, tailStart)) tailStart++;
50
+ return `${text.slice(0, headEnd)} …[${tailStart - headEnd} of ${text.length} characters omitted]… ${text.slice(tailStart)}`;
51
+ }
52
+
53
+ /**
54
+ * A MembraneError, from this framework's copy of membrane or from another: the
55
+ * host supplies the Membrane instance (config.membrane), so with two copies
56
+ * installed its errors are instances of a class this framework's `instanceof`
57
+ * doesn't match. Recognised by name as well, as context-manager's errors are;
58
+ * its fields are the same, so callers still check each one's type.
59
+ */
60
+ export function isMembraneError(error: unknown): error is MembraneError {
61
+ return error instanceof MembraneError || (error instanceof Error && error.name === 'MembraneError');
62
+ }
63
+
64
+ export function compileRefusalType(err: Error): 'over_budget' | 'context_refusal' | undefined {
65
+ if (err instanceof OverBudgetError || err.name === 'OverBudgetError') return 'over_budget';
66
+ // context-manager's fatal coverage invariant (fix/coverage-invariant-fatal,
67
+ // 2026-07-26): a compile REFUSES rather than shipping a context with
68
+ // silently-dropped messages. Recovery is identical to over_budget — kick
69
+ // the compression drain so summaries cover the un-represented span.
70
+ if (err instanceof UncoveredDropError || err.name === 'UncoveredDropError') return 'context_refusal';
71
+ return undefined;
72
+ }
73
+
74
+ export function houseReason(err: Error): string | undefined {
75
+ return isDeadlineCut(err) || compileRefusalType(err) !== undefined ? String(err.message) : undefined;
76
+ }
77
+
78
+ export function failureWords(err: Error, otherwise = 'the turn failed'): string {
79
+ const own = houseReason(err);
80
+ if (own !== undefined) return own;
81
+ if (!isMembraneError(err)) return otherwise;
82
+ if (err.type === 'safety') return "the request couldn't be completed";
83
+ const status = typeof err.httpStatus === 'number' ? `HTTP ${err.httpStatus}` : undefined;
84
+ if (err.type === 'timeout') return `the model call timed out${status ? ` (${status})` : ''}`;
85
+ const type = typeof err.type === 'string' ? boundFailureText(err.type, PROVIDER_ERROR_CODE_MAX_CHARS) : undefined;
86
+ const parts = [type, status].filter((part): part is string => part !== undefined);
87
+ return `the model call failed${parts.length > 0 ? ` (${parts.join(', ')})` : ''}`;
88
+ }