@animalabs/agent-framework 0.28.0 → 0.30.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 (289) hide show
  1. package/README.md +88 -25
  2. package/dist/src/admission/admit.d.ts +90 -0
  3. package/dist/src/admission/admit.d.ts.map +1 -0
  4. package/dist/src/admission/admit.js +43 -0
  5. package/dist/src/admission/admit.js.map +1 -0
  6. package/dist/src/agent.d.ts +21 -0
  7. package/dist/src/agent.d.ts.map +1 -1
  8. package/dist/src/agent.js +47 -7
  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 +3 -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 +7 -3
  15. package/dist/src/api/server.js.map +1 -1
  16. package/dist/src/author-presentation.d.ts +50 -0
  17. package/dist/src/author-presentation.d.ts.map +1 -0
  18. package/dist/src/author-presentation.js +58 -0
  19. package/dist/src/author-presentation.js.map +1 -0
  20. package/dist/src/auto-continue.d.ts +2 -0
  21. package/dist/src/auto-continue.d.ts.map +1 -1
  22. package/dist/src/auto-continue.js +5 -2
  23. package/dist/src/auto-continue.js.map +1 -1
  24. package/dist/src/code-execution/isolate/runner.d.ts +7 -5
  25. package/dist/src/code-execution/isolate/runner.d.ts.map +1 -1
  26. package/dist/src/code-execution/isolate/runner.js +7 -5
  27. package/dist/src/code-execution/isolate/runner.js.map +1 -1
  28. package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
  29. package/dist/src/code-execution/isolate/scripts.js +3 -2
  30. package/dist/src/code-execution/isolate/scripts.js.map +1 -1
  31. package/dist/src/dendrite/index.d.ts +6 -0
  32. package/dist/src/dendrite/index.d.ts.map +1 -0
  33. package/dist/src/dendrite/index.js +3 -0
  34. package/dist/src/dendrite/index.js.map +1 -0
  35. package/dist/src/dendrite/presets.d.ts +60 -0
  36. package/dist/src/dendrite/presets.d.ts.map +1 -0
  37. package/dist/src/dendrite/presets.js +117 -0
  38. package/dist/src/dendrite/presets.js.map +1 -0
  39. package/dist/src/dendrite/registry.d.ts +260 -0
  40. package/dist/src/dendrite/registry.d.ts.map +1 -0
  41. package/dist/src/dendrite/registry.js +742 -0
  42. package/dist/src/dendrite/registry.js.map +1 -0
  43. package/dist/src/dendrite/types.d.ts +392 -0
  44. package/dist/src/dendrite/types.d.ts.map +1 -0
  45. package/dist/src/dendrite/types.js +24 -0
  46. package/dist/src/dendrite/types.js.map +1 -0
  47. package/dist/src/framework.d.ts +846 -67
  48. package/dist/src/framework.d.ts.map +1 -1
  49. package/dist/src/framework.js +4213 -924
  50. package/dist/src/framework.js.map +1 -1
  51. package/dist/src/gate/event-gate.d.ts +46 -14
  52. package/dist/src/gate/event-gate.d.ts.map +1 -1
  53. package/dist/src/gate/event-gate.js +143 -53
  54. package/dist/src/gate/event-gate.js.map +1 -1
  55. package/dist/src/gate/index.d.ts +1 -1
  56. package/dist/src/gate/index.d.ts.map +1 -1
  57. package/dist/src/gate/index.js +1 -1
  58. package/dist/src/gate/index.js.map +1 -1
  59. package/dist/src/gate/types.d.ts +12 -2
  60. package/dist/src/gate/types.d.ts.map +1 -1
  61. package/dist/src/heartbeat/heartbeat.d.ts +18 -0
  62. package/dist/src/heartbeat/heartbeat.d.ts.map +1 -1
  63. package/dist/src/heartbeat/heartbeat.js +34 -8
  64. package/dist/src/heartbeat/heartbeat.js.map +1 -1
  65. package/dist/src/held-writes.d.ts +3 -3
  66. package/dist/src/held-writes.js +3 -3
  67. package/dist/src/host-presentation.d.ts +174 -0
  68. package/dist/src/host-presentation.d.ts.map +1 -0
  69. package/dist/src/host-presentation.js +565 -0
  70. package/dist/src/host-presentation.js.map +1 -0
  71. package/dist/src/index.d.ts +7 -4
  72. package/dist/src/index.d.ts.map +1 -1
  73. package/dist/src/index.js +7 -2
  74. package/dist/src/index.js.map +1 -1
  75. package/dist/src/line-reader.d.ts +4 -0
  76. package/dist/src/line-reader.d.ts.map +1 -0
  77. package/dist/src/line-reader.js +59 -0
  78. package/dist/src/line-reader.js.map +1 -0
  79. package/dist/src/mcpl/channel-registry.d.ts +118 -15
  80. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  81. package/dist/src/mcpl/channel-registry.js +266 -110
  82. package/dist/src/mcpl/channel-registry.js.map +1 -1
  83. package/dist/src/mcpl/conversation-router.d.ts.map +1 -1
  84. package/dist/src/mcpl/conversation-router.js +2 -1
  85. package/dist/src/mcpl/conversation-router.js.map +1 -1
  86. package/dist/src/mcpl/convert-block.d.ts +1 -0
  87. package/dist/src/mcpl/convert-block.d.ts.map +1 -1
  88. package/dist/src/mcpl/convert-block.js +16 -4
  89. package/dist/src/mcpl/convert-block.js.map +1 -1
  90. package/dist/src/mcpl/hook-orchestrator.d.ts.map +1 -1
  91. package/dist/src/mcpl/hook-orchestrator.js +4 -3
  92. package/dist/src/mcpl/hook-orchestrator.js.map +1 -1
  93. package/dist/src/mcpl/inbound-source.d.ts +3 -1
  94. package/dist/src/mcpl/inbound-source.d.ts.map +1 -1
  95. package/dist/src/mcpl/inbound-source.js +3 -1
  96. package/dist/src/mcpl/inbound-source.js.map +1 -1
  97. package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
  98. package/dist/src/mcpl/modern-connection.js +11 -7
  99. package/dist/src/mcpl/modern-connection.js.map +1 -1
  100. package/dist/src/mcpl/protocol-family.d.ts +3 -2
  101. package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
  102. package/dist/src/mcpl/protocol-family.js +25 -3
  103. package/dist/src/mcpl/protocol-family.js.map +1 -1
  104. package/dist/src/mcpl/push-coalescer.d.ts +9 -0
  105. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  106. package/dist/src/mcpl/push-coalescer.js +21 -0
  107. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  108. package/dist/src/mcpl/push-handler.js +2 -2
  109. package/dist/src/mcpl/push-handler.js.map +1 -1
  110. package/dist/src/mcpl/references.d.ts +12 -40
  111. package/dist/src/mcpl/references.d.ts.map +1 -1
  112. package/dist/src/mcpl/references.js +29 -23
  113. package/dist/src/mcpl/references.js.map +1 -1
  114. package/dist/src/mcpl/server-connection.d.ts.map +1 -1
  115. package/dist/src/mcpl/server-connection.js +20 -20
  116. package/dist/src/mcpl/server-connection.js.map +1 -1
  117. package/dist/src/mcpl/skip-reply-wake.d.ts +25 -0
  118. package/dist/src/mcpl/skip-reply-wake.d.ts.map +1 -1
  119. package/dist/src/mcpl/skip-reply-wake.js +25 -0
  120. package/dist/src/mcpl/skip-reply-wake.js.map +1 -1
  121. package/dist/src/mcpl/tool-presence.d.ts.map +1 -1
  122. package/dist/src/mcpl/tool-presence.js +18 -17
  123. package/dist/src/mcpl/tool-presence.js.map +1 -1
  124. package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
  125. package/dist/src/mcpl/tool-signature.js +17 -16
  126. package/dist/src/mcpl/tool-signature.js.map +1 -1
  127. package/dist/src/mcpl/toolset-host.d.ts +8 -26
  128. package/dist/src/mcpl/toolset-host.d.ts.map +1 -1
  129. package/dist/src/mcpl/toolset-host.js +16 -6
  130. package/dist/src/mcpl/toolset-host.js.map +1 -1
  131. package/dist/src/mcpl/toolsets-module.d.ts +14 -25
  132. package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
  133. package/dist/src/mcpl/toolsets-module.js +187 -50
  134. package/dist/src/mcpl/toolsets-module.js.map +1 -1
  135. package/dist/src/mcpl/transport.d.ts +1 -1
  136. package/dist/src/mcpl/transport.d.ts.map +1 -1
  137. package/dist/src/mcpl/transport.js +16 -16
  138. package/dist/src/mcpl/transport.js.map +1 -1
  139. package/dist/src/mcpl/types.d.ts +28 -12
  140. package/dist/src/mcpl/types.d.ts.map +1 -1
  141. package/dist/src/mcpl/types.js.map +1 -1
  142. package/dist/src/module-registry.d.ts +6 -7
  143. package/dist/src/module-registry.d.ts.map +1 -1
  144. package/dist/src/module-registry.js +6 -7
  145. package/dist/src/module-registry.js.map +1 -1
  146. package/dist/src/modules/api/index.d.ts.map +1 -1
  147. package/dist/src/modules/api/index.js +4 -2
  148. package/dist/src/modules/api/index.js.map +1 -1
  149. package/dist/src/modules/discord/index.d.ts.map +1 -1
  150. package/dist/src/modules/discord/index.js +33 -25
  151. package/dist/src/modules/discord/index.js.map +1 -1
  152. package/dist/src/modules/history/index.js +8 -6
  153. package/dist/src/modules/history/index.js.map +1 -1
  154. package/dist/src/modules/sender-name.d.ts +7 -0
  155. package/dist/src/modules/sender-name.d.ts.map +1 -0
  156. package/dist/src/modules/sender-name.js +10 -0
  157. package/dist/src/modules/sender-name.js.map +1 -0
  158. package/dist/src/modules/workspace/answers.d.ts +5 -1
  159. package/dist/src/modules/workspace/answers.d.ts.map +1 -1
  160. package/dist/src/modules/workspace/answers.js +16 -1
  161. package/dist/src/modules/workspace/answers.js.map +1 -1
  162. package/dist/src/modules/workspace/direct-write.d.ts.map +1 -1
  163. package/dist/src/modules/workspace/direct-write.js +6 -4
  164. package/dist/src/modules/workspace/direct-write.js.map +1 -1
  165. package/dist/src/modules/workspace/effects.d.ts +4 -2
  166. package/dist/src/modules/workspace/effects.d.ts.map +1 -1
  167. package/dist/src/modules/workspace/effects.js +50 -40
  168. package/dist/src/modules/workspace/effects.js.map +1 -1
  169. package/dist/src/modules/workspace/index.d.ts +1 -0
  170. package/dist/src/modules/workspace/index.d.ts.map +1 -1
  171. package/dist/src/modules/workspace/index.js +66 -36
  172. package/dist/src/modules/workspace/index.js.map +1 -1
  173. package/dist/src/modules/workspace/observe.d.ts +51 -20
  174. package/dist/src/modules/workspace/observe.d.ts.map +1 -1
  175. package/dist/src/modules/workspace/observe.js +120 -82
  176. package/dist/src/modules/workspace/observe.js.map +1 -1
  177. package/dist/src/modules/workspace/reconcile.d.ts +7 -1
  178. package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
  179. package/dist/src/modules/workspace/reconcile.js +133 -37
  180. package/dist/src/modules/workspace/reconcile.js.map +1 -1
  181. package/dist/src/notice-tags.d.ts +81 -0
  182. package/dist/src/notice-tags.d.ts.map +1 -0
  183. package/dist/src/notice-tags.js +85 -0
  184. package/dist/src/notice-tags.js.map +1 -0
  185. package/dist/src/reasoning-loss-notice.d.ts.map +1 -1
  186. package/dist/src/reasoning-loss-notice.js +5 -3
  187. package/dist/src/reasoning-loss-notice.js.map +1 -1
  188. package/dist/src/silent-heartbeat.d.ts +5 -55
  189. package/dist/src/silent-heartbeat.d.ts.map +1 -1
  190. package/dist/src/silent-heartbeat.js +63 -7
  191. package/dist/src/silent-heartbeat.js.map +1 -1
  192. package/dist/src/stored-injections.d.ts +4 -0
  193. package/dist/src/stored-injections.d.ts.map +1 -1
  194. package/dist/src/stored-injections.js +6 -0
  195. package/dist/src/stored-injections.js.map +1 -1
  196. package/dist/src/supplied-text.d.ts +35 -0
  197. package/dist/src/supplied-text.d.ts.map +1 -0
  198. package/dist/src/supplied-text.js +141 -0
  199. package/dist/src/supplied-text.js.map +1 -0
  200. package/dist/src/tool-args.d.ts.map +1 -1
  201. package/dist/src/tool-args.js +9 -8
  202. package/dist/src/tool-args.js.map +1 -1
  203. package/dist/src/tool-image-ledger.d.ts.map +1 -1
  204. package/dist/src/tool-image-ledger.js +2 -1
  205. package/dist/src/tool-image-ledger.js.map +1 -1
  206. package/dist/src/tool-presentation.d.ts.map +1 -1
  207. package/dist/src/tool-presentation.js +5 -4
  208. package/dist/src/tool-presentation.js.map +1 -1
  209. package/dist/src/tune-out/coordinator.d.ts +382 -11
  210. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  211. package/dist/src/tune-out/coordinator.js +1100 -119
  212. package/dist/src/tune-out/coordinator.js.map +1 -1
  213. package/dist/src/tune-out/tools.d.ts +101 -5
  214. package/dist/src/tune-out/tools.d.ts.map +1 -1
  215. package/dist/src/tune-out/tools.js +55 -18
  216. package/dist/src/tune-out/tools.js.map +1 -1
  217. package/dist/src/types/agent.d.ts +48 -9
  218. package/dist/src/types/agent.d.ts.map +1 -1
  219. package/dist/src/types/framework.d.ts +142 -0
  220. package/dist/src/types/framework.d.ts.map +1 -1
  221. package/dist/src/types/module.d.ts +22 -11
  222. package/dist/src/types/module.d.ts.map +1 -1
  223. package/dist/src/types/trace.d.ts +136 -0
  224. package/dist/src/types/trace.d.ts.map +1 -1
  225. package/package.json +3 -3
  226. package/src/admission/admit.ts +113 -0
  227. package/src/agent.ts +52 -7
  228. package/src/api/mcp-server-core.ts +3 -14
  229. package/src/api/server.ts +7 -3
  230. package/src/author-presentation.ts +81 -0
  231. package/src/auto-continue.ts +8 -2
  232. package/src/code-execution/isolate/runner.ts +7 -5
  233. package/src/code-execution/isolate/scripts.ts +3 -2
  234. package/src/dendrite/index.ts +32 -0
  235. package/src/dendrite/presets.ts +149 -0
  236. package/src/dendrite/registry.ts +808 -0
  237. package/src/dendrite/types.ts +397 -0
  238. package/src/framework.ts +4362 -852
  239. package/src/gate/event-gate.ts +159 -52
  240. package/src/gate/index.ts +1 -1
  241. package/src/gate/types.ts +12 -2
  242. package/src/heartbeat/heartbeat.ts +48 -9
  243. package/src/held-writes.ts +3 -3
  244. package/src/host-presentation.ts +612 -0
  245. package/src/index.ts +17 -4
  246. package/src/line-reader.ts +58 -0
  247. package/src/mcpl/channel-registry.ts +306 -111
  248. package/src/mcpl/conversation-router.ts +2 -1
  249. package/src/mcpl/convert-block.ts +17 -4
  250. package/src/mcpl/hook-orchestrator.ts +4 -3
  251. package/src/mcpl/inbound-source.ts +3 -1
  252. package/src/mcpl/modern-connection.ts +11 -7
  253. package/src/mcpl/protocol-family.ts +24 -3
  254. package/src/mcpl/push-coalescer.ts +19 -0
  255. package/src/mcpl/push-handler.ts +2 -2
  256. package/src/mcpl/references.ts +31 -23
  257. package/src/mcpl/server-connection.ts +20 -18
  258. package/src/mcpl/skip-reply-wake.ts +35 -0
  259. package/src/mcpl/tool-presence.ts +18 -17
  260. package/src/mcpl/tool-signature.ts +17 -16
  261. package/src/mcpl/toolset-host.ts +18 -6
  262. package/src/mcpl/toolsets-module.ts +149 -50
  263. package/src/mcpl/transport.ts +18 -14
  264. package/src/mcpl/types.ts +29 -12
  265. package/src/module-registry.ts +6 -7
  266. package/src/modules/api/index.ts +4 -2
  267. package/src/modules/discord/index.ts +37 -25
  268. package/src/modules/history/index.ts +8 -6
  269. package/src/modules/sender-name.ts +9 -0
  270. package/src/modules/workspace/answers.ts +20 -2
  271. package/src/modules/workspace/direct-write.ts +4 -3
  272. package/src/modules/workspace/effects.ts +50 -40
  273. package/src/modules/workspace/index.ts +58 -34
  274. package/src/modules/workspace/observe.ts +139 -88
  275. package/src/modules/workspace/reconcile.ts +159 -36
  276. package/src/notice-tags.ts +86 -0
  277. package/src/reasoning-loss-notice.ts +5 -3
  278. package/src/silent-heartbeat.ts +11 -7
  279. package/src/stored-injections.ts +7 -0
  280. package/src/supplied-text.ts +154 -0
  281. package/src/tool-args.ts +9 -8
  282. package/src/tool-image-ledger.ts +2 -1
  283. package/src/tool-presentation.ts +5 -4
  284. package/src/tune-out/coordinator.ts +1252 -123
  285. package/src/tune-out/tools.ts +152 -11
  286. package/src/types/agent.ts +51 -9
  287. package/src/types/framework.ts +146 -0
  288. package/src/types/module.ts +23 -11
  289. package/src/types/trace.ts +150 -0
