@animalabs/agent-framework 0.6.7 → 0.6.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/dist/src/agent.d.ts +1 -34
  2. package/dist/src/agent.d.ts.map +1 -1
  3. package/dist/src/agent.js +55 -137
  4. package/dist/src/agent.js.map +1 -1
  5. package/dist/src/framework.d.ts +0 -56
  6. package/dist/src/framework.d.ts.map +1 -1
  7. package/dist/src/framework.js +45 -1733
  8. package/dist/src/framework.js.map +1 -1
  9. package/dist/src/types/agent.d.ts +0 -12
  10. package/dist/src/types/agent.d.ts.map +1 -1
  11. package/dist/test/helpers/mock-membrane.js +1 -1
  12. package/dist/test/helpers/mock-membrane.js.map +1 -1
  13. package/dist/test/mcpl-awareness-barrier.test.js +1 -228
  14. package/dist/test/mcpl-awareness-barrier.test.js.map +1 -1
  15. package/dist/test/refusal-single-call.test.d.ts +2 -0
  16. package/dist/test/refusal-single-call.test.d.ts.map +1 -0
  17. package/dist/test/refusal-single-call.test.js +177 -0
  18. package/dist/test/refusal-single-call.test.js.map +1 -0
  19. package/dist/tsconfig.tsbuildinfo +1 -1
  20. package/package.json +2 -2
  21. package/src/agent.ts +60 -226
  22. package/src/framework.ts +72 -2387
  23. package/src/types/agent.ts +0 -12
  24. package/dist/src/canonical-json.d.ts +0 -2
  25. package/dist/src/canonical-json.d.ts.map +0 -1
  26. package/dist/src/canonical-json.js +0 -23
  27. package/dist/src/canonical-json.js.map +0 -1
  28. package/dist/test/primary-summary-refusal-fallback-integration.test.d.ts +0 -2
  29. package/dist/test/primary-summary-refusal-fallback-integration.test.d.ts.map +0 -1
  30. package/dist/test/primary-summary-refusal-fallback-integration.test.js +0 -1955
  31. package/dist/test/primary-summary-refusal-fallback-integration.test.js.map +0 -1
  32. package/dist/test/primary-summary-refusal-fallback.test.d.ts +0 -2
  33. package/dist/test/primary-summary-refusal-fallback.test.d.ts.map +0 -1
  34. package/dist/test/primary-summary-refusal-fallback.test.js +0 -753
  35. package/dist/test/primary-summary-refusal-fallback.test.js.map +0 -1
  36. package/src/canonical-json.ts +0 -22
@@ -1,6 +1,5 @@
1
1
  import { join } from 'node:path';
2
2
  import { appendFileSync, mkdirSync } from 'node:fs';
3
- import { createHash } from 'node:crypto';
4
3
  import { JsStore } from '@animalabs/chronicle';
5
4
  import { MembraneError } from '@animalabs/membrane';
6
5
  import { ContextManager, PassthroughStrategy } from '@animalabs/context-manager';
@@ -18,7 +17,6 @@ import { ConversationRouter } from './mcpl/conversation-router.js';
18
17
  import { safeSlice } from './safe-slice.js';
19
18
  import { toolResultDataToHistoryString } from './tool-result-history.js';
20
19
  import { splitProseSegments } from './prose-segments.js';
21
- import { canonicalJsonStringify } from './canonical-json.js';
22
20
  /**
23
21
  * Tools whose presence suppresses auto-routing of the surrounding prose,
24
22
  * for two distinct reasons:
@@ -51,12 +49,6 @@ const FRAMEWORK_STATE_ID = 'framework/state';
51
49
  const CONVERSATION_ROUTER_STATE_ID = 'framework/conversation-router';
52
50
  const INFERENCE_LOG_ID = 'framework/inference-log';
53
51
  const PROCESS_LOG_ID = 'framework/process-log';
54
- const PRIMARY_SUMMARY_FALLBACK_STATE_ID = 'framework/primary-summary-fallback';
55
- const PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY = 'primarySummarySettlement';
56
- const PRIMARY_SUMMARY_SETTLEMENT_VERSION = 1;
57
- const PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH = 1024;
58
- const PRIMARY_SUMMARY_SETTLEMENT_MAX_NAME_LENGTH = 256;
59
- const PRIMARY_SUMMARY_SETTLEMENT_MAX_REASON_LENGTH = 256;
60
52
  const TURN_CHECKPOINTS_ID = 'framework/turn-checkpoints'; // legacy single-map layout, read-only fallback
61
53
  const TURN_CHECKPOINTS_TREE_ID = 'framework/turn-checkpoints/tree';
62
54
  /** Maximum number of turn checkpoints to keep per agent. */
