@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.
- package/dist/src/admission/admit.d.ts +90 -0
- package/dist/src/admission/admit.d.ts.map +1 -0
- package/dist/src/admission/admit.js +43 -0
- package/dist/src/admission/admit.js.map +1 -0
- package/dist/src/agent.d.ts +21 -0
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +43 -6
- package/dist/src/agent.js.map +1 -1
- package/dist/src/author-presentation.d.ts +50 -0
- package/dist/src/author-presentation.d.ts.map +1 -0
- package/dist/src/author-presentation.js +58 -0
- package/dist/src/author-presentation.js.map +1 -0
- package/dist/src/dendrite/index.d.ts +6 -0
- package/dist/src/dendrite/index.d.ts.map +1 -0
- package/dist/src/dendrite/index.js +3 -0
- package/dist/src/dendrite/index.js.map +1 -0
- package/dist/src/dendrite/presets.d.ts +60 -0
- package/dist/src/dendrite/presets.d.ts.map +1 -0
- package/dist/src/dendrite/presets.js +117 -0
- package/dist/src/dendrite/presets.js.map +1 -0
- package/dist/src/dendrite/registry.d.ts +260 -0
- package/dist/src/dendrite/registry.d.ts.map +1 -0
- package/dist/src/dendrite/registry.js +742 -0
- package/dist/src/dendrite/registry.js.map +1 -0
- package/dist/src/dendrite/types.d.ts +392 -0
- package/dist/src/dendrite/types.d.ts.map +1 -0
- package/dist/src/dendrite/types.js +24 -0
- package/dist/src/dendrite/types.js.map +1 -0
- package/dist/src/framework.d.ts +684 -33
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +2998 -350
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +12 -4
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +25 -5
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/host-presentation.d.ts +4 -0
- package/dist/src/host-presentation.d.ts.map +1 -1
- package/dist/src/host-presentation.js +7 -3
- package/dist/src/host-presentation.js.map +1 -1
- package/dist/src/index.d.ts +5 -2
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +6 -1
- package/dist/src/index.js.map +1 -1
- package/dist/src/mcpl/channel-registry.d.ts +84 -9
- package/dist/src/mcpl/channel-registry.d.ts.map +1 -1
- package/dist/src/mcpl/channel-registry.js +136 -58
- package/dist/src/mcpl/channel-registry.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +9 -0
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js +21 -0
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/toolsets-module.d.ts.map +1 -1
- package/dist/src/mcpl/toolsets-module.js +7 -0
- package/dist/src/mcpl/toolsets-module.js.map +1 -1
- package/dist/src/notice-tags.d.ts +2 -0
- package/dist/src/notice-tags.d.ts.map +1 -1
- package/dist/src/notice-tags.js +2 -0
- package/dist/src/notice-tags.js.map +1 -1
- package/dist/src/silent-heartbeat.d.ts +4 -2
- package/dist/src/silent-heartbeat.d.ts.map +1 -1
- package/dist/src/silent-heartbeat.js +7 -4
- package/dist/src/silent-heartbeat.js.map +1 -1
- package/dist/src/stored-injections.d.ts +4 -0
- package/dist/src/stored-injections.d.ts.map +1 -1
- package/dist/src/stored-injections.js +6 -0
- package/dist/src/stored-injections.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +379 -9
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +1080 -97
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +101 -5
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/tune-out/tools.js +55 -18
- package/dist/src/tune-out/tools.js.map +1 -1
- package/dist/src/types/agent.d.ts +25 -0
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +99 -0
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +136 -0
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/admission/admit.ts +113 -0
- package/src/agent.ts +48 -6
- package/src/author-presentation.ts +81 -0
- package/src/dendrite/index.ts +32 -0
- package/src/dendrite/presets.ts +149 -0
- package/src/dendrite/registry.ts +808 -0
- package/src/dendrite/types.ts +397 -0
- package/src/framework.ts +3239 -369
- package/src/gate/event-gate.ts +31 -5
- package/src/host-presentation.ts +10 -3
- package/src/index.ts +15 -2
- package/src/mcpl/channel-registry.ts +170 -60
- package/src/mcpl/push-coalescer.ts +19 -0
- package/src/mcpl/toolsets-module.ts +7 -0
- package/src/notice-tags.ts +2 -0
- package/src/silent-heartbeat.ts +7 -4
- package/src/stored-injections.ts +7 -0
- package/src/tune-out/coordinator.ts +1229 -98
- package/src/tune-out/tools.ts +152 -11
- package/src/types/agent.ts +28 -0
- package/src/types/framework.ts +102 -0
- package/src/types/trace.ts +150 -0
package/src/tune-out/tools.ts
CHANGED
|
@@ -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,
|
|
90
|
-
'as the resident)
|
|
91
|
-
'
|
|
92
|
-
'
|
|
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
|
|
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
|
|
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
|
+
}
|
package/src/types/agent.ts
CHANGED
|
@@ -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.
|
package/src/types/framework.ts
CHANGED
|
@@ -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
|
|
package/src/types/trace.ts
CHANGED
|
@@ -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.
|