@@ -1,3 +1,6 @@
1
+ import type { ContentBlock } from '@animalabs/membrane';
2
+ import { authorLine } from '../author-presentation.js';
3
+ import type { StoredMessage } from '@animalabs/context-manager';
1
4
  /**
2
5
  * The subconscious's tool surface (issue #77).
3
6
  *
@@ -51,11 +54,13 @@ export const SUBCONSCIOUS_TOOLS: ToolDefinition[] = [
51
54
  'End the tune-out on a channel now — use when something needs the ' +
52
55
  'resident\'s full attention. The diverted backlog is delivered to ' +
53
56
  'them (capped), and normal attention resumes. Add your own note in ' +
54
- '`text`; it arrives alongside the backlog, in your voice.',
57
+ '`text`; it arrives alongside the backlog, in your voice. If you were ' +
58
+ 'started to read one hold, you can end only that hold.',
55
59
  inputSchema: {
56
60
  type: 'object',
57
61
  properties: {
58
- channelId: { type: 'string' },
62
+ channelId: { type: 'string', description: 'A tuned-out channel, or a channel held by the all-except hold (which ends that hold, unless scope is "channel").' },
63
+ scope: { type: 'string', enum: ['channel', 'all-except'], description: '"all-except" ends the all-except hold outright; "channel" ends only the channel\'s own tune-out.' },
59
64
  text: {
60
65
  type: 'string',
61
66
  description: 'Your accompanying note to the resident (optional).',
@@ -86,10 +91,14 @@ export const SUBCONSCIOUS_TOOLS: ToolDefinition[] = [
86
91
  {
87
92
  name: 'speak_in_channel',
88
93
  description:
89
- 'Post a message into a tuned-out channel, clearly as yourself (not ' +
90
- 'as the resident). Use sparingly — e.g. to tell someone the resident ' +
91
- 'is tuned out and when to expect them. Disabled unless the resident\'s ' +
92
- 'configuration allows it.',
94
+ 'Post a message into a tuned-out channel, in your own words and ' +
95
+ 'clearly as yourself (not as the resident); it goes out through the ' +
96
+ 'resident\'s publish path, so the channel sees it under their account. ' +
97
+ 'Use sparingly — e.g. to tell someone the resident is tuned out and ' +
98
+ 'when to expect them. If you were started to read one hold, you can ' +
99
+ 'post only into a channel that hold still holds, and a post made once ' +
100
+ 'it has ended is refused; your report can say it instead. Disabled unless ' +
101
+ 'the resident\'s configuration allows it.',
93
102
  inputSchema: {
94
103
  type: 'object',
95
104
  properties: {
@@ -102,11 +111,42 @@ export const SUBCONSCIOUS_TOOLS: ToolDefinition[] = [
102
111
  ];
103
112
 
104
113
  /**
105
- * Configuration for the subconscious resident (FrameworkConfig.subconscious).
114
+ * Configuration for the subconscious (FrameworkConfig.subconscious). Two
115
+ * shapes, by `reader`:
116
+ *
117
+ * - `'persistent'` (default): one persistent side-agent with an isolated
118
+ * slot and a windowed read-only view of the residents' timeline, as
119
+ * issue #77 built it. Its `systemPrompt` is a system prompt.
120
+ * - `'forks'`: a succession of forks of the resident (Dendrite). Each
121
+ * cadence tick, coalesced wake and cancel derives a short-lived fork at
122
+ * the resident's head — the resident's whole prefix, its refusals
123
+ * included, ending with it — hands it the traffic held since the last
124
+ * look as the first message after the fork point, and lets it report
125
+ * back as attributed mail. No agent persists between invocations;
126
+ * dispositions do, in the coordinator's state. Its `voice` is framing,
127
+ * not a system prompt, and residents are shown the reader's four tools
128
+ * in their tool block (refused at dispatch), so the fork's request is
129
+ * the resident's up to the fork point.
106
130
  */