@@ -197,8 +189,6 @@ export class AgentFramework {
197
189
  processLoggingPersist;
198
190
  processLoggingBroadcast;
199
191
  activeStreams = new Map();
200
- activeStreamContexts = new Map();
201
- primarySummaryStateLock = Promise.resolve();
202
192
  /** Per-agent output locus pinned for the CURRENT logical turn (see
203
193
  * resolveTurnLocus in driveStream). Lives here — not in driveStream
204
194
  * locals — so the pin survives a context-budget stream restart, which
@@ -424,12 +414,6 @@ export class AgentFramework {
424
414
  // Already registered
425
415
  }
426
416
  }
427
- try {
428
- store.registerState({ id: PRIMARY_SUMMARY_FALLBACK_STATE_ID, strategy: 'snapshot' });
429
- }
430
- catch {
431
- // Already registered
432
- }
433
417
  const discordAwarenessOutboxPath = config.discordAwarenessOutboxPath
434
418
  ?? (config.storePath ? defaultDiscordAwarenessOutboxPath(config.storePath) : undefined);
435
419
  const discordAwarenessOutbox = discordAwarenessOutboxPath
@@ -453,13 +437,12 @@ export class AgentFramework {
453
437
  throw new DiscordAwarenessAccountingError('startup reconciliation', error);
454
438
  }
455
439
  }
440
+ // Restore persisted usage data (if any) from prior session
441
+ framework.restoreUsageState();
456
442
  // Create agents
457
443
  for (const agentConfig of config.agents) {
458
444
  await framework.createAgent(agentConfig);
459
445
  }
460
- // Restore persisted usage data (if any) from prior session
461
- framework.restoreUsageState();
462
- await framework.reconcilePrimarySummaryFallbackStateOnStartup();
463
446
  // Finish any branch-local suppression interrupted after Chronicle switched
464
447
  // branches. This runs before modules, MCPL connections, or inbound traffic.
465
448
  await framework.resumePreparedDiscordSuppressions();
@@ -680,7 +663,7 @@ export class AgentFramework {
680
663
  const queued = [...this.agents.values()].flatMap((agent) => {
681
664
  const cm = agent.getContextManager();
682
665
  const tools = this.getToolsForAgent(agent.name).filter((tool) => agent.canUseTool(tool.name));
683
- cm.setToolDefinitions?.(tools);
666
+ cm.setToolDefinitions(tools);
684
667
  if (cm.isReady())
685
668
  return [];
686
669
  const pending = cm.getPendingWork()?.description;
@@ -1251,1091 +1234,6 @@ export class AgentFramework {
1251
1234
  });
1252
1235
  return { promise, resolve, reject };
1253
1236
  }
1254
- hashJson(value) {
1255
- return createHash('sha256').update(canonicalJsonStringify(value), 'utf8').digest('hex');
1256
- }
1257
- estimateStringTokens(text, overhead = 0) {
1258
- return Math.ceil(Buffer.byteLength(text, 'utf8') / 4) + overhead;
1259
- }
1260
- estimateUnknownTokens(value, overhead = 0) {
1261
- return this.estimateStringTokens(JSON.stringify(value) ?? 'null', overhead);
1262
- }
1263
- estimateRequestInputBoundTokens(request) {
1264
- let total = 96;
1265
- total += this.estimateStringTokens(request.system ?? '', 48);
1266
- for (const message of request.messages) {
1267
- total += 24;
1268
- total += this.estimateStringTokens(message.participant, 8);
1269
- if (message.cacheBreakpoint)
1270
- total += 8;
1271
- for (const block of message.content) {
1272
- switch (block.type) {
1273
- case 'text':
1274
- total += this.estimateStringTokens(block.text, 24);
1275
- break;
1276
- case 'thinking':
1277
- total += this.estimateStringTokens(block.thinking, 32);
1278
- total += block.signature ? this.estimateStringTokens(block.signature, 8) : 0;
1279
- break;
1280
- case 'redacted_thinking':
1281
- total += this.estimateUnknownTokens(block, 32);
1282
- break;
1283
- default:
1284
- total += this.estimateUnknownTokens(block, 32);
1285
- break;
1286
- }
1287
- }
1288
- }
1289
- if (request.tools && request.tools.length > 0) {
1290
- total += 32;
1291
- for (const tool of request.tools) {
1292
- total += this.estimateUnknownTokens({
1293
- name: tool.name,
1294
- description: tool.description,
1295
- inputSchema: tool.inputSchema,
1296
- }, 48);
1297
- }
1298
- }
1299
- total += this.estimateUnknownTokens(request.config, 48);
1300
- if (request.providerParams !== undefined) {
1301
- total += this.estimateUnknownTokens(request.providerParams, 32);
1302
- }
1303
- total += request.promptCaching ? 16 : 0;
1304
- total += request.cacheTtl ? this.estimateStringTokens(request.cacheTtl, 8) : 0;
1305
- total += request.assistantParticipant
1306
- ? this.estimateStringTokens(request.assistantParticipant, 8)
1307
- : 0;
1308
- return total;
1309
- }
1310
- estimateRequestCompleteBoundTokens(request) {
1311
- const reserve = typeof request.config.maxTokens === 'number' && Number.isFinite(request.config.maxTokens)
1312
- ? request.config.maxTokens
1313
- : 0;
1314
- return this.estimateRequestInputBoundTokens(request) + reserve + 96;
1315
- }
1316
- primarySummaryAdmissionBoundTokens(originalRequestInputTokens, expandedRequest, originalProviderInputTokens) {
1317
- const expandedInput = this.estimateRequestInputBoundTokens(expandedRequest);
1318
- const expandedComplete = this.estimateRequestCompleteBoundTokens(expandedRequest);
1319
- if (originalProviderInputTokens === undefined)
1320
- return expandedComplete;
1321
- const reserve = typeof expandedRequest.config.maxTokens === 'number' && Number.isFinite(expandedRequest.config.maxTokens)
1322
- ? expandedRequest.config.maxTokens
1323
- : 0;
1324
- const positiveDelta = Math.max(0, expandedInput - originalRequestInputTokens);
1325
- return Math.max(expandedComplete, originalProviderInputTokens + positiveDelta + reserve + 96);
1326
- }
1327
- readPrimarySummaryFallbackState() {
1328
- const raw = this.store
1329
- ?.getStateJson?.(PRIMARY_SUMMARY_FALLBACK_STATE_ID);
1330
- if (!raw || typeof raw !== 'object')
1331
- return { requests: [] };
1332
- const requests = Array.isArray(raw.requests)
1333
- ? (raw.requests)
1334
- : [];
1335
- return { requests };
1336
- }
1337
- writePrimarySummaryFallbackState(state) {
1338
- this.store.setStateJson(PRIMARY_SUMMARY_FALLBACK_STATE_ID, state);
1339
- }
1340
- async withPrimarySummaryStateLock(work) {
1341
- const previous = this.primarySummaryStateLock;
1342
- let release;
1343
- this.primarySummaryStateLock = new Promise((resolve) => { release = resolve; });
1344
- await previous;
1345
- try {
1346
- return await work();
1347
- }
1348
- finally {
1349
- release();
1350
- }
1351
- }
1352
- async reconcilePrimarySummaryFallbackStateOnStartup() {
1353
- const alerts = [];
1354
- const unsettledRequests = (await this.withPrimarySummaryStateLock(() => this.readPrimarySummaryFallbackState().requests
1355
- .filter((record) => record.finalStatus === 'pending'
1356
- || record.finalStatus === 'refusal'
1357
- || record.fallbackStatus === 'pending')
1358
- .map((record) => structuredClone(record))));
1359
- const resolvedRequestIds = new Set();
1360
- for (const record of unsettledRequests) {
1361
- const result = await this.finalizePrimarySummarySettlementOnStartup(record);
1362
- if (result === 'finalized') {
1363
- resolvedRequestIds.add(record.requestId);
1364
- continue;
1365
- }
1366
- if (result && typeof result === 'object' && 'invalidReason' in result) {
1367
- resolvedRequestIds.add(record.requestId);
1368
- alerts.push({ agentName: record.agentName, requestId: record.requestId, reason: result.invalidReason });
1369
- await this.markPrimarySummaryRequestRestartFailure(record, result.invalidReason);
1370
- }
1371
- }
1372
- await this.withPrimarySummaryStateLock(() => {
1373
- const state = this.readPrimarySummaryFallbackState();
1374
- let changed = false;
1375
- state.requests = state.requests.map((record) => {
1376
- if (resolvedRequestIds.has(record.requestId))
1377
- return record;
1378
- if (record.dispatchKind === 'primary_summary_fallback_retry' && (record.finalStatus === 'pending' || record.finalStatus === 'refusal')) {
1379
- changed = true;
1380
- const reason = 'primary_summary_fallback_unresolved_on_restart';
1381
- alerts.push({ agentName: record.agentName, requestId: record.requestId, reason });
1382
- return {
1383
- ...record,
1384
- finalStatus: 'held',
1385
- fallbackHeldReason: reason,
1386
- };
1387
- }
1388
- if (record.fallbackIntent && record.fallbackStatus === 'pending') {
1389
- changed = true;
1390
- const reason = 'primary_summary_fallback_unresolved_on_restart';
1391
- alerts.push({ agentName: record.agentName, requestId: record.requestId, reason });
1392
- return {
1393
- ...record,
1394
- fallbackStatus: 'held',
1395
- fallbackHeldReason: reason,
1396
- };
1397
- }
1398
- if (record.finalStatus === 'refusal') {
1399
- changed = true;
1400
- const reason = 'primary_summary_request_unresolved_on_restart';
1401
- alerts.push({ agentName: record.agentName, requestId: record.requestId, reason });
1402
- return {
1403
- ...record,
1404
- finalStatus: 'held',
1405
- fallbackStatus: 'held',
1406
- fallbackHeldReason: reason,
1407
- };
1408
- }
1409
- if (record.finalStatus === 'pending') {
1410
- changed = true;
1411
- const reason = 'primary_summary_request_unresolved_on_restart';
1412
- alerts.push({ agentName: record.agentName, requestId: record.requestId, reason });
1413
- return {
1414
- ...record,
1415
- finalStatus: 'timeout_unknown',
1416
- fallbackHeldReason: reason,
1417
- };
1418
- }
1419
- return record;
1420
- });
1421
- if (changed)
1422
- this.writePrimarySummaryFallbackState({ requests: state.requests.slice(-256) });
1423
- });
1424
- for (const alert of alerts) {
1425
- this.opsAlert('refusal-held', alert.agentName, alert.reason, {
1426
- data: { requestId: alert.requestId },
1427
- });
1428
- }
1429
- }
1430
- primarySummaryIdentityKey(identity) {
1431
- return [
1432
- identity.id,
1433
- identity.contentHash,
1434
- identity.carrierHash,
1435
- identity.sourceLeafHash,
1436
- ].join('\u0000');
1437
- }
1438
- primarySummaryProjectionKeys(projection) {
1439
- return projection?.selectedSummaries.map((selection) => this.primarySummaryIdentityKey(selection.identity)) ?? [];
1440
- }
1441
- primarySummaryFallbackConfig(agent) {
1442
- const raw = agent.refusalHandling?.primarySummaryFallback;
1443
- const fallbackBudgetBase = agent.contextBudgetTokens ?? 100_000;
1444
- return {
1445
- enabled: raw?.enabled === true,
1446
- maxNewSummaries: typeof raw?.maxNewSummaries === 'number' && Number.isSafeInteger(raw.maxNewSummaries)
1447
- ? Math.max(0, raw.maxNewSummaries)
1448
- : 4,
1449
- requestBudgetTokens: typeof raw?.requestBudgetTokens === 'number' && Number.isFinite(raw.requestBudgetTokens) && raw.requestBudgetTokens > 0
1450
- ? raw.requestBudgetTokens
1451
- : fallbackBudgetBase + agent.maxTokens,
1452
- };
1453
- }
1454
- latestCompatibleHealthyPrimaryRequest(current, state) {
1455
- for (let i = state.requests.length - 1; i >= 0; i--) {
1456
- const candidate = state.requests[i];
1457
- if (candidate.requestId === current.requestId)
1458
- continue;
1459
- if (candidate.agentName !== current.agentName)
1460
- continue;
1461
- if (candidate.namespace !== current.namespace)
1462
- continue;
1463
- if (candidate.branch.id !== current.branch.id)
1464
- continue;
1465
- if (candidate.branch.generation !== current.branch.generation)
1466
- continue;
1467
- if (candidate.systemHash !== current.systemHash)
1468
- continue;
1469
- if (candidate.modelConfigHash !== current.modelConfigHash)
1470
- continue;
1471
- if (candidate.toolContractHash !== current.toolContractHash)
1472
- continue;
1473
- if (candidate.dispatchKind !== 'primary')
1474
- continue;
1475
- if (candidate.fallbackIntent || candidate.fallbackStatus)
1476
- continue;
1477
- if (candidate.finalStatus !== 'success')
1478
- continue;
1479
- return candidate;
1480
- }
1481
- return null;
1482
- }
1483
- async persistPrimarySummaryRequestRecord(record) {
1484
- await this.withPrimarySummaryStateLock(() => {
1485
- const state = this.readPrimarySummaryFallbackState();
1486
- const requests = state.requests.filter((entry) => entry.requestId !== record.requestId);
1487
- requests.push(record);
1488
- this.writePrimarySummaryFallbackState({ requests: requests.slice(-256) });
1489
- });
1490
- }
1491
- async updatePrimarySummaryRequestRecord(requestId, update) {
1492
- return this.withPrimarySummaryStateLock(() => {
1493
- const state = this.readPrimarySummaryFallbackState();
1494
- const index = state.requests.findIndex((entry) => entry.requestId === requestId);
1495
- if (index < 0)
1496
- return null;
1497
- state.requests[index] = update(state.requests[index]);
1498
- this.writePrimarySummaryFallbackState({ requests: state.requests.slice(-256) });
1499
- return state.requests[index];
1500
- });
1501
- }
1502
- async getPrimarySummaryRequestRecord(requestId) {
1503
- return this.withPrimarySummaryStateLock(() => {
1504
- const state = this.readPrimarySummaryFallbackState();
1505
- return state.requests.find((entry) => entry.requestId === requestId) ?? null;
1506
- });
1507
- }
1508
- primarySummaryContractsMatch(left, right) {
1509
- return left.systemHash === right.systemHash
1510
- && left.modelConfigHash === right.modelConfigHash
1511
- && left.toolContractHash === right.toolContractHash;
1512
- }
1513
- primarySummaryFallbackOutputPhaseHash(blocks) {
1514
- return this.hashJson(blocks);
1515
- }
1516
- primarySummaryFallbackBlockTypes(blocks) {
1517
- return blocks.map((block) => block.type);
1518
- }
1519
- static NON_EXECUTED_TOOL_RESULT_TEXT = '[tool not executed]';
1520
- buildNonExecutedToolResults(blocks) {
1521
- const seenToolUseIds = new Set();
1522
- return blocks
1523
- .filter((block) => block.type === 'tool_use')
1524
- .filter((block) => {
1525
- if (seenToolUseIds.has(block.id))
1526
- return false;
1527
- seenToolUseIds.add(block.id);
1528
- return true;
1529
- })
1530
- .map((block) => ({
1531
- type: 'tool_result',
1532
- toolUseId: block.id,
1533
- content: AgentFramework.NON_EXECUTED_TOOL_RESULT_TEXT,
1534
- isError: true,
1535
- }));
1536
- }
1537
- buildPrimarySummarySettlementId(record, outcome) {
1538
- return `${record.dispatchKind}:${outcome}:terminal:v${PRIMARY_SUMMARY_SETTLEMENT_VERSION}`;
1539
- }
1540
- buildPrimarySummarySettlementMetadata(record, options) {
1541
- const settlementId = options.settlementId ?? this.buildPrimarySummarySettlementId(record, options.outcome);
1542
- return {
1543
- [PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY]: {
1544
- version: PRIMARY_SUMMARY_SETTLEMENT_VERSION,
1545
- requestId: record.requestId,
1546
- settlementId,
1547
- agentName: record.agentName,
1548
- dispatchKind: record.dispatchKind,
1549
- outcome: options.outcome,
1550
- ...(options.stopReason ? { stopReason: options.stopReason } : {}),
1551
- ...(options.holdReason ? { holdReason: options.holdReason } : {}),
1552
- ...(options.providerInputTokens !== undefined
1553
- ? { providerInputTokens: options.providerInputTokens }
1554
- : {}),
1555
- visibleAssistantOutput: options.visibleAssistantOutput,
1556
- executedToolCalls: options.executedToolCalls,
1557
- branch: {
1558
- id: record.branch.id,
1559
- name: record.branch.name,
1560
- generation: record.branch.generation,
1561
- },
1562
- role: options.role,
1563
- kind: options.kind,
1564
- entryIndex: options.entryIndex,
1565
- entryCount: options.entryCount,
1566
- },
1567
- };
1568
- }
1569
- isBoundedNonEmptyString(value, maxLength) {
1570
- return typeof value === 'string' && value.length > 0 && value.length <= maxLength;
1571
- }
1572
- isSafeNonNegativeInteger(value) {
1573
- return typeof value === 'number' && Number.isSafeInteger(value) && value >= 0;
1574
- }
1575
- primarySummarySettlementRequestId(message) {
1576
- const raw = message.metadata?.[PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY];
1577
- if (!raw || typeof raw !== 'object')
1578
- return null;
1579
- const requestId = raw.requestId;
1580
- return typeof requestId === 'string' ? requestId : null;
1581
- }
1582
- parsePrimarySummarySettlementMetadata(message) {
1583
- const raw = message.metadata?.[PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY];
1584
- if (!raw || typeof raw !== 'object')
1585
- return null;
1586
- const candidate = raw;
1587
- if (candidate.version !== PRIMARY_SUMMARY_SETTLEMENT_VERSION ||
1588
- !this.isBoundedNonEmptyString(candidate.requestId, PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH) ||
1589
- !this.isBoundedNonEmptyString(candidate.settlementId, PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH) ||
1590
- !this.isBoundedNonEmptyString(candidate.agentName, PRIMARY_SUMMARY_SETTLEMENT_MAX_NAME_LENGTH) ||
1591
- (candidate.dispatchKind !== 'primary' && candidate.dispatchKind !== 'primary_summary_fallback_retry') ||
1592
- (candidate.outcome !== 'success' && candidate.outcome !== 'held') ||
1593
- typeof candidate.visibleAssistantOutput !== 'boolean' ||
1594
- !this.isSafeNonNegativeInteger(candidate.executedToolCalls) ||
1595
- !this.isSafeNonNegativeInteger(candidate.entryIndex) ||
1596
- !this.isSafeNonNegativeInteger(candidate.entryCount) ||
1597
- (candidate.role !== 'assistant' && candidate.role !== 'user') ||
1598
- (candidate.kind !== 'assistant_output' && candidate.kind !== 'generated_tool_result') ||
1599
- !candidate.branch ||
1600
- !this.isBoundedNonEmptyString(candidate.branch.id, PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH) ||
1601
- !this.isBoundedNonEmptyString(candidate.branch.name, PRIMARY_SUMMARY_SETTLEMENT_MAX_NAME_LENGTH) ||
1602
- !this.isSafeNonNegativeInteger(candidate.branch.generation) ||
1603
- candidate.entryCount < 1 ||
1604
- candidate.entryIndex >= candidate.entryCount ||
1605
- (candidate.stopReason !== undefined
1606
- && !this.isBoundedNonEmptyString(candidate.stopReason, PRIMARY_SUMMARY_SETTLEMENT_MAX_REASON_LENGTH)) ||
1607
- (candidate.holdReason !== undefined
1608
- && !this.isBoundedNonEmptyString(candidate.holdReason, PRIMARY_SUMMARY_SETTLEMENT_MAX_REASON_LENGTH)) ||
1609
- (candidate.providerInputTokens !== undefined
1610
- && !this.isSafeNonNegativeInteger(candidate.providerInputTokens)) ||
1611
- (candidate.outcome === 'held' && candidate.holdReason === undefined) ||
1612
- (candidate.outcome === 'success' && candidate.holdReason !== undefined)) {
1613
- return { invalidReason: 'primary_summary_settlement_metadata_invalid_on_restart' };
1614
- }
1615
- return candidate;
1616
- }
1617
- primarySummarySettlementMetadataConsistent(left, right) {
1618
- return left.version === right.version
1619
- && left.requestId === right.requestId
1620
- && left.settlementId === right.settlementId
1621
- && left.agentName === right.agentName
1622
- && left.dispatchKind === right.dispatchKind
1623
- && left.outcome === right.outcome
1624
- && left.stopReason === right.stopReason
1625
- && left.holdReason === right.holdReason
1626
- && left.providerInputTokens === right.providerInputTokens
1627
- && left.visibleAssistantOutput === right.visibleAssistantOutput
1628
- && left.executedToolCalls === right.executedToolCalls
1629
- && left.branch.id === right.branch.id
1630
- && left.branch.name === right.branch.name
1631
- && left.branch.generation === right.branch.generation
1632
- && left.entryCount === right.entryCount;
1633
- }
1634
- primarySummaryShardEnvelopeConsistent(participant, left, candidateParticipant, right) {
1635
- return participant === candidateParticipant
1636
- && this.primarySummarySettlementMetadataConsistent(left, right)
1637
- && left.role === right.role
1638
- && left.kind === right.kind
1639
- && left.entryIndex === right.entryIndex;
1640
- }
1641
- primarySummaryLogicalSettlementEntries(agent, requestId) {
1642
- const allMessages = agent.getContextManager().getAllMessages()
1643
- .slice()
1644
- .sort((left, right) => left.sequence - right.sequence);
1645
- const entries = [];
1646
- for (let index = 0; index < allMessages.length;) {
1647
- const message = allMessages[index];
1648
- if (this.primarySummarySettlementRequestId(message) !== requestId) {
1649
- index++;
1650
- continue;
1651
- }
1652
- const parsed = this.parsePrimarySummarySettlementMetadata(message);
1653
- if (!parsed) {
1654
- index++;
1655
- continue;
1656
- }
1657
- if ('invalidReason' in parsed)
1658
- return parsed;
1659
- if (message.bodyGroupId === undefined) {
1660
- if (message.shardIndex !== undefined) {
1661
- return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
1662
- }
1663
- entries.push({
1664
- messages: [message],
1665
- settlement: parsed,
1666
- participant: message.participant,
1667
- content: message.content,
1668
- });
1669
- index++;
1670
- continue;
1671
- }
1672
- const groupId = message.bodyGroupId;
1673
- if (!this.isBoundedNonEmptyString(groupId, PRIMARY_SUMMARY_SETTLEMENT_MAX_ID_LENGTH)) {
1674
- return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
1675
- }
1676
- if (index > 0 && allMessages[index - 1]?.bodyGroupId === groupId) {
1677
- return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
1678
- }
1679
- const groupMessages = [];
1680
- while (index < allMessages.length && allMessages[index].bodyGroupId === groupId) {
1681
- groupMessages.push(allMessages[index]);
1682
- index++;
1683
- }
1684
- if (allMessages.slice(index).some((candidate) => candidate.bodyGroupId === groupId)) {
1685
- return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
1686
- }
1687
- const orderedGroupMessages = [];
1688
- let expectedShardIndex = 0;
1689
- for (const groupMessage of groupMessages) {
1690
- if (this.primarySummarySettlementRequestId(groupMessage) !== requestId) {
1691
- return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
1692
- }
1693
- const groupParsed = this.parsePrimarySummarySettlementMetadata(groupMessage);
1694
- if (!groupParsed) {
1695
- return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
1696
- }
1697
- if ('invalidReason' in groupParsed)
1698
- return groupParsed;
1699
- if (!this.primarySummaryShardEnvelopeConsistent(message.participant, parsed, groupMessage.participant, groupParsed)) {
1700
- return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
1701
- }
1702
- if (groupMessage.shardIndex !== expectedShardIndex) {
1703
- return { invalidReason: 'primary_summary_settlement_shard_group_invalid_on_restart' };
1704
- }
1705
- orderedGroupMessages.push(groupMessage);
1706
- expectedShardIndex++;
1707
- }
1708
- entries.push({
1709
- messages: orderedGroupMessages,
1710
- settlement: parsed,
1711
- participant: message.participant,
1712
- content: orderedGroupMessages.flatMap((groupMessage) => groupMessage.content),
1713
- bodyGroupId: groupId,
1714
- });
1715
- }
1716
- return entries;
1717
- }
1718
- primarySummaryContentMatches(left, right) {
1719
- return this.primarySummaryFallbackOutputPhaseHash(left) === this.primarySummaryFallbackOutputPhaseHash(right);
1720
- }
1721
- async persistPrimarySummaryChronicleEntries(agent, entries) {
1722
- const messageIds = [];
1723
- for (const entry of entries) {
1724
- if (entry.content.length === 0)
1725
- continue;
1726
- messageIds.push(agent.getContextManager().addMessage(entry.participant, entry.content, entry.metadata));
1727
- }
1728
- return messageIds;
1729
- }
1730
- async markPrimarySummaryRequestRestartFailure(record, reason) {
1731
- if (record.dispatchKind === 'primary_summary_fallback_retry') {
1732
- const parent = await this.findPrimarySummaryRetryParent(record.requestId);
1733
- await this.updatePrimarySummaryRequestRecord(record.requestId, (entry) => ({
1734
- ...entry,
1735
- finalStatus: 'held',
1736
- fallbackHeldReason: reason,
1737
- }));
1738
- if (parent) {
1739
- await this.updatePrimarySummaryRequestRecord(parent.originalRequestId, (entry) => ({
1740
- ...entry,
1741
- finalStatus: 'held',
1742
- fallbackStatus: 'held',
1743
- fallbackHeldReason: reason,
1744
- }));
1745
- }
1746
- return;
1747
- }
1748
- await this.updatePrimarySummaryRequestRecord(record.requestId, (current) => ({
1749
- ...current,
1750
- finalStatus: 'held',
1751
- fallbackStatus: 'held',
1752
- fallbackHeldReason: reason,
1753
- }));
1754
- }
1755
- async persistPrimarySummaryHeldOutput(agent, requestId, holdReason, options) {
1756
- const record = await this.getPrimarySummaryRequestRecord(requestId);
1757
- if (!record) {
1758
- throw new Error(`Primary summary request '${requestId}' disappeared before held-output persistence`);
1759
- }
1760
- const placeholderBlocks = this.buildNonExecutedToolResults(options.assistantBlocks);
1761
- const entryCount = placeholderBlocks.length > 0 ? 2 : 1;
1762
- const assistantMetadata = this.buildPrimarySummarySettlementMetadata(record, {
1763
- outcome: 'held',
1764
- stopReason: options.stopReason,
1765
- holdReason,
1766
- providerInputTokens: options.providerInputTokens,
1767
- visibleAssistantOutput: options.visibleAssistantOutput,
1768
- executedToolCalls: options.executedToolCalls,
1769
- entryIndex: 0,
1770
- entryCount,
1771
- role: 'assistant',
1772
- kind: 'assistant_output',
1773
- });
1774
- const settlementId = assistantMetadata[PRIMARY_SUMMARY_SETTLEMENT_METADATA_KEY].settlementId;
1775
- const placeholderMetadata = placeholderBlocks.length > 0
1776
- ? this.buildPrimarySummarySettlementMetadata(record, {
1777
- outcome: 'held',
1778
- settlementId,
1779
- stopReason: options.stopReason,
1780
- holdReason,
1781
- providerInputTokens: options.providerInputTokens,
1782
- visibleAssistantOutput: options.visibleAssistantOutput,
1783
- executedToolCalls: options.executedToolCalls,
1784
- entryIndex: 1,
1785
- entryCount,
1786
- role: 'user',
1787
- kind: 'generated_tool_result',
1788
- })
1789
- : undefined;
1790
- const messageIds = await this.persistPrimarySummaryChronicleEntries(agent, [
1791
- { participant: agent.name, content: options.assistantBlocks, metadata: assistantMetadata },
1792
- ...(placeholderBlocks.length > 0 && placeholderMetadata
1793
- ? [{ participant: 'user', content: placeholderBlocks, metadata: placeholderMetadata }]
1794
- : []),
1795
- ]);
1796
- if (options.retryDispatch && options.retryPhase && options.assistantBlocks.length > 0) {
1797
- await this.recordPrimarySummaryRetryOutput(options.retryDispatch.originalRequestId, options.retryDispatch.intentId, {
1798
- phase: options.retryPhase,
1799
- contentHash: this.primarySummaryFallbackOutputPhaseHash(options.assistantBlocks),
1800
- messageId: messageIds[0] ?? '',
1801
- blockTypes: this.primarySummaryFallbackBlockTypes(options.assistantBlocks),
1802
- persistedAt: Date.now(),
1803
- });
1804
- }
1805
- if (options.retryDispatch) {
1806
- await this.holdPrimarySummaryRetryFamily(agent, requestId, options.retryDispatch, holdReason, options.holdData);
1807
- return;
1808
- }
1809
- await this.holdPrimarySummaryRequest(agent, requestId, holdReason, options.holdData);
1810
- }
1811
- async findPrimarySummaryRetryParent(retryRequestId) {
1812
- return this.withPrimarySummaryStateLock(() => {
1813
- const state = this.readPrimarySummaryFallbackState();
1814
- for (const record of state.requests) {
1815
- const intent = record.fallbackIntent;
1816
- if (intent?.retryRequestId === retryRequestId) {
1817
- return { originalRequestId: record.requestId, intentId: intent.intentId };
1818
- }
1819
- }
1820
- return null;
1821
- });
1822
- }
1823
- async finalizePrimarySummarySettlementOnStartup(record) {
1824
- const agent = this.agents.get(record.agentName);
1825
- if (!agent)
1826
- return false;
1827
- const settlementEntries = this.primarySummaryLogicalSettlementEntries(agent, record.requestId);
1828
- if ('invalidReason' in settlementEntries)
1829
- return settlementEntries;
1830
- if (settlementEntries.length === 0) {
1831
- return false;
1832
- }
1833
- const currentBranch = agent.getCurrentBranchGeneration();
1834
- const assistantEntry = settlementEntries[0];
1835
- const settlement = assistantEntry.settlement;
1836
- if (assistantEntry.participant !== record.agentName ||
1837
- settlement.agentName !== record.agentName ||
1838
- settlement.dispatchKind !== record.dispatchKind ||
1839
- settlement.entryIndex !== 0 ||
1840
- settlement.role !== 'assistant' ||
1841
- settlement.kind !== 'assistant_output') {
1842
- return { invalidReason: 'primary_summary_settlement_invalid_assistant_marker_on_restart' };
1843
- }
1844
- if (!currentBranch) {
1845
- return { invalidReason: 'primary_summary_settlement_branch_unknown_on_restart' };
1846
- }
1847
- if (settlement.branch.id !== record.branch.id ||
1848
- settlement.branch.name !== record.branch.name ||
1849
- settlement.branch.generation !== record.branch.generation ||
1850
- settlement.branch.id !== currentBranch.id ||
1851
- settlement.branch.name !== currentBranch.name ||
1852
- settlement.branch.generation !== currentBranch.generation) {
1853
- return { invalidReason: 'primary_summary_settlement_branch_mismatch_on_restart' };
1854
- }
1855
- if (settlement.entryCount < 1 || settlement.entryCount > 2) {
1856
- return { invalidReason: 'primary_summary_settlement_entry_count_invalid_on_restart' };
1857
- }
1858
- const entriesByIndex = new Map();
1859
- for (const entry of settlementEntries) {
1860
- if (!this.primarySummarySettlementMetadataConsistent(settlement, entry.settlement)) {
1861
- return { invalidReason: 'primary_summary_settlement_marker_conflict_on_restart' };
1862
- }
1863
- if (entriesByIndex.has(entry.settlement.entryIndex)) {
1864
- return { invalidReason: 'primary_summary_settlement_duplicate_entry_on_restart' };
1865
- }
1866
- entriesByIndex.set(entry.settlement.entryIndex, entry);
1867
- }
1868
- const placeholderEntry = entriesByIndex.get(1);
1869
- if (placeholderEntry
1870
- && (placeholderEntry.participant !== 'user'
1871
- || placeholderEntry.settlement.role !== 'user'
1872
- || placeholderEntry.settlement.kind !== 'generated_tool_result')) {
1873
- return { invalidReason: 'primary_summary_settlement_placeholder_marker_invalid_on_restart' };
1874
- }
1875
- if ([...entriesByIndex.keys()].some((index) => index !== 0 && index !== 1)) {
1876
- return { invalidReason: 'primary_summary_settlement_out_of_order_entries_on_restart' };
1877
- }
1878
- const expectedPlaceholder = settlement.outcome === 'held'
1879
- ? this.buildNonExecutedToolResults(assistantEntry.content)
1880
- : [];
1881
- if (settlement.outcome === 'success') {
1882
- if (settlement.entryCount !== 1 || placeholderEntry) {
1883
- return { invalidReason: 'primary_summary_success_settlement_shape_invalid_on_restart' };
1884
- }
1885
- }
1886
- else {
1887
- const expectedEntryCount = expectedPlaceholder.length > 0 ? 2 : 1;
1888
- if (settlement.entryCount !== expectedEntryCount) {
1889
- return { invalidReason: 'primary_summary_held_settlement_shape_invalid_on_restart' };
1890
- }
1891
- if (placeholderEntry) {
1892
- if (!this.primarySummaryContentMatches(placeholderEntry.content, expectedPlaceholder)) {
1893
- return { invalidReason: 'primary_summary_settlement_placeholder_content_mismatch_on_restart' };
1894
- }
1895
- }
1896
- else if (expectedPlaceholder.length > 0) {
1897
- const placeholderMetadata = this.buildPrimarySummarySettlementMetadata(record, {
1898
- outcome: 'held',
1899
- settlementId: settlement.settlementId,
1900
- stopReason: settlement.stopReason,
1901
- holdReason: settlement.holdReason,
1902
- providerInputTokens: settlement.providerInputTokens,
1903
- visibleAssistantOutput: settlement.visibleAssistantOutput,
1904
- executedToolCalls: settlement.executedToolCalls,
1905
- entryIndex: 1,
1906
- entryCount: settlement.entryCount,
1907
- role: 'user',
1908
- kind: 'generated_tool_result',
1909
- });
1910
- await this.persistPrimarySummaryChronicleEntries(agent, [
1911
- { participant: 'user', content: expectedPlaceholder, metadata: placeholderMetadata },
1912
- ]);
1913
- }
1914
- }
1915
- if (record.dispatchKind === 'primary_summary_fallback_retry') {
1916
- const parent = await this.findPrimarySummaryRetryParent(record.requestId);
1917
- if (!parent)
1918
- return { invalidReason: 'primary_summary_fallback_parent_missing_on_restart' };
1919
- if (settlement.outcome === 'success') {
1920
- await this.updatePrimarySummaryRequestRecord(record.requestId, (entry) => ({
1921
- ...entry,
1922
- stopReason: settlement.stopReason ?? entry.stopReason,
1923
- providerInputTokens: settlement.providerInputTokens ?? entry.providerInputTokens,
1924
- visibleAssistantOutput: settlement.visibleAssistantOutput,
1925
- executedToolCalls: settlement.executedToolCalls,
1926
- finalStatus: 'success',
1927
- fallbackHeldReason: undefined,
1928
- }));
1929
- await this.updatePrimarySummaryRequestRecord(parent.originalRequestId, (entry) => {
1930
- const intent = entry.fallbackIntent;
1931
- return {
1932
- ...entry,
1933
- finalStatus: 'success',
1934
- fallbackStatus: 'success',
1935
- fallbackHeldReason: undefined,
1936
- fallbackIntent: intent && intent.intentId === parent.intentId
1937
- ? { ...intent, finalizedAt: intent.finalizedAt ?? Date.now() }
1938
- : intent,
1939
- };
1940
- });
1941
- return 'finalized';
1942
- }
1943
- await this.updatePrimarySummaryRequestRecord(record.requestId, (entry) => ({
1944
- ...entry,
1945
- stopReason: settlement.stopReason ?? entry.stopReason,
1946
- providerInputTokens: settlement.providerInputTokens ?? entry.providerInputTokens,
1947
- visibleAssistantOutput: settlement.visibleAssistantOutput,
1948
- executedToolCalls: settlement.executedToolCalls,
1949
- finalStatus: 'held',
1950
- fallbackHeldReason: settlement.holdReason,
1951
- }));
1952
- await this.updatePrimarySummaryRequestRecord(parent.originalRequestId, (entry) => ({
1953
- ...entry,
1954
- finalStatus: 'held',
1955
- fallbackStatus: 'held',
1956
- fallbackHeldReason: settlement.holdReason,
1957
- }));
1958
- return 'finalized';
1959
- }
1960
- await this.updatePrimarySummaryRequestRecord(record.requestId, (entry) => ({
1961
- ...entry,
1962
- stopReason: settlement.stopReason ?? entry.stopReason,
1963
- providerInputTokens: settlement.providerInputTokens ?? entry.providerInputTokens,
1964
- visibleAssistantOutput: settlement.visibleAssistantOutput,
1965
- executedToolCalls: settlement.executedToolCalls,
1966
- finalStatus: settlement.outcome === 'success' ? 'success' : 'held',
1967
- fallbackStatus: settlement.outcome === 'success' ? entry.fallbackStatus : 'held',
1968
- fallbackHeldReason: settlement.outcome === 'held' ? settlement.holdReason : undefined,
1969
- }));
1970
- return 'finalized';
1971
- }
1972
- summarizePrimarySummaryRetryRequest(request) {
1973
- const blockCounts = new Map();
1974
- for (const message of request.messages) {
1975
- for (const block of message.content) {
1976
- blockCounts.set(block.type, (blockCounts.get(block.type) ?? 0) + 1);
1977
- }
1978
- }
1979
- return {
1980
- kind: 'primary_summary_fallback_retry',
1981
- requestHash: this.hashJson(request),
1982
- systemHash: this.hashJson(request.system ?? ''),
1983
- messageCount: request.messages.length,
1984
- toolCount: request.tools?.length ?? 0,
1985
- participantCount: [...new Set(request.messages.map((message) => message.participant))].length,
1986
- blockCounts: Object.fromEntries([...blockCounts.entries()].sort(([left], [right]) => left.localeCompare(right))),
1987
- promptCaching: request.promptCaching === true,
1988
- cacheTtl: request.cacheTtl,
1989
- assistantParticipant: request.assistantParticipant,
1990
- requestCompleteBoundTokens: this.estimateRequestCompleteBoundTokens(request),
1991
- };
1992
- }
1993
- summarizePrimarySummaryRetryResponse(response) {
1994
- const blockCounts = new Map();
1995
- for (const block of response.content) {
1996
- blockCounts.set(block.type, (blockCounts.get(block.type) ?? 0) + 1);
1997
- }
1998
- return {
1999
- kind: 'primary_summary_fallback_retry',
2000
- stopReason: response.stopReason,
2001
- rawAssistantTextHash: response.rawAssistantText
2002
- ? this.hashJson(response.rawAssistantText)
2003
- : undefined,
2004
- blockCounts: Object.fromEntries([...blockCounts.entries()].sort(([left], [right]) => left.localeCompare(right))),
2005
- toolCallIds: response.toolCalls.map((call) => call.id),
2006
- usage: response.usage
2007
- ? {
2008
- inputTokens: response.usage.inputTokens,
2009
- outputTokens: response.usage.outputTokens,
2010
- }
2011
- : undefined,
2012
- };
2013
- }
2014
- async claimPrimarySummaryRetryIntent(originalRequestId, intentId, retryRequestId) {
2015
- let outcome = 'missing';
2016
- await this.updatePrimarySummaryRequestRecord(originalRequestId, (record) => {
2017
- const intent = record.fallbackIntent;
2018
- if (!intent || intent.intentId !== intentId || record.fallbackStatus !== 'pending') {
2019
- return record;
2020
- }
2021
- if (intent.retryRequestId && intent.retryRequestId !== retryRequestId) {
2022
- outcome = 'duplicate';
2023
- return record;
2024
- }
2025
- outcome = 'claimed';
2026
- return {
2027
- ...record,
2028
- fallbackIntent: {
2029
- ...intent,
2030
- retryRequestId,
2031
- dispatchedAt: intent.dispatchedAt ?? Date.now(),
2032
- },
2033
- };
2034
- });
2035
- return outcome;
2036
- }
2037
- async validatePrimarySummaryRetryMutation(agent, requestId, retryDispatch, compiledRequest, artifacts, phase) {
2038
- const branch = agent.getCurrentBranchGeneration();
2039
- if (!branch || branch.id !== retryDispatch.branch.id || branch.generation !== retryDispatch.branch.generation) {
2040
- return {
2041
- ok: false,
2042
- reason: `primary_summary_fallback_branch_changed_${phase}`,
2043
- data: {
2044
- expectedBranchId: retryDispatch.branch.id,
2045
- expectedGeneration: retryDispatch.branch.generation,
2046
- actualBranchId: branch?.id,
2047
- actualGeneration: branch?.generation,
2048
- },
2049
- };
2050
- }
2051
- const requestHash = this.hashJson(compiledRequest);
2052
- if (requestHash !== retryDispatch.expectedRetryRequestHash) {
2053
- return {
2054
- ok: false,
2055
- reason: `primary_summary_fallback_request_changed_${phase}`,
2056
- data: {
2057
- expectedRetryRequestHash: retryDispatch.expectedRetryRequestHash,
2058
- actualRetryRequestHash: requestHash,
2059
- },
2060
- };
2061
- }
2062
- if (this.primarySummaryProjectionKeys(artifacts.compileResult.primarySummaryProjection ?? null).join('\u0000') !==
2063
- retryDispatch.originalProjectionKeys.join('\u0000')) {
2064
- return {
2065
- ok: false,
2066
- reason: `primary_summary_fallback_projection_changed_${phase}`,
2067
- data: {
2068
- expectedProjectionKeys: retryDispatch.originalProjectionKeys,
2069
- actualProjectionKeys: this.primarySummaryProjectionKeys(artifacts.compileResult.primarySummaryProjection ?? null),
2070
- },
2071
- };
2072
- }
2073
- if (!this.primarySummaryContractsMatch(artifacts.contract, retryDispatch.originalArtifacts.contract)) {
2074
- return {
2075
- ok: false,
2076
- reason: `primary_summary_fallback_contract_changed_${phase}`,
2077
- data: {
2078
- expectedContract: retryDispatch.originalArtifacts.contract,
2079
- actualContract: artifacts.contract,
2080
- },
2081
- };
2082
- }
2083
- const original = await this.getPrimarySummaryRequestRecord(retryDispatch.originalRequestId);
2084
- if (!original?.fallbackIntent ||
2085
- original.fallbackIntent.intentId !== retryDispatch.intentId ||
2086
- original.fallbackIntent.retryRequestId !== requestId ||
2087
- original.fallbackStatus !== 'pending') {
2088
- return {
2089
- ok: false,
2090
- reason: `primary_summary_fallback_intent_changed_${phase}`,
2091
- data: {
2092
- expectedIntentId: retryDispatch.intentId,
2093
- actualIntentId: original?.fallbackIntent?.intentId,
2094
- retryRequestId: original?.fallbackIntent?.retryRequestId,
2095
- fallbackStatus: original?.fallbackStatus,
2096
- },
2097
- };
2098
- }
2099
- return { ok: true };
2100
- }
2101
- async recordPrimarySummaryRetryQuarantine(originalRequestId, intentId) {
2102
- await this.updatePrimarySummaryRequestRecord(originalRequestId, (record) => {
2103
- const intent = record.fallbackIntent;
2104
- if (!intent || intent.intentId !== intentId || intent.quarantinePersistedAt)
2105
- return record;
2106
- return {
2107
- ...record,
2108
- fallbackIntent: {
2109
- ...intent,
2110
- quarantinePersistedAt: Date.now(),
2111
- },
2112
- };
2113
- });
2114
- }
2115
- async recordPrimarySummaryRetryOutput(originalRequestId, intentId, output) {
2116
- await this.updatePrimarySummaryRequestRecord(originalRequestId, (record) => {
2117
- const intent = record.fallbackIntent;
2118
- if (!intent || intent.intentId !== intentId)
2119
- return record;
2120
- const existing = intent.outputs?.find((entry) => entry.phase === output.phase);
2121
- if (existing &&
2122
- existing.contentHash === output.contentHash &&
2123
- existing.messageId === output.messageId) {
2124
- return record;
2125
- }
2126
- if (existing) {
2127
- throw new Error(`Primary summary fallback output phase '${output.phase}' changed after persistence`);
2128
- }
2129
- return {
2130
- ...record,
2131
- fallbackIntent: {
2132
- ...intent,
2133
- outputs: [...(intent.outputs ?? []), output],
2134
- },
2135
- };
2136
- });
2137
- }
2138
- async recordPrimarySummaryRetryToolDispatch(originalRequestId, intentId, call) {
2139
- await this.updatePrimarySummaryRequestRecord(originalRequestId, (record) => {
2140
- const intent = record.fallbackIntent;
2141
- if (!intent || intent.intentId !== intentId)
2142
- return record;
2143
- const inputHash = this.hashJson(call.input);
2144
- const existing = intent.toolDispatches?.find((entry) => entry.callId === call.id);
2145
- if (existing?.inputHash === inputHash)
2146
- return record;
2147
- if (existing) {
2148
- throw new Error(`Primary summary fallback tool dispatch '${call.id}' changed after persistence`);
2149
- }
2150
- return {
2151
- ...record,
2152
- fallbackIntent: {
2153
- ...intent,
2154
- toolDispatches: [
2155
- ...(intent.toolDispatches ?? []),
2156
- {
2157
- callId: call.id,
2158
- inputHash,
2159
- recordedAt: Date.now(),
2160
- },
2161
- ],
2162
- },
2163
- };
2164
- });
2165
- }
2166
- async resolveContextBudgetRestartDispatch(requestId, retryDispatch) {
2167
- const retryIdentity = retryDispatch
2168
- ? {
2169
- requestId,
2170
- dispatchKind: 'primary_summary_fallback_retry',
2171
- originalRequestId: retryDispatch.originalRequestId,
2172
- intentId: retryDispatch.intentId,
2173
- }
2174
- : null;
2175
- const record = await this.getPrimarySummaryRequestRecord(requestId);
2176
- if (record?.dispatchKind === 'primary_summary_fallback_retry' && retryIdentity) {
2177
- return retryIdentity;
2178
- }
2179
- if (retryIdentity) {
2180
- const original = await this.getPrimarySummaryRequestRecord(retryIdentity.originalRequestId);
2181
- if (original?.fallbackIntent?.intentId === retryIdentity.intentId
2182
- && original.fallbackIntent.retryRequestId === requestId) {
2183
- return retryIdentity;
2184
- }
2185
- }
2186
- return {
2187
- requestId,
2188
- dispatchKind: 'primary',
2189
- };
2190
- }
2191
- contextBudgetRestartMatchesIdentity(request, identity) {
2192
- if (request.reason !== 'context_budget_restart')
2193
- return false;
2194
- const restart = request.contextBudgetRestart;
2195
- if (!restart)
2196
- return false;
2197
- if (restart.dispatchKind !== identity.dispatchKind)
2198
- return false;
2199
- if (restart.requestId !== identity.requestId)
2200
- return false;
2201
- if (identity.dispatchKind !== 'primary_summary_fallback_retry')
2202
- return true;
2203
- return restart.originalRequestId === identity.originalRequestId
2204
- && restart.intentId === identity.intentId;
2205
- }
2206
- clearQueuedContextBudgetRestart(identity) {
2207
- const before = this.pendingRequests.length;
2208
- this.pendingRequests = this.pendingRequests.filter((request) => !this.contextBudgetRestartMatchesIdentity(request, identity));
2209
- return before - this.pendingRequests.length;
2210
- }
2211
- async markPrimarySummaryRetrySuccess(requestId, retryDispatch, stopReason, providerInputTokens, visibleAssistantOutput, executedToolCalls) {
2212
- await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
2213
- ...record,
2214
- stopReason,
2215
- providerInputTokens,
2216
- visibleAssistantOutput,
2217
- executedToolCalls,
2218
- finalStatus: 'success',
2219
- fallbackHeldReason: undefined,
2220
- }));
2221
- await this.updatePrimarySummaryRequestRecord(retryDispatch.originalRequestId, (record) => {
2222
- const intent = record.fallbackIntent;
2223
- return {
2224
- ...record,
2225
- finalStatus: 'success',
2226
- fallbackStatus: 'success',
2227
- fallbackHeldReason: undefined,
2228
- fallbackIntent: intent && intent.intentId === retryDispatch.intentId
2229
- ? { ...intent, finalizedAt: Date.now() }
2230
- : intent,
2231
- };
2232
- });
2233
- }
2234
- async holdPrimarySummaryRetryFamily(agent, requestId, retryDispatch, reason, data, options) {
2235
- const clearedQueuedContextBudgetRestarts = this.clearQueuedContextBudgetRestart({
2236
- requestId,
2237
- dispatchKind: 'primary_summary_fallback_retry',
2238
- originalRequestId: retryDispatch.originalRequestId,
2239
- intentId: retryDispatch.intentId,
2240
- });
2241
- const holdData = clearedQueuedContextBudgetRestarts > 0
2242
- ? { ...(data ?? {}), clearedQueuedContextBudgetRestarts }
2243
- : data;
2244
- await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
2245
- ...record,
2246
- ...(options?.stopReason ? { stopReason: options.stopReason } : {}),
2247
- finalStatus: 'held',
2248
- fallbackHeldReason: reason,
2249
- }));
2250
- await this.updatePrimarySummaryRequestRecord(retryDispatch.originalRequestId, (record) => ({
2251
- ...record,
2252
- finalStatus: 'held',
2253
- fallbackStatus: 'held',
2254
- fallbackHeldReason: reason,
2255
- }));
2256
- this.opsAlert('refusal-held', agent.name, reason, { data: holdData });
2257
- if (options?.emitChronicleMarker === false)
2258
- return;
2259
- try {
2260
- agent.getContextManager().addMessage('user', [{
2261
- type: 'text',
2262
- text: `[refusal-held] Your previous turn was refused by the provider and ` +
2263
- `is being held for operator review rather than silently retried. ` +
2264
- `Automatic raw-expansion recovery was either unsafe or unsuccessful.`,
2265
- }], { system: true, kind: 'refusal-held', reason });
2266
- }
2267
- catch (error) {
2268
- console.error('[refusal-held] could not record chronicle marker:', error);
2269
- }
2270
- }
2271
- async primarySummaryRetryAlreadySucceeded(requestId, retryDispatch) {
2272
- const [retryRecord, originalRecord] = await Promise.all([
2273
- this.getPrimarySummaryRequestRecord(requestId),
2274
- this.getPrimarySummaryRequestRecord(retryDispatch.originalRequestId),
2275
- ]);
2276
- return retryRecord?.finalStatus === 'success'
2277
- || originalRecord?.fallbackStatus === 'success'
2278
- || originalRecord?.finalStatus === 'success';
2279
- }
2280
- async primarySummaryRetryFamilyAlreadySettled(requestId, retryDispatch) {
2281
- const [retryRecord, originalRecord] = await Promise.all([
2282
- this.getPrimarySummaryRequestRecord(requestId),
2283
- this.getPrimarySummaryRequestRecord(retryDispatch.originalRequestId),
2284
- ]);
2285
- return retryRecord?.finalStatus === 'held'
2286
- || retryRecord?.finalStatus === 'success'
2287
- || originalRecord?.fallbackStatus === 'held'
2288
- || originalRecord?.fallbackStatus === 'success'
2289
- || originalRecord?.finalStatus === 'held'
2290
- || originalRecord?.finalStatus === 'success';
2291
- }
2292
- async holdPrimarySummaryRequest(agent, requestId, reason, data) {
2293
- await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
2294
- ...record,
2295
- finalStatus: 'held',
2296
- fallbackStatus: 'held',
2297
- fallbackHeldReason: reason,
2298
- }));
2299
- this.opsAlert('refusal-held', agent.name, reason, { data });
2300
- try {
2301
- agent.getContextManager().addMessage('user', [{
2302
- type: 'text',
2303
- text: `[refusal-held] Your previous turn was refused by the provider and ` +
2304
- `is being held for operator review rather than silently retried. ` +
2305
- `Automatic raw-expansion recovery was either unsafe or unsuccessful.`,
2306
- }], { system: true, kind: 'refusal-held', reason });
2307
- }
2308
- catch (error) {
2309
- console.error('[refusal-held] could not record chronicle marker:', error);
2310
- }
2311
- }
2312
- hasVisibleAssistantOutput(blocks) {
2313
- return blocks.some((block) => block.type !== 'thinking' && block.type !== 'redacted_thinking');
2314
- }
2315
- buildPrimarySummaryRequestRecord(agent, requestId, dispatchKind, request, artifacts, branch, stopReason, visibleAssistantOutput, executedToolCalls, providerInputTokens, finalStatus) {
2316
- const projection = artifacts?.compileResult.primarySummaryProjection ?? null;
2317
- return {
2318
- requestId,
2319
- agentName: agent.name,
2320
- namespace: projection?.namespace ?? `agents/${agent.name}`,
2321
- timestamp: Date.now(),
2322
- dispatchKind,
2323
- branch: branch
2324
- ? { id: branch.id, name: branch.name, generation: branch.generation }
2325
- : { id: 'unknown', name: 'unknown', generation: -1 },
2326
- projection,
2327
- requestInputBoundTokens: this.estimateRequestInputBoundTokens(request),
2328
- requestCompleteBoundTokens: this.estimateRequestCompleteBoundTokens(request),
2329
- providerInputTokens,
2330
- systemHash: artifacts?.contract.systemHash ?? this.hashJson(request.system ?? ''),
2331
- modelConfigHash: artifacts?.contract.modelConfigHash ?? this.hashJson({ request: request.config }),
2332
- toolContractHash: artifacts?.contract.toolContractHash ?? this.hashJson(request.tools ?? []),
2333
- stopReason,
2334
- visibleAssistantOutput,
2335
- executedToolCalls,
2336
- finalStatus,
2337
- };
2338
- }
2339
1237
  /** Liveness ping for an active ephemeral run (no-op otherwise). */
2340
1238
  touchEphemeralRun(agentName, inferenceStarted = false) {
2341
1239
  const run = this.ephemeralRuns.get(agentName);
@@ -3684,7 +2582,6 @@ export class AgentFramework {
3684
2582
  // Cast to AgentState to bypass TypeScript's control flow narrowing
3685
2583
  const currentState = agent.state;
3686
2584
  if (currentState.status === 'ready') {
3687
- const activeStreamContext = this.activeStreamContexts.get(agent.name);
3688
2585
  // Flush pending assistant blocks (tool_use + preamble text) to context
3689
2586
  const pendingBlocks = this.pendingAssistantBlocks.get(agent.name);
3690
2587
  if (pendingBlocks) {
@@ -3795,34 +2692,6 @@ export class AgentFramework {
3795
2692
  // a terminal failure — rejecting an ephemeral's promise mid-run
3796
2693
  // and emitting a spurious inference:exhausted (same race shape as
3797
2694
  // endTurn above).
3798
- const requestId = activeStreamContext?.requestId;
3799
- const primarySummaryRetry = activeStreamContext?.trigger?.primarySummaryFallbackRetry;
3800
- const compiledRequest = activeStreamContext?.compiledRequest;
3801
- const artifacts = activeStreamContext?.artifacts;
3802
- const restartDispatch = await this.resolveContextBudgetRestartDispatch(requestId ?? `${agent.name}:context_budget_restart`, primarySummaryRetry);
3803
- if (restartDispatch.dispatchKind === 'primary_summary_fallback_retry'
3804
- && requestId
3805
- && primarySummaryRetry
3806
- && compiledRequest
3807
- && artifacts) {
3808
- const validation = await this.validatePrimarySummaryRetryMutation(agent, requestId, primarySummaryRetry, compiledRequest, artifacts, 'before_context_budget_restart_hold');
3809
- if (!validation.ok) {
3810
- await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, validation.reason, validation.data, { emitChronicleMarker: false, stopReason: 'abort' });
3811
- }
3812
- else {
3813
- await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, 'primary_summary_fallback_context_budget_restart_required', {
3814
- inputTokens: agent.lastStreamInputTokens,
3815
- budget: agent.maxStreamTokens,
3816
- originalRequestId: primarySummaryRetry.originalRequestId,
3817
- intentId: primarySummaryRetry.intentId,
3818
- }, { emitChronicleMarker: false, stopReason: 'abort' });
3819
- }
3820
- this.frameworkCancelledStreams.set(`${agent.name}:${agent.streamId}`, 'budget_restart');
3821
- currentState.stream?.cancel();
3822
- agent.reset();
3823
- this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
3824
- return;
3825
- }
3826
2695
  if (currentState.stream) {
3827
2696
  this.frameworkCancelledStreams.set(`${agent.name}:${agent.streamId}`, 'budget_restart');
3828
2697
  }
@@ -3839,8 +2708,6 @@ export class AgentFramework {
3839
2708
  reason: 'context_budget_restart',
3840
2709
  source: 'framework',
3841
2710
  timestamp: Date.now(),
3842
- channelId: activeStreamContext?.trigger?.channelId,
3843
- contextBudgetRestart: restartDispatch,
3844
2711
  });
3845
2712
  }
3846
2713
  else if (currentState.stream) {
@@ -4338,8 +3205,7 @@ export class AgentFramework {
4338
3205
  // Clear pending (we'll re-add if inference doesn't run)
4339
3206
  this.pendingRequests = [];
4340
3207
  // Check each agent
4341
- for (const [agentName, queuedRequests] of requestsByAgent) {
4342
- let requests = queuedRequests;
3208
+ for (const [agentName, requests] of requestsByAgent) {
4343
3209
  const agent = this.agents.get(agentName);
4344
3210
  if (!agent) {
4345
3211
  // Agent not found — request is orphaned. Emit warning and drop.
@@ -4378,15 +3244,6 @@ export class AgentFramework {
4378
3244
  this.pendingRequests.push(...requests);
4379
3245
  continue;
4380
3246
  }
4381
- if (this.discordAwarenessBarrier) {
4382
- this.pendingRequests.push(...requests);
4383
- continue;
4384
- }
4385
- requests = requests.filter((request) => request.reason !== 'context_budget_restart'
4386
- || request.contextBudgetRestart?.dispatchKind !== 'primary_summary_fallback_retry');
4387
- if (requests.length === 0) {
4388
- continue;
4389
- }
4390
3247
  // Check policy
4391
3248
  if (!this.inferencePolicy.shouldInfer(agentName, requests, state)) {
4392
3249
  // Loud drop: a queued request that dies here is otherwise invisible —
@@ -4397,13 +3254,7 @@ export class AgentFramework {
4397
3254
  continue;
4398
3255
  }
4399
3256
  // Start streaming inference (non-blocking — driveStream runs in background)
4400
- const trigger = requests.find((request) => request.primarySummaryFallbackRetry) ?? requests[0];
4401
- if (trigger.primarySummaryFallbackRetry) {
4402
- for (const request of requests) {
4403
- if (request !== trigger)
4404
- this.pendingRequests.push(request);
4405
- }
4406
- }
3257
+ const trigger = requests[0];
4407
3258
  // Route this turn's auto-published speech to the channel that triggered
4408
3259
  // it (item-3 redux). A batched wake may carry several triggering channels
4409
3260
  // (messages arrived in >1 channel while the agent was busy/idle) — reply
@@ -4418,7 +3269,7 @@ export class AgentFramework {
4418
3269
  }
4419
3270
  async startAgentStream(agent, trigger, attempt = 0) {
4420
3271
  // Record turn checkpoint before inference (only on first attempt, not retries)
4421
- if (attempt === 0 && trigger?.reason !== 'primary-summary-fallback-retry') {
3272
+ if (attempt === 0) {
4422
3273
  this.recordTurnCheckpoint(agent.name);
4423
3274
  this.redoStacks.delete(agent.name); // new work invalidates redo
4424
3275
  }
@@ -4438,180 +3289,46 @@ export class AgentFramework {
4438
3289
  this.emitTrace({ type: 'inference:started', agentName: agent.name });
4439
3290
  this.eventGate?.onInferenceStarted(agent.name);
4440
3291
  this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), startedAt: Date.now() });
4441
- const requestId = `${agent.name}-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
4442
3292
  try {
4443
3293
  const requestSnapshot = this.captureInferenceToolSnapshot(agent);
4444
3294
  const allTools = this.getToolsForAgent(agent.name, requestSnapshot);
4445
3295
  const tools = allTools.filter((t) => agent.canUseTool(t.name));
4446
- const fallbackConfig = this.primarySummaryFallbackConfig(agent);
4447
- const retryDispatch = trigger?.primarySummaryFallbackRetry;
4448
- let compiledRequest;
4449
- let artifacts;
4450
- let branch = agent.getCurrentBranchGeneration();
4451
- if (retryDispatch) {
4452
- if (!fallbackConfig.enabled) {
4453
- await this.holdPrimarySummaryRequest(agent, retryDispatch.originalRequestId, 'primary_summary_fallback_disabled_before_retry');
4454
- agent.reset();
4455
- this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
4456
- this.eventGate?.onInferenceEnded(agent.name);
4457
- this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
4458
- return;
4459
- }
4460
- const claim = await this.claimPrimarySummaryRetryIntent(retryDispatch.originalRequestId, retryDispatch.intentId, requestId);
4461
- if (claim !== 'claimed') {
4462
- await this.holdPrimarySummaryRequest(agent, retryDispatch.originalRequestId, claim === 'duplicate'
4463
- ? 'primary_summary_fallback_duplicate_retry_blocked'
4464
- : 'primary_summary_fallback_retry_intent_missing', { intentId: retryDispatch.intentId, retryRequestId: requestId });
4465
- agent.reset();
4466
- this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
4467
- this.eventGate?.onInferenceEnded(agent.name);
4468
- this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
4469
- return;
4470
- }
4471
- if (!branch ||
4472
- branch.id !== retryDispatch.branch.id ||
4473
- branch.generation !== retryDispatch.branch.generation) {
4474
- await this.holdPrimarySummaryRequest(agent, retryDispatch.originalRequestId, 'primary_summary_fallback_branch_changed_before_retry', {
4475
- expectedBranchId: retryDispatch.branch.id,
4476
- expectedGeneration: retryDispatch.branch.generation,
4477
- actualBranchId: branch?.id,
4478
- actualGeneration: branch?.generation,
4479
- });
4480
- agent.reset();
4481
- this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
4482
- this.eventGate?.onInferenceEnded(agent.name);
4483
- this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
4484
- return;
4485
- }
4486
- const rebuilt = agent.buildPrimarySummaryRawExpansionRequest(retryDispatch.originalArtifacts, retryDispatch.candidateSummaries, tools);
4487
- compiledRequest = rebuilt.request;
4488
- artifacts = rebuilt.artifacts;
4489
- const rebuiltHash = this.hashJson(compiledRequest);
4490
- if (rebuiltHash !== retryDispatch.expectedRetryRequestHash ||
4491
- this.primarySummaryProjectionKeys(artifacts.compileResult.primarySummaryProjection ?? null).join('\u0000') !==
4492
- retryDispatch.originalProjectionKeys.join('\u0000')) {
4493
- await this.holdPrimarySummaryRequest(agent, retryDispatch.originalRequestId, 'primary_summary_fallback_revalidation_failed_before_retry', {
4494
- expectedRetryRequestHash: retryDispatch.expectedRetryRequestHash,
4495
- actualRetryRequestHash: rebuiltHash,
4496
- });
4497
- agent.reset();
4498
- this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
4499
- this.eventGate?.onInferenceEnded(agent.name);
4500
- this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
4501
- return;
4502
- }
4503
- const retryRecord = this.buildPrimarySummaryRequestRecord(agent, requestId, 'primary_summary_fallback_retry', compiledRequest, artifacts, branch, undefined, false, 0, undefined, 'pending');
4504
- await this.persistPrimarySummaryRequestRecord(retryRecord);
4505
- const admittedTokens = this.primarySummaryAdmissionBoundTokens(retryDispatch.originalRequestInputBoundTokens, compiledRequest, retryDispatch.originalProviderInputTokens);
4506
- if (admittedTokens > retryDispatch.requestBudgetTokens) {
4507
- await this.holdPrimarySummaryRequest(agent, requestId, 'primary_summary_fallback_retry_over_budget', {
4508
- admittedTokens,
4509
- requestBudgetTokens: retryDispatch.requestBudgetTokens,
4510
- retryRequestHash: rebuiltHash,
4511
- });
4512
- await this.updatePrimarySummaryRequestRecord(retryDispatch.originalRequestId, (record) => ({
4513
- ...record,
4514
- fallbackStatus: 'held',
4515
- fallbackHeldReason: 'primary_summary_fallback_retry_over_budget',
4516
- }));
4517
- agent.reset();
4518
- this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
4519
- this.eventGate?.onInferenceEnded(agent.name);
4520
- this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
4521
- return;
3296
+ // Gather context from modules (pull-based) and MCPL hooks (push-based)
3297
+ // Both produce ContextInjection[] that get merged before inference.
3298
+ let injections;
3299
+ // Module gatherContext (fail-open, per-module timeout — the module's
3300
+ // contextTimeoutMs, else the registry default). Injections are
3301
+ // channel-scoped via scopeInjectionsForAgent, matching the MCPL hook
3302
+ // injections below, so conversation forks don't see cross-channel
3303
+ // content.
3304
+ try {
3305
+ const moduleInjections = this.scopeInjectionsForAgent(agent.name, await this.moduleRegistry.gatherContext(agent.name));
3306
+ if (moduleInjections.length > 0) {
3307
+ injections = moduleInjections;
4522
3308
  }
4523
3309
  }
4524
- else {
4525
- // Gather context from modules (pull-based) and MCPL hooks (push-based)
4526
- // Both produce ContextInjection[] that get merged before inference.
4527
- let injections;
4528
- // Module gatherContext (fail-open, per-module timeout — the module's
4529
- // contextTimeoutMs, else the registry default). Injections are
4530
- // channel-scoped via scopeInjectionsForAgent, matching the MCPL hook
4531
- // injections below, so conversation forks don't see cross-channel
4532
- // content.
3310
+ catch (error) {
3311
+ console.error('Module gatherContext error:', error);
3312
+ }
3313
+ // MCPL beforeInference hooks (fail-open)
3314
+ if (this.hookOrchestrator) {
4533
3315
  try {
4534
- const moduleInjections = this.scopeInjectionsForAgent(agent.name, await this.moduleRegistry.gatherContext(agent.name));
4535
- if (moduleInjections.length > 0) {
4536
- injections = moduleInjections;
3316
+ const hookParams = this.buildBeforeInferenceParams(agent, trigger);
3317
+ const hookInjections = this.scopeInjectionsForAgent(agent.name, await this.hookOrchestrator.beforeInference(hookParams));
3318
+ if (hookInjections.length > 0) {
3319
+ injections = injections ? [...injections, ...hookInjections] : hookInjections;
4537
3320
  }
4538
3321
  }
4539
3322
  catch (error) {
4540
- console.error('Module gatherContext error:', error);
4541
- }
4542
- // MCPL beforeInference hooks (fail-open)
4543
- if (this.hookOrchestrator) {
4544
- try {
4545
- const hookParams = this.buildBeforeInferenceParams(agent, trigger);
4546
- const hookInjections = this.scopeInjectionsForAgent(agent.name, await this.hookOrchestrator.beforeInference(hookParams));
4547
- if (hookInjections.length > 0) {
4548
- injections = injections ? [...injections, ...hookInjections] : hookInjections;
4549
- }
4550
- }
4551
- catch (error) {
4552
- console.error('beforeInference hook error:', error);
4553
- }
4554
- }
4555
- const prepared = await agent.prepareActivationRequest(tools, injections);
4556
- compiledRequest = prepared.request;
4557
- artifacts = prepared.artifacts;
4558
- branch = agent.getCurrentBranchGeneration();
4559
- if (fallbackConfig.enabled) {
4560
- const record = this.buildPrimarySummaryRequestRecord(agent, requestId, 'primary', compiledRequest, artifacts, branch, undefined, false, 0, undefined, 'pending');
4561
- await this.persistPrimarySummaryRequestRecord(record);
4562
- const expandedByQuarantine = artifacts.compileResult.primarySummaryProjection?.selectedSummaries.some((selection) => selection.renderedAs === 'raw_expansion') ?? false;
4563
- if (expandedByQuarantine) {
4564
- const admittedTokens = this.estimateRequestCompleteBoundTokens(compiledRequest);
4565
- if (admittedTokens > fallbackConfig.requestBudgetTokens) {
4566
- await this.holdPrimarySummaryRequest(agent, requestId, 'primary_summary_quarantine_over_budget', {
4567
- admittedTokens,
4568
- requestBudgetTokens: fallbackConfig.requestBudgetTokens,
4569
- });
4570
- agent.reset();
4571
- this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
4572
- this.eventGate?.onInferenceEnded(agent.name);
4573
- this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
4574
- return;
4575
- }
4576
- }
3323
+ console.error('beforeInference hook error:', error);
4577
3324
  }
4578
3325
  }
4579
- const { stream } = agent.startStreamFromRequest(compiledRequest, artifacts);
4580
- this.activeStreamContexts.set(agent.name, {
4581
- requestId,
4582
- trigger,
4583
- compiledRequest,
4584
- artifacts,
4585
- });
4586
- const handle = this.driveStream(agent, stream, requestSnapshot, requestId, trigger, attempt, compiledRequest, artifacts);
3326
+ const { stream, request: compiledRequest } = await agent.startStreamWithInjections(tools, injections);
3327
+ const handle = this.driveStream(agent, stream, requestSnapshot, trigger, attempt, compiledRequest);
4587
3328
  this.activeStreams.set(agent.name, handle);
4588
3329
  }
4589
3330
  catch (error) {
4590
3331
  const err = error instanceof Error ? error : new Error(String(error));
4591
- if (trigger?.primarySummaryFallbackRetry) {
4592
- await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
4593
- ...record,
4594
- finalStatus: 'held',
4595
- fallbackHeldReason: err.message,
4596
- }));
4597
- await this.updatePrimarySummaryRequestRecord(trigger.primarySummaryFallbackRetry.originalRequestId, (record) => ({
4598
- ...record,
4599
- finalStatus: 'held',
4600
- fallbackStatus: 'held',
4601
- fallbackHeldReason: err.message,
4602
- }));
4603
- this.opsAlert('refusal-held', agent.name, err.message, {
4604
- data: { requestId, originalRequestId: trigger.primarySummaryFallbackRetry.originalRequestId },
4605
- });
4606
- agent.reset();
4607
- this.settleAgent(agent.name, {
4608
- stopReason: 'completed',
4609
- speech: '',
4610
- });
4611
- this.eventGate?.onInferenceEnded(agent.name);
4612
- this.lastInferenceAt.set(agent.name, { ...this.lastInferenceAt.get(agent.name), endedAt: Date.now() });
4613
- return;
4614
- }
4615
3332
  this.emitTrace({
4616
3333
  type: 'inference:failed',
4617
3334
  agentName: agent.name,
@@ -4650,17 +3367,11 @@ export class AgentFramework {
4650
3367
  }
4651
3368
  }
4652
3369
  }
4653
- async driveStream(agent, stream, requestSnapshot, requestId, trigger, attempt = 0, compiledRequest, artifacts) {
3370
+ async driveStream(agent, stream, requestSnapshot, trigger, attempt = 0, compiledRequest) {
4654
3371
  const startTime = Date.now();
3372
+ const requestId = `${agent.name}-${startTime}-${Math.random().toString(36).slice(2, 8)}`;
4655
3373
  const myStreamId = agent.streamId;
4656
3374
  let hadToolCalls = false;
4657
- let executedToolCalls = 0;
4658
- const primarySummaryConfig = this.primarySummaryFallbackConfig(agent);
4659
- const primarySummaryRetry = trigger?.primarySummaryFallbackRetry;
4660
- let primarySummaryQuarantinePersisted = false;
4661
- const fallbackRetryRequestForLog = primarySummaryRetry && compiledRequest
4662
- ? this.summarizePrimarySummaryRetryRequest(compiledRequest)
4663
- : compiledRequest ?? { note: 'streaming request' };
4664
3375
  // ---- Present-while-acting turn state ---------------------------------
4665
3376
  // Output locus for the current CONVERSATIONAL ROUND: resolved lazily and
4666
3377
  // kept stable across tool-only rounds. A channel message injected at a
@@ -4668,7 +3379,7 @@ export class AgentFramework {
4668
3379
  // single provider inference can now contain several conversations. The
4669
3380
  // pin lives in `turnLocusPins` so it survives a context-budget restart. A
4670
3381
  // null resolution is not pinned.
4671
- if (trigger?.reason !== 'context_budget_restart' && trigger?.reason !== 'primary-summary-fallback-retry') {
3382
+ if (trigger?.reason !== 'context_budget_restart') {
4672
3383
  this.turnLocusPins.delete(agent.name);
4673
3384
  this.midTurnRoutingResets.delete(agent.name);
4674
3385
  }
@@ -4736,61 +3447,6 @@ export class AgentFramework {
4736
3447
  }
4737
3448
  }
4738
3449
  };
4739
- const persistPrimarySummaryRetryQuarantine = async () => {
4740
- if (!primarySummaryRetry || primarySummaryQuarantinePersisted || !artifacts)
4741
- return true;
4742
- const validation = await this.validatePrimarySummaryRetryMutation(agent, requestId, primarySummaryRetry, compiledRequest, artifacts, 'before_quarantine');
4743
- if (!validation.ok) {
4744
- await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, validation.reason, validation.data, { emitChronicleMarker: false });
4745
- stream.cancel();
4746
- agent.reset();
4747
- this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
4748
- return false;
4749
- }
4750
- try {
4751
- const quarantinePrimarySummaryForPrimaryLane = agent.getContextManager().quarantinePrimarySummaryForPrimaryLane;
4752
- if (typeof quarantinePrimarySummaryForPrimaryLane !== 'function') {
4753
- throw new Error('Primary summary quarantine is unavailable in the active context strategy');
4754
- }
4755
- await quarantinePrimarySummaryForPrimaryLane.call(agent.getContextManager(), artifacts.contract, primarySummaryRetry.candidateSummaries);
4756
- primarySummaryQuarantinePersisted = true;
4757
- await this.recordPrimarySummaryRetryQuarantine(primarySummaryRetry.originalRequestId, primarySummaryRetry.intentId);
4758
- return true;
4759
- }
4760
- catch (error) {
4761
- const reason = 'primary_summary_fallback_quarantine_failed';
4762
- await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, reason, {
4763
- error: error instanceof Error ? error.message : String(error),
4764
- });
4765
- stream.cancel();
4766
- agent.reset();
4767
- this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
4768
- return false;
4769
- }
4770
- };
4771
- const persistPrimarySummaryRetryOutput = async (phase, blocks, participant = agent.name, metadata) => {
4772
- if (!primarySummaryRetry || !artifacts)
4773
- return true;
4774
- if (blocks.length === 0)
4775
- return true;
4776
- const validation = await this.validatePrimarySummaryRetryMutation(agent, requestId, primarySummaryRetry, compiledRequest, artifacts, `before_${phase}`);
4777
- if (!validation.ok) {
4778
- await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, validation.reason, validation.data, { emitChronicleMarker: false });
4779
- stream.cancel();
4780
- agent.reset();
4781
- this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
4782
- return false;
4783
- }
4784
- const messageId = agent.getContextManager().addMessage(participant, blocks, metadata);
4785
- await this.recordPrimarySummaryRetryOutput(primarySummaryRetry.originalRequestId, primarySummaryRetry.intentId, {
4786
- phase,
4787
- contentHash: this.primarySummaryFallbackOutputPhaseHash(blocks),
4788
- messageId,
4789
- blockTypes: this.primarySummaryFallbackBlockTypes(blocks),
4790
- persistedAt: Date.now(),
4791
- });
4792
- return true;
4793
- };
4794
3450
  try {
4795
3451
  for await (const event of stream) {
4796
3452
  this.touchEphemeralRun(agent.name, true);
@@ -4818,6 +3474,7 @@ export class AgentFramework {
4818
3474
  case 'tool-calls': {
4819
3475
  adoptInjectedRound();
4820
3476
  hadToolCalls = true;
3477
+ this.recordEphemeralToolCalls(agent.name, event.calls.length);
4821
3478
  this.emitTrace({
4822
3479
  type: 'inference:tool_calls_yielded',
4823
3480
  agentName: agent.name,
@@ -4851,30 +3508,7 @@ export class AgentFramework {
4851
3508
  // Truncated responses yield 'complete' with stop_reason 'max_tokens' directly.
4852
3509
  // So all calls here have valid input — {} is legitimate for parameterless tools.
4853
3510
  agent.enterWaitingForTools(event.calls, stream);
4854
- if (!(await persistPrimarySummaryRetryQuarantine())) {
4855
- return;
4856
- }
4857
- if (primarySummaryRetry) {
4858
- const phase = `tool_round_${event.context.depth ?? 0}`;
4859
- if (!(await persistPrimarySummaryRetryOutput(phase, assistantBlocks))) {
4860
- return;
4861
- }
4862
- this.pendingAssistantBlocks.delete(agent.name);
4863
- }
4864
- executedToolCalls += event.calls.length;
4865
- this.recordEphemeralToolCalls(agent.name, event.calls.length);
4866
3511
  for (const call of event.calls) {
4867
- if (primarySummaryRetry) {
4868
- const validation = await this.validatePrimarySummaryRetryMutation(agent, requestId, primarySummaryRetry, compiledRequest, artifacts, `before_tool_dispatch_${call.id}`);
4869
- if (!validation.ok) {
4870
- await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, validation.reason, validation.data, { emitChronicleMarker: false });
4871
- stream.cancel();
4872
- agent.reset();
4873
- this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
4874
- return;
4875
- }
4876
- await this.recordPrimarySummaryRetryToolDispatch(primarySummaryRetry.originalRequestId, primarySummaryRetry.intentId, call);
4877
- }
4878
3512
  this.dispatchToolCall(agent.name, call);
4879
3513
  }
4880
3514
  // Speak-while-acting: route THIS round's prose to the locus NOW
@@ -4956,167 +3590,6 @@ export class AgentFramework {
4956
3590
  agent.getContextManager().addMessage('user', toolResultContent);
4957
3591
  }
4958
3592
  }
4959
- const lastToolIdx = response.content.reduce((last, b, i) => b.type === 'tool_use' || b.type === 'tool_result' ? i : last, -1);
4960
- const terminalContent = lastToolIdx >= 0
4961
- ? response.content.slice(lastToolIdx + 1)
4962
- : response.content;
4963
- const heldAssistantContent = response.content;
4964
- const visibleAssistantOutput = this.hasVisibleAssistantOutput(response.content);
4965
- const providerInputTokens = response.usage?.inputTokens ??
4966
- response.details?.usage?.inputTokens;
4967
- if (primarySummaryConfig.enabled && compiledRequest && artifacts && response.stopReason === 'refusal') {
4968
- const stopDetails = response.raw?.response?.stop_details;
4969
- const category = stopDetails?.category ?? 'unknown';
4970
- console.error(`[inference-refusal] agent=${agent.name} category=${category}` +
4971
- (stopDetails?.explanation ? ` explanation=${stopDetails.explanation}` : ''));
4972
- this.noteRefusal(agent.name, category, response.details?.usage
4973
- ? {
4974
- input: response.details.usage.inputTokens,
4975
- output: response.details.usage.outputTokens,
4976
- }
4977
- : undefined);
4978
- const currentRecord = await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
4979
- ...record,
4980
- stopReason: response.stopReason,
4981
- providerInputTokens,
4982
- visibleAssistantOutput,
4983
- executedToolCalls,
4984
- finalStatus: 'refusal',
4985
- }));
4986
- const holdReasonData = {
4987
- category,
4988
- visibleAssistantOutput,
4989
- executedToolCalls,
4990
- candidateLimit: primarySummaryConfig.maxNewSummaries,
4991
- };
4992
- if (primarySummaryRetry) {
4993
- const reason = visibleAssistantOutput
4994
- ? 'primary_summary_fallback_retry_partial_output'
4995
- : executedToolCalls > 0
4996
- ? 'primary_summary_fallback_retry_tool_already_executed'
4997
- : 'primary_summary_fallback_retry_refusal';
4998
- if (visibleAssistantOutput || executedToolCalls > 0) {
4999
- await this.persistPrimarySummaryHeldOutput(agent, requestId, reason, {
5000
- stopReason: response.stopReason,
5001
- providerInputTokens,
5002
- visibleAssistantOutput,
5003
- executedToolCalls,
5004
- assistantBlocks: heldAssistantContent,
5005
- retryDispatch: primarySummaryRetry,
5006
- retryPhase: 'refusal_visible_output',
5007
- holdData: holdReasonData,
5008
- });
5009
- }
5010
- else {
5011
- await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, reason, holdReasonData);
5012
- }
5013
- }
5014
- else if (visibleAssistantOutput || executedToolCalls > 0 || !currentRecord?.projection) {
5015
- const reason = visibleAssistantOutput
5016
- ? 'primary_summary_refusal_partial_output'
5017
- : executedToolCalls > 0
5018
- ? 'primary_summary_refusal_tool_already_executed'
5019
- : 'primary_summary_refusal_no_projection';
5020
- if (visibleAssistantOutput || executedToolCalls > 0) {
5021
- await this.persistPrimarySummaryHeldOutput(agent, requestId, reason, {
5022
- stopReason: response.stopReason,
5023
- providerInputTokens,
5024
- visibleAssistantOutput,
5025
- executedToolCalls,
5026
- assistantBlocks: heldAssistantContent,
5027
- holdData: holdReasonData,
5028
- });
5029
- }
5030
- else {
5031
- await this.holdPrimarySummaryRequest(agent, requestId, reason, holdReasonData);
5032
- }
5033
- }
5034
- else {
5035
- const state = this.readPrimarySummaryFallbackState();
5036
- const baseline = this.latestCompatibleHealthyPrimaryRequest(currentRecord, state);
5037
- if (!baseline?.projection) {
5038
- await this.holdPrimarySummaryRequest(agent, requestId, 'primary_summary_refusal_no_compatible_healthy_baseline', holdReasonData);
5039
- }
5040
- else {
5041
- const baselineKeys = new Set(this.primarySummaryProjectionKeys(baseline.projection));
5042
- const candidates = currentRecord.projection.selectedSummaries
5043
- .filter((selection) => !baselineKeys.has(this.primarySummaryIdentityKey(selection.identity)))
5044
- .map((selection) => selection.identity);
5045
- if (candidates.length === 0) {
5046
- await this.holdPrimarySummaryRequest(agent, requestId, 'primary_summary_refusal_no_newly_admitted_candidates', holdReasonData);
5047
- }
5048
- else if (candidates.length > primarySummaryConfig.maxNewSummaries) {
5049
- await this.holdPrimarySummaryRequest(agent, requestId, 'primary_summary_refusal_too_many_candidates', { ...holdReasonData, candidateCount: candidates.length });
5050
- }
5051
- else {
5052
- try {
5053
- const retry = agent.buildPrimarySummaryRawExpansionRequest(artifacts, candidates, compiledRequest.tools ?? []);
5054
- const retryRequestHash = this.hashJson(retry.request);
5055
- const admittedTokens = this.primarySummaryAdmissionBoundTokens(currentRecord.requestInputBoundTokens, retry.request, providerInputTokens);
5056
- if (admittedTokens > primarySummaryConfig.requestBudgetTokens) {
5057
- await this.holdPrimarySummaryRequest(agent, requestId, 'primary_summary_refusal_retry_over_budget', {
5058
- ...holdReasonData,
5059
- admittedTokens,
5060
- requestBudgetTokens: primarySummaryConfig.requestBudgetTokens,
5061
- });
5062
- }
5063
- else {
5064
- const intentId = `${requestId}:fallback:${Math.random().toString(36).slice(2, 8)}`;
5065
- await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
5066
- ...record,
5067
- fallbackIntent: {
5068
- intentId,
5069
- createdAt: Date.now(),
5070
- baselineRequestId: baseline.requestId,
5071
- candidateSummaries: candidates,
5072
- retryRequestHash,
5073
- retryCompleteBoundTokens: admittedTokens,
5074
- requestBudgetTokens: primarySummaryConfig.requestBudgetTokens,
5075
- },
5076
- fallbackStatus: 'pending',
5077
- }));
5078
- this.pendingRequests.push({
5079
- agentName: agent.name,
5080
- reason: 'primary-summary-fallback-retry',
5081
- source: 'framework',
5082
- timestamp: Date.now(),
5083
- channelId: trigger?.channelId,
5084
- primarySummaryFallbackRetry: {
5085
- originalRequestId: requestId,
5086
- baselineRequestId: baseline.requestId,
5087
- intentId,
5088
- branch: {
5089
- id: currentRecord.branch.id,
5090
- generation: currentRecord.branch.generation,
5091
- },
5092
- candidateSummaries: candidates,
5093
- originalArtifacts: artifacts,
5094
- originalProjectionKeys: this.primarySummaryProjectionKeys(currentRecord.projection),
5095
- expectedRetryRequestHash: retryRequestHash,
5096
- requestBudgetTokens: primarySummaryConfig.requestBudgetTokens,
5097
- originalRequestInputBoundTokens: currentRecord.requestInputBoundTokens,
5098
- originalProviderInputTokens: providerInputTokens,
5099
- },
5100
- });
5101
- }
5102
- }
5103
- catch (error) {
5104
- await this.holdPrimarySummaryRequest(agent, requestId, 'primary_summary_refusal_retry_build_failed', {
5105
- ...holdReasonData,
5106
- error: error instanceof Error ? error.message : String(error),
5107
- });
5108
- }
5109
- }
5110
- }
5111
- }
5112
- agent.reset();
5113
- this.eventGate?.onInferenceEnded(agent.name);
5114
- this.settleAgent(agent.name, { stopReason: 'completed', speech: '' });
5115
- continue;
5116
- }
5117
- if (!(await persistPrimarySummaryRetryQuarantine())) {
5118
- return;
5119
- }
5120
3593
  // Add assistant response to context.
5121
3594
  // If we had tool calls, each round's blocks (thinking + text +
5122
3595
  // tool_use) were already stored as pendingAssistantBlocks and
@@ -5125,55 +3598,17 @@ export class AgentFramework {
5125
3598
  // double-store earlier rounds' text/thinking AND detach signed
5126
3599
  // thinking blocks from their tool_use turn (the API requires
5127
3600
  // thinking to precede tool_use in the same assistant turn).
3601
+ const lastToolIdx = response.content.reduce((last, b, i) => b.type === 'tool_use' || b.type === 'tool_result' ? i : last, -1);
3602
+ const terminalContent = lastToolIdx >= 0
3603
+ ? response.content.slice(lastToolIdx + 1)
3604
+ : response.content;
5128
3605
  if (lastToolIdx >= 0) {
5129
3606
  if (terminalContent.length > 0) {
5130
- if (primarySummaryRetry) {
5131
- const retryRecord = await this.getPrimarySummaryRequestRecord(requestId);
5132
- const terminalMetadata = retryRecord
5133
- ? this.buildPrimarySummarySettlementMetadata(retryRecord, {
5134
- outcome: 'success',
5135
- stopReason: response.stopReason,
5136
- providerInputTokens,
5137
- visibleAssistantOutput,
5138
- executedToolCalls,
5139
- entryIndex: 0,
5140
- entryCount: 1,
5141
- role: 'assistant',
5142
- kind: 'assistant_output',
5143
- })
5144
- : undefined;
5145
- if (!(await persistPrimarySummaryRetryOutput('terminal', terminalContent, agent.name, terminalMetadata))) {
5146
- return;
5147
- }
5148
- }
5149
- else {
5150
- agent.addAssistantResponse(terminalContent);
5151
- }
3607
+ agent.addAssistantResponse(terminalContent);
5152
3608
  }
5153
3609
  }
5154
3610
  else {
5155
- if (primarySummaryRetry) {
5156
- const retryRecord = await this.getPrimarySummaryRequestRecord(requestId);
5157
- const terminalMetadata = retryRecord && terminalContent.length > 0
5158
- ? this.buildPrimarySummarySettlementMetadata(retryRecord, {
5159
- outcome: 'success',
5160
- stopReason: response.stopReason,
5161
- providerInputTokens,
5162
- visibleAssistantOutput,
5163
- executedToolCalls,
5164
- entryIndex: 0,
5165
- entryCount: 1,
5166
- role: 'assistant',
5167
- kind: 'assistant_output',
5168
- })
5169
- : undefined;
5170
- if (!(await persistPrimarySummaryRetryOutput('terminal', terminalContent, agent.name, terminalMetadata))) {
5171
- return;
5172
- }
5173
- }
5174
- else {
5175
- agent.addAssistantResponse(terminalContent);
5176
- }
3611
+ agent.addAssistantResponse(terminalContent);
5177
3612
  }
5178
3613
  // Run afterInference hooks (no-op if no MCPL servers)
5179
3614
  if (this.hookOrchestrator) {
@@ -5228,28 +3663,6 @@ export class AgentFramework {
5228
3663
  cacheRead: du.cacheReadTokens,
5229
3664
  }
5230
3665
  : undefined;
5231
- if (primarySummaryRetry && artifacts) {
5232
- const validation = await this.validatePrimarySummaryRetryMutation(agent, requestId, primarySummaryRetry, compiledRequest, artifacts, 'before_success_markers');
5233
- if (!validation.ok) {
5234
- await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, validation.reason, validation.data, { emitChronicleMarker: false });
5235
- return;
5236
- }
5237
- }
5238
- if (primarySummaryConfig.enabled) {
5239
- if (primarySummaryRetry) {
5240
- await this.markPrimarySummaryRetrySuccess(requestId, primarySummaryRetry, response.stopReason, providerInputTokens, visibleAssistantOutput, executedToolCalls);
5241
- }
5242
- else {
5243
- await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
5244
- ...record,
5245
- stopReason: response.stopReason,
5246
- providerInputTokens,
5247
- visibleAssistantOutput,
5248
- executedToolCalls,
5249
- finalStatus: 'success',
5250
- }));
5251
- }
5252
- }
5253
3666
  // Reset agent state before emitting inference:completed. Traces are
5254
3667
  // observability-only, but external synchronous listeners should
5255
3668
  // still see the terminal state at the terminal trace boundary.
@@ -5285,10 +3698,8 @@ export class AgentFramework {
5285
3698
  agentName: agent.name,
5286
3699
  requestId,
5287
3700
  success: true,
5288
- request: fallbackRetryRequestForLog,
5289
- response: primarySummaryRetry
5290
- ? this.summarizePrimarySummaryRetryResponse(response)
5291
- : response.raw ?? { note: 'streaming response' },
3701
+ request: compiledRequest ?? { note: 'streaming request' },
3702
+ response: response.raw ?? { note: 'streaming response' },
5292
3703
  durationMs,
5293
3704
  tokenUsage,
5294
3705
  stopReason: response.stopReason,
@@ -5481,22 +3892,12 @@ export class AgentFramework {
5481
3892
  requestId,
5482
3893
  success: false,
5483
3894
  error: err.message,
5484
- request: fallbackRetryRequestForLog,
3895
+ request: compiledRequest ?? { note: 'streaming request failed' },
5485
3896
  durationMs,
5486
3897
  });
5487
3898
  // Only reset + retry if this is still the active stream
5488
3899
  if (agent.streamId !== myStreamId)
5489
3900
  break;
5490
- if (primarySummaryRetry) {
5491
- await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, 'primary_summary_fallback_retry_stream_error', { error: err.message });
5492
- agent.reset();
5493
- this.eventGate?.onInferenceEnded(agent.name);
5494
- this.settleAgent(agent.name, {
5495
- stopReason: 'completed',
5496
- speech: '',
5497
- });
5498
- return;
5499
- }
5500
3901
  agent.reset();
5501
3902
  const action = this.errorPolicy.onInferenceError(err, agent.name, attempt);
5502
3903
  if (action.retry) {
@@ -5535,11 +3936,6 @@ export class AgentFramework {
5535
3936
  // reject an ephemeral's promise mid-run and bump the failure
5536
3937
  // streak). Gate release + stream teardown happen in `finally`.
5537
3938
  if (this.frameworkCancelledStreams.delete(`${agent.name}:${myStreamId}`)) {
5538
- if (primarySummaryRetry) {
5539
- if (!(await this.primarySummaryRetryFamilyAlreadySettled(requestId, primarySummaryRetry))) {
5540
- await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, 'primary_summary_fallback_retry_cancelled_before_success', { frameworkCancelled: true });
5541
- }
5542
- }
5543
3939
  this.eventGate?.onInferenceEnded(agent.name);
5544
3940
  return;
5545
3941
  }
@@ -5548,33 +3944,6 @@ export class AgentFramework {
5548
3944
  // may have already started a new stream, bumping streamId)
5549
3945
  if (agent.streamId === myStreamId) {
5550
3946
  const durationMs = Date.now() - startTime;
5551
- if (primarySummaryRetry) {
5552
- await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, `stream_aborted:${reason}`, { reason }, { stopReason: 'abort' });
5553
- agent.reset();
5554
- this.eventGate?.onInferenceEnded(agent.name);
5555
- this.logInference({
5556
- timestamp: startTime,
5557
- agentName: agent.name,
5558
- requestId,
5559
- success: false,
5560
- error: `Stream aborted: ${reason}`,
5561
- request: fallbackRetryRequestForLog,
5562
- durationMs,
5563
- });
5564
- this.settleAgent(agent.name, {
5565
- stopReason: 'completed',
5566
- speech: '',
5567
- });
5568
- return;
5569
- }
5570
- if (primarySummaryConfig.enabled) {
5571
- await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
5572
- ...record,
5573
- stopReason: 'abort',
5574
- finalStatus: 'error',
5575
- fallbackHeldReason: `stream_aborted:${reason}`,
5576
- }));
5577
- }
5578
3947
  agent.reset();
5579
3948
  this.settleAgent(agent.name, {
5580
3949
  stopReason: 'exhausted',
@@ -5598,7 +3967,7 @@ export class AgentFramework {
5598
3967
  requestId,
5599
3968
  success: false,
5600
3969
  error: `Stream aborted: ${reason}`,
5601
- request: fallbackRetryRequestForLog,
3970
+ request: compiledRequest ?? { note: 'streaming request aborted' },
5602
3971
  durationMs,
5603
3972
  });
5604
3973
  this.eventGate?.onInferenceEnded(agent.name);
@@ -5644,58 +4013,12 @@ export class AgentFramework {
5644
4013
  // inference:exhausted so ephemeral agent promises can settle.
5645
4014
  const err = error instanceof Error ? error : new Error(String(error));
5646
4015
  const durationMs = Date.now() - startTime;
5647
- if (primarySummaryRetry) {
5648
- if (await this.primarySummaryRetryAlreadySucceeded(requestId, primarySummaryRetry)) {
5649
- this.logInference({
5650
- timestamp: startTime,
5651
- agentName: agent.name,
5652
- requestId,
5653
- success: true,
5654
- request: fallbackRetryRequestForLog,
5655
- response: { kind: 'primary_summary_fallback_retry', postSuccessError: err.message },
5656
- durationMs,
5657
- });
5658
- agent.reset();
5659
- this.eventGate?.onInferenceEnded(agent.name);
5660
- this.settleAgent(agent.name, {
5661
- stopReason: 'completed',
5662
- speech: '',
5663
- });
5664
- return;
5665
- }
5666
- await this.holdPrimarySummaryRetryFamily(agent, requestId, primarySummaryRetry, 'primary_summary_fallback_retry_stream_threw', { error: err.message });
5667
- }
5668
- else if (primarySummaryConfig.enabled) {
5669
- await this.updatePrimarySummaryRequestRecord(requestId, (record) => ({
5670
- ...record,
5671
- finalStatus: 'error',
5672
- fallbackHeldReason: err.message,
5673
- }));
5674
- }
5675
4016
  this.emitTrace({
5676
4017
  type: 'inference:failed',
5677
4018
  agentName: agent.name,
5678
4019
  error: err.message,
5679
4020
  stack: err.stack,
5680
4021
  });
5681
- if (primarySummaryRetry) {
5682
- this.logInference({
5683
- timestamp: startTime,
5684
- agentName: agent.name,
5685
- requestId,
5686
- success: false,
5687
- error: `Stream threw: ${err.message}`,
5688
- request: fallbackRetryRequestForLog,
5689
- durationMs,
5690
- });
5691
- agent.reset();
5692
- this.eventGate?.onInferenceEnded(agent.name);
5693
- this.settleAgent(agent.name, {
5694
- stopReason: 'completed',
5695
- speech: '',
5696
- });
5697
- return;
5698
- }
5699
4022
  this.settleAgent(agent.name, {
5700
4023
  stopReason: 'exhausted',
5701
4024
  speech: '',
@@ -5716,7 +4039,7 @@ export class AgentFramework {
5716
4039
  requestId,
5717
4040
  success: false,
5718
4041
  error: `Stream threw: ${err.message}`,
5719
- request: fallbackRetryRequestForLog,
4042
+ request: compiledRequest ?? { note: 'streaming request threw' },
5720
4043
  durationMs,
5721
4044
  });
5722
4045
  agent.reset();
@@ -5737,7 +4060,6 @@ export class AgentFramework {
5737
4060
  this.channelRegistry?.stopTyping();
5738
4061
  this.frameworkCancelledStreams.delete(`${agent.name}:${myStreamId}`);
5739
4062
  this.activeStreams.delete(agent.name);
5740
- this.activeStreamContexts.delete(agent.name);
5741
4063
  this.pendingAssistantBlocks.delete(agent.name);
5742
4064
  // A conversation fork whose TTL closure turn just finished is done for
5743
4065
  // good — dispose it so the agent map doesn't grow monotonically.
@@ -6224,22 +4546,12 @@ export class AgentFramework {
6224
4546
  * work, per-agent status + last inference activity. Cheap and read-only.
6225
4547
  */
6226
4548
  healthSnapshot() {
6227
- const primarySummary = this.readPrimarySummaryFallbackState();
6228
4549
  return {
6229
4550
  at: new Date().toISOString(),
6230
4551
  uptimeSec: Math.round(process.uptime()),
6231
4552
  gate: this.eventGate?.inferenceDiagnostics() ?? null,
6232
4553
  pendingRequests: this.pendingRequests.length,
6233
4554
  activeStreams: [...this.activeStreams.keys()],
6234
- primarySummaryFallback: {
6235
- requests: primarySummary.requests.length,
6236
- pendingDispatches: primarySummary.requests.filter((record) => record.finalStatus === 'pending').length,
6237
- unresolvedIntents: primarySummary.requests.filter((record) => record.fallbackStatus === 'pending').length,
6238
- held: primarySummary.requests.filter((record) => record.finalStatus === 'held' ||
6239
- record.finalStatus === 'timeout_unknown' ||
6240
- record.fallbackStatus === 'held' ||
6241
- record.fallbackStatus === 'timeout_unknown').length,
6242
- },
6243
4555
  agents: [...this.agents.entries()].map(([name, agent]) => ({
6244
4556
  name,
6245
4557
  status: agent.state.status,