@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/README.md CHANGED
@@ -148,10 +148,29 @@ Each agent has a `ContextManager` (from `@connectome/context-manager`) that main
148
148
  - **PassthroughStrategy** - No compression, raw message replay up to budget
149
149
  - **AutobiographicalStrategy** - Chunks old messages and summarizes them into diary entries, preserving recent context uncompressed
150
150
 
151
- A turn keeps the messages it opened with whole: at a start that opens a turn, the framework pins raw (`pinRange`, named `agent-framework:turn-inputs`) the rows the agent sees that were stored after both the newest of its own messages that it sees and its consumed watermark, starting at the first such row that holds more than tool results, and removes the pin when the turn ends. The pinned rows' tokens count against the budget. If a committing compile refuses as over budget with the pin held, the pin is removed and the compile runs once more, so the turn goes on as it would have without it. A silent heartbeat tick pins nothing, and neither does a strategy without pins.
151
+ A turn keeps the messages it opened with whole: at a start that opens a turn, the framework pins raw (`pinRange`, named `agent-framework:turn-inputs`) the rows of the agent's window (#291) that it sees after both the newest of its own messages there and its consumed watermark, in the window's order, starting at the first such row that holds more than tool results, and removes the pin when the turn ends. Those are the rows presented to it since the mark, a row stored before the mark and presented after it included, and a row presented to it again, also when the mark was taken before its window was written down; a row stored and not yet presented is in neither its window nor the pin. The pinned rows' tokens count against the budget. If a committing compile refuses as over budget with the pin held, the pin is removed and the compile runs once more, so the turn goes on as it would have without it. A silent heartbeat tick pins nothing, and neither does a strategy without pins, or an agent whose configuration sets `turnInputsPin: false`.
152
152
 
153
153
  Before inference, modules and MCPL hooks can inject additional context via `gatherContext()`. An `afterUser` injection is stored in the agent's window, as the message `compile()` would have placed after the last user message, so it stays where the agent saw it: the replies after it keep their thinking, and the cache keeps the turns before it. A source's unchanged content isn't stored again while the copy the agent will read as current has it whole: the one its last request held, or a newer one a start stored whose compile then didn't record (it threw, or the start failed before it); once a fold has summarized it or the strategy's window has passed it, it's stored again, as one more row. Where a changed one lands right after the agent's own latest message, with no one else's message between, `[Continue]` still ends the request, after it. A stored row is history to the strategy, capped, sharded, budgeted and folded like any message. Its metadata carries `system: true`, `kind: 'context-injection'` and `contextInjection: { agent, namespace, contentHash, part, parts }`, which `storedInjectionMarkOf` reads, and every other agent's view leaves it out. A module's injection can set `placedEachCompile: true` to be placed by `compile()` at each start instead, near the end, at the cost of the thinking of every reply after its old place.
154
154
 
155
+ #### Legacy reasoning-loss notices
156
+
157
+ Rows marked with both `metadata.system === true` and
158
+ `metadata.kind === 'reasoning-loss'` are omitted from the agent's compiled
159
+ context and compression inputs only. Stored history is unchanged: history
160
+ `extract`, `search` and around-message lookup still return these notices
161
+ verbatim, and tool output or previously written summaries can reintroduce their
162
+ text. Selection never matches notice text.
163
+
164
+ Hosts opening the same stores should use the package's
165
+ `residentViewFilter(message, agentName)` or
166
+ `subconsciousViewFilter(message, agentName)` rather than copy the view rules.
167
+ On context-manager releases up to at least 0.25.1, a hidden first source can place that
168
+ summary after newer messages; a hidden first or last source makes it permanently
169
+ unmergeable and absent from history overview until anima-research/context-manager#369 or equivalent lands. The release that raises the context-manager dependency floor to a version
170
+ containing that fix should remove this migration limit and the migration test
171
+ `todo`. See the
172
+ [release note](changelog.d/973-reasoning-loss.fixed.md).
173
+
155
174
  ### Persistence
156
175
 