107
- export interface SubconsciousConfig {
131
+ export type SubconsciousConfig = PersistentReaderConfig | ForkReaderConfig;
132
+
133
+ interface SubconsciousConfigBase {
108
134
  /** Master switch. */
109
135
  enabled: boolean;
136
+ /**
137
+ * Allow speak_in_channel. Default false until the voice block has passed
138
+ * its canary round. A reader's post goes out through the resident's own
139
+ * publish path, so the channel sees it under the resident's account, in
140
+ * the reader's words; nothing in the request names the reader. With it
141
+ * off, a reader fork can post nowhere: every tool but its own is refused
142
+ * at dispatch (the block it is shown stays the resident's, for the
143
+ * shared prefix).
144
+ */
145
+ allowChannelSpeech?: boolean;
146
+ }
147
+
148
+ export interface PersistentReaderConfig extends SubconsciousConfigBase {
149
+ reader?: 'persistent';
110
150
  /**
111
151
  * Registry + participant name. Default 'Subconscious' — following the
112
152
  * Context Manager precedent: a title-case functional voice, not an
@@ -124,9 +164,110 @@ export interface SubconsciousConfig {
124
164
  * person toward the resident. Canary before fleet use (issue #77).
125
165
  */
126
166
  systemPrompt: string;
127
- /** Allow speak_in_channel. Default false until the voice block has
128
- * passed its canary round. */
129
- allowChannelSpeech?: boolean;
130
167
  /** WindowedPassthroughStrategy re-anchor fraction (default 0.5). */
131
168
  reAnchorFraction?: number;
132
169
  }
170
+
171
+ export interface ForkReaderConfig extends SubconsciousConfigBase {
172
+ reader: 'forks';
173
+ /**
174
+ * Whose weights run the fork. Required, never defaulted: a fork holds the
175
+ * resident's whole prefix, and which model reads it is the one fact the
176
+ * resident's consent names. The resident's own model is the copy; another
177
+ * model is allowed when stated here. `create` refuses the omission.
178
+ */
179
+ model: string;
180
+ /**
181
+ * The reader's voice block — report-shaped, second person toward the
182
+ * resident, co-authored with them. Delivered as the first message after
183
+ * the fork point, with the notice, the standing dispositions and the held
184
+ * traffic; never a system prompt.
185
+ */
186
+ voice: string;
187
+ /**
188
+ * A fresh context strategy instance for each fork, same class and
189
+ * configuration as the resident's, so the fork reuses the resident's fold
190
+ * state and rendering. Required unless the resident runs a passthrough
191
+ * strategy.
192
+ */
193
+ strategyFactory?: () => import('@animalabs/context-manager').ContextStrategy;
194
+ /** Idle timeout for one reader fork (default 10 minutes). */
195
+ forkIdleTimeoutMs?: number;
196
+ /**
197
+ * How a reader fork is prompted: the content of the one message it
198
+ * receives after the fork point. Default `defaultReaderFraming`. The
199
+ * framework stores whatever this returns as a user message with
200
+ * `metadata.kind = 'tune-out-reader-framing'`; the notice, voice,
201
+ * dispositions and held traffic are in the context, and nothing else
202
+ * about the fork's prompt is fixed by the path.
203
+ */
204
+ framing?: (context: ReaderFramingContext) => ContentBlock[];
205
+ }
206
+
207
+ /** What a reader fork's framing is built from. */
208
+ export interface ReaderFramingContext {
209
+ /** The resident the fork was derived from. */
210
+ resident: string;
211
+ /** The fork's own name. */
212
+ fork: string;
213
+ /** `ForkReaderConfig.voice`. */
214
+ voice: string;
215
+ channelId: string;
216
+ epochId: string;
217
+ trigger: 'cadence' | 'wake' | 'cancel';
218
+ /** The coordinator's notice for this invocation (`[Tune-out wake: …]` etc.). */
219
+ notice: string;
220
+ /** Standing dispositions recorded on the epoch, or null. */
221
+ dispositions: string | null;
222
+ /**
223
+ * The traffic held since the last look, oldest first; empty when nothing
224
+ * arrived. It includes the messages still queued behind the resident's
225
+ * turn (admitted, not yet stored), last: their ids are held handles and
226
+ * their sequences provisional, as a module's held rows are.
227
+ */
228
+ backlog: StoredMessage[];
229
+ /** How many of `backlog`'s last entries are still queued behind the resident's turn. Absent is none. */
230
+ queuedCount?: number;
231
+ }
232
+
233
+ /**
234
+ * The default reader-fork framing: a header naming the channel, epoch and
235
+ * trigger; the notice; the voice block; standing dispositions if any; then
236
+ * the held traffic as `author: text` lines with non-text blocks (images,
237
+ * documents) preserved in order as the source supplied them.
238
+ */
239
+ export function defaultReaderFraming(ctx: ReaderFramingContext): ContentBlock[] {
240
+ const header =
241
+ `[Tune-out reader — ${ctx.channelId}, epoch ${ctx.epochId.slice(0, 8)}, ${ctx.trigger}]\n` +
242
+ `${ctx.notice}\n\n${ctx.voice}` +
243
+ (ctx.dispositions ? `\n\n[Standing dispositions]\n${ctx.dispositions}` : '');
244
+ const content: ContentBlock[] = [{ type: 'text', text: header }];
245
+ if (ctx.backlog.length === 0) {
246
+ content.push({ type: 'text', text: '\n[Nothing new has been held since your last look.]' });
247
+ return content;
248
+ }
249
+ const n = ctx.backlog.length;
250
+ const q = Math.min(Math.max(0, Math.floor(ctx.queuedCount ?? 0)), n);
251
+ const lines: string[] = [`\n[Held in ${ctx.channelId} since your last look: ${n} message${n === 1 ? '' : 's'}]`];
252
+ const flush = () => {
253
+ if (lines.length > 0) content.push({ type: 'text', text: lines.splice(0).join('\n') });
254
+ };
255
+ const which = q === n ? (q === 1 ? 'It' : 'They') : (q === 1 ? 'The last one' : `The last ${q}`);
256
+ const queuedNote = `[${which} arrived while ${ctx.resident}'s turn was running, so ${q === 1 ? 'it is' : 'they are'} still queued, not yet stored.]`;
257
+ for (const [i, message] of ctx.backlog.entries()) {
258
+ if (q > 0 && i === n - q) lines.push(queuedNote);
259
+ const author = (message.metadata as { author?: { name?: string } } | undefined)?.author?.name ?? message.participant;
260
+ const text = message.content
261
+ .filter((b): b is Extract<ContentBlock, { type: 'text' }> => b.type === 'text')
262
+ .map((b) => b.text)
263
+ .join('\n');
264
+ lines.push(authorLine(author, text));
265
+ const media = message.content.filter((b) => b.type !== 'text');
266
+ if (media.length > 0) {
267
+ flush();
268
+ content.push(...media);
269
+ }
270
+ }
271
+ flush();
272
+ return content;
273
+ }
@@ -55,13 +55,15 @@ export interface AgentConfig {
55
55
  * Enforced at dispatch for registered agent callers; omitted means 'all'.
56
56
  * - 'all': all available tools
57
57
  * - string[]: exact tool names, including module--utility names for utilities.
58
- * Utilities aren't tools of their own: an agent's model and its scripts are
59
- * offered them only through 'utils', whose run checks the utility's own
60
- * name. An agent that should use a utility lists both 'utils' and
61
- * 'module--utility'. The name alone passes the dispatch check for any call
62
- * by that name, whether the host makes it with executeToolCall on the
63
- * agent's behalf or the model or a script makes it without being offered
64
- * it. The slot is hidden when no utility is permitted.
58
+ * Utilities and the host's other rarely-used tools are in the host's
59
+ * built-in toolset ("settings"), out of the tool list: the model reaches
60
+ * them with toolsets--open {toolset:"settings"} and toolsets--act, each
61
+ * checked under the tool's own name. An agent that should use a utility
62
+ * from its model lists 'toolsets--open', 'toolsets--act' and
63
+ * 'module--utility' (an agent without the two meta-tools keeps every other
64
+ * tool in its list, but no utility). The name alone passes the dispatch
65
+ * check for any call by that name, whether the host makes it with
66
+ * executeToolCall on the agent's behalf or the model or a script makes it.
65
67
  * Trusted module calls are exempt. Genuinely unidentified public callers,
66
68
  * including unknown legacy names, remain unrestricted. Framework-created
67
69
  * identities have tool authority only while registered.
@@ -338,6 +340,34 @@ export interface AgentConfig {
338
340
  */
339
341
  proseRouting?: 'locus' | 'explicit' | 'hybrid' | 'disabled';
340
342
 
343
+ /**
344
+ * Stored participants that are this agent's own voice, in addition to its
345
+ * name: their turns are presented to the model as this agent's assistant
346
+ * turns. A derived agent sets this to the agent it inherited from, so the
347
+ * parent's turns read as its own history. Model-facing role assignment
348
+ * only, applied at request assembly — stored authorship is never rewritten.
349
+ */
350
+ selfParticipants?: string[];
351
+
352
+ /**
353
+ * The participant name this agent's own turns are presented under at
354
+ * request assembly. Defaults to `name`. A derived agent that should share
355
+ * its parent's provider prefix sets the parent's name, so the two
356
+ * requests are identical in formatters that render participant names as
357
+ * well as in those that only assign roles. New turns are still STORED
358
+ * under the agent's own name.
359
+ */
360
+ presentAs?: string;
361
+
362
+ /**
363
+ * Tools refused when this agent calls them, although they stay advertised.
364
+ * Restricts a derived agent without changing the tool block at the front
365
+ * of its request — removing a tool from `allowedTools` instead would
366
+ * change those bytes and forfeit the provider prefix it shares with its
367
+ * parent.
368
+ */
369
+ denyToolsAtDispatch?: string[];
370
+
341
371
  /**
342
372
  * Fail-closed containment for a text response whose entire visible prose is
343
373
  * an invocation-shaped wrapper naming a tool registered on that exact turn.
@@ -580,8 +610,9 @@ export interface InferenceRequest {
580
610
  * the turn's auto-published plain-text speech here so a single TRUNK agent
581
611
  * replies in the channel it is answering — not the process-global most-recent-
582
612
  * inbound locus, which a concurrent message elsewhere can hijack (item-3
583
- * redux). Undefined for non-channel wakes (heartbeat, timers, module events),
584
- * which correctly fall back to the global default channel, except an
613
+ * redux). Undefined for non-channel wakes (heartbeat, timers, module events).
614
+ * A heartbeat's turn takes no channel at all, and holds its plain speech as
615
+ * drafts; any other falls back to the global default channel, except an
585
616
  * ephemeral run's (a subagent's), which takes no global fallback.
586
617
  */
587
618
  channelId?: string;
@@ -604,6 +635,17 @@ export interface InferenceRequest {
604
635
  * it starts. Hosts don't set it.
605
636
  */
606
637
  autoContinueRun?: AutoContinueRun;
638
+ /**
639
+ * Framework-set: whether this wake's turn is a heartbeat's, which has no
640
+ * channel and holds its plain speech as drafts. True on a wake that
641
+ * continues a heartbeat's turn with no channel of its own (an autoContinue
642
+ * continuation, a bounce's resend wake, a ` !` continuation, and a re-arm,
643
+ * which copies its trigger), and on a merged batch whose causes include a
644
+ * heartbeat's wake and name no channel; false on a merged batch with none,
645
+ * so the batch's first request doesn't decide it. Unset, a wake is a
646
+ * heartbeat's when its `source` is `heartbeat`. Hosts don't set it.
647
+ */
648
+ heartbeatTurn?: boolean;
607
649
  /**
608
650
  * True when the triggering message explicitly addressed the agent (mention,
609
651
  * reply-to-bot, DM — `chat:addressed` in MCPL RFC-001 terms). When a wake
@@ -116,6 +116,48 @@ export type ProviderHoldHook = (
116
116
  ) => ProviderHold | undefined;
117
117
 
118
118
  export interface FrameworkConfig {
119
+ /**
120
+ * Dendrite agent lifecycle options.
121
+ */
122
+ dendrite?: {
123
+ /**
124
+ * When an agent ends while task work it spawned is still running, the
125
+ * orphan is always told. With this on (the default), the first agent
126
+ * that could adopt it — its nearest live ancestor, else the primary —
127
+ * also receives a factual notice, which is what makes the reparenting
128
+ * offer visible to a model. Turn it off when the host makes adoption
129
+ * decisions itself from the `dendrite:agent-orphaned` trace.
130
+ */
131
+ announceOrphans?: boolean;
132
+ /**
133
+ * By default every derivation tells the agent it was made from, in one
134
+ * host-framed line. After this many derivations of one kind from one
135
+ * agent, that agent hears a count line every so many instead of a line
136
+ * per fork, so a reader loop that forks a hundred times a session doesn't
137
+ * fill its source's context with stamps; every derivation still emits its
138
+ * `dendrite:agent-created` trace. Default 10; 0 never collapses.
139
+ */
140
+ collapseDerivationNoticesAfter?: number;
141
+ /**
142
+ * A host's say over each derivation, ephemeral spawn and resume, before the
143
+ * agent or its new incarnation exists, given the spec of what is about to
144
+ * exist (name, kind, spawner, route, inheritance, metadata). A shared
145
+ * derivation's record then adds the child's branch, and names the
146
+ * parent's namespace as its own. Return nothing to allow, or `{ refuse }`
147
+ * with the reason, which the caller gets as the error. The framework
148
+ * attaches no meaning to the decision: consent, budget, hours, a mind's
149
+ * standing instruction, a use not yet imagined are all the host's.
150
+ */
151
+ beforeDerive?: (spec: import('../dendrite/types.js').AgentSpec) => void | { refuse: string };
152
+ /**
153
+ * What an agent is told when something is derived from it. Given the
154
+ * record, the agent it was derived from and the framework's default line,
155
+ * return the text to add, or null to add nothing. Absent, the default
156
+ * line is used. The `dendrite:agent-created` trace is emitted either way.
157
+ */
158
+ derivationNotice?: (record: import('../dendrite/types.js').AgentRecord, from: string, defaultText: string) => string | null;
159
+ };
160
+
119
161
  /**
120
162
  * The subconscious resident (issue #77): a persistent side-agent that
121
163
  * receives diverted traffic from tuned-out channels and reports to the
@@ -123,6 +165,46 @@ export interface FrameworkConfig {
123
165
  */
124
166
  subconscious?: import('../tune-out/tools.js').SubconsciousConfig;
125
167
 
168
+ /**
169
+ * Tune-out without a reader (issue #232 §3.3). The hold machinery always
170
+ * runs when MCPL channels exist, so a persisted epoch is resumed and
171
+ * released whatever the configuration; this switch only decides whether
172
+ * residents are offered `tune_out` when no reader is configured. Without
173
+ * a reader a tune-out is a silent hold: no cadence, no wake budget,
174
+ * mentions are acknowledged, the backlog is released on cancel.
175
+ */
176
+ tuneOut?: {
177
+ /** Offer `tune_out` without a subconscious. Default: only with one. */
178
+ enabled?: boolean;
179
+ /**
180
+ * Announce the all-except scope in the excepted channel: one bracketed
181
+ * line under the resident's name when it opens (who, for how long) and
182
+ * one when it closes. Default true — a hold the room cannot see is the
183
+ * failure the scope exists to prevent. `false` keeps both quiet.
184
+ */
185
+ announce?: boolean;
186
+ };
187
+
188
+ /**
189
+ * Where a turn's plain prose lands when nothing names a destination
190
+ * (architecture channel, 2026-10-06: the §3 carve-out accepted by the
191
+ * residents).
192
+ *
193
+ * - `'delivery'` (default): the locus comes only from the delivery that
194
+ * woke the turn, a conversation fork's home channel, or a channel the
195
+ * agent opened during the turn. Incoming traffic never moves it — not
196
+ * between turns, not mid-turn. A turn with no such locus keeps its
197
+ * prose private, and says so in a `[routing]` notice.
198
+ * - `'latest-inbound'`: the previous rule. The most recent delivered
199
+ * inbound message is a global fallback locus for turns without one, and
200
+ * an addressed message injected mid-turn re-pins the turn's locus.
201
+ *
202
+ * Held traffic never moves the locus under either rule.
203
+ */
204
+ speech?: {
205
+ locus?: 'delivery' | 'latest-inbound';
206
+ };
207
+
126
208
  /**
127
209
  * IANA zone of the agent's wall clock: it renders wall-clock times for the
128
210
  * agent, and it sets where clock rows' boundaries fall (AgentConfig.clock,
@@ -160,6 +242,26 @@ export interface FrameworkConfig {
160
242
  */
161
243
  deferredWritesPath?: string;
162
244
 
245
+ /**
246
+ * The Dendrite agent registry's recovery file: its live records and the
247
+ * results it holds for delivery, written atomically and fsynced, and read
248
+ * first at boot. Defaults to `<storePath>/recovery/dendrite-registry.json`.
249
+ * Outside the chronicle for the reason `hostModePath` is: a branch switch
250
+ * or historical rollback would otherwise take back held results. Set
251
+ * explicitly when providing an app-owned `store` without `storePath`;
252
+ * without either, the registry persists only in the branch-local store,
253
+ * with a warning at create.
254
+ */
255
+ agentRegistryPath?: string;
256
+
257
+ /**
258
+ * The registry's ended-record log recovery file (its newest records, as
259
+ * many as a restart reads). Defaults to
260
+ * `<storePath>/recovery/dendrite-ended.json`; same rationale and fallback
261
+ * as `agentRegistryPath`.
262
+ */
263
+ agentRegistryEndedPath?: string;
264
+
163
265
  /** Emoji used for branch-awareness markers (default: 💤). */
164
266
  discordAwarenessEmoji?: string;
165
267
 
@@ -218,6 +320,50 @@ export interface FrameworkConfig {
218
320
  */
219
321
  providerHold?: ProviderHoldHook;
220
322
 
323
+ /**
324
+ * Delays before a turn whose retries ran out is sent again, in ms (default
325
+ * `[120000, 480000, 1920000]`: 2, 8 and 32 minutes), or `false` never to.
326
+ * When a persistent agent's turn ends exhausted on a transient failure (a
327
+ * server error, an overload, a rate limit, a network failure, or a timeout
328
+ * membrane marks retryable and not as a deadline it reports as run out, such
329
+ * as a 408 or a socket's ETIMEDOUT), its trigger is queued again after the
330
+ * first delay, as a wake of its own. It opens where the exhausted turn's
331
+ * plain speech was going: its locus, where a message mid-turn may have moved
332
+ * it, else its trigger channel. Carrying that channel, it ranks as addressed
333
+ * in a merged wake's choice of channel, so a newer ambient wake from
334
+ * elsewhere doesn't take it; an autoContinue continuation's still yields its
335
+ * channel to any wake's, as it does on main; a continuation's, such as a
336
+ * budget restart's, names no author. A re-arm that would have to guess a
337
+ * channel isn't sent again: one whose turn was woken for a channel id that
338
+ * names no one registered channel and never found one, and whose request
339
+ * names none of its own. A failure that states a wait is held for that wait
340
+ * first, as any turn's is, and a deadline ends the turn as before, whether
341
+ * the host set it or membrane marks one as run out: the runtime's or
342
+ * undici's on a sent request, the Anthropic adapter's watchdogs, or the
343
+ * Anthropic SDK's timeout, which covers its connect timeout too. Until one
344
+ * of the re-armed turn's rounds has run or had its tool calls cut off, its
345
+ * failure gets no retries from the error policy: on another transient
346
+ * failure the next delay follows, until the list runs out, and a failure of
347
+ * any other kind ends the re-arming. A wake for the agent while it waits
348
+ * starts its turn at once, with the re-armed trigger merged in. That turn is
349
+ * the wake's: the error policy retries its failures. A completed turn starts
350
+ * the count again, and so does a message said to the agent: not a system
351
+ * message or notice; not an edit, reaction or deletion, unless an edit is
352
+ * the first version of a message the agent sees; not a message its own view
353
+ * leaves out, such as one a tune-out holds from it; and not the agent's own
354
+ * schedule, the heartbeat's tick or reminder, a self-wake timer it set, or a
355
+ * script's wake. A notice a module or an MCPL server stores as a message
356
+ * counts. A message in the residents' shared slot counts for each resident
357
+ * that reads it. Any other wake leaves the count going on. A re-armed turn's
358
+ * transient failure writes no `[inference-failed]` marker unless a round of
359
+ * it ran or had its tool calls cut off; any other failure of it writes one.
360
+ * Each counts toward the hard-down streak. Ephemeral runs and conversation
361
+ * forks are never re-armed. The wait and the count are kept in memory:
362
+ * `stop()` drops them, and so does a restart. The wait isn't a turn, so
363
+ * `abortInference()` doesn't end it.
364
+ */
365
+ exhaustedTurnRearmMs?: number[] | false;
366
+
221
367
  /** Interval for periodic store sync in milliseconds (default: 1000ms, 0 to disable) */
222
368
  syncIntervalMs?: number;
223
369
 
@@ -72,10 +72,11 @@ export type ModuleMessage = StoredMessage & {
72
72
  * True on a message a tune-out holds from the residents: one that arrived
73
73
  * in a tuned-out channel, stamped `metadata.tuneOut` as it came in. The
74
74
  * store keeps it, but no resident is shown the row itself: no resident's
75
- * compiled view includes it, during the tune-out or after it, and a tool
76
- * boundary of a turn alive when it arrived stores it without injecting it
77
- * into the live turn. The subconscious reads it. Its text can still reach
78
- * a resident in rows of their own, which aren't marked, such as the
75
+ * compiled view includes it, during the tune-out or after it, and a
76
+ * resident's tool boundary of a turn alive when it arrived stores it
77
+ * without injecting it into the live turn. The subconscious reads it. Its
78
+ * text can still reach a resident in rows of their own, which aren't
79
+ * marked, such as the
79
80
  * cancel's `<tuned-out-backlog>` row, with the text of the tune-out's newest
80
81
  * messages, up to `backlogCap` of them and as many as fit in one of the
81
82
  * residents' messages, the stored ones first and then those still held;
@@ -142,16 +143,27 @@ export interface Module {
142
143
 
143
144
  /**
144
145
  * Utilities: agent-invocable operations that deliberately do NOT get a
145
- * first-class tool slot. Every tool schema taxes every inference; a
146
- * capability used twice a month shouldn't. Utilities are listed, described,
147
- * and invoked through the framework's single `utils` tool instead — same
148
- * ToolDefinition shape, same un-prefixed names, and dispatch arrives at the
149
- * SAME handleToolCall as getTools() output, so a tool migrates between
150
- * surfaces by moving its definition from one list to the other and nothing
151
- * else. Optional; omitting it means the module contributes no utilities.
146
+ * slot in the tool list. Every tool schema taxes every inference; a
147
+ * capability used twice a month shouldn't. Utilities belong to the host's
148
+ * built-in toolset ("settings"): an agent sees them with
149
+ * `toolsets--look {toolset:"settings"}` and calls them with
150
+ * `toolsets--act` once it has opened it. Same ToolDefinition shape, same
151
+ * un-prefixed names, and dispatch arrives at the SAME handleToolCall as
152
+ * getTools() output. Optional; omitting it means the module contributes no
153
+ * utilities.
152
154
  */
153
155
  getUtilities?(): ToolDefinition[];
154
156
 
157
+ /**
158
+ * Where one of this module's tools (a getTools() name, unprefixed) is
159
+ * presented: in the agent's tool list (`'header'`, the default), or in the
160
+ * host's built-in toolset (`'builtin'`), out of the tool list, opened with
161
+ * `toolsets--open {toolset:"settings"}` and called with `toolsets--act`.
162
+ * For tools an agent uses rarely: settings, administration, status reads.
163
+ * Optional; omitting it keeps every tool in the tool list.
164
+ */
165
+ toolPlacement?(toolName: string): 'header' | 'builtin' | undefined;
166
+
155
167
  /**
156
168
  * Handle a tool call.
157
169
  * Tool name is without module prefix.
@@ -887,6 +887,156 @@ export type TraceEvent =
887
887
  since?: number;
888
888
  })
889
889
 
890
+ /** A reader fork ended in prose without `deliver_summary`; its final prose was delivered as its report. */
891
+ | (TraceEventBase & {
892
+ type: 'tune-out:reader-prose-delivered';
893
+ agentName: string;
894
+ serverId: string;
895
+ channelId: string;
896
+ epochId: string;
897
+ chars: number;
898
+ })
899
+
900
+ /** Bounded work the host's stop() cut off: left live for the next boot to end as `host-restart`. */
901
+ | (TraceEventBase & {
902
+ type: 'dendrite:agent-interrupted';
903
+ agentName: string;
904
+ kind: string;
905
+ incarnation: number;
906
+ reason: 'host-stop';
907
+ })
908
+
909
+ /** A tune-out reader fork was derived for one invocation (subconscious.reader: 'forks'). */
910
+ | (TraceEventBase & {
911
+ type: 'tune-out:reader-fork';
912
+ agentName: string;
913
+ serverId: string;
914
+ channelId: string;
915
+ epochId: string;
916
+ trigger: 'cadence' | 'wake' | 'cancel';
917
+ /** Held messages the fork was handed, stored and queued. */
918
+ held: number;
919
+ /** Of those, the ones still queued behind the resident's turn. */
920
+ queued: number;
921
+ })
922
+
923
+ /**
924
+ * A tune-out reader fork threw: its framing, its derive or its run. One
925
+ * that failed before it reported gives its held messages back to the
926
+ * channel's backlog while the epoch still holds the channel (`returned`
927
+ * counts them), so the next reader for the channel is handed them.
928
+ */
929
+ | (TraceEventBase & {
930
+ type: 'tune-out:reader-fork-failed';
931
+ agentName: string;
932
+ serverId: string;
933
+ channelId: string;
934
+ epochId: string;
935
+ trigger: 'cadence' | 'wake' | 'cancel';
936
+ error: string;
937
+ /** Whether the fork had reported through `deliver_summary` before it failed. */
938
+ reported: boolean;
939
+ returned: number;
940
+ })
941
+
942
+ // Dendrite: agent registry and lifecycle.
943
+ // `agent-created` is the consent event: it names the kind of agent, what
944
+ // ends it, and what happens to it when its spawner ends.
945
+ | (TraceEventBase & {
946
+ type: 'dendrite:agent-created';
947
+ agentName: string;
948
+ kind: string;
949
+ /** Whose weights; `inherit.from` is whose context. */
950
+ model?: string;
951
+ incarnation: number;
952
+ lifetime: import('../dendrite/types.js').AgentLifetime;
953
+ spawnedBy?: string;
954
+ onParentEnd?: import('../dendrite/types.js').ParentEndPolicy;
955
+ resultTo?: import('../dendrite/types.js').ResultRoute;
956
+ inherit?: import('../dendrite/types.js').ContextInheritance;
957
+ })
958
+ | (TraceEventBase & {
959
+ type: 'dendrite:agent-ended';
960
+ agentName: string;
961
+ kind: string;
962
+ incarnation: number;
963
+ reason: string;
964
+ by?: string;
965
+ /** The agent that spawned it, when its record names one. */
966
+ spawnedBy?: string;
967
+ })
968
+ | (TraceEventBase & {
969
+ /** The last event a replay of create()'s traces delivers when the
970
+ * buffer kept only their first 1,000: how many came after. */
971
+ type: 'dendrite:early-traces-dropped';
972
+ dropped: number;
973
+ })
974
+ | (TraceEventBase & {
975
+ type: 'dendrite:agent-orphaned';
976
+ agentName: string;
977
+ formerParent: string;
978
+ reason: string;
979
+ /** Agents offered as a new parent, nearest first. */
980
+ candidates: string[];
981
+ })
982
+ | (TraceEventBase & {
983
+ type: 'dendrite:agent-reparented';
984
+ agentName: string;
985
+ parent: string;
986
+ by?: string;
987
+ })
988
+ | (TraceEventBase & {
989
+ type: 'dendrite:activation-cancelled';
990
+ agentName: string;
991
+ by?: string;
992
+ reason?: string;
993
+ })
994
+ /** The message entered the recipient's store (not merely queued for it). */
995
+ | (TraceEventBase & {
996
+ type: 'dendrite:mail-delivered';
997
+ mailId: string;
998
+ kind: string;
999
+ from: string;
1000
+ to: string;
1001
+ })
1002
+ /** Queued for the recipient's next boundary; `mail-delivered` follows when it lands. */
1003
+ | (TraceEventBase & {
1004
+ type: 'dendrite:mail-deferred';
1005
+ mailId: string;
1006
+ kind: string;
1007
+ from: string;
1008
+ to: string;
1009
+ })
1010
+ | (TraceEventBase & {
1011
+ type: 'dendrite:mail-held';
1012
+ mailId: string;
1013
+ kind: string;
1014
+ from: string;
1015
+ to: string;
1016
+ reason: string;
1017
+ })
1018
+ /**
1019
+ * The admission stage's decision for one inbound event (Dendrite §D):
1020
+ * who may receive it, whether it is held (the epoch id) or delivered,
1021
+ * whether it may wake them, and the gate's verdict.
1022
+ */
1023
+ | (TraceEventBase & {
1024
+ type: 'admission:decided';
1025
+ lane: 'channel' | 'push';
1026
+ serverId: string;
1027
+ channelId?: string;
1028
+ messageId: string;
1029
+ recipients: string[];
1030
+ held: string | null;
1031
+ wake: boolean;
1032
+ gatePass: boolean;
1033
+ })
1034
+ /** Batched gate events dropped at delivery because their channel is held now. */
1035
+ | (TraceEventBase & {
1036
+ type: 'gate:held-dropped';
1037
+ dropped: number;
1038
+ channels: Array<string | undefined>;
1039
+ })
890
1040
  // Inbound acceptance observation (mcpl/inbound-source.ts): an installed
891
1041
  // InboundAcceptanceObserver threw. Delivery is unaffected; the observer's
892
1042
  // own coverage is interrupted for that acceptance.