@animalabs/agent-framework 0.29.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 (104) hide show
  1. package/dist/src/admission/admit.d.ts +90 -0
  2. package/dist/src/admission/admit.d.ts.map +1 -0
  3. package/dist/src/admission/admit.js +43 -0
  4. package/dist/src/admission/admit.js.map +1 -0
  5. package/dist/src/agent.d.ts +21 -0
  6. package/dist/src/agent.d.ts.map +1 -1
  7. package/dist/src/agent.js +43 -6
  8. package/dist/src/agent.js.map +1 -1
  9. package/dist/src/author-presentation.d.ts +50 -0
  10. package/dist/src/author-presentation.d.ts.map +1 -0
  11. package/dist/src/author-presentation.js +58 -0
  12. package/dist/src/author-presentation.js.map +1 -0
  13. package/dist/src/dendrite/index.d.ts +6 -0
  14. package/dist/src/dendrite/index.d.ts.map +1 -0
  15. package/dist/src/dendrite/index.js +3 -0
  16. package/dist/src/dendrite/index.js.map +1 -0
  17. package/dist/src/dendrite/presets.d.ts +60 -0
  18. package/dist/src/dendrite/presets.d.ts.map +1 -0
  19. package/dist/src/dendrite/presets.js +117 -0
  20. package/dist/src/dendrite/presets.js.map +1 -0
  21. package/dist/src/dendrite/registry.d.ts +260 -0
  22. package/dist/src/dendrite/registry.d.ts.map +1 -0
  23. package/dist/src/dendrite/registry.js +742 -0
  24. package/dist/src/dendrite/registry.js.map +1 -0
  25. package/dist/src/dendrite/types.d.ts +392 -0
  26. package/dist/src/dendrite/types.d.ts.map +1 -0
  27. package/dist/src/dendrite/types.js +24 -0
  28. package/dist/src/dendrite/types.js.map +1 -0
  29. package/dist/src/framework.d.ts +684 -33
  30. package/dist/src/framework.d.ts.map +1 -1
  31. package/dist/src/framework.js +2998 -350
  32. package/dist/src/framework.js.map +1 -1
  33. package/dist/src/gate/event-gate.d.ts +12 -4
  34. package/dist/src/gate/event-gate.d.ts.map +1 -1
  35. package/dist/src/gate/event-gate.js +25 -5
  36. package/dist/src/gate/event-gate.js.map +1 -1
  37. package/dist/src/host-presentation.d.ts +4 -0
  38. package/dist/src/host-presentation.d.ts.map +1 -1
  39. package/dist/src/host-presentation.js +7 -3
  40. package/dist/src/host-presentation.js.map +1 -1
  41. package/dist/src/index.d.ts +5 -2
  42. package/dist/src/index.d.ts.map +1 -1
  43. package/dist/src/index.js +6 -1
  44. package/dist/src/index.js.map +1 -1
  45. package/dist/src/mcpl/channel-registry.d.ts +84 -9
  46. package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
  47. package/dist/src/mcpl/channel-registry.js +136 -58
  48. package/dist/src/mcpl/channel-registry.js.map +1 -1
  49. package/dist/src/mcpl/push-coalescer.d.ts +9 -0
  50. package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
  51. package/dist/src/mcpl/push-coalescer.js +21 -0
  52. package/dist/src/mcpl/push-coalescer.js.map +1 -1
  53. package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
  54. package/dist/src/mcpl/toolsets-module.js +7 -0
  55. package/dist/src/mcpl/toolsets-module.js.map +1 -1
  56. package/dist/src/notice-tags.d.ts +2 -0
  57. package/dist/src/notice-tags.d.ts.map +1 -1
  58. package/dist/src/notice-tags.js +2 -0
  59. package/dist/src/notice-tags.js.map +1 -1
  60. package/dist/src/silent-heartbeat.d.ts +4 -2
  61. package/dist/src/silent-heartbeat.d.ts.map +1 -1
  62. package/dist/src/silent-heartbeat.js +7 -4
  63. package/dist/src/silent-heartbeat.js.map +1 -1
  64. package/dist/src/stored-injections.d.ts +4 -0
  65. package/dist/src/stored-injections.d.ts.map +1 -1
  66. package/dist/src/stored-injections.js +6 -0
  67. package/dist/src/stored-injections.js.map +1 -1
  68. package/dist/src/tune-out/coordinator.d.ts +379 -9
  69. package/dist/src/tune-out/coordinator.d.ts.map +1 -1
  70. package/dist/src/tune-out/coordinator.js +1080 -97
  71. package/dist/src/tune-out/coordinator.js.map +1 -1
  72. package/dist/src/tune-out/tools.d.ts +101 -5
  73. package/dist/src/tune-out/tools.d.ts.map +1 -1
  74. package/dist/src/tune-out/tools.js +55 -18
  75. package/dist/src/tune-out/tools.js.map +1 -1
  76. package/dist/src/types/agent.d.ts +25 -0
  77. package/dist/src/types/agent.d.ts.map +1 -1
  78. package/dist/src/types/framework.d.ts +99 -0
  79. package/dist/src/types/framework.d.ts.map +1 -1
  80. package/dist/src/types/trace.d.ts +136 -0
  81. package/dist/src/types/trace.d.ts.map +1 -1
  82. package/package.json +3 -3
  83. package/src/admission/admit.ts +113 -0
  84. package/src/agent.ts +48 -6
  85. package/src/author-presentation.ts +81 -0
  86. package/src/dendrite/index.ts +32 -0
  87. package/src/dendrite/presets.ts +149 -0
  88. package/src/dendrite/registry.ts +808 -0
  89. package/src/dendrite/types.ts +397 -0
  90. package/src/framework.ts +3239 -369
  91. package/src/gate/event-gate.ts +31 -5
  92. package/src/host-presentation.ts +10 -3
  93. package/src/index.ts +15 -2
  94. package/src/mcpl/channel-registry.ts +170 -60
  95. package/src/mcpl/push-coalescer.ts +19 -0
  96. package/src/mcpl/toolsets-module.ts +7 -0
  97. package/src/notice-tags.ts +2 -0
  98. package/src/silent-heartbeat.ts +7 -4
  99. package/src/stored-injections.ts +7 -0
  100. package/src/tune-out/coordinator.ts +1229 -98
  101. package/src/tune-out/tools.ts +152 -11
  102. package/src/types/agent.ts +28 -0
  103. package/src/types/framework.ts +102 -0
  104. 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
+ }
@@ -340,6 +340,34 @@ export interface AgentConfig {
340
340
  */
341
341
  proseRouting?: 'locus' | 'explicit' | 'hybrid' | 'disabled';
342
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
+
343
371
  /**
344
372
  * Fail-closed containment for a text response whose entire visible prose is
345
373
  * an invocation-shaped wrapper naming a tool registered on that exact turn.
@@ -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
 
@@ -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.