157
176
  All state is persisted in a [Chronicle](https://github.com/antra-tess/chronicle) store:
@@ -180,10 +199,17 @@ agent-framework-recover \
180
199
  ```
181
200
 
182
201
  The specified Discord message is the last message the agent will still see;
183
- everything after it is left on the old branch. The command scans in bounded
184
- windows, never compiles message content, and activates the new Chronicle
185
- branch. By default the recovery is local to the resident: nothing is posted to
186
- Discord. To let the people involved see that the agent no longer has their
202
+ everything after it is left on the old branch (on a window presented since
203
+ #291, but for the rows stored before the write that presented it, which the
204
+ new branch keeps and the window takes again after that message, and the rows
205
+ that one write presented together with it, which stay after it in the window).
206
+ The command
207
+ scans in bounded windows (on a presented window, a `--messages` count and the
208
+ walk of the rows after the anchor, which the cut returns or keeps, read the
209
+ window whole), never compiles
210
+ message content, and activates the new Chronicle branch. By default the
211
+ recovery is local to the resident: nothing is posted to Discord. To let the
212
+ people involved see that the agent no longer has their
187
213
  messages, choose awareness marks explicitly with `--marks addressed` (removed
188
214
  messages that mentioned or replied to the bot, and DMs) or `--marks all`
189
215
  (every removed Discord message). The output counts what will be marked and
@@ -229,7 +255,9 @@ agent-framework-recover \
229
255
 
230
256
  `--suppress` may be repeated or given a comma-separated list.
231
257
  `--suppress-range <first>..<last>` suppresses the inclusive context interval
232
- between two Discord messages, including intervening agent/tool entries. These
258
+ between two Discord messages, including intervening agent/tool entries: every
259
+ row stored between the two, any agent's, so on a slot residents share, rows
260
+ an agent's window never held too. These
233
261
  removals exist only on the recovery branch; the source branch remains intact.
234
262
  Suppressed Discord messages follow the same `--marks` choice.
235
263
  Selections that split a sharded body group or a tool-use/tool-result exchange
@@ -298,18 +326,19 @@ A pattern that matches no tool is reported once, as a console line and an
298
326
  A pattern that could name a server's tools is not judged until that server
299
327
  has listed them, so late connects and reconnects don't cause early reports.
300
328
 
301
- #### Modern MCP servers (2026-07-28)
329
+ #### Modern MCP servers
302
330
 
303
- The same `mcplServers` list also takes servers that speak modern MCP
304
- (revision `2026-07-28`), reached through the official SDK client. The
305
- configuration chooses the family, and nothing is probed:
331
+ The same `mcplServers` list also takes servers that speak modern MCP,
332
+ reached through the official SDK client: revision `2026-07-28`, and over
333
+ HTTP the 2025 revisions too. The configuration chooses the family, and a
334
+ stdio server speaks `2026-07-28` alone:
306
335
 
307
336
  | Configuration | Family | Transport |
308
337
  |---|---|---|
309
338
  | `url: 'ws://…'` / `'wss://…'` | MCPL (MCP `2024-11-05`) | WebSocket (`token` / `accessProvider` as `?token=`, or as bearer with `tokenTransport: 'header'`) |
310
- | `url: 'http://…'` / `'https://…'` | modern MCP | Streamable HTTP (`token` / `accessProvider` as bearer) |
339
+ | `url: 'http://…'` / `'https://…'` | modern MCP (`2026-07-28`, `2025-11-25`, `2025-06-18` or `2025-03-26`, negotiated) | Streamable HTTP (`token` / `accessProvider` as bearer) |
311
340
  | `command` | MCPL | stdio |
312
- | `command` + `protocol: 'modern'` | modern MCP | stdio |
341
+ | `command` + `protocol: 'modern'` | modern MCP (`2026-07-28`) | stdio |
313
342
 
314
343
  ```typescript
315
344
  {
@@ -410,6 +439,114 @@ revisions the server says it supports ("none listed" for an empty list), or its
410
439
  and when the list includes `2026-07-28` it says to configure the server with
411
440
  `protocol: 'modern'` (stdio) or reach it over http(s).
412
441
 
442
+ Over HTTP the revision is negotiated at each connect. The client asks
443
+ `server/discover` first and connects at `2026-07-28` when the server offers
444
+ it. A server that doesn't gets the 2025 `initialize` handshake, which asks
445
+ for `2025-11-25` and accepts `2025-11-25`, `2025-06-18` or `2025-03-26` in
446
+ the answer.
447
+ `listMcplServers()` shows the revision it settles on as `protocolVersion`,
448
+ and its `family` stays `modern`. On a 2025 revision:
449
+ - List changes arrive on the session's standalone stream, which is kept
450
+ open while the session lives, whether or not the server issues a session
451
+ id, unless the server answers its first open with a status that won't
452
+ pass: a 405, which says it offers no stream, a 501 or 505, a 4xx other
453
+ than 408, 409 and 429, or a redirect the SDK doesn't follow, such as one
454
+ off the server's origin. With a `token` or `accessProvider` set, a 401
455
+ first gets one more try, after the SDK refreshes the credential.
456
+ Such an open isn't tried again, and the session's list changes don't
457
+ arrive. A failure to open it that may pass is reported as the
458
+ connection's `error` and tried again, and a stream that drops is
459
+ reopened: the first try waits the server's `retry` hint, or the first
460
+ step of the connection's backoff when it gave none or a hint of 0, and a
461
+ try after a failed one waits the longer of the hint and the connection's
462
+ backoff.
463
+ Its first open, once the connection is up, and a reopen that missed what
464
+ came meanwhile each relist the tools of a server that announces list
465
+ changes, since a change announced while no stream was open never
466
+ arrives.
467
+ - A request's stream that the server closes after a priming event is
468
+ resumed for as long as the request lives. Its cancellation at the
469
+ deadline stops the resume too. A stream cut again before it carried a
470
+ new event id can't be resumed: the SDK sends its resume without the id
471
+ (modelcontextprotocol/typescript-sdk#2499), which the server can't match
472
+ to the call's stream, so the call doesn't get its answer, and fails as
473
+ `no-response`, at the latest at its deadline.
474
+ A call whose stream ends unanswered with nothing to resume from fails
475
+ then, as `no-response`, not at its deadline.
476
+ - The server ends the session by answering 404 to a request that carried
477
+ its id (except the stream's first open, whose 404 can also mean the
478
+ server has no GET route). A call whose POST met the 404 fails as
479
+ `error-response` with `data.httpStatus` 404, and isn't replayed. A call
480
+ whose stream the server's restart cut is `no-response`, since the server
481
+ may have run it. A session that had answered a request gets a new one at
482
+ once, whatever `reconnect` says: the connection emits `close` while its
483
+ `willReconnect` reads true, then `reconnect`, and the framework keeps the
484
+ server and its tools and relists. If the new session can't start,
485
+ `reconnect` decides, as for any lost connection, and a revision verdict
486
+ stops it for good, as on any reconnect. Without `reconnect`, that failure
487
+ emits a second `close`, while `willReconnect` reads false: the server is
488
+ gone. An answered session that ended less than the base interval
489
+ (`reconnectIntervalMs`) after it started gets a new one at once only if
490
+ the last session restarted that way had lived the interval, or there was
491
+ none: a server restarted once soon after a connect costs a restart, not
492
+ the server. A second such young end in a row is a failed connect, as is
493
+ a session's end before it answered anything: it emits `reconnect-failed`
494
+ and `close`, and follows `reconnect` with a growing backoff. So a load
495
+ balancer without sticky sessions, or a rolling upgrade that lands a
496
+ connect on an old node and its requests on a new one, shows up as a
497
+ failing server, with the framework's alert at the fifth attempt, when its
498
+ sessions end within the interval. A deployment whose sessions each live
499
+ longer restarts at most once an interval, and each restart costs the
500
+ calls in flight: the client can't tell a balancer that moves a session's
501
+ requests from a server that restarts. Once the framework's `stop()` has
502
+ begun, an ended session isn't started again, and a new one already
503
+ connecting doesn't come up.
504
+ - A server that moves to `2026-07-28` in place is followed, since the
505
+ session's end brings a new connect, which negotiates afresh. Two more
506
+ answers end a 2025 session, with a session id or without: a 400 carrying
507
+ JSON-RPC `-32022` to any request, as the SDK's 2026 handler answers 2025
508
+ traffic it refuses (`legacy: 'reject'`), and a 405 to a GET for one of
509
+ the session's streams after the server had served it (the standalone
510
+ stream's reopen, or a request stream's resume with its event id), as that
511
+ handler answers a 2025 session it serves statelessly (its default). A call
512
+ refused with `-32022` fails as `error-response` with the server's data,
513
+ its code in the host's log, and isn't replayed. Its words say the server
514
+ refused the session's revision, so the request didn't run, and whether
515
+ this host is reconnecting. A server that keeps serving a 2025 revision
516
+ statelessly to a connection that never had a standalone stream isn't
517
+ followed, unless its upgrade cut a call's stream, whose resume with its
518
+ event id then meets the 405: that connection stays on 2025 until its next
519
+ connect.
520
+ - Each session the server holds by its id is ended with a `DELETE` when the
521
+ connection lets it go, waiting at most 3 s: at `close()`, and so `stop()`
522
+ or a disconnect, a connect in flight included, and when a connect fails
523
+ after its `initialize` was answered. A session the server let go, with a
524
+ 404, a `-32022` or such a 405, gets none.
525
+
526
+ On `2026-07-28` too, a dropped stream's retries follow that timing. They
527
+ stop after two failed tries, the SDK's default, on every stream: a listen
528
+ stream that can't be resumed is reopened, with a relist, and a call whose
529
+ stream can't be resumed fails then, as `no-response`, rather than at its
530
+ deadline.
531
+
532
+ A server over HTTP that this client can't negotiate a revision with fails
533
+ with `McplProtocolVersionError`, naming what each side speaks, and is not
534
+ retried: the SDK's own refusal, after which it negotiates no further (a
535
+ `-32022` whose list names a revision from `2026-07-28` on that this client
536
+ doesn't speak, even if the list also names a 2025 revision it does, which
537
+ the message then says); a discovery or `-32022` error that lists no
538
+ revision in common, once the server has also refused the `initialize`
539
+ tried after it (`rejected`); or an `initialize` answered with a revision
540
+ the 2025 handshake doesn't accept, or with none (`mismatch`). `2026-07-28` is
541
+ negotiated through `server/discover` alone, so it isn't accepted from
542
+ `initialize`. An `initialize` that fails without an answer, such as a
543
+ timeout, is no verdict, and is retried as any failed connect is. The
544
+ error's `offeredRevisions` lists the revisions this client offered, and
545
+ `offered` is the first of them. `2024-11-05` isn't one of them over HTTP:
546
+ its HTTP binding was HTTP+SSE, another transport. Over stdio, a discovery
547
+ answer that lists revisions without `2026-07-28` is a verdict too, even one
548
+ the SDK can't read as a DiscoverResult.
549
+
413
550
  Server and transport text the framework records has the server's configured credentials replaced, in both engines. That covers connection traces, console lines, stderr traces, ops alerts, and a tool call's failure as its caller, the model and the `tool:failed` trace get it. The credentials are those the configuration holds and those its dials sent: from the url as each dial sends it (a WebSocket dial sets the token in the query, which writes the whole query again, form-encoded, and an access provider's token can be in it; with `tokenTransport: 'header'` it sets none), credential-like path segments and query names, credential-shaped or credential-named query values, and a WebSocket url's userinfo, in their encoded and decoded forms, replaced wherever they occur; every non-empty query parameter as its whole `name=value`, where no letter or digit adjoins it; the static `token`, and each token an access provider handed a dial, the newest sixteen, raw and url-encoded; and a stdio server's `env` values whose names have a part ending in a credential word, such as `GITHUB_TOKEN`, `PGPASSWORD` or `SESSION_COOKIE`, raw and url-encoded. A stdio server's command and arguments aren't derived. Standalone strings shorter than eight characters stay unchanged because they may be ordinary words; short query values are still removed with their parameter names. Treat a submitted configuration as immutable while the server uses it: apply changes with `restartMcplServer(id, newConfig)`. Secrets neither the configuration holds nor a dial sent remain outside this filtering. A JSON-RPC tool answer's result passes through as sent. No error a host's own call gets, whether thrown, rejected with or in an error answer's data, carries a credential this filtering replaces, whoever wrote its words: a server's words are redacted, then bounded, where an error is built from them, the strings in an error answer's data are redacted, and an error a library raised, such as a failed modern connect's, is handed on as its name and its redacted words alone. A stdio child that couldn't be started is refused with Node's words redacted, stopping before Node's quote of a value it refused at once, and a cause that keeps Node's error's name, those words and its `code`. With a configuration that can't be read, the server's words and data are withheld. A failed handshake's `stderrTail` is the end of the server's stderr, with every credential of up to 14,336 characters that reaches into it replaced whole. A number in an error answer's data is matched as JavaScript reads it, so one with more significant digits than a double holds has lost its last digits before it's matched. A close's or a cleanup's failure carries the transports' own words, a child's pid, its server's id and the wait it was given. A connection's events are inputs to the framework's records, which redact them.
414
551
 
415
552
  #### Feature sets
@@ -449,14 +586,15 @@ first.
449
586
 
450
587
  Plain content stays in context and is delivered on arrival through the ordinary
451
588
  channel/push path; the host only remembers where it landed. "Unread" means:
452
- above the agent's consumed watermark (set when the agent is created, and
589
+ above the agent's consumed watermark (a position in the agent's window, #291:
590
+ a row not in the window yet is unread wherever it is in the store; set when the agent is created, and
453
591
  advanced at every compile of a request it sends, which a preview isn't, and at
454
592
  a tool boundary that injects messages into a stream declaring it carries them,
455
593
  as membrane's native path does from 0.6.0), on the current branch, not a
456
594
  sharded message, and not folded into a summary by compression. A stream that
457
595
  doesn't say whether it carries injections is taken as one that doesn't: at a
458
- tool boundary nothing is injected into it, and what's queued waits for the
459
- turn's end. On any doubt — restart, branch switch, eviction — the host
596
+ tool boundary nothing is injected into it, and what arrived for the agent
597
+ waits out of its window for the turn's end. On any doubt — restart, branch switch, eviction — the host
460
598
  appends, as it does today. There is no per-subject cap: a busy channel's unread
461
599
  backlog is simply its unread backlog. Receipts and subject history are kept in
462
600
  the `mcpl/coalescing` state (retry window 1 h); audit lines are
@@ -514,9 +652,150 @@ tool list itself (RFC-014 §8.5 host-made toolsets, §8.1 surface presentation;
514
652
  and works for every server, with or without RFC-014. The `toolsets--` tools
515
653
  are registered for every agent, MCPL or none, unless a module holds the name.
516
654
 
655
+ - **Wake onboarding** (`src/gate/wake-onboarding.ts`). An MCPL server's
656
+ events are often let by, without waking the agent, by a rule written for
657
+ something else (a catch-all with no `source`) or by the gate's default. The
658
+ first such event from a server is followed by a `[wake onboarding]` notice:
659
+ the rule that let it by, and the server's suggested treatment (MCPL RFC-001
660
+ §5), numbered, with each rule's rationale, presented as the claims of a
661
+ server that may not be trustworthy. An event tagged `chat:addressed`,
662
+ `surface:turn` or `surface:alert` wakes the agent to decide.
663
+ `wake_accept_suggestions { server, rules? }` writes the chosen suggestions as
664
+ `suggested:<server>:<n>` rules scoped to that server, ahead of the first
665
+ catch-all; `wake_decline { server }` removes them. Decisions are kept in
666
+ `wake-decisions.json` beside `gate.json`, keyed to the server's declared
667
+ ontology, so the agent is asked again only when it changes (and at most once
668
+ a day per server). `gate_status.catchAll` names the catch-all; `event_tags`
669
+ shows each server's offer. `gate.onboarding: false` turns it off.
670
+
671
+ - **Event suppression** (`EventGate.decideSuppression`, `src/gate/suppress-scripts.ts`,
672
+ `src/gate/suppression-stats.ts`, `src/gate/suppression-review.ts`). A wake
673
+ rule with `suppress: { description, script? }` keeps the events it takes out
674
+ of the agent's context entirely. A rule that only doesn't wake still shows
675
+ its events. A suppressed event is stored in the chronicle as it arrives,
676
+ with `metadata.suppressed = { rule, eventType }`. Every view filter the
677
+ framework sets leaves stamped rows out: the residents', the subconscious's,
678
+ and every view derived from them. The row is stamped for nobody (an empty
679
+ `metadata.audience`), so the presentation index presents it to no window.
680
+ So a suppressed row is never compiled,
681
+ never injected at a tool boundary, never folded into memory, and never
682
+ woken for. It never becomes the speech locus and never brings a clock row
683
+ in ahead of it. The decision is made once, at arrival, before the hold, the
684
+ veto and the gate's evaluate, so no debounce batch forms for the event. The
685
+ suppress rule's `matchCount` counts what it took, and a passthrough rule ahead
686
+ of it counts it too, so a sampler fires on schedule. Because the view reads only the stamp, editing or removing the
687
+ rule changes only what arrives afterwards; the rows already hidden stay
688
+ hidden, which keeps each row's visibility fixed, as context-manager's
689
+ `viewFilter` requires. **Rollback needs a step.** A release without
690
+ suppression doesn't read the stamp. Where compression formed a summary around
691
+ a hidden row, that summary is non-contiguous under such a release's view, and
692
+ context-manager refuses the store at open (`StoreTopologyError`), unless the
693
+ strategy sets `topologyPolicy` or a kv-unified gap flag. Before rolling back,
694
+ with the host stopped, run this release's `agent-framework-recover --store
695
+ <path> --agent <name> --remove-suppressed`: it makes and activates a branch
696
+ without the hidden rows, which such a release opens, and the source branch
697
+ keeps them. That holds unless this release has made a window write of two or
698
+ more rows (the first write-down of a window that holds a row, as at the first
699
+ boot on a store with history, or a presentation of more than one row): the
700
+ store's MANIFEST is then at version 2, which no earlier release opens, and
701
+ only a copy taken before the upgrade goes back (the 291-presentation-mandatory
702
+ changelog entry). Skipped where no summary spans one, the rows render again where
703
+ they were stored (a prompt-cache miss from the first). Suppress rules load as
704
+ `defer` in every release back to 0.22, so nothing wakes for their events.
705
+ Releases 0.22 to 0.26 rewrite gate.json without `suppress` at their first
706
+ rule edit, so rolling back that far and forward again leaves rules that defer
707
+ and no longer suppress.
708
+ - **The rule.** It needs `behavior: "defer"` (or the legacy `"skip"`) written out, so an earlier
709
+ release that doesn't know `suppress` defers those events instead of
710
+ waking on them, a match that narrows it, and a `description` of what it's
711
+ for, which the review checks against. Three kinds of event are never
712
+ suppressed and go on to the next rules: an event addressed to the agent
713
+ (`chat:addressed`), the heartbeat's, and console or API input. A suppress
714
+ rule decides no wake for an event suppression was asked about: `evaluate`
715
+ and `probe` pass over it there, and the rule-order notes and the catch-all
716
+ check pass over it always. Where suppression isn't
717
+ asked, as for an event delivered to a conversation fork's own store, a
718
+ suppress rule defers the events it matches, as its behavior says.
719
+ - **The agent's own logic.** `script` is the source of a function
720
+ `(event, state) => boolean`, called for each event the rule's match takes
721
+ that reaches it (not one an earlier rule decides, an addressed one, or the
722
+ heartbeat's);
723
+ true hides it, and anything else (a truthy string included) lets the event
724
+ on to the rules after it.
725
+ `event` carries the event type, source, channel, author, tags, text,
726
+ arrival time (`at`, ms) and metadata. `state` is the rule's own JSON object
727
+ (at most 64 KB), which the script reads and changes as it likes, so a rule
728
+ can hide repeats, count, or keep a window of its own. The framework keeps
729
+ it between events and across restarts in `suppress-state.json` beside
730
+ gate.json, and starts it afresh when the script changes or the rule is
731
+ removed (not for a new description). Scripts run on one worker thread,
732
+ called synchronously with a 50 ms timeout (`gate.suppressScriptTimeoutMs`), as
733
+ gate.js is: a robustness boundary for the agent's own code, not a security
734
+ sandbox. A script that throws, times out, isn't a function, or leaves a
735
+ state it can't keep lets the event on to the rules after it, as one that
736
+ says show does, keeps its state as it was, and is named in `gate_status`
737
+ (`suppress.scriptStatus`). A timeout restarts the worker, which loads every
738
+ script again one at a time; a script that times out three times in its
739
+ last twenty runs, probes included, isn't run again until its script
740
+ changes or the host restarts, and a worker that fails to start is tried again with a later
741
+ event, later each time. wake_add_rule compiles a
742
+ script on the worker before it saves the rule. `suppressionFor` runs a
743
+ script on a copy of its state, for the probe table; the arrival paths call
744
+ `decideSuppression`, once per event, which keeps the state the script
745
+ leaves. The worker starts with the first rule that has a script.
746
+ - **Covered:** MCPL channel messages and push events (admission; a deferred
747
+ coalesced push is decided once, at its wake, and a suppressed batch is
748
+ stored at once; a batch's row follows its latest occurrence, as its wake
749
+ does, unless the batch holds an addressed occurrence, when it's shown), a module's events (every message its responses add, and
750
+ every wake they ask for, the event decided once whichever module answers
751
+ first), and a server's `[toolsets]` notices, gated as its
752
+ `mcpl:tool-changes` events.
753
+ Not covered: deliveries into a conversation fork's own store, which has no
754
+ view filter.
755
+ - **Tools.** `wake_add_rule { …, suppress: {…} }` adds a suppress rule (saved
756
+ with behavior "defer"), and `wake_remove_rule` removes it. `gate_status`
757
+ shows each suppress rule's settings and how its script has run, and counts
758
+ what it suppressed since start. `suppression_stats { rule?, recent?, since? }`
759
+ reads the stamped rows in the caller's message slot on its current
760
+ branch, so its counts cover every start and follow the branch. Per rule it
761
+ reports the count, the text size, the first and last arrival, a breakdown
762
+ by source and event type, and the newest rows with a one-line preview.
763
+ Rules removed since are listed with `inForce: false`.
764
+ `dump_suppressed { path?, rule?, since?, until?, format? }` writes the
765
+ rows to a workspace file, their text in full and any other block named:
766
+ a transcript, or JSON lines. Times are when each row was stored. At most
767
+ 4 MB; past that, the newest that fit, and the result counts the rest.
768
+ Hosts read the same rows with `AgentFramework.suppressedEvents()`.
769
+ - **Review.** Every `gate.suppressionReview.intervalMs` (two hours by
770
+ default), for each rule that hid events since the last review, a small
771
+ model (`model`, Claude Haiku 5.5 by default, on the reply judge's
772
+ membrane) reads the rule's description, its match and script while it's in
773
+ force (the script cut to 3,000 characters), and a sample of the events it hid: at most
774
+ `maxEventsPerRule` (60) spread across the period, sharing 24,000 characters
775
+ of text, so a short event is read whole and a long one takes what the
776
+ others leave. These bounds are chosen, not measured: they keep one rule's
777
+ request to roughly 8,000 input tokens. It flags any that don't fit the
778
+ description or that the agent would plausibly need to see. The agent gets a `[suppression review]`
779
+ notice naming each rule, what it hid and what was flagged, with the first
780
+ five flagged events of each rule shown, each cut short. A rule the
781
+ reviewer couldn't answer about is reviewed again next time, from where its
782
+ events begin (at most a week back; older events aren't reviewed again). A flag wakes the agent (`wakeOnFlag`, default true), unless it's asleep. With
783
+ `notify: "flagged"` the agent is told only of a flag; with the default,
784
+ `"always"`, after every review in which something was hidden. The notice
785
+ is the framework's own and never suppressed. Progress, retries and each
786
+ rule's last description (noted as the rule hides a row) are kept in
787
+ `suppression-review.json` beside gate.json, written as the review starts,
788
+ so a restart resumes and a removed rule's events are still judged. Its
789
+ calls count toward the agent's usage, as the reply judge's do, and a wake
790
+ on a flag is a full turn of the agent's own model, so a false flag costs
791
+ that turn.
792
+ `gate.suppressionReview: false` turns it off; `AgentFramework.reviewSuppression()`
793
+ runs one now. The `gate:suppression-review` trace records each review.
794
+
517
795
  - **The built-in toolset, `settings`.** The host tools an agent reaches for
518
796
  rarely: `agent_settings`, `wake_add_rule`, `wake_remove_rule`,
519
- `gate_status`, `event_tags`, `set_tool_visibility`, `set_tool_description`,
797
+ `wake_accept_suggestions`, `wake_decline`,
798
+ `gate_status`, `suppression_stats`, `dump_suppressed`, `event_tags`, `set_tool_visibility`, `set_tool_description`,
520
799
  `heartbeat--heartbeat_status`, `heartbeat--heartbeat_trigger`,
521
800
  `workspace--status`, every module utility (`Module.getUtilities`), and a
522
801
  module's tools it places there (`Module.toolPlacement` answering
@@ -634,7 +913,7 @@ A model call that stops making progress is cut, so an agent isn't held by a prov
634
913
  - **A resident's streamed call**, and an MCPL server's streamed inference request, get membrane's progress bounds where the adapter's stream reports content (`AnthropicAdapter`, `BedrockAdapter`). Before content, a call ends 60 s after its send, or, once a thinking block has started, at the later of that and the block's allowance, its `max_tokens` at 87 tokens a second. Once content has begun, it ends after 150 s without a provider event, or after the block's allowance when the last event was a thinking block's start and the allowance is longer. Membrane's README gives the rules whole.
635
914
  - **Every streamed call the framework makes** also gets a flat bound from its last provider event: the larger of a setup term plus that allowance and 1,862 s (`FLAT_MINIMUM_MS`). The setup term is the larger of the before-content and between-events bounds, 150 s by default. So on an adapter with progress bounds the flat bound never runs out first. On one without, such as `OpenAICompatibleAdapter`, it's the only progress bound, and an SSE keepalive comment doesn't restart it, though a data frame that parses as an event does, a provider's own ping object included.
636
915
  - **A whole non-streamed call**, a compression's, the reply judge's, or an MCPL server's non-streamed inference request, ends the setup term plus its allowance after its send, or, when it names no `max_tokens`, at the larger of the setup term and the flat minimum, unless its caller passed a `progressTimeoutMs` of its own. Its caller gets the `timeout` error. The event gate's own 20 s judge timeout ends the reply judge's call first at the defaults. Context-manager takes a cut compression call as a transient failure: the lane pauses and tries again, so compression that never lands within its bound shows as a lane that keeps pausing, not as a failed turn, and is a reason to raise the bound. On an adapter whose `complete()` streams under the hood, such as membrane's OpenAI Responses adapter in subscription mode, membrane renews the bound at each parsed provider event, so there it runs from the last event, as a stream's flat bound does.
637
- - **A turn's call a bound cuts** fails as a `timeout`, and `DefaultErrorPolicy` gives it one fresh call, 1 s later, whatever other failures its turn had, except in a turn sent again after its retries ran out, before one of its rounds has run or had its tool calls cut off, whose failures get no retries. A fresh call that's cut too, with no other failure between, ends the turn, and its `[inference-failed]` note says the call timed out, that the call was made again and timed out again, and the reason. For a bound's cut the reason names the bound that ran out and the host's setting for it. A deadline expiry membrane throws (the Anthropic adapter's own watchdogs, the SDK's request timeout, a runtime's fetch timeout on a silent connection) counts the same toward the fresh call and the turn's end, and its reason is its own message, which names no setting. A bound's cut isn't `retryable`, so a host's own `errorPolicy` that retries only retryable errors gives it no fresh call; an expiry membrane throws stays `retryable`, as membrane made it.
916
+ - **A turn's call a bound cuts** fails as a `timeout`, and `DefaultErrorPolicy` gives it one fresh call, 1 s later, whatever other failures its turn had, except in a turn sent again after its retries ran out, before one of its rounds has run or had its tool calls cut off, whose failures get no retries. A fresh call that's cut too, with no other failure between, ends the turn, and its `[inference-failed]` note says the call timed out, that the call was made again and timed out again, and, for a bound's cut, the reason, which names the bound that ran out and the host's setting for it. A deadline expiry membrane throws (the Anthropic adapter's own watchdogs, the SDK's request timeout, a runtime's fetch timeout on a silent connection) counts the same toward the fresh call and the turn's end, and its note gives no reason: the note gives one only for a bound's cut, a stream's abort or context-manager's refusal of a compile. A bound's cut isn't `retryable`, so a host's own `errorPolicy` that retries only retryable errors gives it no fresh call; an expiry membrane throws stays `retryable`, as membrane made it.
638
917
  - **A stream that keeps sending content** isn't ended by any of these, since content is progress.
639
918
 
640
919
  These bounds don't replace the deadlines below them: a silent connection still meets the runtime's own fetch timeout (360 s on Bun 1.4.4) and, on `AnthropicAdapter`, the adapter's 600 s first-event and idle watchdogs, so on a silent connection a bound raised past those is met by them first.
@@ -668,7 +947,7 @@ The non-streaming path uses `AbortSignal` forwarded to `membrane.stream()`, whic
668
947
 
669
948
  A turn whose stream is cancelled this way, or by `agent.cancelStream()`, ends as a stop, whether the stream reports the cancel as `aborted`, as an `error`, or by just closing: one `inference:aborted` carrying the caller's reason (`user` when none was given), a `[turn-interrupted]` note in the agent's context, and never `inference:exhausted`, which is for failed inferences. A stream that membrane aborts on its own, at a deadline or on an error (reason `timeout` or `error`), is a failed inference. `stop()` and an operator's quiesce abandon end a turn as a stop too, with their own reasons, `shutdown` and `quiesce_abandoned`, and write no note, except the `[speech-not-posted]` notice when the turn's rounds left speech unposted (XML tool mode, or a membrane without `roundContent`; see below). A stopped stream still writes its inference-log row, with `success: false`.
670
949
 
671
- A caller's stop and an operator's quiesce abandon also reach a turn that has no live stream: one running its modules' `gatherContext`, its MCPL hooks or its compile, one waiting to retry a failed inference or compile (`abortInference()` and an operator's abandon end the wait, and a stop through `Agent.cancelStream()` waits for it to run out), and one whose tool-result guard is waiting to retry after a refusal, which holds only the round's finished stream. The turn ends at its next check, before it sends anything more, as a stop on a live stream ends it, and a turn whose compile fails after the stop ends at the failure, before a provider hold, the error policy's retry or `inference:exhausted`. A caller's stop leaves the note saying the turn was stopped before it sent anything or before it finished, and an operator's abandon leaves none, except the `[speech-not-posted]` notice when the turn's rounds left speech unposted. `abortInference()` returns `true` for such a turn. In the guard's wait, `stop()` ends the turn as a stop with reason `shutdown`, and an operator's abandon ends it as the abandon, even when a caller's stop came first; with both, it ends as `stop()`'s. `stop()` drops a turn in its hooks, its compile or a wait to retry before it sends anything, as before, and drops one whose compile fails while it runs, once a caller's stop has reached it, rather than ending it as that stop. While the failure of a turn's stream is reported (`inference:failed`), the agent no longer holds that stream, so `abortInference()` from a listener reports `false`. A failure to build a turn's request is reported while the turn's check is still ahead, so there `abortInference()` reports `true`, and the turn ends at that check. A caller's stop made where no check of a turn is ahead stops nothing, and doesn't end the agent's next turn: while the agent is idle, while a turn finishes its last delivery, and while a non-streaming turn's tool results wait to start the turn's next call. A turn whose continuation (a budget restart's, or a tool-result guard's retry) waits to start, or whose failed call a provider-capacity hold or a provider's stated wait keeps for its retry, is ended by `abortInference()`, by an operator's quiesce abandon and by `stop()`: the continuation or retry is dropped, and the turn ends with the caller's reason, `quiesce_abandoned` or `shutdown`. After `abortInference()` or an abandon, wakes that arrived meanwhile still run (after the abandon, once the host resumes): those a hold keeps beside the retry, and those the scheduler merged into the retry's or the continuation's start before that start began, which the stop or the abandon puts back in the queue. A stop through `Agent.cancelStream()` stops nothing there.
950
+ A caller's stop and an operator's quiesce abandon also reach a turn that has no live stream: one running its modules' `gatherContext`, its MCPL hooks or its compile, one waiting to retry a failed inference or compile (`abortInference()` and an operator's abandon end the wait, and a stop through `Agent.cancelStream()` waits for it to run out), and one whose tool-result guard is waiting to retry after a refusal, which holds only the round's finished stream. The turn ends at its next check, before it sends anything more, as a stop on a live stream ends it, and a turn whose compile fails after the stop ends at the failure, before a provider hold, the error policy's retry or `inference:exhausted`. A caller's stop leaves the note saying the turn was stopped before it sent anything or before it finished, and an operator's abandon leaves none, except the `[speech-not-posted]` notice when the turn's rounds left speech unposted. `abortInference()` returns `true` for such a turn. In the guard's wait, `stop()` ends the turn as a stop with reason `shutdown`, and an operator's abandon ends it as the abandon, even when a caller's stop came first; with both, it ends as `stop()`'s. `stop()` drops a turn in its hooks, its compile or a wait to retry before it sends anything, as before, and drops one whose compile fails while it runs, once a caller's stop has reached it, rather than ending it as that stop. While the failure of a turn's stream is reported (`inference:failed`), the agent no longer holds that stream, so `abortInference()` from a listener reports `false`. A failure to build a turn's request is reported while the turn's check is still ahead, so there `abortInference()` reports `true`, and the turn ends at that check. A caller's stop made where no check of a turn is ahead stops nothing, and doesn't end the agent's next turn: while the agent is idle, except for a turn a budget hold keeps (below), while a turn finishes its last delivery, and while a non-streaming turn's tool results wait to start the turn's next call. A turn whose continuation (a budget restart's, or a tool-result guard's retry) waits to start, or whose failed call a provider-capacity hold or a provider's stated wait keeps for its retry, is ended by `abortInference()`, by an operator's quiesce abandon and by `stop()`: the continuation or retry is dropped, and the turn ends with the caller's reason, `quiesce_abandoned` or `shutdown`. After `abortInference()` or an abandon, wakes that arrived meanwhile still run (after the abandon, once the host resumes): those a hold keeps beside the retry, and those the scheduler merged into the retry's or the continuation's start before that start began, which the stop or the abandon puts back in the queue. A stop through `Agent.cancelStream()` stops nothing there. A turn a budget hold keeps waiting for compression is idle: `abortInference()` and `stop()` end it with one `inference:aborted`, and after `abortInference()` the wakes that joined it go back in the queue; a quiesce has nothing of it to abandon. Once released, a first start is a queued wake until its start begins, which `abortInference()` leaves to run, and a continuation is a queued continuation, which it ends.
672
951
 
673
952
  In XML tool mode, or with a membrane that gives a tool round no `roundContent`, such a round's speech reaches a channel only when its stream completes. When the stream ends any other way (a failure, a stop, a tool that ends the turn, an error policy's retry, a context-budget or tool-result-guard restart, a quiesce abandon or `stop()`), the rounds' words are already in the agent's context, and a `[speech-not-posted]` notice tells the agent that nothing it wrote before its tool calls in those rounds was posted, so it can send what should go out again. It's written once for those rounds: where the turn ends, or before a retry's or a restart's request is sent. A turn its own `skip_reply` ends, a silent turn, a round still waiting on its tools when its stream ends (which never reaches the context) and the rounds of a stream that completes get none. In XML tool mode with thinking on, a round whose text was only thinking gets the notice too, since the framework has only the round's raw text there. A failed turn's `[inference-failed]` note says that its earlier rounds ran when any round reached the context with its results, and that a round's tool calls were started and may have taken effect when any of its streams ended before that round's results came back; a caller's stop says the latter in its `[turn-interrupted]` note too. A turn that fails, or that a caller stops, writes no `[delivered]` receipt, so when its rounds' plain speech was delivered live (native tool mode, in locus or hybrid routing), its `[inference-failed]` note, or the stop's `[turn-interrupted]` note, says that speech was handed on for delivery as written and that no `[delivered]` line was written for it. When some of its speech was still being routed as the turn ended, the note also says it may yet be posted and to check the channel before sending it again. A delivery that finished undelivered gets neither: a channel delivery's `[discord-send-failed]` note, or a refused `>>>` envelope's bounce, already says what happened. A quiesce abandon and `stop()` write neither note.
674
953
 
@@ -691,10 +970,14 @@ Pause the inference thread and MCPL data planes while keeping the framework,
691
970
  context managers, and membrane hot — so compression/refold/quarantine work runs
692
971
  through the live machinery instead of offline scripts (issue #122). Quiesce
693
972
  persists across restarts; `resume` gates on a fresh per-agent feasibility
694
- preview of the current runtime settings (`force` overrides).
973
+ preview of the current runtime settings (`force` overrides). While quiesced, a
974
+ context write (a module event, an API `message.send`) is stored at once, unless
975
+ a surgery's hold or a context manager refusing its work keeps it in memory, and
976
+ enters the agents' windows at `resume`, or, for an agent whose turn is still
977
+ draining then, at that turn's next point (#291).
695
978
 
696
979
  ```typescript
697
- await framework.quiesce({ reason: 'refold', timeoutMs: 120_000, abandon: false }); // throws QuiesceBlockedError if the deferred writes can't reach disk
980
+ await framework.quiesce({ reason: 'refold', timeoutMs: 120_000, abandon: false });
698
981
  await framework.maintenanceTick(); // drain quarantine / advance merges
699
982
  await framework.resume(); // throws ResumeBlockedError if the layout won't compile
700
983
  ```
@@ -715,7 +998,8 @@ for deployments that front the port with a proxy.
715
998
  message and switches to the fork; `suppressMessages(agent, { messageIds })`
716
999
  forks at the head and removes the chosen messages on the fork. The source
717
1000
  branch stays intact either way. Removing Discord messages from an agent's
718
- context is local to that agent unless the operator also chooses to mark them:
1001
+ context marks nothing on Discord unless the operator also chooses to mark
1002
+ them:
719
1003
 
720
1004
  ```typescript
721
1005
  const preview = framework.previewSurgeryMarks('cairn', { rollbackTo: messageId });
@@ -727,6 +1011,36 @@ const result = await framework.rollbackToMessage('cairn', {
727
1011
  // result.markers: { status: 'none' | 'queued' | 'not-scheduled', ... }
728
1012
  ```
729
1013
 
1014
+ An agent's context follows its own window (#291), so the cut is the write
1015
+ that presented the target: a rollback removes only the rows stored after
1016
+ that write, and the rows stored before it that the window took after the
1017
+ target leave the window (`returnedToArrivals`, on the result and the
1018
+ preview). The agent's own rows among them enter it again at its next
1019
+ presentation point, the surgery's release; the rest wait outside it until
1020
+ the agent's next turn start, so for them the cut holds until the agent
1021
+ speaks again, on the rollback's branch: a later move of the store's branch,
1022
+ a suppression or rollback through another agent included, ends it. That hold
1023
+ is in memory: a restart before then presents them at its pass over every
1024
+ agent at rest, or, where it boots quiesced, at the resume. Rows that one write presented together with
1025
+ the target stay after it (`keptInWindowAfterTarget`), since no branch cut
1026
+ separates them from it: a rollback that only they follow is refused and
1027
+ names them, and `suppressMessages` (on the slot's owner, for a row of a slot
1028
+ the agent observes, which the refusal names), or, for those with a Discord message id,
1029
+ the host's `hide` by it, run on the agent whose own slot holds the row (the
1030
+ refusal names it), takes them out. A row holding a result of a call at or
1031
+ before the target (a tool boundary presents a round's reply and its results
1032
+ in one write) isn't advised for suppression, which would leave the call
1033
+ without its result: the refusal says to roll back before the call. The preview's `neverPresented` counts the messages it
1034
+ removes that no live agent is shown: no window holds them, or only windows
1035
+ whose view leaves them out (a row a tune-out holds); a message that may be in
1036
+ a window that couldn't be read isn't counted, and is logged. The host command's
1037
+ `/undo n` counts back over the rows of the agent's own slot in its window. A
1038
+ removal in place
1039
+ (a `hide`, a suppression on its fork, a module's or the coalescer's removal)
1040
+ takes the rows out of every other live agent's window on that branch that
1041
+ held them too, each at its own place; a window on a branch whose record
1042
+ still holds them keeps them.
1043
+
730
1044
  - **`marks`** is `'none'` (the default), or `{ scope: 'addressed' | 'all', refs? }`.
731
1045
  `addressed` covers messages tagged `chat:addressed` (mentions, replies to
732
1046
  the bot, DMs). Passing a preview's `refs` binds the choice to exactly that
@@ -878,7 +1192,11 @@ framework.onTrace((event) => {
878
1192
  Trace events are observability-only. They are intended for logs, metrics, UI
879
1193
  updates, and debugging, not framework-internal control flow. Internal lifecycle
880
1194
  code should use state-machine signals or process events instead of consuming
881
- traces, so trace emit order does not become an API contract.
1195
+ traces, so trace emit order does not become an API contract. One exception: the
1196
+ Discord module applies the edits and deletes it kept for a held create on
1197
+ `message:added` (source `surgery-release`), and ends a lost create's record on
1198
+ `message:lost`, so those two traces, emitted as a surgery's release stores a
1199
+ held write or loses it, are a contract.
882
1200
 
883
1201
  Query raw inference logs:
884
1202
 
@@ -22,6 +22,11 @@
22
22
  * gets the same disposition. A reader of the slot (the subconscious, or a
23
23
  * reader fork) sees held traffic through its own view; that is not a
24
24
  * delivery decision of this stage.
25
+ *
26
+ * Suppression is the other disposition: a suppress rule (a gate rule with
27
+ * a `suppress` object) took the event, so it's stored and never rendered, for
28
+ * any reader. It's decided first. A suppressed event is never held, never
29
+ * shown to the gate, never woken for, and never the locus.
25
30
  */
26
31
  export interface InboundEnvelope {
27
32
  /** Which MCPL lane carried it. */
@@ -70,11 +75,24 @@ export interface AdmissionPolicy {
70
75
  evaluate(env: InboundEnvelope): boolean;
71
76
  probe(env: InboundEnvelope): boolean;
72
77
  };
78
+ /**
79
+ * The suppress rule that takes this event, or null; null where the event
80
+ * would land in a view that doesn't hide suppressed rows. Asked once per
81
+ * event, since it commits (a rule's script keeps the state it leaves, the
82
+ * gate's decideSuppression). Left out, nothing is suppressed.
83
+ */
84
+ suppressionFor?(env: InboundEnvelope): string | null;
85
+ }
86
+ /** The record a suppressed event's row carries: the rule that took it. */
87
+ export interface SuppressionRecord {
88
+ rule: string;
73
89
  }
74
90
  export interface AdmissionDecision {
75
91
  recipients: string[];
76
92
  /** Null = delivered into the recipients' view; otherwise held, with the record to stamp. */
77
93
  held: HoldRecord | null;
94
+ /** Null unless a suppress rule took the event: then it's stored, never rendered, and nothing else is decided for it. */
95
+ suppressed: SuppressionRecord | null;
78
96
  /** Whether the recipients may be woken by this event. */
79
97
  wake: boolean;
80
98
  /**
@@ -1 +1 @@
1
- {"version":3,"file":"admit.d.ts","sourceRoot":"","sources":["../../../src/admission/admit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAEH,MAAM,WAAW,eAAe;IAC9B,kCAAkC;IAClC,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB;qEACiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,0EAA0E;IAC1E,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oFAAoF;IACpF,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,oBAAoB,IAAI,MAAM,EAAE,CAAC;IACjC,mDAAmD;IACnD,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IAChE;;;;;OAKG;IACH,IAAI,CAAC,EAAE;QACL,QAAQ,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC;QACxC,KAAK,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC;KACtC,CAAC;CACH;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,4FAA4F;IAC5F,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB,yDAAyD;IACzD,IAAI,EAAE,OAAO,CAAC;IACd;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,eAAe,GAAG,iBAAiB,CAmBtF"}
1
+ {"version":3,"file":"admit.d.ts","sourceRoot":"","sources":["../../../src/admission/admit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAEH,MAAM,WAAW,eAAe;IAC9B,kCAAkC;IAClC,IAAI,EAAE,SAAS,GAAG,MAAM,CAAC;IACzB,QAAQ,EAAE,MAAM,CAAC;IACjB;qEACiE;IACjE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,iDAAiD;IACjD,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,0EAA0E;IAC1E,SAAS,EAAE,OAAO,CAAC;IACnB;;;;;OAKG;IACH,MAAM,EAAE,OAAO,CAAC;IAChB,sDAAsD;IACtD,YAAY,CAAC,EAAE,MAAM,EAAE,CAAC;IACxB,oFAAoF;IACpF,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACvC;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,UAAU,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,QAAQ,EAAE,MAAM,CAAC;IACjB,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,eAAe;IAC9B,kDAAkD;IAClD,oBAAoB,IAAI,MAAM,EAAE,CAAC;IACjC,mDAAmD;IACnD,OAAO,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAAC;IAChE;;;;;OAKG;IACH,IAAI,CAAC,EAAE;QACL,QAAQ,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC;QACxC,KAAK,CAAC,GAAG,EAAE,eAAe,GAAG,OAAO,CAAC;KACtC,CAAC;IACF;;;;;OAKG;IACH,cAAc,CAAC,CAAC,GAAG,EAAE,eAAe,GAAG,MAAM,GAAG,IAAI,CAAC;CACtD;AAED,0EAA0E;AAC1E,MAAM,WAAW,iBAAiB;IAChC,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,iBAAiB;IAChC,UAAU,EAAE,MAAM,EAAE,CAAC;IACrB,4FAA4F;IAC5F,IAAI,EAAE,UAAU,GAAG,IAAI,CAAC;IACxB,wHAAwH;IACxH,UAAU,EAAE,iBAAiB,GAAG,IAAI,CAAC;IACrC,yDAAyD;IACzD,IAAI,EAAE,OAAO,CAAC;IACd;;;;OAIG;IACH,QAAQ,EAAE,OAAO,CAAC;IAClB,8EAA8E;IAC9E,KAAK,EAAE,OAAO,CAAC;CAChB;AAED,wBAAgB,KAAK,CAAC,GAAG,EAAE,eAAe,EAAE,MAAM,EAAE,eAAe,GAAG,iBAAiB,CA6BtF"}
@@ -22,22 +22,35 @@
22
22
  * gets the same disposition. A reader of the slot (the subconscious, or a
23
23
  * reader fork) sees held traffic through its own view; that is not a
24
24
  * delivery decision of this stage.
25
+ *
26
+ * Suppression is the other disposition: a suppress rule (a gate rule with
27
+ * a `suppress` object) took the event, so it's stored and never rendered, for
28
+ * any reader. It's decided first. A suppressed event is never held, never
29
+ * shown to the gate, never woken for, and never the locus.
25
30
  */
26
31
  export function admit(env, policy) {
27
32
  const recipients = env.targetAgents ?? policy.untargetedRecipients();
33
+ // Suppressed: stored where it arrived and never rendered, so nothing
34
+ // downstream acts for anyone. Not held (a reader would see it), not shown
35
+ // to the gate's evaluate (no batch forms; the passthrough rules ahead of its
36
+ // rule have counted it), not a wake, not the locus.
37
+ const rule = policy.suppressionFor?.(env) ?? null;
38
+ if (rule !== null) {
39
+ return { recipients, held: null, suppressed: { rule }, wake: false, gatePass: false, locus: false };
40
+ }
28
41
  const held = env.channelId ? policy.holdFor(env.serverId, env.channelId) : null;
29
42
  if (held) {
30
43
  // Held: nothing downstream may act for the recipients. The gate is only
31
44
  // asked what it would have done — no batch forms, no counter moves.
32
45
  const gatePass = env.vetoed ? false : (policy.gate ? policy.gate.probe(env) : true);
33
- return { recipients, held, wake: false, gatePass, locus: false };
46
+ return { recipients, held, suppressed: null, wake: false, gatePass, locus: false };
34
47
  }
35
48
  if (env.vetoed) {
36
- return { recipients, held: null, wake: false, gatePass: false, locus: true };
49
+ return { recipients, held: null, suppressed: null, wake: false, gatePass: false, locus: true };
37
50
  }
38
51
  // Deliverable: now the gate may take its side effects (a debounce batch
39
52
  // is the gate's own deferred wake and is consumed later by the same rule).
40
53
  const gatePass = policy.gate ? policy.gate.evaluate(env) : true;
41
- return { recipients, held: null, wake: gatePass, gatePass, locus: true };
54
+ return { recipients, held: null, suppressed: null, wake: gatePass, gatePass, locus: true };
42
55
  }
43
56
  //# sourceMappingURL=admit.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"admit.js","sourceRoot":"","sources":["../../../src/admission/admit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AAqEH,MAAM,UAAU,KAAK,CAAC,GAAoB,EAAE,MAAuB;IACjE,MAAM,UAAU,GAAG,GAAG,CAAC,YAAY,IAAI,MAAM,CAAC,oBAAoB,EAAE,CAAC;IACrE,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAEhF,IAAI,IAAI,EAAE,CAAC;QACT,wEAAwE;QACxE,oEAAoE;QACpE,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACpF,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACnE,CAAC;IAED,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACf,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IAC/E,CAAC;IAED,wEAAwE;IACxE,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAC3E,CAAC"}
1
+ {"version":3,"file":"admit.js","sourceRoot":"","sources":["../../../src/admission/admit.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AAmFH,MAAM,UAAU,KAAK,CAAC,GAAoB,EAAE,MAAuB;IACjE,MAAM,UAAU,GAAG,GAAG,CAAC,YAAY,IAAI,MAAM,CAAC,oBAAoB,EAAE,CAAC;IAErE,qEAAqE;IACrE,0EAA0E;IAC1E,6EAA6E;IAC7E,oDAAoD;IACpD,MAAM,IAAI,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC,GAAG,CAAC,IAAI,IAAI,CAAC;IAClD,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;QAClB,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACtG,CAAC;IAED,MAAM,IAAI,GAAG,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAEhF,IAAI,IAAI,EAAE,CAAC;QACT,wEAAwE;QACxE,oEAAoE;QACpE,MAAM,QAAQ,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC;QACpF,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC;IACrF,CAAC;IAED,IAAI,GAAG,CAAC,MAAM,EAAE,CAAC;QACf,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;IACjG,CAAC;IAED,wEAAwE;IACxE,2EAA2E;IAC3E,MAAM,QAAQ,GAAG,MAAM,CAAC,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,IAAI,CAAC,QAAQ,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC;IAChE,OAAO,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,UAAU,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC;AAC7F,CAAC"}