@animalabs/agent-framework 0.30.0 → 0.32.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/README.md +71 -8
- package/dist/src/agent.d.ts +29 -9
- package/dist/src/agent.d.ts.map +1 -1
- package/dist/src/agent.js +82 -17
- package/dist/src/agent.js.map +1 -1
- package/dist/src/api/mcp-server-core.d.ts.map +1 -1
- package/dist/src/api/mcp-server-core.js +10 -3
- package/dist/src/api/mcp-server-core.js.map +1 -1
- package/dist/src/code-execution/isolate/render.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/render.js +7 -6
- package/dist/src/code-execution/isolate/render.js.map +1 -1
- package/dist/src/code-execution/isolate/scripts.d.ts.map +1 -1
- package/dist/src/code-execution/isolate/scripts.js +5 -4
- package/dist/src/code-execution/isolate/scripts.js.map +1 -1
- package/dist/src/framework.d.ts +138 -102
- package/dist/src/framework.d.ts.map +1 -1
- package/dist/src/framework.js +1133 -752
- package/dist/src/framework.js.map +1 -1
- package/dist/src/gate/event-gate.d.ts +24 -17
- package/dist/src/gate/event-gate.d.ts.map +1 -1
- package/dist/src/gate/event-gate.js +198 -144
- package/dist/src/gate/event-gate.js.map +1 -1
- package/dist/src/gate/recipe-reconcile.d.ts.map +1 -1
- package/dist/src/gate/recipe-reconcile.js +6 -5
- package/dist/src/gate/recipe-reconcile.js.map +1 -1
- package/dist/src/host-presentation.d.ts.map +1 -1
- package/dist/src/host-presentation.js +5 -7
- package/dist/src/host-presentation.js.map +1 -1
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +1 -0
- package/dist/src/index.js.map +1 -1
- package/dist/src/inference-bounds.d.ts +37 -0
- package/dist/src/inference-bounds.d.ts.map +1 -0
- package/dist/src/inference-bounds.js +84 -0
- package/dist/src/inference-bounds.js.map +1 -0
- package/dist/src/line-reader.d.ts +7 -1
- package/dist/src/line-reader.d.ts.map +1 -1
- package/dist/src/line-reader.js +17 -6
- package/dist/src/line-reader.js.map +1 -1
- package/dist/src/mcpl/errors.d.ts +1 -0
- package/dist/src/mcpl/errors.d.ts.map +1 -1
- package/dist/src/mcpl/errors.js +3 -0
- package/dist/src/mcpl/errors.js.map +1 -1
- package/dist/src/mcpl/inbound-source.d.ts +2 -2
- package/dist/src/mcpl/inbound-source.js +2 -2
- package/dist/src/mcpl/inference-router.d.ts +11 -1
- package/dist/src/mcpl/inference-router.d.ts.map +1 -1
- package/dist/src/mcpl/inference-router.js +65 -9
- package/dist/src/mcpl/inference-router.js.map +1 -1
- package/dist/src/mcpl/modern-connection.d.ts +10 -7
- package/dist/src/mcpl/modern-connection.d.ts.map +1 -1
- package/dist/src/mcpl/modern-connection.js +85 -74
- package/dist/src/mcpl/modern-connection.js.map +1 -1
- package/dist/src/mcpl/protocol-family.d.ts +1 -1
- package/dist/src/mcpl/protocol-family.d.ts.map +1 -1
- package/dist/src/mcpl/protocol-family.js +45 -3
- package/dist/src/mcpl/protocol-family.js.map +1 -1
- package/dist/src/mcpl/push-coalescer.d.ts +2 -7
- package/dist/src/mcpl/push-coalescer.d.ts.map +1 -1
- package/dist/src/mcpl/push-coalescer.js.map +1 -1
- package/dist/src/mcpl/references.d.ts +1 -2
- package/dist/src/mcpl/references.d.ts.map +1 -1
- package/dist/src/mcpl/references.js +3 -3
- package/dist/src/mcpl/references.js.map +1 -1
- package/dist/src/mcpl/server-connection.d.ts +14 -0
- package/dist/src/mcpl/server-connection.d.ts.map +1 -1
- package/dist/src/mcpl/server-connection.js +26 -22
- package/dist/src/mcpl/server-connection.js.map +1 -1
- package/dist/src/mcpl/tool-signature.d.ts +1 -0
- package/dist/src/mcpl/tool-signature.d.ts.map +1 -1
- package/dist/src/mcpl/tool-signature.js +16 -11
- package/dist/src/mcpl/tool-signature.js.map +1 -1
- package/dist/src/mcpl/transport.d.ts +44 -21
- package/dist/src/mcpl/transport.d.ts.map +1 -1
- package/dist/src/mcpl/transport.js +283 -64
- package/dist/src/mcpl/transport.js.map +1 -1
- package/dist/src/mcpl/types.d.ts +17 -1
- package/dist/src/mcpl/types.d.ts.map +1 -1
- package/dist/src/mcpl/types.js.map +1 -1
- package/dist/src/module-registry.d.ts +32 -4
- package/dist/src/module-registry.d.ts.map +1 -1
- package/dist/src/module-registry.js +189 -25
- package/dist/src/module-registry.js.map +1 -1
- package/dist/src/modules/discord/discord-js-client.d.ts.map +1 -1
- package/dist/src/modules/discord/discord-js-client.js +24 -16
- package/dist/src/modules/discord/discord-js-client.js.map +1 -1
- package/dist/src/modules/sender-name.d.ts +5 -4
- package/dist/src/modules/sender-name.d.ts.map +1 -1
- package/dist/src/modules/sender-name.js +7 -5
- package/dist/src/modules/sender-name.js.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.d.ts +16 -8
- package/dist/src/modules/workspace/disk-agreement.d.ts.map +1 -1
- package/dist/src/modules/workspace/disk-agreement.js +33 -12
- package/dist/src/modules/workspace/disk-agreement.js.map +1 -1
- package/dist/src/modules/workspace/index.d.ts +6 -3
- package/dist/src/modules/workspace/index.d.ts.map +1 -1
- package/dist/src/modules/workspace/index.js +61 -15
- package/dist/src/modules/workspace/index.js.map +1 -1
- package/dist/src/modules/workspace/observe.d.ts +9 -1
- package/dist/src/modules/workspace/observe.d.ts.map +1 -1
- package/dist/src/modules/workspace/observe.js +50 -10
- package/dist/src/modules/workspace/observe.js.map +1 -1
- package/dist/src/modules/workspace/reconcile.d.ts +11 -1
- package/dist/src/modules/workspace/reconcile.d.ts.map +1 -1
- package/dist/src/modules/workspace/reconcile.js +19 -8
- package/dist/src/modules/workspace/reconcile.js.map +1 -1
- package/dist/src/modules/workspace/types.d.ts +8 -0
- package/dist/src/modules/workspace/types.d.ts.map +1 -1
- package/dist/src/modules/workspace/types.js.map +1 -1
- package/dist/src/modules/workspace/watcher.d.ts +7 -0
- package/dist/src/modules/workspace/watcher.d.ts.map +1 -1
- package/dist/src/modules/workspace/watcher.js +7 -5
- package/dist/src/modules/workspace/watcher.js.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.d.ts.map +1 -1
- package/dist/src/recovery/discord-awareness-outbox.js +1 -9
- package/dist/src/recovery/discord-awareness-outbox.js.map +1 -1
- package/dist/src/supplied-text.d.ts +2 -0
- package/dist/src/supplied-text.d.ts.map +1 -1
- package/dist/src/supplied-text.js +16 -7
- package/dist/src/supplied-text.js.map +1 -1
- package/dist/src/tool-result-guard.js +1 -1
- package/dist/src/tool-result-guard.js.map +1 -1
- package/dist/src/tune-out/coordinator.d.ts +2 -20
- package/dist/src/tune-out/coordinator.d.ts.map +1 -1
- package/dist/src/tune-out/coordinator.js +175 -70
- package/dist/src/tune-out/coordinator.js.map +1 -1
- package/dist/src/tune-out/tools.d.ts +14 -2
- package/dist/src/tune-out/tools.d.ts.map +1 -1
- package/dist/src/tune-out/tools.js.map +1 -1
- package/dist/src/types/agent.d.ts +6 -12
- package/dist/src/types/agent.d.ts.map +1 -1
- package/dist/src/types/framework.d.ts +65 -24
- package/dist/src/types/framework.d.ts.map +1 -1
- package/dist/src/types/module.d.ts +42 -3
- package/dist/src/types/module.d.ts.map +1 -1
- package/dist/src/types/trace.d.ts +4 -3
- package/dist/src/types/trace.d.ts.map +1 -1
- package/package.json +3 -3
- package/src/agent.ts +90 -25
- package/src/api/mcp-server-core.ts +10 -3
- package/src/code-execution/isolate/render.ts +7 -6
- package/src/code-execution/isolate/scripts.ts +5 -4
- package/src/framework.ts +1163 -770
- package/src/gate/event-gate.ts +220 -145
- package/src/gate/recipe-reconcile.ts +7 -5
- package/src/host-presentation.ts +4 -7
- package/src/index.ts +1 -0
- package/src/inference-bounds.ts +111 -0
- package/src/line-reader.ts +22 -5
- package/src/mcpl/errors.ts +4 -0
- package/src/mcpl/inbound-source.ts +2 -2
- package/src/mcpl/inference-router.ts +67 -10
- package/src/mcpl/modern-connection.ts +83 -68
- package/src/mcpl/protocol-family.ts +37 -4
- package/src/mcpl/push-coalescer.ts +2 -7
- package/src/mcpl/references.ts +3 -4
- package/src/mcpl/server-connection.ts +30 -21
- package/src/mcpl/tool-signature.ts +18 -11
- package/src/mcpl/transport.ts +261 -60
- package/src/mcpl/types.ts +18 -1
- package/src/module-registry.ts +178 -25
- package/src/modules/discord/discord-js-client.ts +33 -29
- package/src/modules/sender-name.ts +8 -5
- package/src/modules/workspace/disk-agreement.ts +39 -15
- package/src/modules/workspace/index.ts +65 -14
- package/src/modules/workspace/observe.ts +51 -10
- package/src/modules/workspace/reconcile.ts +22 -8
- package/src/modules/workspace/types.ts +5 -0
- package/src/modules/workspace/watcher.ts +8 -6
- package/src/recovery/discord-awareness-outbox.ts +1 -11
- package/src/supplied-text.ts +17 -6
- package/src/tool-result-guard.ts +1 -1
- package/src/tune-out/coordinator.ts +187 -85
- package/src/tune-out/tools.ts +14 -2
- package/src/types/agent.ts +6 -12
- package/src/types/framework.ts +66 -24
- package/src/types/module.ts +42 -3
- package/src/types/trace.ts +4 -3
- package/dist/src/reasoning-loss-notice.d.ts +0 -23
- package/dist/src/reasoning-loss-notice.d.ts.map +0 -1
- package/dist/src/reasoning-loss-notice.js +0 -52
- package/dist/src/reasoning-loss-notice.js.map +0 -1
- package/dist/src/reasoning-loss.d.ts +0 -24
- package/dist/src/reasoning-loss.d.ts.map +0 -1
- package/dist/src/reasoning-loss.js +0 -102
- package/dist/src/reasoning-loss.js.map +0 -1
- package/src/reasoning-loss-notice.ts +0 -64
- package/src/reasoning-loss.ts +0 -120
|
@@ -98,6 +98,69 @@ function writeIdOf(m: Pick<StoredMessage, 'metadata'>): string | undefined {
|
|
|
98
98
|
return typeof writeId === 'string' ? writeId : undefined;
|
|
99
99
|
}
|
|
100
100
|
|
|
101
|
+
type HeldRow = Pick<StoredMessage, 'participant' | 'content' | 'metadata'> & { id?: string; writeId?: string };
|
|
102
|
+
|
|
103
|
+
interface RowKeys {
|
|
104
|
+
ids: string[];
|
|
105
|
+
writeIds: string[];
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
interface ReleaseRecord extends RowKeys {
|
|
109
|
+
total: number;
|
|
110
|
+
truncated: number;
|
|
111
|
+
report: RowKeys;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function keysOf(rows: readonly HeldRow[]): RowKeys {
|
|
115
|
+
const keys: RowKeys = { ids: [], writeIds: [] };
|
|
116
|
+
for (const m of rows) {
|
|
117
|
+
const writeId = m.writeId ?? writeIdOf(m);
|
|
118
|
+
if (writeId !== undefined) keys.writeIds.push(writeId);
|
|
119
|
+
else if (m.id !== undefined) keys.ids.push(m.id);
|
|
120
|
+
}
|
|
121
|
+
return keys;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
function keyedBy(keys: readonly RowKeys[]): (m: HeldRow) => boolean {
|
|
125
|
+
const ids = new Set(keys.flatMap((k) => k.ids));
|
|
126
|
+
const writeIds = new Set(keys.flatMap((k) => k.writeIds));
|
|
127
|
+
return (m) => {
|
|
128
|
+
const writeId = m.writeId ?? writeIdOf(m);
|
|
129
|
+
return writeId !== undefined ? writeIds.has(writeId) : m.id !== undefined && ids.has(m.id);
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
const isStrings = (value: unknown): value is string[] => Array.isArray(value) && value.every((v) => typeof v === 'string');
|
|
134
|
+
const isRowKeys = (value: unknown): value is RowKeys =>
|
|
135
|
+
typeof value === 'object' && value !== null && isStrings((value as RowKeys).ids) && isStrings((value as RowKeys).writeIds);
|
|
136
|
+
|
|
137
|
+
function isReleaseRecord(value: unknown): value is ReleaseRecord {
|
|
138
|
+
if (!isRowKeys(value)) return false;
|
|
139
|
+
const record = value as Partial<ReleaseRecord>;
|
|
140
|
+
return typeof record.total === 'number' && typeof record.truncated === 'number' && isRowKeys(record.report);
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
function releaseRecordsOf(rows: readonly HeldRow[], epochId: string): ReleaseRecord[] {
|
|
144
|
+
const records: ReleaseRecord[] = [];
|
|
145
|
+
for (const m of rows) {
|
|
146
|
+
const meta = m.metadata as { kind?: unknown; epochId?: unknown; released?: unknown } | undefined;
|
|
147
|
+
if (meta?.kind !== 'tune-out-cancel' || meta.epochId !== epochId) continue;
|
|
148
|
+
if (isReleaseRecord(meta.released) && !records.some((r) => JSON.stringify(r) === JSON.stringify(meta.released))) {
|
|
149
|
+
records.push(meta.released);
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
return records;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
function truncationNote(records: readonly ReleaseRecord[]): string {
|
|
156
|
+
const truncated = records.reduce((n, r) => n + r.truncated, 0);
|
|
157
|
+
if (truncated === 0) return '';
|
|
158
|
+
const total = records.reduce((n, r) => n + r.total, 0);
|
|
159
|
+
return records.length === 1
|
|
160
|
+
? ` ${truncated} of ${total} diverted messages, the oldest, weren't delivered raw to the resident.`
|
|
161
|
+
: ` ${truncated} of ${total} diverted messages across ${records.length} dumps, the oldest in each dump that left any out, weren't delivered raw to the resident.`;
|
|
162
|
+
}
|
|
163
|
+
|
|
101
164
|
/**
|
|
102
165
|
* Whether a held message is one of this channel's: by its channel id, and by
|
|
103
166
|
* its server where it records one, since two servers can register the same
|
|
@@ -145,7 +208,7 @@ export interface TuneOutCheck {
|
|
|
145
208
|
peek?: string[];
|
|
146
209
|
}
|
|
147
210
|
|
|
148
|
-
/** How long after an end time
|
|
211
|
+
/** How long after an end time's or a wake budget's cancel threw the cancel is tried again. */
|
|
149
212
|
const EXPIRY_RETRY_MS = 60_000;
|
|
150
213
|
|
|
151
214
|
/** Ends a backlog line cut to fit in one of the residents' messages. */
|
|
@@ -215,12 +278,7 @@ export interface TuneOutFrameworkHooks {
|
|
|
215
278
|
* message held mid-turn is in the backlog it belongs to (#232 gap 7).
|
|
216
279
|
*/
|
|
217
280
|
getDeferredMessages?: () => Array<Pick<StoredMessage, 'participant' | 'content' | 'metadata'> & { writeId?: string; heldAt?: number }>;
|
|
218
|
-
|
|
219
|
-
* Make what was just delivered durable, including anything the delivery
|
|
220
|
-
* left in the deferred queue: a release flips durable epoch state, so its
|
|
221
|
-
* dump must survive a crash in the same window (#232 gap 6).
|
|
222
|
-
*/
|
|
223
|
-
commitDeliveries?: () => void;
|
|
281
|
+
commitDeliveries?: () => boolean;
|
|
224
282
|
/**
|
|
225
283
|
* The history-reading tool the resident actually has (e.g. a server's
|
|
226
284
|
* `fetch_history`), or null. A truncation note names it only when it exists
|
|
@@ -321,8 +379,6 @@ export class TuneOutCoordinator {
|
|
|
321
379
|
private lateForks = new WeakSet<object>();
|
|
322
380
|
/** The all-except scope's durable record, cached (undefined = not read yet). */
|
|
323
381
|
private scopeCache: AllExceptState | null | undefined;
|
|
324
|
-
/** The epoch whose release this process delivered and whose hold is not yet recorded as cleared. */
|
|
325
|
-
private scopeReleased: string | undefined;
|
|
326
382
|
/** Timer key for the scope's cadence and expiry. */
|
|
327
383
|
private static readonly SCOPE_KEY = '\0all-except';
|
|
328
384
|
|
|
@@ -378,25 +434,53 @@ export class TuneOutCoordinator {
|
|
|
378
434
|
// An epoch whose deadline passed while the process was down cancels
|
|
379
435
|
// immediately — the resident gets their backlog on the schedule they
|
|
380
436
|
// committed to, not whenever the host happened to restart.
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
437
|
+
const inForce = (): boolean => this.channelRegistry.getTuneOutState(serverId, channelId)?.params.epochId === params.epochId;
|
|
438
|
+
const expired = params.expiresAtMs !== undefined && params.expiresAtMs <= Date.now();
|
|
439
|
+
if (expired) {
|
|
440
|
+
try {
|
|
441
|
+
this.cancel(serverId, channelId, 'duration', 'duration elapsed');
|
|
442
|
+
continue;
|
|
443
|
+
} catch (err) {
|
|
444
|
+
if (!inForce()) {
|
|
445
|
+
console.error(`[tune-out] ${this.shown(serverId, channelId)} reached its end time while the process was down and its cancel ended it, but a step after the end failed:`, err);
|
|
446
|
+
continue;
|
|
447
|
+
}
|
|
448
|
+
console.error(`[tune-out] ${this.shown(serverId, channelId)} reached its end time while the process was down, and its cancel failed, so it stays tuned out and its cancel is tried again in ${EXPIRY_RETRY_MS / 1000} s:`, err);
|
|
449
|
+
}
|
|
384
450
|
}
|
|
385
451
|
this.startCadence(serverId, channelId, params.cadenceSeconds);
|
|
386
|
-
this.
|
|
452
|
+
const wakeCount = this.channelRegistry.getTuneOutState(serverId, channelId)?.wakeCount ?? 0;
|
|
453
|
+
if (wakeCount > params.maxWakes) {
|
|
454
|
+
this.cancelPastBudget(serverId, channelId, params.maxWakes, 'start');
|
|
455
|
+
if (!inForce()) continue;
|
|
456
|
+
} else {
|
|
457
|
+
this.armExpiry(serverId, channelId, expired ? { ...params, expiresAtMs: Date.now() + EXPIRY_RETRY_MS } : params);
|
|
458
|
+
}
|
|
387
459
|
if (oldestAnchor === null || params.startedAtSequence < oldestAnchor) {
|
|
388
460
|
oldestAnchor = params.startedAtSequence;
|
|
389
461
|
}
|
|
390
462
|
}
|
|
391
463
|
const scope = this.getAllExcept();
|
|
392
464
|
if (scope) {
|
|
465
|
+
let inForce = true;
|
|
393
466
|
if (scope.params.expiresAtMs <= Date.now()) {
|
|
394
|
-
|
|
467
|
+
try {
|
|
468
|
+
this.cancelAllExcept('duration', 'duration elapsed');
|
|
469
|
+
inForce = false;
|
|
470
|
+
} catch (err) {
|
|
471
|
+
if (this.getAllExcept()?.params.epochId !== scope.params.epochId) {
|
|
472
|
+
console.error('[tune-out] the all-except hold reached its end time while the process was down and its release ended it, but a step after the end failed:', err);
|
|
473
|
+
inForce = false;
|
|
474
|
+
} else {
|
|
475
|
+
console.error(`[tune-out] the all-except hold reached its end time while the process was down, and its release failed, so it stays in force and its release is tried again in ${EXPIRY_RETRY_MS / 1000} s:`, err);
|
|
476
|
+
this.armScopeTimers({ ...scope.params, expiresAtMs: Date.now() + EXPIRY_RETRY_MS });
|
|
477
|
+
}
|
|
478
|
+
}
|
|
395
479
|
} else {
|
|
396
480
|
this.armScopeTimers(scope.params);
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
481
|
+
}
|
|
482
|
+
if (inForce && (oldestAnchor === null || scope.params.startedAtSequence < oldestAnchor)) {
|
|
483
|
+
oldestAnchor = scope.params.startedAtSequence;
|
|
400
484
|
}
|
|
401
485
|
}
|
|
402
486
|
if (oldestAnchor !== null) {
|
|
@@ -600,13 +684,6 @@ export class TuneOutCoordinator {
|
|
|
600
684
|
return this.readerAvailable();
|
|
601
685
|
}
|
|
602
686
|
|
|
603
|
-
/**
|
|
604
|
-
* End a tune-out: durable flip, system-framed backlog dump into the
|
|
605
|
-
* resident's window (capped), lifecycle notice, one wake for the
|
|
606
|
-
* resident. `subconsciousNote` is the subconscious's own text (from its
|
|
607
|
-
* cancel_tuneout call) — delivered verbatim under its name, never
|
|
608
|
-
* synthesized here.
|
|
609
|
-
*/
|
|
610
687
|
cancel(
|
|
611
688
|
serverId: string,
|
|
612
689
|
channelId: string,
|
|
@@ -618,31 +695,25 @@ export class TuneOutCoordinator {
|
|
|
618
695
|
/** The lifecycle state the channel returns to (a tool close ends in 'closed'). */
|
|
619
696
|
nextState: 'open' | 'closed' = 'open',
|
|
620
697
|
): { ok: true } | { ok: false; error: string } {
|
|
621
|
-
const
|
|
622
|
-
if (!
|
|
698
|
+
const ending = this.channelRegistry.getTuneOutState(serverId, channelId);
|
|
699
|
+
if (!ending) {
|
|
623
700
|
// Its reader is the resident's tune_out: cancel_tuneout finds the channel first.
|
|
624
701
|
return { ok: false, error: this.notTunedOut(this.quotedShown(serverId, channelId), 'you') };
|
|
625
702
|
}
|
|
626
|
-
this.clearTimers(serverId, channelId);
|
|
627
|
-
this.divertedSinceInvocation.delete(this.key(serverId, channelId));
|
|
628
703
|
|
|
629
|
-
|
|
630
|
-
// store order, then those still in the deferred queue (held mid-turn,
|
|
631
|
-
// not yet written), oldest first. That's arrival order, except when a
|
|
632
|
-
// held message stays queued while a later one is stored: a store write
|
|
633
|
-
// refused at a turn-start flush leaves it queued, and that turn's
|
|
634
|
-
// assembly can then store later coalesced messages directly.
|
|
635
|
-
const epochId = ended.params.epochId;
|
|
704
|
+
const epochId = ending.params.epochId;
|
|
636
705
|
const ofEpoch = (m: Pick<StoredMessage, 'metadata'>): boolean =>
|
|
637
706
|
(m.metadata as { tuneOut?: { epochId?: string } } | undefined)?.tuneOut?.epochId === epochId;
|
|
638
|
-
const
|
|
639
|
-
const
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
707
|
+
const storedRows: HeldRow[] = this.hooks.getStoredMessages();
|
|
708
|
+
const queuedRows: HeldRow[] = this.hooks.getDeferredMessages?.() ?? [];
|
|
709
|
+
const earlier = releaseRecordsOf([...storedRows, ...queuedRows], epochId);
|
|
710
|
+
const dumpedEarlier = keyedBy(earlier);
|
|
711
|
+
const unreleased = (m: HeldRow): boolean => ofEpoch(m) && !dumpedEarlier(m);
|
|
712
|
+
const held = queuedRows.filter(unreleased);
|
|
713
|
+
const backlog: HeldRow[] = [...storedRows.filter(unreleased), ...held];
|
|
643
714
|
// Whole messages: a cap below one shows none, where slice(-0.5) would
|
|
644
715
|
// start at 0 and take them all.
|
|
645
|
-
const cap = Math.floor(
|
|
716
|
+
const cap = Math.floor(ending.params.backlogCap);
|
|
646
717
|
const authorOf = (m: Pick<StoredMessage, 'participant' | 'metadata'>): string =>
|
|
647
718
|
suppliedValue((m.metadata as { author?: { name?: string } } | undefined)?.author?.name ?? m.participant);
|
|
648
719
|
// A line names its author as the resident knows them and marks what a
|
|
@@ -653,7 +724,7 @@ export class TuneOutCoordinator {
|
|
|
653
724
|
const historyTool = this.hooks.historyTool ? this.hooks.historyTool() : 'fetch_history';
|
|
654
725
|
// The channel as the resident and the subconscious are shown it.
|
|
655
726
|
const name = this.shown(serverId, channelId);
|
|
656
|
-
const lifecycle = `[${NOTICE_TAG.tuneOut}: ${name} cancelled — ${reason}, ${
|
|
727
|
+
const lifecycle = `[${NOTICE_TAG.tuneOut}: ${name} cancelled — ${reason}, ${ending.wakeCount} wake${ending.wakeCount === 1 ? '' : 's'}]`;
|
|
657
728
|
const render = (lines: string[], truncated: number): string => {
|
|
658
729
|
const attrs = [
|
|
659
730
|
`channel=${this.quotedShown(serverId, channelId)}`,
|
|
@@ -725,14 +796,30 @@ export class TuneOutCoordinator {
|
|
|
725
796
|
m.content.some((b) => b.type !== 'text') || (newestCut && m === shown[0])),
|
|
726
797
|
];
|
|
727
798
|
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
799
|
+
const records = [...earlier];
|
|
800
|
+
if (earlier.length === 0 || backlog.length > 0) {
|
|
801
|
+
const record: ReleaseRecord = { ...keysOf(backlog), total: backlog.length, truncated, report: keysOf(heldForReport) };
|
|
802
|
+
this.hooks.addMessage(
|
|
803
|
+
'user',
|
|
804
|
+
[{ type: 'text', text: render(lines, truncated) }],
|
|
805
|
+
{ system: true, kind: 'tune-out-cancel', channelId, epochId, released: record },
|
|
806
|
+
);
|
|
807
|
+
records.push(record);
|
|
808
|
+
}
|
|
809
|
+
if (this.hooks.commitDeliveries?.() === false) {
|
|
810
|
+
throw new Error(`the backlog of ${this.quotedShown(serverId, channelId)} could not be made durable, so its tune-out stays in force`);
|
|
811
|
+
}
|
|
812
|
+
this.channelRegistry.cancelTuneOut(serverId, channelId, nextState, source);
|
|
813
|
+
this.clearTimers(serverId, channelId);
|
|
814
|
+
this.divertedSinceInvocation.delete(this.key(serverId, channelId));
|
|
815
|
+
const reportedEarlier = keyedBy(earlier.map((r) => r.report));
|
|
816
|
+
const release = {
|
|
817
|
+
truncated: records.reduce((n, r) => n + r.truncated, 0),
|
|
818
|
+
heldForReport: [
|
|
819
|
+
...[...storedRows, ...queuedRows].filter((m) => ofEpoch(m) && reportedEarlier(m)),
|
|
820
|
+
...(records.length > earlier.length ? heldForReport : []),
|
|
821
|
+
],
|
|
822
|
+
};
|
|
736
823
|
// The subconscious's own words, under its own name, verbatim.
|
|
737
824
|
const subName = this.hooks.subconsciousName();
|
|
738
825
|
if (subconsciousNote && subName) {
|
|
@@ -766,12 +853,10 @@ export class TuneOutCoordinator {
|
|
|
766
853
|
// and when it wrote no note and the dump left any message out, since
|
|
767
854
|
// nothing else then covers it. Otherwise its note speaks for what it saw.
|
|
768
855
|
const selfCancel = source === 'subconscious';
|
|
769
|
-
if (subName && (!selfCancel || heldForReport.length > 0 || (!subconsciousNote && truncated > 0))) {
|
|
770
|
-
const truncNote =
|
|
771
|
-
|
|
772
|
-
: ''
|
|
773
|
-
const heldNote = heldForReport.length > 0
|
|
774
|
-
? ` Still held at the cancel, so your view may not have them yet: the ${heldForReport.length} message${heldForReport.length === 1 ? '' : 's'} after this one.`
|
|
856
|
+
if (subName && (!selfCancel || release.heldForReport.length > 0 || (!subconsciousNote && release.truncated > 0))) {
|
|
857
|
+
const truncNote = truncationNote(records);
|
|
858
|
+
const heldNote = release.heldForReport.length > 0
|
|
859
|
+
? ` Still held at the cancel, so your view may not have them yet: the ${release.heldForReport.length} message${release.heldForReport.length === 1 ? '' : 's'} after this one.`
|
|
775
860
|
: '';
|
|
776
861
|
this.hooks.addMessage(
|
|
777
862
|
'user',
|
|
@@ -781,7 +866,7 @@ export class TuneOutCoordinator {
|
|
|
781
866
|
);
|
|
782
867
|
// Each held message is a row of its own, so a per-message cap cuts each
|
|
783
868
|
// one on its own, its author's label included, not the batch's newest.
|
|
784
|
-
for (const m of heldForReport) {
|
|
869
|
+
for (const m of release.heldForReport) {
|
|
785
870
|
this.hooks.addMessage(
|
|
786
871
|
'user',
|
|
787
872
|
[{ type: 'text', text: `${authorOf(m)}:` }, ...m.content],
|
|
@@ -803,9 +888,7 @@ export class TuneOutCoordinator {
|
|
|
803
888
|
const rest = this.unhandedBacklog(this.handOffKey(epochId, serverId, channelId), stored, queued);
|
|
804
889
|
const unread = rest.backlog.length + rest.queued.length;
|
|
805
890
|
if (!selfCancel || unread > 0) {
|
|
806
|
-
const truncNote =
|
|
807
|
-
? ` ${truncated} of ${backlog.length} diverted messages, the oldest, weren't delivered raw to the resident.`
|
|
808
|
-
: '';
|
|
891
|
+
const truncNote = truncationNote(records);
|
|
809
892
|
const lateNote = selfCancel
|
|
810
893
|
? ` ${unread === 1 ? 'This message' : `These ${unread} messages`} arrived after the reader that cancelled was handed its own.`
|
|
811
894
|
: '';
|
|
@@ -818,7 +901,7 @@ export class TuneOutCoordinator {
|
|
|
818
901
|
// there that its reader hasn't looked at are counted again, as the
|
|
819
902
|
// channel's own tune-out's count stood in for theirs.
|
|
820
903
|
this.rebuildTally(serverId, channelId);
|
|
821
|
-
this.hooks.emitTrace({ type: 'tune-out:cancelled', serverId, channelId, epochId, reason, wakeCount:
|
|
904
|
+
this.hooks.emitTrace({ type: 'tune-out:cancelled', serverId, channelId, epochId, reason, wakeCount: ending.wakeCount });
|
|
822
905
|
return { ok: true };
|
|
823
906
|
}
|
|
824
907
|
|
|
@@ -1043,11 +1126,6 @@ export class TuneOutCoordinator {
|
|
|
1043
1126
|
return counts;
|
|
1044
1127
|
}
|
|
1045
1128
|
|
|
1046
|
-
/**
|
|
1047
|
-
* End the all-except hold: durable flip, one release notice with a
|
|
1048
|
-
* per-channel backlog (each capped), the reader's note if any, one wake
|
|
1049
|
-
* for the resident, and the reader's own look at what it had not seen.
|
|
1050
|
-
*/
|
|
1051
1129
|
cancelAllExcept(
|
|
1052
1130
|
source: string,
|
|
1053
1131
|
reason: string,
|
|
@@ -1060,9 +1138,14 @@ export class TuneOutCoordinator {
|
|
|
1060
1138
|
|
|
1061
1139
|
const ofEpoch = (m: Pick<StoredMessage, 'metadata'>): boolean =>
|
|
1062
1140
|
(m.metadata as { tuneOut?: { epochId?: string } } | undefined)?.tuneOut?.epochId === epochId;
|
|
1063
|
-
const
|
|
1064
|
-
const
|
|
1065
|
-
|
|
1141
|
+
const storedRows: HeldRow[] = this.hooks.getStoredMessages();
|
|
1142
|
+
const queuedRows: HeldRow[] = this.hooks.getDeferredMessages?.() ?? [];
|
|
1143
|
+
const all: HeldRow[] = [...storedRows.filter(ofEpoch), ...queuedRows.filter(ofEpoch)];
|
|
1144
|
+
const earlier = releaseRecordsOf([...storedRows, ...queuedRows], epochId);
|
|
1145
|
+
const releasedEarlier = keyedBy(earlier);
|
|
1146
|
+
const fresh = all.filter((m) => !releasedEarlier(m));
|
|
1147
|
+
const byChannel = new Map<string, { serverId: string; channelId: string; backlog: HeldRow[] }>();
|
|
1148
|
+
for (const m of fresh) {
|
|
1066
1149
|
const meta = m.metadata as { channelId?: string; serverId?: string } | undefined;
|
|
1067
1150
|
const serverId = meta?.serverId ?? '';
|
|
1068
1151
|
const channelId = meta?.channelId ?? '(unknown channel)';
|
|
@@ -1072,12 +1155,14 @@ export class TuneOutCoordinator {
|
|
|
1072
1155
|
byChannel.set(k, entry);
|
|
1073
1156
|
}
|
|
1074
1157
|
const historyTool = this.hooks.historyTool?.() ?? null;
|
|
1158
|
+
let truncatedInAll = 0;
|
|
1075
1159
|
const dumps = [...byChannel.values()].map(({ serverId, channelId, backlog }) => {
|
|
1076
1160
|
// Floored here too: a hold stored before entry floored its cap can hold
|
|
1077
1161
|
// a fraction.
|
|
1078
1162
|
const cap = Math.floor(backlogCap);
|
|
1079
1163
|
const shown = cap > 0 ? backlog.slice(-cap) : [];
|
|
1080
1164
|
const truncated = backlog.length - shown.length;
|
|
1165
|
+
truncatedInAll += truncated;
|
|
1081
1166
|
const attrs = [
|
|
1082
1167
|
`channel=${quotedValue(this.shown(serverId, channelId))}`,
|
|
1083
1168
|
`messages=${backlog.length}`,
|
|
@@ -1087,22 +1172,19 @@ export class TuneOutCoordinator {
|
|
|
1087
1172
|
});
|
|
1088
1173
|
const summary = byChannel.size === 0
|
|
1089
1174
|
? 'Nothing was held.'
|
|
1090
|
-
: `${
|
|
1091
|
-
if (
|
|
1175
|
+
: `${fresh.length} message${fresh.length === 1 ? '' : 's'} held in ${byChannel.size} channel${byChannel.size === 1 ? '' : 's'}.`;
|
|
1176
|
+
if (earlier.length === 0 || fresh.length > 0) {
|
|
1177
|
+
const record: ReleaseRecord = { ...keysOf(fresh), total: fresh.length, truncated: truncatedInAll, report: { ids: [], writeIds: [] } };
|
|
1092
1178
|
this.hooks.addMessage(
|
|
1093
1179
|
'user',
|
|
1094
1180
|
[{ type: 'text', text: `[${NOTICE_TAG.tuneOut}: all channels except ${this.shown(except.serverId, except.channelId)} released — ${reason}, ${scope.wakeCount} wake${scope.wakeCount === 1 ? '' : 's'}]\n\n${summary}${dumps.length > 0 ? '\n\n' + dumps.join('\n\n') : ''}` }],
|
|
1095
|
-
{ system: true, kind: 'tune-out-cancel', scope: 'all-except', channelId: except.channelId, epochId },
|
|
1181
|
+
{ system: true, kind: 'tune-out-cancel', scope: 'all-except', channelId: except.channelId, epochId, released: record },
|
|
1096
1182
|
);
|
|
1097
|
-
this.hooks.commitDeliveries?.();
|
|
1098
|
-
this.scopeReleased = epochId;
|
|
1099
1183
|
}
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
// delivers the release again, where the other order lost it outright.
|
|
1184
|
+
if (this.hooks.commitDeliveries?.() === false) {
|
|
1185
|
+
throw new Error('the all-except release could not be made durable, so the hold stays in force');
|
|
1186
|
+
}
|
|
1104
1187
|
this.writeScope(null);
|
|
1105
|
-
this.scopeReleased = undefined;
|
|
1106
1188
|
this.clearScopeTimers();
|
|
1107
1189
|
for (const [key, pending] of this.pendingWakes) {
|
|
1108
1190
|
const [serverId, channelId] = key.split('\0') as [string, string];
|
|
@@ -1335,6 +1417,31 @@ export class TuneOutCoordinator {
|
|
|
1335
1417
|
this.pendingWakes.set(key, { count: 1, timer });
|
|
1336
1418
|
}
|
|
1337
1419
|
|
|
1420
|
+
private cancelPastBudget(serverId: string, channelId: string, maxWakes: number, from: 'wake' | 'start' = 'wake'): void {
|
|
1421
|
+
const key = this.key(serverId, channelId);
|
|
1422
|
+
const existing = this.expiryTimers.get(key);
|
|
1423
|
+
if (existing) clearTimeout(existing);
|
|
1424
|
+
const end = (first: boolean): void => {
|
|
1425
|
+
try {
|
|
1426
|
+
this.expiryTimers.set(key, setTimeout(() => end(false), EXPIRY_RETRY_MS));
|
|
1427
|
+
if (!this.cancel(serverId, channelId, 'tune-out-max-wakes', `wake budget exhausted (${maxWakes})`).ok) {
|
|
1428
|
+
this.clearTimers(serverId, channelId);
|
|
1429
|
+
}
|
|
1430
|
+
} catch (err) {
|
|
1431
|
+
if (first && from === 'start') {
|
|
1432
|
+
if (this.channelRegistry.getTuneOutState(serverId, channelId)) {
|
|
1433
|
+
console.error(`[tune-out] ${this.shown(serverId, channelId)} was past its wake budget at start, and its cancel failed, so it stays tuned out and its cancel is tried again in ${EXPIRY_RETRY_MS / 1000} s:`, err);
|
|
1434
|
+
} else {
|
|
1435
|
+
console.error(`[tune-out] ${this.shown(serverId, channelId)} was past its wake budget at start and its cancel ended it, but a step after the end failed:`, err);
|
|
1436
|
+
}
|
|
1437
|
+
} else {
|
|
1438
|
+
this.timerFailed('wake', serverId, channelId, err);
|
|
1439
|
+
}
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
end(true);
|
|
1443
|
+
}
|
|
1444
|
+
|
|
1338
1445
|
/** One coalesced wake: counted durably against the epoch that holds the channel. */
|
|
1339
1446
|
private fireWake(serverId: string, channelId: string, count: number): void {
|
|
1340
1447
|
if (this.channelRegistry.getTuneOutState(serverId, channelId)) {
|
|
@@ -1342,12 +1449,7 @@ export class TuneOutCoordinator {
|
|
|
1342
1449
|
const counted = this.channelRegistry.recordTuneOutWake(serverId, channelId);
|
|
1343
1450
|
if (!counted) return; // cancelled inside the window
|
|
1344
1451
|
if (counted.wakeCount > counted.params.maxWakes) {
|
|
1345
|
-
this.
|
|
1346
|
-
serverId,
|
|
1347
|
-
channelId,
|
|
1348
|
-
'tune-out-max-wakes',
|
|
1349
|
-
`wake budget exhausted (${counted.params.maxWakes})`,
|
|
1350
|
-
);
|
|
1452
|
+
this.cancelPastBudget(serverId, channelId, counted.params.maxWakes);
|
|
1351
1453
|
return;
|
|
1352
1454
|
}
|
|
1353
1455
|
this.invokeSubconscious(serverId, channelId, 'wake', count);
|
package/src/tune-out/tools.ts
CHANGED
|
@@ -14,7 +14,7 @@ import type { StoredMessage } from '@animalabs/context-manager';
|
|
|
14
14
|
* never leak bare prose to the default locus.
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
|
-
import type { ToolDefinition } from '../types/index.js';
|
|
17
|
+
import type { AgentConfig, ToolDefinition } from '../types/index.js';
|
|
18
18
|
|
|
19
19
|
export const SUBCONSCIOUS_TOOL_NAMES = [
|
|
20
20
|
'deliver_summary',
|
|
@@ -143,6 +143,17 @@ interface SubconsciousConfigBase {
|
|
|
143
143
|
* shared prefix).
|
|
144
144
|
*/
|
|
145
145
|
allowChannelSpeech?: boolean;
|
|
146
|
+
/** The reader's model's context window, as AgentConfig.physicalWindowTokens,
|
|
147
|
+
* in place of the primary's. Left out or null, a reader on the primary's
|
|
148
|
+
* model has the primary's; on another model, the persistent reader has
|
|
149
|
+
* none and a fork has the primary's. */
|
|
150
|
+
physicalWindowTokens?: AgentConfig['physicalWindowTokens'];
|
|
151
|
+
/** The reader's model's thinking binding, as AgentConfig.thinkingBinding,
|
|
152
|
+
* in place of the primary's. Left out, null or false, a reader on the
|
|
153
|
+
* primary's model has the primary's; on another model, the persistent
|
|
154
|
+
* reader has none and a fork has the primary's. `{ binds: false }` gives
|
|
155
|
+
* a reader none. */
|
|
156
|
+
thinkingBinding?: AgentConfig['thinkingBinding'];
|
|
146
157
|
}
|
|
147
158
|
|
|
148
159
|
export interface PersistentReaderConfig extends SubconsciousConfigBase {
|
|
@@ -156,7 +167,8 @@ export interface PersistentReaderConfig extends SubconsciousConfigBase {
|
|
|
156
167
|
/** Model id; defaults to the primary agent's (same-model side-process).
|
|
157
168
|
* Another id, compared exactly, makes the subconscious leave out the
|
|
158
169
|
* fields that describe the primary's model, its window and its thinking
|
|
159
|
-
* binding (SUBCONSCIOUS_MODEL_FIELDS);
|
|
170
|
+
* binding (SUBCONSCIOUS_MODEL_FIELDS); `physicalWindowTokens` and
|
|
171
|
+
* `thinkingBinding` set its own. */
|
|
160
172
|
model?: string;
|
|
161
173
|
/**
|
|
162
174
|
* The voice/criteria mode block — recipe-side and co-authored with the
|
package/src/types/agent.ts
CHANGED
|
@@ -258,17 +258,10 @@ export interface AgentConfig {
|
|
|
258
258
|
* `thinking-binding-loss` ops alert, once per incident and again if a
|
|
259
259
|
* call drops more. The provider reports drops only on a request that
|
|
260
260
|
* carries the drop control; without it, such a block is refused;
|
|
261
|
-
* - the agent is told
|
|
262
|
-
*
|
|
263
|
-
*
|
|
264
|
-
*
|
|
265
|
-
* fold or other change to an earlier message that takes a reply of the
|
|
266
|
-
* current turn, and, with `dropBlock` set too, when the provider drops
|
|
267
|
-
* blocks it can place as the agent's stored replies or the stream's
|
|
268
|
-
* rounds, once per incident and again when a call drops more. The
|
|
269
|
-
* framework hears compiles through `Agent.onThinkingStrip` from the
|
|
270
|
-
* agent's first stream on, unless a host has set its own, so an agent
|
|
271
|
-
* driven only through the direct API is never told.
|
|
261
|
+
* - the agent is told nothing of reasoning left out or dropped: no line
|
|
262
|
+
* about the mechanics of its own memory or reasoning is put in its
|
|
263
|
+
* context. Hosts can observe compiles' strips through
|
|
264
|
+
* `Agent.onThinkingStrip`;
|
|
272
265
|
* The record and the notes
|
|
273
266
|
* vouch for requests as agent-framework hands them to membrane, so a
|
|
274
267
|
* membrane `beforeRequest` hook that changes a request must change every
|
|
@@ -539,7 +532,8 @@ export interface InferenceOptions {
|
|
|
539
532
|
* `supportsInjectedMessages` is what the stream declared with the round's
|
|
540
533
|
* tool calls (membrane's `ToolContext.supportsInjectedMessages`): whether it
|
|
541
534
|
* can carry messages injected with that round's results. It's absent when
|
|
542
|
-
* the stream doesn't say, as
|
|
535
|
+
* the stream doesn't say, which reads as a stream that can't, and when there
|
|
536
|
+
* is no stream, as for the direct API.
|
|
543
537
|
*/
|
|
544
538
|
export type AgentState =
|
|
545
539
|
| { status: 'idle' }
|
package/src/types/framework.ts
CHANGED
|
@@ -6,6 +6,7 @@ import type { ProcessEvent } from './events.js';
|
|
|
6
6
|
import type { McplServerConfig, InferenceRoutingPolicy } from '../mcpl/types.js';
|
|
7
7
|
import type { ConversationRouterConfig } from '../mcpl/conversation-router.js';
|
|
8
8
|
import type { GateOptions } from '../gate/types.js';
|
|
9
|
+
import type { InferenceBoundsOptions } from '../inference-bounds.js';
|
|
9
10
|
|
|
10
11
|
// Re-export trace types
|
|
11
12
|
export type { TraceEvent, TraceEventListener } from './trace.js';
|
|
@@ -130,12 +131,12 @@ export interface FrameworkConfig {
|
|
|
130
131
|
*/
|
|
131
132
|
announceOrphans?: boolean;
|
|
132
133
|
/**
|
|
133
|
-
*
|
|
134
|
-
*
|
|
135
|
-
*
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
* `
|
|
134
|
+
* With a `derivationNotice`, after this many derivations of one kind from
|
|
135
|
+
* one agent, the line it's given is a count line every so many instead of
|
|
136
|
+
* a line per fork, so a reader loop that forks a hundred times a session
|
|
137
|
+
* doesn't fill its source's context with stamps; every derivation still
|
|
138
|
+
* emits its `dendrite:agent-created` trace. Default 10; 0 never collapses.
|
|
139
|
+
* Without a `derivationNotice` the source is told nothing.
|
|
139
140
|
*/
|
|
140
141
|
collapseDerivationNoticesAfter?: number;
|
|
141
142
|
/**
|
|
@@ -151,9 +152,11 @@ export interface FrameworkConfig {
|
|
|
151
152
|
beforeDerive?: (spec: import('../dendrite/types.js').AgentSpec) => void | { refuse: string };
|
|
152
153
|
/**
|
|
153
154
|
* 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
|
|
155
|
-
* return the text to add, or
|
|
156
|
-
*
|
|
155
|
+
* record, the agent it was derived from and the framework's own line,
|
|
156
|
+
* return the text to add (that line, or the host's own), or null to add
|
|
157
|
+
* nothing. Absent, nothing is added: an agent isn't told of derivations
|
|
158
|
+
* unless the host asks for it. The `dendrite:agent-created` trace is
|
|
159
|
+
* emitted either way.
|
|
157
160
|
*/
|
|
158
161
|
derivationNotice?: (record: import('../dendrite/types.js').AgentRecord, from: string, defaultText: string) => string | null;
|
|
159
162
|
};
|
|
@@ -299,6 +302,30 @@ export interface FrameworkConfig {
|
|
|
299
302
|
/** Custom error policy */
|
|
300
303
|
errorPolicy?: ErrorPolicy;
|
|
301
304
|
|
|
305
|
+
/**
|
|
306
|
+
* How long a model call may go without progress before it is cut. A
|
|
307
|
+
* resident's streamed call, and an MCPL server's, gets membrane's progress
|
|
308
|
+
* bounds where its adapter's stream reports content (Anthropic, Bedrock),
|
|
309
|
+
* and on every adapter a flat bound from its last provider event: the
|
|
310
|
+
* larger of a setup term (the larger of `beforeContentMs` and
|
|
311
|
+
* `betweenEventsMs`) plus the request's `maxTokens` at
|
|
312
|
+
* `thinkTokensPerSecond`, and `flatMinimumMs`. An auxiliary call
|
|
313
|
+
* (compression, the reply judge) and an MCPL server's non-streamed call get
|
|
314
|
+
* a bound on the whole call from its send, the setup term plus that
|
|
315
|
+
* allowance (the larger of the setup term and `flatMinimumMs` when it
|
|
316
|
+
* names no `maxTokens`), unless its
|
|
317
|
+
* caller passed a `progressTimeoutMs` of its own; where an adapter's
|
|
318
|
+
* `complete()` streams under the hood, membrane renews it at each parsed
|
|
319
|
+
* provider event. A call a bound cuts fails
|
|
320
|
+
* with type `timeout`, and on an agent's turn DefaultErrorPolicy gives each
|
|
321
|
+
* such call one fresh call, except in a turn that re-arms an exhausted one,
|
|
322
|
+
* where `exhaustedTurnRearmMs` says what a cut does. One setting covers
|
|
323
|
+
* every agent. `false` sets
|
|
324
|
+
* none of these bounds. Default: membrane's PROGRESS_BOUNDS and
|
|
325
|
+
* FLAT_MINIMUM_MS.
|
|
326
|
+
*/
|
|
327
|
+
inferenceBounds?: false | InferenceBoundsOptions;
|
|
328
|
+
|
|
302
329
|
/**
|
|
303
330
|
* Host verdict on a failed inference: "this cannot succeed until later".
|
|
304
331
|
* Consulted before the error policy and before the built-in
|
|
@@ -337,20 +364,32 @@ export interface FrameworkConfig {
|
|
|
337
364
|
* channel isn't sent again: one whose turn was woken for a channel id that
|
|
338
365
|
* names no one registered channel and never found one, and whose request
|
|
339
366
|
* 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
|
|
341
|
-
*
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
* of the re-armed turn's rounds has run
|
|
345
|
-
* failure gets no retries from the error
|
|
346
|
-
* failure the next delay follows, until the
|
|
347
|
-
* any other kind ends the re-arming.
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
367
|
+
* first, as any turn's is, and a turn that ends on a deadline, whether the
|
|
368
|
+
* host set it or membrane marks one as run out (the runtime's or undici's
|
|
369
|
+
* on a sent request, the Anthropic adapter's watchdogs, or the Anthropic
|
|
370
|
+
* SDK's timeout, which covers its connect timeout too), isn't re-armed,
|
|
371
|
+
* save the exception below. Until one of the re-armed turn's rounds has run
|
|
372
|
+
* or had its tool calls cut off, its failure gets no retries from the error
|
|
373
|
+
* policy: on another transient failure the next delay follows, until the
|
|
374
|
+
* list runs out, and a failure of any other kind ends the re-arming. The
|
|
375
|
+
* one exception is a re-armed turn's send that fails with a deadline marked
|
|
376
|
+
* as run out, membrane's or a bound the host sets on the call
|
|
377
|
+
* (`inferenceBounds`), as when a stream stalls. Such a cut counts as a
|
|
378
|
+
* transient failure, so a turn that ends on it is re-armed as for a
|
|
379
|
+
* transient failure, unless it follows another such cut with no transient
|
|
380
|
+
* failure, answered round or message said to the agent between them: two
|
|
381
|
+
* such cuts in a row end the re-arming, and the second gets no retry from
|
|
382
|
+
* the error policy, even in a re-armed turn that got partway. A transient
|
|
383
|
+
* failure or an answered round in any of the agent's turns clears an
|
|
384
|
+
* earlier cut, and so does a message said to the agent, or the agent's end.
|
|
385
|
+
* Every such cut counts, including one the error policy retries or a host's
|
|
386
|
+
* provider hold takes. A wake for the agent while it waits starts its turn
|
|
387
|
+
* at once, with the re-armed trigger merged in. That turn is the
|
|
388
|
+
* wake's: the error policy retries its failures. A completed turn starts the
|
|
389
|
+
* count again, and so does a message said to the agent: not a system message
|
|
390
|
+
* or notice; not an edit, reaction or deletion, unless an edit is the first
|
|
391
|
+
* version of a message the agent sees; not a message its own view leaves
|
|
392
|
+
* out, such as one a tune-out holds from it; and not the agent's own
|
|
354
393
|
* schedule, the heartbeat's tick or reminder, a self-wake timer it set, or a
|
|
355
394
|
* script's wake. A notice a module or an MCPL server stores as a message
|
|
356
395
|
* counts. A message in the residents' shared slot counts for each resident
|
|
@@ -558,7 +597,10 @@ export interface InferencePolicy {
|
|
|
558
597
|
*/
|
|
559
598
|
export interface ErrorPolicy {
|
|
560
599
|
/**
|
|
561
|
-
* Handle an inference error.
|
|
600
|
+
* Handle an inference error. `attempt` is how many times the turn's
|
|
601
|
+
* request has failed before this one; for a deadline expiry (a progress
|
|
602
|
+
* bound's cut, or one membrane marks), how many times that call's request
|
|
603
|
+
* has expired in a row before this one.
|
|
562
604
|
*/
|
|
563
605
|
onInferenceError(
|
|
564
606
|
error: Error,
|