@lloyal-labs/lloyal-agents 1.5.6 → 1.7.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/Agent.d.ts +34 -1
- package/dist/Agent.d.ts.map +1 -1
- package/dist/Agent.js +65 -1
- package/dist/Agent.js.map +1 -1
- package/dist/AgentPolicy.d.ts +51 -8
- package/dist/AgentPolicy.d.ts.map +1 -1
- package/dist/AgentPolicy.js +105 -63
- package/dist/AgentPolicy.js.map +1 -1
- package/dist/Tool.d.ts +5 -7
- package/dist/Tool.d.ts.map +1 -1
- package/dist/Tool.js +5 -7
- package/dist/Tool.js.map +1 -1
- package/dist/agent-pool.d.ts +9 -3
- package/dist/agent-pool.d.ts.map +1 -1
- package/dist/agent-pool.js +446 -407
- package/dist/agent-pool.js.map +1 -1
- package/dist/combinators.d.ts +29 -0
- package/dist/combinators.d.ts.map +1 -0
- package/dist/combinators.js +37 -0
- package/dist/combinators.js.map +1 -0
- package/dist/create-agent-pool.d.ts +78 -0
- package/dist/create-agent-pool.d.ts.map +1 -0
- package/dist/create-agent-pool.js +60 -0
- package/dist/create-agent-pool.js.map +1 -0
- package/dist/index.d.ts +6 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -8
- package/dist/index.js.map +1 -1
- package/dist/source.d.ts.map +1 -1
- package/dist/source.js.map +1 -1
- package/dist/trace-types.d.ts +4 -2
- package/dist/trace-types.d.ts.map +1 -1
- package/dist/trace-writer.d.ts +4 -1
- package/dist/trace-writer.d.ts.map +1 -1
- package/dist/trace-writer.js +6 -2
- package/dist/trace-writer.js.map +1 -1
- package/dist/types.d.ts +9 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/use-agent.d.ts +92 -0
- package/dist/use-agent.d.ts.map +1 -0
- package/dist/use-agent.js +131 -0
- package/dist/use-agent.js.map +1 -0
- package/package.json +2 -2
- package/dist/generate.d.ts +0 -77
- package/dist/generate.d.ts.map +0 -1
- package/dist/generate.js +0 -166
- package/dist/generate.js.map +0 -1
- package/dist/run-agents.d.ts +0 -39
- package/dist/run-agents.d.ts.map +0 -1
- package/dist/run-agents.js +0 -46
- package/dist/run-agents.js.map +0 -1
- package/dist/spawn-agents.d.ts +0 -104
- package/dist/spawn-agents.d.ts.map +0 -1
- package/dist/spawn-agents.js +0 -255
- package/dist/spawn-agents.js.map +0 -1
package/dist/agent-pool.js
CHANGED
|
@@ -7,12 +7,17 @@ const sdk_1 = require("@lloyal-labs/sdk");
|
|
|
7
7
|
const context_1 = require("./context");
|
|
8
8
|
const sdk_2 = require("@lloyal-labs/sdk");
|
|
9
9
|
const trace_scope_1 = require("./trace-scope");
|
|
10
|
-
const generate_1 = require("./generate");
|
|
11
10
|
const Agent_1 = require("./Agent");
|
|
12
11
|
const AgentPolicy_1 = require("./AgentPolicy");
|
|
13
12
|
/**
|
|
14
13
|
* Immutable KV budget snapshot for one tick of the agent loop
|
|
15
14
|
*
|
|
15
|
+
* Frozen at phase boundaries (PRODUCE, SETTLE, DISPATCH) so that all
|
|
16
|
+
* decisions within a phase are evaluated against the same baseline.
|
|
17
|
+
* Without this, items processed earlier in a loop would see different
|
|
18
|
+
* pressure than items processed later — making reject/nudge/kill
|
|
19
|
+
* decisions order-dependent and nondeterministic.
|
|
20
|
+
*
|
|
16
21
|
* Created from `SessionContext._storeKvPressure()` which returns
|
|
17
22
|
* `{ nCtx, cellsUsed, remaining }` where `remaining = nCtx - cellsUsed`.
|
|
18
23
|
* `cellsUsed` tracks unique KV cells per branch — incremented on
|
|
@@ -93,6 +98,119 @@ class ContextPressure {
|
|
|
93
98
|
}
|
|
94
99
|
}
|
|
95
100
|
exports.ContextPressure = ContextPressure;
|
|
101
|
+
/**
|
|
102
|
+
* Inline recovery for a single killed agent (trailing stop).
|
|
103
|
+
*
|
|
104
|
+
* Prefills the extraction prompt into the agent's own branch, sets eager
|
|
105
|
+
* report grammar, generates to stop token, parses JSON, reports result,
|
|
106
|
+
* and prunes the branch — all before the tick loop continues. The freed
|
|
107
|
+
* KV lets remaining agents keep researching.
|
|
108
|
+
*
|
|
109
|
+
* Returns true if the agent reported findings.
|
|
110
|
+
*/
|
|
111
|
+
function* recoverInline(agent, policy, ctx, store, tw, parentTraceId, events) {
|
|
112
|
+
const recovery = policy.onRecovery?.(agent);
|
|
113
|
+
if (!recovery || recovery.type === 'skip') {
|
|
114
|
+
if (!agent.branch.disposed)
|
|
115
|
+
agent.branch.pruneSync();
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
// Build the nudge prompt — a minimal turn injection that triggers
|
|
119
|
+
// report behavior. The agent's KV already contains the full
|
|
120
|
+
// conversation context; the prompt is just a nudge.
|
|
121
|
+
const { prompt } = ctx.formatChatSync(JSON.stringify([
|
|
122
|
+
{ role: 'system', content: recovery.prompt.system },
|
|
123
|
+
{ role: 'user', content: recovery.prompt.user },
|
|
124
|
+
]), { enableThinking: false });
|
|
125
|
+
const sep = ctx.getTurnSeparator();
|
|
126
|
+
const delta = ctx.tokenizeSync(prompt, false);
|
|
127
|
+
const tokens = [...sep, ...delta];
|
|
128
|
+
// Eager report grammar — forces { result: string } output
|
|
129
|
+
const reportGrammar = yield* (0, effection_1.call)(() => ctx.jsonSchemaToGrammar(JSON.stringify({
|
|
130
|
+
type: 'object',
|
|
131
|
+
properties: { result: { type: 'string' } },
|
|
132
|
+
required: ['result'],
|
|
133
|
+
})));
|
|
134
|
+
// Recovery runs in its own scope — if prefill or decode fails
|
|
135
|
+
// (KV exhaustion), the scope tears down cleanly.
|
|
136
|
+
let reported = false;
|
|
137
|
+
try {
|
|
138
|
+
yield* (0, effection_1.scoped)(function* () {
|
|
139
|
+
yield* (0, effection_1.call)(() => store.prefill([[agent.branch, tokens]]));
|
|
140
|
+
agent.branch.setGrammar(reportGrammar);
|
|
141
|
+
tw.write({
|
|
142
|
+
traceId: tw.nextId(), parentTraceId, ts: performance.now(),
|
|
143
|
+
type: 'branch:prefill', branchHandle: agent.id,
|
|
144
|
+
tokenCount: tokens.length, role: 'recovery',
|
|
145
|
+
});
|
|
146
|
+
// Single-agent produce/commit loop
|
|
147
|
+
let output = '';
|
|
148
|
+
let tokenCount = 0;
|
|
149
|
+
for (;;) {
|
|
150
|
+
const { token, text, isStop } = agent.branch.produceSync();
|
|
151
|
+
if (isStop)
|
|
152
|
+
break;
|
|
153
|
+
output += text;
|
|
154
|
+
tokenCount++;
|
|
155
|
+
yield* (0, effection_1.call)(() => store.commit([[agent.branch, token]]));
|
|
156
|
+
yield* events.send({ type: 'agent:produce', agentId: agent.id, text, tokenCount });
|
|
157
|
+
}
|
|
158
|
+
// Parse + report
|
|
159
|
+
const parsed = JSON.parse(output);
|
|
160
|
+
if (parsed?.result) {
|
|
161
|
+
agent.reportResult(parsed.result, 'scratchpad');
|
|
162
|
+
yield* events.send({ type: 'agent:report', agentId: agent.id, result: agent.result });
|
|
163
|
+
reported = true;
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
catch { /* prefill overflow, decode failure, or malformed JSON — non-fatal */ }
|
|
168
|
+
// Always prune after scope exits (success or failure)
|
|
169
|
+
if (!agent.branch.disposed)
|
|
170
|
+
agent.branch.pruneSync();
|
|
171
|
+
// Emit tick so TUI updates pressure percentage after prune
|
|
172
|
+
const postPressure = new ContextPressure(ctx);
|
|
173
|
+
yield* events.send({ type: 'agent:tick', cellsUsed: postPressure.cellsUsed, nCtx: postPressure.nCtx });
|
|
174
|
+
return reported;
|
|
175
|
+
}
|
|
176
|
+
// ── PRODUCE action handlers ─────────────────────────────────────
|
|
177
|
+
// Each handler encapsulates state transitions, events, and trace for one
|
|
178
|
+
// policy action outcome. The PRODUCE switch dispatches to these.
|
|
179
|
+
function* handleFreeTextReport(a, content, events) {
|
|
180
|
+
a.reportResult(content, 'free_text');
|
|
181
|
+
a.transition('idle');
|
|
182
|
+
yield* events.send({ type: 'agent:report', agentId: a.id, result: a.result });
|
|
183
|
+
yield* events.send({ type: 'agent:done', agentId: a.id });
|
|
184
|
+
}
|
|
185
|
+
function* handleIdleDrop(a, reason, events, tw, parentTraceId) {
|
|
186
|
+
a.transition('idle');
|
|
187
|
+
if (reason !== 'free_text_stop') {
|
|
188
|
+
tw.write({ traceId: tw.nextId(), parentTraceId, ts: performance.now(),
|
|
189
|
+
type: 'pool:agentDrop', agentId: a.id,
|
|
190
|
+
reason: reason === 'max_turns' ? 'maxTurns' : 'pressure_softcut' });
|
|
191
|
+
}
|
|
192
|
+
yield* events.send({ type: 'agent:done', agentId: a.id });
|
|
193
|
+
}
|
|
194
|
+
function* handleNudge(a, message, tc, ctx, tools) {
|
|
195
|
+
const callId = tc?.id || `call_${a.toolCallCount}`;
|
|
196
|
+
const nudgeResult = { error: message };
|
|
197
|
+
a.incrementTurns();
|
|
198
|
+
a.transition('awaiting_tool');
|
|
199
|
+
const prefillTokens = (0, sdk_2.buildToolResultDelta)(ctx, JSON.stringify(nudgeResult), callId);
|
|
200
|
+
const probe = tools?.get(tc?.name || '')?.probe(nudgeResult) ?? undefined;
|
|
201
|
+
a.resetTurn();
|
|
202
|
+
return { agentId: a.id, prefillTokens, toolName: tc?.name || '', callId, args: tc?.arguments || '', probe };
|
|
203
|
+
}
|
|
204
|
+
function* handleReport(a, result, tc, terminalTool, pruneOnReport, events) {
|
|
205
|
+
a.reportResult(result, 'report_tool');
|
|
206
|
+
a.transition('idle');
|
|
207
|
+
a.incrementToolCalls();
|
|
208
|
+
yield* events.send({ type: 'agent:tool_call', agentId: a.id, tool: terminalTool, args: tc.arguments });
|
|
209
|
+
yield* events.send({ type: 'agent:report', agentId: a.id, result: a.result });
|
|
210
|
+
yield* events.send({ type: 'agent:done', agentId: a.id });
|
|
211
|
+
if (pruneOnReport && !a.branch.disposed)
|
|
212
|
+
a.branch.pruneSync();
|
|
213
|
+
}
|
|
96
214
|
/**
|
|
97
215
|
* Fork an agent from a parent branch with its own system prompt and task.
|
|
98
216
|
*
|
|
@@ -197,13 +315,13 @@ function useAgentPool(opts) {
|
|
|
197
315
|
return (0, effection_1.resource)(function* (provide) {
|
|
198
316
|
const ctx = yield* context_1.Ctx.expect();
|
|
199
317
|
const store = yield* context_1.Store.expect();
|
|
200
|
-
const
|
|
318
|
+
const poolChannel = (0, effection_1.createChannel)();
|
|
201
319
|
// Bridge for onProgress callbacks — Signal is correct here (external callback).
|
|
202
|
-
// A spawned forwarder drains the bridge into the
|
|
320
|
+
// A spawned forwarder drains the bridge into the poolChannel with proper scope context.
|
|
203
321
|
const progressBridge = (0, effection_1.createSignal)();
|
|
204
322
|
yield* (0, effection_1.spawn)(function* () {
|
|
205
323
|
for (const ev of yield* (0, effection_1.each)(progressBridge)) {
|
|
206
|
-
yield*
|
|
324
|
+
yield* poolChannel.send(ev);
|
|
207
325
|
yield* effection_1.each.next();
|
|
208
326
|
}
|
|
209
327
|
});
|
|
@@ -296,11 +414,6 @@ function useAgentPool(opts) {
|
|
|
296
414
|
taskSuffixTokens: prefillSetup.map(([, t]) => t.length),
|
|
297
415
|
pressure: { remaining: initPressure.remaining, softLimit: initPressure.softLimit, headroom: initPressure.headroom },
|
|
298
416
|
});
|
|
299
|
-
// Emit spawn events and activate agents
|
|
300
|
-
for (const a of agents) {
|
|
301
|
-
a.transition('active');
|
|
302
|
-
yield* events.send({ type: 'agent:spawn', agentId: a.id, parentAgentId: a.parentId });
|
|
303
|
-
}
|
|
304
417
|
// ── Lazy grammar setup ───────────────────────────────────
|
|
305
418
|
const applyLazyGrammar = (a) => {
|
|
306
419
|
if (a.fmt.grammar && a.fmt.grammarLazy && a.fmt.grammarTriggers.length > 0) {
|
|
@@ -318,434 +431,360 @@ function useAgentPool(opts) {
|
|
|
318
431
|
};
|
|
319
432
|
for (const a of agents)
|
|
320
433
|
applyLazyGrammar(a);
|
|
321
|
-
// ── Tool dispatch coordination ───────────────────────────
|
|
322
|
-
// Tool results land in settledBuffer during DISPATCH, drained by SETTLE
|
|
323
|
-
// in the next tick. DISPATCH awaits each tool to completion via
|
|
324
|
-
// scoped() + call() — no concurrent llama_decode possible.
|
|
325
|
-
const settledBuffer = [];
|
|
326
|
-
const dispatchedProbes = new Map();
|
|
327
434
|
const agentById = new Map(agents.map(a => [a.id, a]));
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
catch { }
|
|
343
|
-
}
|
|
344
|
-
const entries = [];
|
|
345
|
-
const toolCalls = [];
|
|
346
|
-
for (const a of agents) {
|
|
347
|
-
if (a.status !== 'active')
|
|
348
|
-
continue;
|
|
349
|
-
const policyExit = policy.shouldExit?.(a, pressure);
|
|
350
|
-
if (policyExit ?? pressure.critical) {
|
|
351
|
-
a.transition('idle');
|
|
352
|
-
const exitReason = pressure.critical ? 'pressure_critical'
|
|
353
|
-
: policyExit ? 'policy_exit'
|
|
354
|
-
: 'pressure_critical';
|
|
355
|
-
tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(),
|
|
356
|
-
type: 'pool:agentDrop', agentId: a.id, reason: exitReason });
|
|
357
|
-
yield* events.send({ type: 'agent:done', agentId: a.id });
|
|
358
|
-
continue;
|
|
435
|
+
// Subscribe BEFORE spawning tick loop — no events missed
|
|
436
|
+
const subscription = yield* poolChannel;
|
|
437
|
+
// Spawn tick loop — runs concurrently with Subscription consumption.
|
|
438
|
+
// scoped() creates an error boundary: if llama_decode fails (KV exhaustion),
|
|
439
|
+
// the scope tears down and the channel closes with whatever results exist.
|
|
440
|
+
yield* (0, effection_1.spawn)(function* () {
|
|
441
|
+
let steps = 0;
|
|
442
|
+
let totalToolCalls = 0;
|
|
443
|
+
const counters = { warmPrefillCalls: 0, warmPrefillBranches: 0 };
|
|
444
|
+
try {
|
|
445
|
+
// Emit spawn events and activate agents
|
|
446
|
+
for (const a of agents) {
|
|
447
|
+
a.transition('active');
|
|
448
|
+
yield* poolChannel.send({ type: 'agent:spawn', agentId: a.id, parentAgentId: a.parentId });
|
|
359
449
|
}
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
a.reportResult(action.content, 'free_text');
|
|
379
|
-
a.transition('idle');
|
|
380
|
-
yield* events.send({ type: 'agent:report', agentId: a.id, result: a.result });
|
|
381
|
-
yield* events.send({ type: 'agent:done', agentId: a.id });
|
|
450
|
+
// ── Phase operations (close over pool scope) ────────────
|
|
451
|
+
/** SETTLE: prefill tool results that fit, defer oversized items for next tick */
|
|
452
|
+
function* settle(items) {
|
|
453
|
+
const settlePressure = new ContextPressure(ctx, pressureOpts);
|
|
454
|
+
let headroom = settlePressure.headroom;
|
|
455
|
+
if (trace) {
|
|
456
|
+
const desc = items.map(s => `${s.toolName}:${s.prefillTokens.length}`).join(', ');
|
|
457
|
+
try {
|
|
458
|
+
process.stderr.write(`[SETTLE] remaining=${settlePressure.remaining} headroom=${headroom} cellsUsed=${settlePressure.cellsUsed} nCtx=${settlePressure.nCtx} items=[${desc}]\n`);
|
|
459
|
+
}
|
|
460
|
+
catch { }
|
|
461
|
+
}
|
|
462
|
+
const prefillPairs = [];
|
|
463
|
+
const settledAgents = [];
|
|
464
|
+
const deferred = [];
|
|
465
|
+
for (const item of items) {
|
|
466
|
+
const a = agentById.get(item.agentId);
|
|
467
|
+
if (!a || a.status === 'idle')
|
|
382
468
|
continue;
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
469
|
+
if (item.prefillTokens.length > headroom) {
|
|
470
|
+
if (trace) {
|
|
471
|
+
try {
|
|
472
|
+
process.stderr.write(`[SETTLE] DEFER ${item.toolName}:${item.prefillTokens.length} > headroom=${headroom}\n`);
|
|
473
|
+
}
|
|
474
|
+
catch { }
|
|
389
475
|
}
|
|
390
|
-
|
|
391
|
-
continue;
|
|
392
|
-
case 'nudge': {
|
|
393
|
-
const tc = parsed.toolCalls[0];
|
|
394
|
-
const callId = tc?.id || `call_${a.toolCallCount}`;
|
|
395
|
-
const nudgeMsg = JSON.stringify({ error: action.message });
|
|
396
|
-
a.incrementTurns();
|
|
397
|
-
a.transition('awaiting_tool');
|
|
398
|
-
const prefillTokens = (0, sdk_2.buildToolResultDelta)(ctx, nudgeMsg, callId);
|
|
399
|
-
settledBuffer.push({ agentId: a.id, prefillTokens, toolName: tc?.name || '', callId });
|
|
400
|
-
a.resetTurn();
|
|
401
|
-
tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(),
|
|
402
|
-
type: 'pool:agentNudge', agentId: a.id, reason: 'pressure_softcut' });
|
|
476
|
+
deferred.push(item);
|
|
403
477
|
continue;
|
|
404
478
|
}
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
a.transition('awaiting_tool');
|
|
419
|
-
toolCalls.push({ agent: a, tc: action.tc });
|
|
420
|
-
a.resetTurn();
|
|
421
|
-
continue;
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
entries.push([a.branch, token]);
|
|
425
|
-
if (trace) {
|
|
426
|
-
const entropy = a.branch.modelEntropy();
|
|
427
|
-
const surprisal = a.branch.modelSurprisal(token);
|
|
428
|
-
a.accumulateTokenWithTrace(text, entropy, surprisal);
|
|
429
|
-
yield* events.send({
|
|
430
|
-
type: 'agent:produce', agentId: a.id, text, tokenCount: a.tokenCount,
|
|
431
|
-
entropy, surprisal,
|
|
432
|
-
});
|
|
433
|
-
}
|
|
434
|
-
else {
|
|
435
|
-
a.accumulateToken(text);
|
|
436
|
-
yield* events.send({ type: 'agent:produce', agentId: a.id, text, tokenCount: a.tokenCount });
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
// -- Phase 2: COMMIT -- batch-decode produced tokens
|
|
440
|
-
if (entries.length > 0) {
|
|
441
|
-
yield* (0, effection_1.call)(() => store.commit(entries));
|
|
442
|
-
steps++;
|
|
443
|
-
const commitPressure = new ContextPressure(ctx, pressureOpts);
|
|
444
|
-
yield* events.send({ type: 'agent:tick', cellsUsed: commitPressure.cellsUsed, nCtx: commitPressure.nCtx });
|
|
445
|
-
}
|
|
446
|
-
// -- Phase 3: SETTLE -- drain settled tool buffer, batch prefill
|
|
447
|
-
const settled = settledBuffer.splice(0);
|
|
448
|
-
if (settled.length > 0) {
|
|
449
|
-
// Fresh snapshot — Phase 2 commits may have advanced positions
|
|
450
|
-
const settlePressure = new ContextPressure(ctx, pressureOpts);
|
|
451
|
-
let headroom = settlePressure.headroom;
|
|
452
|
-
if (trace) {
|
|
453
|
-
const items = settled.map(s => `${s.toolName}:${s.prefillTokens.length}`).join(', ');
|
|
454
|
-
try {
|
|
455
|
-
process.stderr.write(`[SETTLE] remaining=${settlePressure.remaining} headroom=${headroom} cellsUsed=${settlePressure.cellsUsed} nCtx=${settlePressure.nCtx} items=[${items}]\n`);
|
|
479
|
+
prefillPairs.push([a.branch, item.prefillTokens]);
|
|
480
|
+
settledAgents.push(a);
|
|
481
|
+
headroom -= item.prefillTokens.length;
|
|
482
|
+
const postSettle = new ContextPressure(ctx, pressureOpts);
|
|
483
|
+
a.recordToolResult({
|
|
484
|
+
name: item.toolName, args: item.args,
|
|
485
|
+
resultTokenCount: item.prefillTokens.length,
|
|
486
|
+
contextAfterPercent: postSettle.percentAvailable,
|
|
487
|
+
timestamp: performance.now(),
|
|
488
|
+
});
|
|
489
|
+
tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(),
|
|
490
|
+
type: 'branch:prefill', branchHandle: a.id,
|
|
491
|
+
tokenCount: item.prefillTokens.length, role: 'toolResult' });
|
|
456
492
|
}
|
|
457
|
-
|
|
458
|
-
}
|
|
459
|
-
const prefillPairs = [];
|
|
460
|
-
const settledAgents = [];
|
|
461
|
-
for (const item of settled) {
|
|
462
|
-
const a = agentById.get(item.agentId);
|
|
463
|
-
if (!a || a.status === 'idle')
|
|
464
|
-
continue;
|
|
465
|
-
if (item.prefillTokens.length > headroom) {
|
|
493
|
+
if (prefillPairs.length > 0) {
|
|
466
494
|
if (trace) {
|
|
495
|
+
const total = prefillPairs.reduce((s, [, t]) => s + t.length, 0);
|
|
467
496
|
try {
|
|
468
|
-
process.stderr.write(`[SETTLE]
|
|
497
|
+
process.stderr.write(`[SETTLE] PREFILL ${prefillPairs.length} branches, ${total} tokens, headroom_after=${headroom}\n`);
|
|
469
498
|
}
|
|
470
499
|
catch { }
|
|
471
500
|
}
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
501
|
+
yield* (0, effection_1.call)(() => store.prefill(prefillPairs));
|
|
502
|
+
counters.warmPrefillCalls++;
|
|
503
|
+
counters.warmPrefillBranches += prefillPairs.length;
|
|
504
|
+
// Probe prefill from DISPATCH
|
|
505
|
+
const probePairs = [];
|
|
506
|
+
for (const a of settledAgents) {
|
|
507
|
+
const probe = items.find(s => s.agentId === a.id)?.probe;
|
|
508
|
+
if (probe) {
|
|
509
|
+
const probeTokens = ctx.tokenizeSync(probe, false);
|
|
510
|
+
probePairs.push([a.branch, probeTokens]);
|
|
480
511
|
tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(),
|
|
481
|
-
type: '
|
|
482
|
-
|
|
512
|
+
type: 'branch:prefill', branchHandle: a.id,
|
|
513
|
+
tokenCount: probeTokens.length, role: 'probe', probeText: probe });
|
|
483
514
|
}
|
|
484
515
|
}
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
a
|
|
489
|
-
|
|
490
|
-
|
|
516
|
+
if (probePairs.length > 0) {
|
|
517
|
+
yield* (0, effection_1.call)(() => store.prefill(probePairs));
|
|
518
|
+
}
|
|
519
|
+
for (const a of settledAgents) {
|
|
520
|
+
a.transition('active');
|
|
521
|
+
a.resetTurn();
|
|
522
|
+
applyLazyGrammar(a);
|
|
523
|
+
}
|
|
491
524
|
}
|
|
492
|
-
|
|
493
|
-
settledAgents.push(a);
|
|
494
|
-
headroom -= item.prefillTokens.length;
|
|
495
|
-
// Record tool history for policy decisions
|
|
496
|
-
const postSettle = new ContextPressure(ctx, pressureOpts);
|
|
497
|
-
a.recordToolResult({
|
|
498
|
-
name: item.toolName,
|
|
499
|
-
args: item.callId,
|
|
500
|
-
resultTokenCount: item.prefillTokens.length,
|
|
501
|
-
contextAfterPercent: postSettle.percentAvailable,
|
|
502
|
-
timestamp: performance.now(),
|
|
503
|
-
});
|
|
504
|
-
tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(),
|
|
505
|
-
type: 'branch:prefill', branchHandle: a.id,
|
|
506
|
-
tokenCount: item.prefillTokens.length, role: 'toolResult' });
|
|
525
|
+
return deferred;
|
|
507
526
|
}
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
527
|
+
/** DISPATCH: execute tool calls sequentially, return settled items for next tick */
|
|
528
|
+
function* dispatch(calls) {
|
|
529
|
+
const results = [];
|
|
530
|
+
for (const { agent, tc } of calls) {
|
|
531
|
+
let toolArgs;
|
|
511
532
|
try {
|
|
512
|
-
|
|
533
|
+
toolArgs = JSON.parse(tc.arguments);
|
|
534
|
+
}
|
|
535
|
+
catch {
|
|
536
|
+
toolArgs = {};
|
|
537
|
+
}
|
|
538
|
+
const callId = tc.id || `call_${agent.toolCallCount}`;
|
|
539
|
+
agent.incrementToolCalls();
|
|
540
|
+
totalToolCalls++;
|
|
541
|
+
agent.incrementTurns();
|
|
542
|
+
yield* poolChannel.send({ type: 'agent:tool_call', agentId: agent.id, tool: tc.name, args: tc.arguments });
|
|
543
|
+
const tool = tools.get(tc.name);
|
|
544
|
+
const dispatchPressure = new ContextPressure(ctx, pressureOpts);
|
|
545
|
+
const explore = policy.shouldExplore?.(agent, dispatchPressure) ?? true;
|
|
546
|
+
const dispatchTraceId = tw.nextId();
|
|
547
|
+
const toolT0 = performance.now();
|
|
548
|
+
tw.write({
|
|
549
|
+
traceId: dispatchTraceId, parentTraceId: poolScope.traceId, ts: toolT0,
|
|
550
|
+
type: 'tool:dispatch', agentId: agent.id, tool: tc.name,
|
|
551
|
+
toolIndex: toolIndexMap.get(tc.name) ?? -1, toolkitSize,
|
|
552
|
+
args: toolArgs, callId,
|
|
553
|
+
explore, percentAvailable: dispatchPressure.percentAvailable,
|
|
554
|
+
});
|
|
555
|
+
const peerHistory = agents
|
|
556
|
+
.filter(a => a.id !== agent.id)
|
|
557
|
+
.flatMap(a => a.toolHistory);
|
|
558
|
+
const toolContext = {
|
|
559
|
+
agentId: agent.id, branch: agent.branch,
|
|
560
|
+
onProgress: (p) => {
|
|
561
|
+
progressBridge.send({ type: 'agent:tool_progress', agentId: agent.id, tool: tc.name, filled: p.filled, total: p.total });
|
|
562
|
+
},
|
|
563
|
+
scorer: opts.scorer, explore,
|
|
564
|
+
pressurePercentAvailable: dispatchPressure.percentAvailable,
|
|
565
|
+
peerHistory,
|
|
566
|
+
};
|
|
567
|
+
try {
|
|
568
|
+
yield* context_1.TraceParent.set(dispatchTraceId);
|
|
569
|
+
yield* context_1.CallingAgent.set(agent);
|
|
570
|
+
const result = yield* (0, effection_1.scoped)(function* () {
|
|
571
|
+
return yield* (0, effection_1.call)(() => tool ? tool.execute(toolArgs, toolContext) : Promise.resolve({ error: `Unknown tool: ${tc.name}` }));
|
|
572
|
+
});
|
|
573
|
+
const postToolPressure = new ContextPressure(ctx, pressureOpts);
|
|
574
|
+
const contextAvailablePercent = postToolPressure.percentAvailable;
|
|
575
|
+
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
|
576
|
+
result._contextAvailablePercent = contextAvailablePercent;
|
|
577
|
+
const resultObj = result;
|
|
578
|
+
if (Array.isArray(resultObj.results)) {
|
|
579
|
+
agent.addNestedResults(resultObj.results.filter((f) => typeof f === 'string'));
|
|
580
|
+
}
|
|
581
|
+
if (Array.isArray(resultObj.nestedResults)) {
|
|
582
|
+
agent.addNestedResults(resultObj.nestedResults.filter((f) => typeof f === 'string'));
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
const resultStr = JSON.stringify(result);
|
|
586
|
+
yield* poolChannel.send({ type: 'agent:tool_result', agentId: agent.id, tool: tc.name, result: resultStr, contextAvailablePercent });
|
|
587
|
+
const prefillTokens = (0, sdk_2.buildToolResultDelta)(ctx, resultStr, callId);
|
|
588
|
+
const probe = tool?.probe(result) ?? undefined;
|
|
589
|
+
results.push({ agentId: agent.id, prefillTokens, toolName: tc.name, callId, args: tc.arguments, probe });
|
|
590
|
+
tw.write({ traceId: tw.nextId(), parentTraceId: dispatchTraceId, ts: performance.now(),
|
|
591
|
+
type: 'tool:result', agentId: agent.id, tool: tc.name,
|
|
592
|
+
result, prefillTokenCount: prefillTokens.length,
|
|
593
|
+
durationMs: performance.now() - toolT0 });
|
|
594
|
+
}
|
|
595
|
+
catch (err) {
|
|
596
|
+
agent.transition('idle');
|
|
597
|
+
agent.reportResult(`Tool error: ${err.message}`, 'tool_error');
|
|
598
|
+
tw.write({ traceId: tw.nextId(), parentTraceId: dispatchTraceId, ts: performance.now(),
|
|
599
|
+
type: 'tool:error', agentId: agent.id, tool: tc.name,
|
|
600
|
+
error: err.message });
|
|
513
601
|
}
|
|
514
|
-
catch { }
|
|
515
602
|
}
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
603
|
+
return results;
|
|
604
|
+
}
|
|
605
|
+
// ── Four-phase tick loop ─────────────────────────────────
|
|
606
|
+
let pendingSettled = [];
|
|
607
|
+
// ── Four-phase tick loop ─────────────────────────────────
|
|
608
|
+
let recoveryAttempted = false;
|
|
609
|
+
for (;;) {
|
|
610
|
+
// -- Phase 1: PRODUCE -- sample from active agents, collect tool calls
|
|
611
|
+
policy.resetTick?.();
|
|
612
|
+
const pressure = new ContextPressure(ctx, pressureOpts);
|
|
613
|
+
if (trace && (pressure.critical || pressure.headroom < 0)) {
|
|
614
|
+
try {
|
|
615
|
+
process.stderr.write(`[PRODUCE] ${pressure.critical ? 'CRITICAL' : 'SOFT_LIMIT'} remaining=${pressure.remaining} headroom=${pressure.headroom} cellsUsed=${pressure.cellsUsed} nCtx=${pressure.nCtx}\n`);
|
|
616
|
+
}
|
|
617
|
+
catch { }
|
|
528
618
|
}
|
|
529
|
-
|
|
530
|
-
|
|
619
|
+
const entries = [];
|
|
620
|
+
const toolCalls = [];
|
|
621
|
+
const nudges = [];
|
|
622
|
+
for (const a of agents) {
|
|
623
|
+
if (a.status !== 'active')
|
|
624
|
+
continue;
|
|
625
|
+
const policyExit = policy.shouldExit?.(a, pressure);
|
|
626
|
+
if (policyExit ?? pressure.critical) {
|
|
627
|
+
a.transition('idle');
|
|
628
|
+
const exitReason = pressure.critical ? 'pressure_critical'
|
|
629
|
+
: policyExit ? 'policy_exit'
|
|
630
|
+
: 'pressure_critical';
|
|
631
|
+
tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(),
|
|
632
|
+
type: 'pool:agentDrop', agentId: a.id, reason: exitReason });
|
|
633
|
+
yield* poolChannel.send({ type: 'agent:done', agentId: a.id });
|
|
634
|
+
// Trailing stop: extract findings inline, free KV for remaining agents
|
|
635
|
+
yield* recoverInline(a, policy, ctx, store, tw, poolScope.traceId, poolChannel);
|
|
636
|
+
continue;
|
|
637
|
+
}
|
|
638
|
+
const { token, text, isStop } = a.branch.produceSync();
|
|
639
|
+
if (isStop) {
|
|
640
|
+
const parsed = a.finalize(ctx);
|
|
641
|
+
tw.write({
|
|
642
|
+
traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(),
|
|
643
|
+
type: 'agent:turn', agentId: a.id, turn: a.turns,
|
|
644
|
+
rawOutput: a.rawOutput,
|
|
645
|
+
parsedContent: parsed.content || null,
|
|
646
|
+
parsedToolCalls: parsed.toolCalls.map(tc => ({ name: tc.name, arguments: tc.arguments })),
|
|
647
|
+
});
|
|
648
|
+
// Policy decides what to do with the parsed output
|
|
649
|
+
const action = policy.onProduced(a, parsed, pressure, policyConfig);
|
|
650
|
+
switch (action.type) {
|
|
651
|
+
case 'free_text_report':
|
|
652
|
+
yield* handleFreeTextReport(a, action.content, poolChannel);
|
|
653
|
+
continue;
|
|
654
|
+
case 'idle':
|
|
655
|
+
yield* handleIdleDrop(a, action.reason, poolChannel, tw, poolScope.traceId);
|
|
656
|
+
continue;
|
|
657
|
+
case 'nudge':
|
|
658
|
+
nudges.push(yield* handleNudge(a, action.message, parsed.toolCalls[0], ctx, tools));
|
|
659
|
+
tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(),
|
|
660
|
+
type: 'pool:agentNudge', agentId: a.id, reason: 'nudge', message: action.message });
|
|
661
|
+
continue;
|
|
662
|
+
case 'report':
|
|
663
|
+
yield* handleReport(a, action.result, parsed.toolCalls[0], terminalTool, pruneOnReport, poolChannel);
|
|
664
|
+
totalToolCalls++;
|
|
665
|
+
continue;
|
|
666
|
+
case 'tool_call':
|
|
667
|
+
a.transition('awaiting_tool');
|
|
668
|
+
toolCalls.push({ agent: a, tc: action.tc });
|
|
669
|
+
a.resetTurn();
|
|
670
|
+
continue;
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
entries.push([a.branch, token]);
|
|
674
|
+
if (trace) {
|
|
675
|
+
const entropy = a.branch.modelEntropy();
|
|
676
|
+
const surprisal = a.branch.modelSurprisal(token);
|
|
677
|
+
a.accumulateTokenWithTrace(text, entropy, surprisal);
|
|
678
|
+
a.observe(ctx);
|
|
679
|
+
yield* poolChannel.send({
|
|
680
|
+
type: 'agent:produce', agentId: a.id, text, tokenCount: a.tokenCount,
|
|
681
|
+
entropy, surprisal,
|
|
682
|
+
});
|
|
683
|
+
}
|
|
684
|
+
else {
|
|
685
|
+
a.accumulateToken(text);
|
|
686
|
+
a.observe(ctx);
|
|
687
|
+
yield* poolChannel.send({ type: 'agent:produce', agentId: a.id, text, tokenCount: a.tokenCount });
|
|
688
|
+
}
|
|
531
689
|
}
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
690
|
+
// -- Phase 2: COMMIT -- batch-decode produced tokens
|
|
691
|
+
if (entries.length > 0) {
|
|
692
|
+
yield* (0, effection_1.call)(() => store.commit(entries));
|
|
693
|
+
steps++;
|
|
694
|
+
const commitPressure = new ContextPressure(ctx, pressureOpts);
|
|
695
|
+
yield* poolChannel.send({ type: 'agent:tick', cellsUsed: commitPressure.cellsUsed, nCtx: commitPressure.nCtx });
|
|
538
696
|
}
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
const callId = tc.id || `call_${agent.toolCallCount}`;
|
|
555
|
-
agent.incrementToolCalls();
|
|
556
|
-
totalToolCalls++;
|
|
557
|
-
agent.incrementTurns();
|
|
558
|
-
yield* events.send({ type: 'agent:tool_call', agentId: agent.id, tool: tc.name, args: tc.arguments });
|
|
559
|
-
const tool = tools.get(tc.name);
|
|
560
|
-
// Fresh pressure snapshot — SETTLE may have consumed significant KV
|
|
561
|
-
// since the PRODUCE-phase snapshot at tick-top. On 16K context, a
|
|
562
|
-
// single SETTLE pass can drain 12-18% of capacity (3 agents' tool
|
|
563
|
-
// results). Using stale PRODUCE pressure here would keep agents in
|
|
564
|
-
// explore mode past the threshold.
|
|
565
|
-
const dispatchPressure = new ContextPressure(ctx, pressureOpts);
|
|
566
|
-
const explore = policy.shouldExplore?.(agent, dispatchPressure) ?? true;
|
|
567
|
-
const dispatchTraceId = tw.nextId();
|
|
568
|
-
const toolT0 = performance.now();
|
|
569
|
-
tw.write({
|
|
570
|
-
traceId: dispatchTraceId, parentTraceId: poolScope.traceId, ts: toolT0,
|
|
571
|
-
type: 'tool:dispatch', agentId: agent.id, tool: tc.name,
|
|
572
|
-
toolIndex: toolIndexMap.get(tc.name) ?? -1, toolkitSize,
|
|
573
|
-
args: toolArgs, callId,
|
|
574
|
-
explore, percentAvailable: dispatchPressure.percentAvailable,
|
|
575
|
-
});
|
|
576
|
-
const toolContext = {
|
|
577
|
-
agentId: agent.id,
|
|
578
|
-
branch: agent.branch,
|
|
579
|
-
onProgress: (p) => {
|
|
580
|
-
progressBridge.send({ type: 'agent:tool_progress', agentId: agent.id, tool: tc.name, filled: p.filled, total: p.total });
|
|
581
|
-
},
|
|
582
|
-
scorer: opts.scorer,
|
|
583
|
-
explore,
|
|
584
|
-
pressurePercentAvailable: dispatchPressure.percentAvailable,
|
|
585
|
-
};
|
|
586
|
-
try {
|
|
587
|
-
// Set TraceParent + CallingAgent so inner pools inherit lineage
|
|
588
|
-
yield* context_1.TraceParent.set(dispatchTraceId);
|
|
589
|
-
yield* context_1.CallingAgent.set(agent);
|
|
590
|
-
const result = yield* (0, effection_1.scoped)(function* () {
|
|
591
|
-
return yield* (0, effection_1.call)(() => tool ? tool.execute(toolArgs, toolContext) : Promise.resolve({ error: `Unknown tool: ${tc.name}` }));
|
|
592
|
-
});
|
|
593
|
-
// Inject context availability into tool result so agent can make pressure-aware decisions
|
|
594
|
-
const postToolPressure = new ContextPressure(ctx, pressureOpts);
|
|
595
|
-
const contextAvailablePercent = postToolPressure.percentAvailable;
|
|
596
|
-
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
|
597
|
-
result._contextAvailablePercent = contextAvailablePercent;
|
|
598
|
-
// Collect nested results from recursive tool returns
|
|
599
|
-
const resultObj = result;
|
|
600
|
-
if (Array.isArray(resultObj.results)) {
|
|
601
|
-
agent.addNestedResults(resultObj.results.filter((f) => typeof f === 'string'));
|
|
602
|
-
}
|
|
603
|
-
if (Array.isArray(resultObj.nestedResults)) {
|
|
604
|
-
agent.addNestedResults(resultObj.nestedResults.filter((f) => typeof f === 'string'));
|
|
697
|
+
// -- Phase 3: SETTLE (settle what fits, defer what doesn't)
|
|
698
|
+
const toSettle = [...pendingSettled, ...nudges];
|
|
699
|
+
const deferred = toSettle.length > 0 ? yield* settle(toSettle) : [];
|
|
700
|
+
// Stall-breaker: if items are deferred and no active agents remain,
|
|
701
|
+
// sacrifice an awaiting_tool agent to free KV. Without this, agents
|
|
702
|
+
// with oversized results stay awaiting_tool indefinitely — PRODUCE
|
|
703
|
+
// skips them, headroom never recovers, the pool loops forever.
|
|
704
|
+
if (deferred.length > 0 && !agents.some(a => a.status === 'active')) {
|
|
705
|
+
const victim = agents.find(a => a.status === 'awaiting_tool' && !a.branch.disposed);
|
|
706
|
+
if (victim) {
|
|
707
|
+
victim.transition('idle');
|
|
708
|
+
tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(),
|
|
709
|
+
type: 'pool:agentDrop', agentId: victim.id, reason: 'pressure_settle_reject' });
|
|
710
|
+
yield* poolChannel.send({ type: 'agent:done', agentId: victim.id });
|
|
711
|
+
yield* recoverInline(victim, policy, ctx, store, tw, poolScope.traceId, poolChannel);
|
|
605
712
|
}
|
|
606
713
|
}
|
|
607
|
-
|
|
608
|
-
yield*
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
if (
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
catch (err) {
|
|
622
|
-
agent.transition('idle');
|
|
623
|
-
agent.reportResult(`Tool error: ${err.message}`, 'tool_error');
|
|
624
|
-
tw.write({
|
|
625
|
-
traceId: tw.nextId(), parentTraceId: dispatchTraceId, ts: performance.now(),
|
|
626
|
-
type: 'tool:error', agentId: agent.id, tool: tc.name,
|
|
627
|
-
error: err.message,
|
|
628
|
-
});
|
|
629
|
-
}
|
|
630
|
-
}
|
|
631
|
-
// -- Termination
|
|
632
|
-
if (agents.every(a => a.status === 'idle' || a.status === 'disposed'))
|
|
633
|
-
break;
|
|
634
|
-
}
|
|
635
|
-
// ── Idle processing: scratchpad recovery ─────────────────
|
|
636
|
-
// Policy decides per-agent whether to extract findings from killed agents.
|
|
637
|
-
// The pool owns the grammar and fork/generate/parse mechanics.
|
|
638
|
-
// Free KV from agents that already reported — gives room for extraction.
|
|
639
|
-
for (const a of agents) {
|
|
640
|
-
if (a.result && !a.branch.disposed) {
|
|
641
|
-
a.branch.pruneSync();
|
|
642
|
-
}
|
|
643
|
-
}
|
|
644
|
-
// Check if any agent needs recovery before setting up grammar
|
|
645
|
-
const needsRecovery = agents.some(a => a.status === 'idle' && !a.result && !a.branch.disposed &&
|
|
646
|
-
policy.onRecovery?.(a)?.type === 'extract');
|
|
647
|
-
if (needsRecovery) {
|
|
648
|
-
const reportSchema = {
|
|
649
|
-
type: 'object',
|
|
650
|
-
properties: { result: { type: 'string' } },
|
|
651
|
-
required: ['result'],
|
|
652
|
-
};
|
|
653
|
-
const reportGrammar = yield* (0, effection_1.call)(() => ctx.jsonSchemaToGrammar(JSON.stringify(reportSchema)));
|
|
654
|
-
// Cache formatted prompts per unique prompt object
|
|
655
|
-
const promptCache = new Map();
|
|
656
|
-
for (const a of agents) {
|
|
657
|
-
if (a.status !== 'idle' || a.result || a.branch.disposed)
|
|
658
|
-
continue;
|
|
659
|
-
const recovery = policy.onRecovery?.(a);
|
|
660
|
-
if (!recovery || recovery.type === 'skip') {
|
|
661
|
-
if (!a.branch.disposed)
|
|
662
|
-
a.branch.pruneSync();
|
|
663
|
-
continue;
|
|
664
|
-
}
|
|
665
|
-
// Format extraction prompt (cache by system+user key)
|
|
666
|
-
const cacheKey = recovery.prompt.system + '\0' + recovery.prompt.user;
|
|
667
|
-
let extractionPromptStr = promptCache.get(cacheKey);
|
|
668
|
-
if (!extractionPromptStr) {
|
|
669
|
-
const reportMessages = [
|
|
670
|
-
{ role: 'system', content: recovery.prompt.system },
|
|
671
|
-
{ role: 'user', content: recovery.prompt.user },
|
|
672
|
-
];
|
|
673
|
-
const { prompt } = ctx.formatChatSync(JSON.stringify(reportMessages), { enableThinking: false });
|
|
674
|
-
extractionPromptStr = prompt;
|
|
675
|
-
promptCache.set(cacheKey, prompt);
|
|
676
|
-
}
|
|
677
|
-
try {
|
|
678
|
-
yield* events.send({ type: 'agent:spawn', agentId: a.id, parentAgentId: a.parentId });
|
|
679
|
-
const branch = yield* (0, generate_1.prepare)({
|
|
680
|
-
prompt: extractionPromptStr,
|
|
681
|
-
grammar: reportGrammar,
|
|
682
|
-
parent: a.branch,
|
|
683
|
-
});
|
|
684
|
-
try {
|
|
685
|
-
let output = '';
|
|
686
|
-
let tokenCount = 0;
|
|
687
|
-
yield* (0, effection_1.call)(async () => {
|
|
688
|
-
for await (const { text } of branch) {
|
|
689
|
-
output += text;
|
|
690
|
-
tokenCount++;
|
|
714
|
+
// -- Phase 4: DISPATCH
|
|
715
|
+
const dispatched = yield* dispatch(toolCalls);
|
|
716
|
+
// Deferred + new dispatch results → next tick's SETTLE
|
|
717
|
+
pendingSettled = [...deferred, ...dispatched];
|
|
718
|
+
// -- Termination + recovery
|
|
719
|
+
if (agents.every(a => a.status === 'idle' || a.status === 'disposed')) {
|
|
720
|
+
if (!recoveryAttempted) {
|
|
721
|
+
recoveryAttempted = true;
|
|
722
|
+
// Recover any idle agents that weren't handled by inline recovery
|
|
723
|
+
// (e.g., killed by max_turns, time budget, or free_text_stop)
|
|
724
|
+
for (const a of agents) {
|
|
725
|
+
if (a.status === 'idle' && !a.result && !a.branch.disposed) {
|
|
726
|
+
yield* recoverInline(a, policy, ctx, store, tw, poolScope.traceId, poolChannel);
|
|
727
|
+
}
|
|
691
728
|
}
|
|
692
|
-
});
|
|
693
|
-
const tickPressure = new ContextPressure(ctx, pressureOpts);
|
|
694
|
-
yield* events.send({
|
|
695
|
-
type: 'agent:tick', cellsUsed: tickPressure.cellsUsed, nCtx: tickPressure.nCtx,
|
|
696
|
-
});
|
|
697
|
-
const parsed = JSON.parse(output);
|
|
698
|
-
if (parsed?.result) {
|
|
699
|
-
a.reportResult(parsed.result, 'scratchpad');
|
|
700
|
-
yield* events.send({ type: 'agent:report', agentId: a.id, result: a.result });
|
|
701
729
|
}
|
|
730
|
+
break;
|
|
702
731
|
}
|
|
703
|
-
finally {
|
|
704
|
-
if (!branch.disposed)
|
|
705
|
-
branch.pruneSync();
|
|
706
|
-
}
|
|
707
|
-
}
|
|
708
|
-
catch {
|
|
709
|
-
/* extraction failure non-fatal */
|
|
710
732
|
}
|
|
711
|
-
|
|
712
|
-
|
|
733
|
+
// ── Close channel with result — consumers get AgentPoolResult as close value ───────
|
|
734
|
+
// Branch cleanup is handled by each branch's ensure() from setupAgent —
|
|
735
|
+
// when this resource's scope exits, all ensure() callbacks fire.
|
|
736
|
+
tw.write({
|
|
737
|
+
traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(),
|
|
738
|
+
type: 'pool:close',
|
|
739
|
+
agents: agents.map(a => ({
|
|
740
|
+
agentId: a.id, tokenCount: a.tokenCount,
|
|
741
|
+
toolCallCount: a.toolCallCount, result: a.result,
|
|
742
|
+
ppl: a.branch.disposed ? 0 : a.branch.perplexity,
|
|
743
|
+
})),
|
|
744
|
+
totalTokens: agents.reduce((s, a) => s + a.tokenCount, 0),
|
|
745
|
+
steps, durationMs: performance.now() - poolT0,
|
|
746
|
+
});
|
|
747
|
+
poolScope.close();
|
|
748
|
+
const result = {
|
|
749
|
+
agents: agents.map(a => ({
|
|
750
|
+
agentId: a.id,
|
|
751
|
+
parentAgentId: a.parentId,
|
|
752
|
+
branch: a.branch,
|
|
753
|
+
agent: a,
|
|
754
|
+
result: a.result,
|
|
755
|
+
toolCallCount: a.toolCallCount,
|
|
756
|
+
tokenCount: a.tokenCount,
|
|
757
|
+
ppl: a.branch.disposed ? 0 : a.branch.perplexity,
|
|
758
|
+
samplingPpl: a.branch.disposed ? 0 : a.branch.samplingPerplexity,
|
|
759
|
+
trace: trace ? a.traceBuffer : undefined,
|
|
760
|
+
nestedResults: [...a.nestedResults],
|
|
761
|
+
})),
|
|
762
|
+
totalTokens: agents.reduce((s, a) => s + a.tokenCount, 0),
|
|
763
|
+
totalToolCalls,
|
|
764
|
+
steps,
|
|
765
|
+
counters,
|
|
766
|
+
};
|
|
767
|
+
yield* poolChannel.close(result);
|
|
713
768
|
}
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
parentAgentId: a.parentId,
|
|
734
|
-
branch: a.branch,
|
|
735
|
-
result: a.result,
|
|
736
|
-
toolCallCount: a.toolCallCount,
|
|
737
|
-
tokenCount: a.tokenCount,
|
|
738
|
-
ppl: a.branch.disposed ? 0 : a.branch.perplexity,
|
|
739
|
-
samplingPpl: a.branch.disposed ? 0 : a.branch.samplingPerplexity,
|
|
740
|
-
trace: trace ? a.traceBuffer : undefined,
|
|
741
|
-
nestedResults: [...a.nestedResults],
|
|
742
|
-
})),
|
|
743
|
-
totalTokens: agents.reduce((s, a) => s + a.tokenCount, 0),
|
|
744
|
-
totalToolCalls,
|
|
745
|
-
steps,
|
|
746
|
-
counters,
|
|
747
|
-
};
|
|
748
|
-
yield* provide(result);
|
|
769
|
+
catch {
|
|
770
|
+
// KV exhaustion or other decode failure — close with partial results
|
|
771
|
+
poolScope.close();
|
|
772
|
+
const partial = {
|
|
773
|
+
agents: agents.map(a => ({
|
|
774
|
+
agentId: a.id, parentAgentId: a.parentId, branch: a.branch, agent: a,
|
|
775
|
+
result: a.result, toolCallCount: a.toolCallCount, tokenCount: a.tokenCount,
|
|
776
|
+
ppl: a.branch.disposed ? 0 : a.branch.perplexity,
|
|
777
|
+
samplingPpl: a.branch.disposed ? 0 : a.branch.samplingPerplexity,
|
|
778
|
+
trace: trace ? a.traceBuffer : undefined,
|
|
779
|
+
nestedResults: [...a.nestedResults],
|
|
780
|
+
})),
|
|
781
|
+
totalTokens: agents.reduce((s, a) => s + a.tokenCount, 0),
|
|
782
|
+
totalToolCalls, steps, counters,
|
|
783
|
+
};
|
|
784
|
+
yield* poolChannel.close(partial);
|
|
785
|
+
}
|
|
786
|
+
}); // end spawn — tick loop
|
|
787
|
+
yield* provide(subscription);
|
|
749
788
|
});
|
|
750
789
|
}
|
|
751
790
|
//# sourceMappingURL=agent-pool.js.map
|