@lloyal-labs/lloyal-agents 1.5.8 → 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 +25 -6
- package/dist/AgentPolicy.d.ts.map +1 -1
- package/dist/AgentPolicy.js +40 -31
- package/dist/AgentPolicy.js.map +1 -1
- package/dist/agent-pool.d.ts +3 -3
- package/dist/agent-pool.d.ts.map +1 -1
- package/dist/agent-pool.js +343 -320
- 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 +2 -1
- 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
|
@@ -115,6 +115,9 @@ function* recoverInline(agent, policy, ctx, store, tw, parentTraceId, events) {
|
|
|
115
115
|
agent.branch.pruneSync();
|
|
116
116
|
return false;
|
|
117
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.
|
|
118
121
|
const { prompt } = ctx.formatChatSync(JSON.stringify([
|
|
119
122
|
{ role: 'system', content: recovery.prompt.system },
|
|
120
123
|
{ role: 'user', content: recovery.prompt.user },
|
|
@@ -122,23 +125,14 @@ function* recoverInline(agent, policy, ctx, store, tw, parentTraceId, events) {
|
|
|
122
125
|
const sep = ctx.getTurnSeparator();
|
|
123
126
|
const delta = ctx.tokenizeSync(prompt, false);
|
|
124
127
|
const tokens = [...sep, ...delta];
|
|
125
|
-
//
|
|
126
|
-
const pressure = new ContextPressure(ctx);
|
|
127
|
-
if (pressure.remaining < tokens.length) {
|
|
128
|
-
if (!agent.branch.disposed)
|
|
129
|
-
agent.branch.pruneSync();
|
|
130
|
-
return false;
|
|
131
|
-
}
|
|
132
|
-
// Eager report grammar
|
|
128
|
+
// Eager report grammar — forces { result: string } output
|
|
133
129
|
const reportGrammar = yield* (0, effection_1.call)(() => ctx.jsonSchemaToGrammar(JSON.stringify({
|
|
134
130
|
type: 'object',
|
|
135
131
|
properties: { result: { type: 'string' } },
|
|
136
132
|
required: ['result'],
|
|
137
133
|
})));
|
|
138
|
-
// Recovery runs in its own scope — if decode fails
|
|
139
|
-
// the scope tears down cleanly
|
|
140
|
-
// Mirrors the old prepare()-based recovery which used try/catch around
|
|
141
|
-
// a Resource with its own ensure().
|
|
134
|
+
// Recovery runs in its own scope — if prefill or decode fails
|
|
135
|
+
// (KV exhaustion), the scope tears down cleanly.
|
|
142
136
|
let reported = false;
|
|
143
137
|
try {
|
|
144
138
|
yield* (0, effection_1.scoped)(function* () {
|
|
@@ -149,7 +143,6 @@ function* recoverInline(agent, policy, ctx, store, tw, parentTraceId, events) {
|
|
|
149
143
|
type: 'branch:prefill', branchHandle: agent.id,
|
|
150
144
|
tokenCount: tokens.length, role: 'recovery',
|
|
151
145
|
});
|
|
152
|
-
yield* events.send({ type: 'agent:spawn', agentId: agent.id, parentAgentId: agent.parentId });
|
|
153
146
|
// Single-agent produce/commit loop
|
|
154
147
|
let output = '';
|
|
155
148
|
let tokenCount = 0;
|
|
@@ -171,8 +164,8 @@ function* recoverInline(agent, policy, ctx, store, tw, parentTraceId, events) {
|
|
|
171
164
|
}
|
|
172
165
|
});
|
|
173
166
|
}
|
|
174
|
-
catch { /* decode failure or malformed JSON — non-fatal
|
|
175
|
-
// Always prune after scope exits (success or
|
|
167
|
+
catch { /* prefill overflow, decode failure, or malformed JSON — non-fatal */ }
|
|
168
|
+
// Always prune after scope exits (success or failure)
|
|
176
169
|
if (!agent.branch.disposed)
|
|
177
170
|
agent.branch.pruneSync();
|
|
178
171
|
// Emit tick so TUI updates pressure percentage after prune
|
|
@@ -206,7 +199,7 @@ function* handleNudge(a, message, tc, ctx, tools) {
|
|
|
206
199
|
const prefillTokens = (0, sdk_2.buildToolResultDelta)(ctx, JSON.stringify(nudgeResult), callId);
|
|
207
200
|
const probe = tools?.get(tc?.name || '')?.probe(nudgeResult) ?? undefined;
|
|
208
201
|
a.resetTurn();
|
|
209
|
-
return { agentId: a.id, prefillTokens, toolName: tc?.name || '', callId, probe };
|
|
202
|
+
return { agentId: a.id, prefillTokens, toolName: tc?.name || '', callId, args: tc?.arguments || '', probe };
|
|
210
203
|
}
|
|
211
204
|
function* handleReport(a, result, tc, terminalTool, pruneOnReport, events) {
|
|
212
205
|
a.reportResult(result, 'report_tool');
|
|
@@ -322,13 +315,13 @@ function useAgentPool(opts) {
|
|
|
322
315
|
return (0, effection_1.resource)(function* (provide) {
|
|
323
316
|
const ctx = yield* context_1.Ctx.expect();
|
|
324
317
|
const store = yield* context_1.Store.expect();
|
|
325
|
-
const
|
|
318
|
+
const poolChannel = (0, effection_1.createChannel)();
|
|
326
319
|
// Bridge for onProgress callbacks — Signal is correct here (external callback).
|
|
327
|
-
// A spawned forwarder drains the bridge into the
|
|
320
|
+
// A spawned forwarder drains the bridge into the poolChannel with proper scope context.
|
|
328
321
|
const progressBridge = (0, effection_1.createSignal)();
|
|
329
322
|
yield* (0, effection_1.spawn)(function* () {
|
|
330
323
|
for (const ev of yield* (0, effection_1.each)(progressBridge)) {
|
|
331
|
-
yield*
|
|
324
|
+
yield* poolChannel.send(ev);
|
|
332
325
|
yield* effection_1.each.next();
|
|
333
326
|
}
|
|
334
327
|
});
|
|
@@ -421,11 +414,6 @@ function useAgentPool(opts) {
|
|
|
421
414
|
taskSuffixTokens: prefillSetup.map(([, t]) => t.length),
|
|
422
415
|
pressure: { remaining: initPressure.remaining, softLimit: initPressure.softLimit, headroom: initPressure.headroom },
|
|
423
416
|
});
|
|
424
|
-
// Emit spawn events and activate agents
|
|
425
|
-
for (const a of agents) {
|
|
426
|
-
a.transition('active');
|
|
427
|
-
yield* events.send({ type: 'agent:spawn', agentId: a.id, parentAgentId: a.parentId });
|
|
428
|
-
}
|
|
429
417
|
// ── Lazy grammar setup ───────────────────────────────────
|
|
430
418
|
const applyLazyGrammar = (a) => {
|
|
431
419
|
if (a.fmt.grammar && a.fmt.grammarLazy && a.fmt.grammarTriggers.length > 0) {
|
|
@@ -444,324 +432,359 @@ function useAgentPool(opts) {
|
|
|
444
432
|
for (const a of agents)
|
|
445
433
|
applyLazyGrammar(a);
|
|
446
434
|
const agentById = new Map(agents.map(a => [a.id, a]));
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
function*
|
|
453
|
-
|
|
454
|
-
let
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
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 });
|
|
459
449
|
}
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
for (const item of items) {
|
|
466
|
-
const a = agentById.get(item.agentId);
|
|
467
|
-
if (!a || a.status === 'idle')
|
|
468
|
-
continue;
|
|
469
|
-
if (item.prefillTokens.length > headroom) {
|
|
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;
|
|
470
455
|
if (trace) {
|
|
456
|
+
const desc = items.map(s => `${s.toolName}:${s.prefillTokens.length}`).join(', ');
|
|
471
457
|
try {
|
|
472
|
-
process.stderr.write(`[SETTLE]
|
|
458
|
+
process.stderr.write(`[SETTLE] remaining=${settlePressure.remaining} headroom=${headroom} cellsUsed=${settlePressure.cellsUsed} nCtx=${settlePressure.nCtx} items=[${desc}]\n`);
|
|
473
459
|
}
|
|
474
460
|
catch { }
|
|
475
461
|
}
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
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]);
|
|
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')
|
|
468
|
+
continue;
|
|
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 { }
|
|
475
|
+
}
|
|
476
|
+
deferred.push(item);
|
|
477
|
+
continue;
|
|
478
|
+
}
|
|
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
|
+
});
|
|
511
489
|
tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(),
|
|
512
490
|
type: 'branch:prefill', branchHandle: a.id,
|
|
513
|
-
tokenCount:
|
|
491
|
+
tokenCount: item.prefillTokens.length, role: 'toolResult' });
|
|
514
492
|
}
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
}
|
|
538
|
-
const callId = tc.id || `call_${agent.toolCallCount}`;
|
|
539
|
-
agent.incrementToolCalls();
|
|
540
|
-
totalToolCalls++;
|
|
541
|
-
agent.incrementTurns();
|
|
542
|
-
yield* events.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 toolContext = {
|
|
556
|
-
agentId: agent.id, branch: agent.branch,
|
|
557
|
-
onProgress: (p) => {
|
|
558
|
-
progressBridge.send({ type: 'agent:tool_progress', agentId: agent.id, tool: tc.name, filled: p.filled, total: p.total });
|
|
559
|
-
},
|
|
560
|
-
scorer: opts.scorer, explore,
|
|
561
|
-
pressurePercentAvailable: dispatchPressure.percentAvailable,
|
|
562
|
-
};
|
|
563
|
-
try {
|
|
564
|
-
yield* context_1.TraceParent.set(dispatchTraceId);
|
|
565
|
-
yield* context_1.CallingAgent.set(agent);
|
|
566
|
-
const result = yield* (0, effection_1.scoped)(function* () {
|
|
567
|
-
return yield* (0, effection_1.call)(() => tool ? tool.execute(toolArgs, toolContext) : Promise.resolve({ error: `Unknown tool: ${tc.name}` }));
|
|
568
|
-
});
|
|
569
|
-
const postToolPressure = new ContextPressure(ctx, pressureOpts);
|
|
570
|
-
const contextAvailablePercent = postToolPressure.percentAvailable;
|
|
571
|
-
if (result && typeof result === 'object' && !Array.isArray(result)) {
|
|
572
|
-
result._contextAvailablePercent = contextAvailablePercent;
|
|
573
|
-
const resultObj = result;
|
|
574
|
-
if (Array.isArray(resultObj.results)) {
|
|
575
|
-
agent.addNestedResults(resultObj.results.filter((f) => typeof f === 'string'));
|
|
493
|
+
if (prefillPairs.length > 0) {
|
|
494
|
+
if (trace) {
|
|
495
|
+
const total = prefillPairs.reduce((s, [, t]) => s + t.length, 0);
|
|
496
|
+
try {
|
|
497
|
+
process.stderr.write(`[SETTLE] PREFILL ${prefillPairs.length} branches, ${total} tokens, headroom_after=${headroom}\n`);
|
|
498
|
+
}
|
|
499
|
+
catch { }
|
|
500
|
+
}
|
|
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]);
|
|
511
|
+
tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(),
|
|
512
|
+
type: 'branch:prefill', branchHandle: a.id,
|
|
513
|
+
tokenCount: probeTokens.length, role: 'probe', probeText: probe });
|
|
514
|
+
}
|
|
576
515
|
}
|
|
577
|
-
if (
|
|
578
|
-
|
|
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);
|
|
579
523
|
}
|
|
580
524
|
}
|
|
581
|
-
|
|
582
|
-
yield* events.send({ type: 'agent:tool_result', agentId: agent.id, tool: tc.name, result: resultStr, contextAvailablePercent });
|
|
583
|
-
const prefillTokens = (0, sdk_2.buildToolResultDelta)(ctx, resultStr, callId);
|
|
584
|
-
const probe = tool?.probe(result) ?? undefined;
|
|
585
|
-
results.push({ agentId: agent.id, prefillTokens, toolName: tc.name, callId, probe });
|
|
586
|
-
tw.write({ traceId: tw.nextId(), parentTraceId: dispatchTraceId, ts: performance.now(),
|
|
587
|
-
type: 'tool:result', agentId: agent.id, tool: tc.name,
|
|
588
|
-
result, prefillTokenCount: prefillTokens.length,
|
|
589
|
-
durationMs: performance.now() - toolT0 });
|
|
525
|
+
return deferred;
|
|
590
526
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
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;
|
|
532
|
+
try {
|
|
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 });
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
return results;
|
|
633
604
|
}
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
}
|
|
648
|
-
|
|
649
|
-
const
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
continue;
|
|
654
|
-
case 'idle':
|
|
655
|
-
yield* handleIdleDrop(a, action.reason, events, tw, poolScope.traceId);
|
|
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 { }
|
|
618
|
+
}
|
|
619
|
+
const entries = [];
|
|
620
|
+
const toolCalls = [];
|
|
621
|
+
const nudges = [];
|
|
622
|
+
for (const a of agents) {
|
|
623
|
+
if (a.status !== 'active')
|
|
656
624
|
continue;
|
|
657
|
-
|
|
658
|
-
|
|
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';
|
|
659
631
|
tw.write({ traceId: tw.nextId(), parentTraceId: poolScope.traceId, ts: performance.now(),
|
|
660
|
-
type: 'pool:
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
yield*
|
|
664
|
-
totalToolCalls++;
|
|
665
|
-
continue;
|
|
666
|
-
case 'tool_call':
|
|
667
|
-
a.transition('awaiting_tool');
|
|
668
|
-
toolCalls.push({ agent: a, tc: action.tc });
|
|
669
|
-
a.resetTurn();
|
|
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);
|
|
670
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
|
+
}
|
|
671
689
|
}
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
}
|
|
712
|
-
// -- Phase 4: DISPATCH
|
|
713
|
-
const dispatched = yield* dispatch(toolCalls);
|
|
714
|
-
// Deferred + new dispatch results → next tick's SETTLE
|
|
715
|
-
pendingSettled = [...deferred, ...dispatched];
|
|
716
|
-
// -- Termination + recovery
|
|
717
|
-
if (agents.every(a => a.status === 'idle' || a.status === 'disposed')) {
|
|
718
|
-
if (!recoveryAttempted) {
|
|
719
|
-
recoveryAttempted = true;
|
|
720
|
-
// Recover any idle agents that weren't handled by inline recovery
|
|
721
|
-
// (e.g., killed by max_turns, time budget, or free_text_stop)
|
|
722
|
-
for (const a of agents) {
|
|
723
|
-
if (a.status === 'idle' && !a.result && !a.branch.disposed) {
|
|
724
|
-
yield* recoverInline(a, policy, ctx, store, tw, poolScope.traceId, events);
|
|
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 });
|
|
696
|
+
}
|
|
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);
|
|
712
|
+
}
|
|
713
|
+
}
|
|
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
|
+
}
|
|
728
|
+
}
|
|
725
729
|
}
|
|
730
|
+
break;
|
|
726
731
|
}
|
|
727
732
|
}
|
|
728
|
-
|
|
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);
|
|
729
768
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
parentAgentId: a.parentId,
|
|
750
|
-
branch: a.branch,
|
|
751
|
-
result: a.result,
|
|
752
|
-
toolCallCount: a.toolCallCount,
|
|
753
|
-
tokenCount: a.tokenCount,
|
|
754
|
-
ppl: a.branch.disposed ? 0 : a.branch.perplexity,
|
|
755
|
-
samplingPpl: a.branch.disposed ? 0 : a.branch.samplingPerplexity,
|
|
756
|
-
trace: trace ? a.traceBuffer : undefined,
|
|
757
|
-
nestedResults: [...a.nestedResults],
|
|
758
|
-
})),
|
|
759
|
-
totalTokens: agents.reduce((s, a) => s + a.tokenCount, 0),
|
|
760
|
-
totalToolCalls,
|
|
761
|
-
steps,
|
|
762
|
-
counters,
|
|
763
|
-
};
|
|
764
|
-
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);
|
|
765
788
|
});
|
|
766
789
|
}
|
|
767
790
|
//# sourceMappingURL=agent-pool.js.map
|