@praxis-ai/praxis 0.1.4 → 0.1.6

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 (64) hide show
  1. package/dist/agentCore/index.d.ts +29 -3
  2. package/dist/agentCore/index.js +3 -0
  3. package/dist/applicationLayer/applicationRuntime.js +7 -1
  4. package/dist/basetool/authoring.d.ts +2 -0
  5. package/dist/basetool/authoring.js +2 -0
  6. package/dist/basetool/catalog.d.ts +1 -1
  7. package/dist/basetool/catalog.js +86 -4
  8. package/dist/basetool/core/index.d.ts +4 -2
  9. package/dist/basetool/core/index.js +8 -0
  10. package/dist/basetool/core/mcpCompletions.d.ts +2 -0
  11. package/dist/basetool/core/mcpCompletions.js +70 -0
  12. package/dist/basetool/core/mcpPrompts.d.ts +2 -0
  13. package/dist/basetool/core/mcpPrompts.js +48 -0
  14. package/dist/basetool/core/mcpResources.js +41 -5
  15. package/dist/basetool/profiles.js +15 -1
  16. package/dist/basetool/registry.d.ts +1 -1
  17. package/dist/basetool/supportCatalog.js +23 -6
  18. package/dist/executionEngine/promptPack/promptAssembler.js +1 -0
  19. package/dist/executionEngine/promptPack/promptDefiner.d.ts +4 -4
  20. package/dist/executionEngine/promptPack/promptDefiner.js +1 -0
  21. package/dist/runtimeImplementation/praxisRuntimeKernel.d.ts +4 -0
  22. package/dist/runtimeImplementation/praxisRuntimeKernel.js +1729 -1498
  23. package/dist/runtimeImplementation/runtime.execEngine/baseToolApprovalScope.js +11 -0
  24. package/dist/runtimeImplementation/runtime.execEngine/baseToolExecutorPortFactory.js +13 -1
  25. package/dist/runtimeImplementation/runtime.execEngine/baseToolPolicyAdjudicator.js +14 -0
  26. package/dist/runtimeImplementation/runtime.execEngine/mcpRuntimeAdapter.d.ts +27 -0
  27. package/dist/runtimeImplementation/runtime.execEngine/mcpRuntimeAdapter.js +648 -56
  28. package/dist/runtimeImplementation/runtime.execEngine/promptContextAssembly.d.ts +2 -0
  29. package/dist/runtimeImplementation/runtime.execEngine/promptContextAssembly.js +35 -0
  30. package/dist/runtimeImplementation/runtime.mcpPlane/index.d.ts +20 -7
  31. package/dist/runtimeImplementation/runtime.mcpPlane/index.js +105 -89
  32. package/dist/runtimeImplementation/runtime.skillPlane/index.d.ts +119 -0
  33. package/dist/runtimeImplementation/runtime.skillPlane/index.js +274 -0
  34. package/dist/runtimeImplementation/runtimeAgentManifest.js +2 -0
  35. package/dist/toolBase/catalog.d.ts +24 -0
  36. package/dist/toolBase/catalog.js +41 -3
  37. package/dist/toolBase/profiles.d.ts +3 -3
  38. package/dist/toolBase/profiles.js +2 -0
  39. package/dist/toolBase/types.d.ts +1 -1
  40. package/examples/fullstack/agents/repoInspector/harness/repoInspectorHarness.ts +23 -0
  41. package/examples/raxode-mcp-plus-ten-server.config.json +229 -0
  42. package/examples/scripts/README.md +8 -2
  43. package/examples/scripts/mcp-plus-native-smoke.ts +1296 -0
  44. package/package.json +3 -1
  45. package/raxode-tui/dist/raxode-cli/backend/agents/codingAgent/prompts/tool-use.md +1 -1
  46. package/raxode-tui/dist/raxode-cli/backend/application/mcpConfig.d.ts +9 -0
  47. package/raxode-tui/dist/raxode-cli/backend/application/mcpConfig.js +65 -0
  48. package/raxode-tui/dist/raxode-cli/backend/application/mcpReadinessSummary.d.ts +28 -0
  49. package/raxode-tui/dist/raxode-cli/backend/application/mcpReadinessSummary.js +57 -0
  50. package/raxode-tui/dist/raxode-cli/backend/application/runtimeReadiness.d.ts +5 -1
  51. package/raxode-tui/dist/raxode-cli/backend/application/runtimeReadiness.js +40 -0
  52. package/raxode-tui/dist/raxode-cli/backend/application/stdioApplicationServer.js +6 -0
  53. package/raxode-tui/dist/raxode-cli/backend/directApplicationBackend.d.ts +4 -0
  54. package/raxode-tui/dist/raxode-cli/backend/directApplicationBackend.js +14 -0
  55. package/raxode-tui/dist/raxode-cli/backend/raxodeBackend.d.ts +1 -1
  56. package/raxode-tui/dist/raxode-cli/backend/raxodeBackend.js +16 -1
  57. package/raxode-tui/dist/raxode-cli/contracts.d.ts +1 -0
  58. package/raxode-tui/dist/raxode-cli/frontend/bridge/readiness.js +24 -0
  59. package/raxode-tui/dist/raxode-cli/frontend/tui/app/direct-tui.js +35 -0
  60. package/raxode-tui/dist/raxode-cli/frontend/tui/cli/raxode-cli.d.ts +2 -0
  61. package/raxode-tui/dist/raxode-cli/frontend/tui/cli/raxode-cli.js +8 -0
  62. package/raxode-tui/dist/raxode-cli/frontend/tui/config/raxode-config.d.ts +31 -0
  63. package/raxode-tui/dist/raxode-cli/frontend/tui/config/raxode-config.js +129 -0
  64. package/raxode-tui/dist/raxode-cli/index.d.ts +1 -0
@@ -27,6 +27,7 @@ import { evaluateBaseToolRuntimeGovernance } from "./runtime.execEngine/baseTool
27
27
  import { preflightBaseToolDependencies, } from "./runtime.execEngine/baseToolDependencyRuntime.js";
28
28
  import { applyBaseToolContextUsage, createBaseToolContextHeatState, } from "./runtime.execEngine/baseToolContextFolding.js";
29
29
  import { assemblePromptContextMaterials, PRAXIS_BASE_TOOL_CALLING_PROTOCOL, } from "./runtime.execEngine/promptContextAssembly.js";
30
+ import { createFileSkillPlaneStore, loadSkillHeadsFromSources, renderSkillIndexMaterial, skillPlaneModuleFrom, } from "./runtime.skillPlane/index.js";
30
31
  import { invokeMountedBaseTool } from "./runtime.execEngine/baseToolRuntimeMount.js";
31
32
  import { evaluateBaseToolRuntimeReadiness } from "./runtime.execEngine/baseToolSupportCatalog.js";
32
33
  import { adjudicateBaseToolPolicy, } from "./runtime.execEngine/baseToolPolicyAdjudicator.js";
@@ -241,7 +242,11 @@ function runtimeGrantedPermissionsForTool(toolId, _profile) {
241
242
  case "mcp.use":
242
243
  return ["mcp:call", "mcp:auth"];
243
244
  case "mcp.resources":
244
- return ["mcp:resource:list", "mcp:resource:read"];
245
+ return ["mcp:resource:list", "mcp:resource:template:list", "mcp:resource:read", "mcp:resource:subscribe"];
246
+ case "mcp.prompts":
247
+ return ["mcp:prompt:list", "mcp:prompt:get"];
248
+ case "mcp.completions":
249
+ return ["mcp:completion"];
245
250
  case "media.viewImage":
246
251
  return ["media:image:read", "filesystem:read"];
247
252
  case "process.wait":
@@ -349,22 +354,56 @@ function runtimeMcpPlusProjectId(input) {
349
354
  return input.explicit.trim();
350
355
  return `project.${createHash("sha256").update(path.resolve(input.workspaceRoot)).digest("hex").slice(0, 16)}`;
351
356
  }
352
- function mcpPlusOverlayToExposureState(overlay) {
357
+ function readMcpPlusOverlayState(overlay) {
353
358
  if (overlay === undefined)
359
+ return undefined;
360
+ return {
361
+ mode: overlay.state?.mode ?? overlay.mode ?? "expanded",
362
+ activeTools: [...(overlay.state?.activeTools ?? overlay.activeTools ?? [])],
363
+ pendingReprofile: overlay.state?.pendingReprofile ?? overlay.pendingReprofile,
364
+ counters: {
365
+ consecutiveIndexedToolCalls: {
366
+ ...(overlay.counters?.consecutiveIndexedToolCalls ?? {}),
367
+ ...(overlay.state?.counters.consecutiveIndexedToolCalls ?? {}),
368
+ },
369
+ },
370
+ };
371
+ }
372
+ function mcpPlusOverlayToExposureState(overlay) {
373
+ const state = readMcpPlusOverlayState(overlay);
374
+ if (state === undefined)
354
375
  return {};
355
376
  return {
356
- mode: overlay.mode,
377
+ mode: state.mode,
357
378
  activeTools: [
358
- ...overlay.activeTools,
359
- ...(overlay.pendingReprofile ? ["mcp_plus.reprofile"] : []),
379
+ ...state.activeTools,
380
+ ...(state.pendingReprofile ? ["mcp_plus.reprofile"] : []),
360
381
  ],
361
382
  };
362
383
  }
384
+ function withMcpPlusOverlayState(overlay, state, input) {
385
+ const { mode: _legacyMode, activeTools: _legacyActiveTools, pendingReprofile: _legacyPendingReprofile, counters: _legacyCounters, ...base } = overlay;
386
+ return {
387
+ ...base,
388
+ state,
389
+ updatedAt: input.updatedAt,
390
+ ...(input.metadata === undefined ? {} : { metadata: input.metadata }),
391
+ };
392
+ }
363
393
  function readStringList(value) {
364
394
  if (!Array.isArray(value))
365
395
  return [];
366
396
  return value.filter((item) => typeof item === "string" && item.trim().length > 0).map((item) => item.trim());
367
397
  }
398
+ function readStringRecord(value) {
399
+ if (!isRecord(value))
400
+ return undefined;
401
+ const entries = Object.entries(value).filter((entry) => typeof entry[1] === "string");
402
+ return entries.length === 0 ? undefined : Object.fromEntries(entries);
403
+ }
404
+ function readProfileRationale(value) {
405
+ return readString(value) ?? readStringRecord(value);
406
+ }
368
407
  function proposalFromArgs(args, fallbackServerId) {
369
408
  const rawToolCards = isRecord(args.toolCards) ? args.toolCards : undefined;
370
409
  const toolCards = rawToolCards === undefined ? undefined : Object.fromEntries(Object.entries(rawToolCards).flatMap(([toolName, card]) => {
@@ -392,7 +431,7 @@ function proposalFromArgs(args, fallbackServerId) {
392
431
  const summary = readString(chapter.summary);
393
432
  return id === undefined || title === undefined || summary === undefined ? [] : [{ id, title, summary }];
394
433
  }),
395
- rationale: readString(args.rationale),
434
+ rationale: readProfileRationale(args.rationale),
396
435
  };
397
436
  if (Object.hasOwn(args, "modeHint")) {
398
437
  return {
@@ -411,9 +450,11 @@ function createRuntimeMcpPlusController(input) {
411
450
  return await input.overlayStore.load({ sessionId: input.sessionId, serverId }) ?? {
412
451
  serverId,
413
452
  sessionId: input.sessionId,
414
- mode: "expanded",
415
- activeTools: [],
416
- counters: { consecutiveIndexedToolCalls: {} },
453
+ state: {
454
+ mode: "expanded",
455
+ activeTools: [],
456
+ counters: { consecutiveIndexedToolCalls: {} },
457
+ },
417
458
  updatedAt: now,
418
459
  };
419
460
  }
@@ -425,7 +466,7 @@ function createRuntimeMcpPlusController(input) {
425
466
  const module = mcpHarnessModuleFrom(manifest.harness);
426
467
  const profiles = {};
427
468
  for (const server of module?.servers ?? []) {
428
- if (server.mode !== "mcp-plus" || server.manifest !== undefined)
469
+ if (server.mode !== "mcp-plus")
429
470
  continue;
430
471
  profiles[server.serverId] = await input.profileStore.load({ projectId: input.projectId, serverId: server.serverId });
431
472
  }
@@ -441,34 +482,55 @@ function createRuntimeMcpPlusController(input) {
441
482
  }
442
483
  return states;
443
484
  },
485
+ async skillIndexByServerId(manifest) {
486
+ const module = mcpHarnessModuleFrom(manifest.harness);
487
+ const indexes = {};
488
+ for (const server of module?.servers ?? []) {
489
+ if (server.mode !== "mcp-plus")
490
+ continue;
491
+ const notes = await input.skillStore.list({ projectId: input.projectId, serverId: server.serverId });
492
+ indexes[server.serverId] = notes.map((note) => ({
493
+ id: note.id,
494
+ title: note.title,
495
+ summary: note.summary,
496
+ serverId: server.serverId,
497
+ whenToUse: note.whenToUse,
498
+ why: note.why,
499
+ pitfallsPreview: note.pitfalls?.slice(0, 3),
500
+ }));
501
+ }
502
+ return indexes;
503
+ },
444
504
  async callControlTool(control) {
445
505
  if (control.controlName === "mcp_plus.init" || control.controlName === "mcp_plus.reprofile") {
446
506
  const proposal = proposalFromArgs(control.args, control.serverId);
447
507
  const nativeTools = nativeInventory[proposal.serverId] ?? [];
448
508
  const existing = await input.profileStore.load({ projectId: input.projectId, serverId: proposal.serverId });
509
+ const server = mcpPlusServerSpec(control.manifest, proposal.serverId);
449
510
  const learned = learnedProfileFromProposal({
450
511
  proposal,
451
512
  nativeTools,
452
513
  projectId: input.projectId,
453
514
  now: control.now,
454
515
  existing,
516
+ protectedAlwaysIndexTools: server?.manifest?.exposure?.alwaysIndexTools,
455
517
  });
456
518
  if (!learned.ok)
457
519
  return { ok: false, error: learned.error };
458
520
  await input.profileStore.save({ projectId: input.projectId, serverId: proposal.serverId }, learned.profile);
459
521
  const overlay = await loadOverlay(proposal.serverId, control.now);
460
- await input.overlayStore.save({ sessionId: input.sessionId, serverId: proposal.serverId }, {
461
- ...overlay,
522
+ await input.overlayStore.save({ sessionId: input.sessionId, serverId: proposal.serverId }, withMcpPlusOverlayState(overlay, {
462
523
  mode: "expanded",
463
524
  activeTools: [],
464
525
  pendingReprofile: false,
465
526
  counters: { consecutiveIndexedToolCalls: {} },
527
+ }, {
466
528
  updatedAt: control.now,
467
529
  metadata: {
468
530
  ...(overlay.metadata ?? {}),
469
531
  lastProfileControl: control.controlName,
470
532
  },
471
- });
533
+ }));
472
534
  return {
473
535
  ok: true,
474
536
  output: {
@@ -530,12 +592,16 @@ function createRuntimeMcpPlusController(input) {
530
592
  ? indexedTools
531
593
  : indexedTools.filter((toolName) => toolName.toLowerCase().includes(request));
532
594
  const overlay = await loadOverlay(serverId, control.now);
533
- await input.overlayStore.save({ sessionId: input.sessionId, serverId }, {
534
- ...overlay,
595
+ const overlayState = readMcpPlusOverlayState(overlay) ?? {
535
596
  mode: "expanded",
536
- activeTools: [...new Set([...overlay.activeTools, ...activatedTools])],
537
- updatedAt: control.now,
538
- });
597
+ activeTools: [],
598
+ counters: { consecutiveIndexedToolCalls: {} },
599
+ };
600
+ await input.overlayStore.save({ sessionId: input.sessionId, serverId }, withMcpPlusOverlayState(overlay, {
601
+ ...overlayState,
602
+ mode: "expanded",
603
+ activeTools: [...new Set([...overlayState.activeTools, ...activatedTools])],
604
+ }, { updatedAt: control.now }));
539
605
  return { ok: true, output: { serverId, activatedTools, mode: "expanded" } };
540
606
  }
541
607
  return { ok: false, error: { code: "MCP_PLUS_CONTROL_UNKNOWN", message: `Unknown MCP+ control tool: ${control.controlName}`, publicSafe: true } };
@@ -558,9 +624,19 @@ function createRuntimeMcpPlusController(input) {
558
624
  : undefined;
559
625
  const indexedTools = new Set(server.manifest?.exposure?.indexedTools ?? profile?.exposure.indexedTools ?? []);
560
626
  const overlay = await loadOverlay(serverId, record.now);
561
- const consecutiveIndexedToolCalls = { ...overlay.counters.consecutiveIndexedToolCalls };
562
- let pendingReprofile = overlay.pendingReprofile;
627
+ const overlayState = readMcpPlusOverlayState(overlay) ?? {
628
+ mode: "expanded",
629
+ activeTools: [],
630
+ counters: { consecutiveIndexedToolCalls: {} },
631
+ };
632
+ const consecutiveIndexedToolCalls = { ...overlayState.counters.consecutiveIndexedToolCalls };
633
+ let pendingReprofile = overlayState.pendingReprofile;
563
634
  if (indexedTools.has(nativeToolName)) {
635
+ for (const toolName of Object.keys(consecutiveIndexedToolCalls)) {
636
+ if (toolName !== nativeToolName) {
637
+ consecutiveIndexedToolCalls[toolName] = 0;
638
+ }
639
+ }
564
640
  consecutiveIndexedToolCalls[nativeToolName] = (consecutiveIndexedToolCalls[nativeToolName] ?? 0) + 1;
565
641
  if (consecutiveIndexedToolCalls[nativeToolName] >= input.reprofileConsecutiveIndexedCalls) {
566
642
  pendingReprofile = true;
@@ -571,38 +647,173 @@ function createRuntimeMcpPlusController(input) {
571
647
  consecutiveIndexedToolCalls[toolName] = 0;
572
648
  }
573
649
  }
574
- await input.overlayStore.save({ sessionId: input.sessionId, serverId }, {
575
- ...overlay,
650
+ await input.overlayStore.save({ sessionId: input.sessionId, serverId }, withMcpPlusOverlayState(overlay, {
576
651
  mode: "expanded",
577
- activeTools: [...new Set([...overlay.activeTools, nativeToolName])],
652
+ activeTools: [...new Set([...overlayState.activeTools, nativeToolName])],
578
653
  pendingReprofile,
579
654
  counters: { consecutiveIndexedToolCalls },
580
- updatedAt: record.now,
581
- });
655
+ }, { updatedAt: record.now }));
582
656
  return true;
583
657
  },
584
658
  };
585
659
  }
586
- async function discoverRuntimeMcpDynamicTools(manifest, executor, mcpPlusRuntime) {
587
- const profiles = buildMcpServerProfilesFromManifest(manifest);
588
- if (profiles.length === 0 || executor.mcp?.listTools === undefined)
660
+ function mcpPlusPromptLine(label, value) {
661
+ const normalized = value?.trim();
662
+ return normalized === undefined || normalized.length === 0 ? undefined : `${label}: ${normalized}`;
663
+ }
664
+ function renderMcpPlusNativePrelude(plan) {
665
+ const mcpPlusServers = plan.servers.filter((server) => server.mode === "mcp-plus");
666
+ if (mcpPlusServers.length === 0)
589
667
  return [];
668
+ const sections = mcpPlusServers.map((server) => {
669
+ const sidecar = server.surface.sidecar;
670
+ const visibleTools = server.surface.tools
671
+ .map((tool) => tool.name)
672
+ .filter((name) => typeof name === "string" && name.trim().length > 0);
673
+ const lines = [
674
+ `Server: ${server.serverId}`,
675
+ mcpPlusPromptLine("Mode", sidecar.serverCard.mode),
676
+ mcpPlusPromptLine("Title", sidecar.serverCard.title),
677
+ mcpPlusPromptLine("Summary", sidecar.serverCard.summary),
678
+ `Visible tools: ${visibleTools.join(", ") || "none"}`,
679
+ ].filter((line) => line !== undefined);
680
+ if (sidecar.toolIndex.length > 0) {
681
+ lines.push("Tool index:");
682
+ for (const card of sidecar.toolIndex) {
683
+ lines.push(`- ${card.activation.toolName} (${card.id}): ${card.title}. ${card.summary}`);
684
+ }
685
+ }
686
+ if (sidecar.skillIndex.length > 0) {
687
+ lines.push("Skill index:");
688
+ for (const skill of sidecar.skillIndex) {
689
+ lines.push(`- ${skill.id}: ${skill.title}. ${skill.summary}`);
690
+ }
691
+ }
692
+ return lines.join("\n");
693
+ });
694
+ return [{
695
+ id: "runtime:mcp-plus-native-exposure",
696
+ kind: "tool-summary",
697
+ text: [
698
+ "# MCP+ Native Exposure",
699
+ "",
700
+ "MCP+ is a compact exposure layer over standard MCP. Praxis owns the runtime lifecycle; MCP remains the protocol boundary.",
701
+ "Use visible pinned or active MCP tools directly. Use indexed tool cards to decide when mcp_plus.expand or mcp_plus.reprofile is needed.",
702
+ "Use skill index cards to decide when mcp_plus.skill_read is relevant. Full skill bodies are read on demand and must not be assumed from this prefix-cached index.",
703
+ "",
704
+ sections.join("\n\n"),
705
+ ].join("\n"),
706
+ source: "runtime.mcpPlus.nativeExposure",
707
+ sourceCategory: "declared-built-in",
708
+ priority: 95.5,
709
+ trusted: true,
710
+ scope: "runtime.mcpPlus.exposure",
711
+ promptSegmentKind: "toolDeclarations",
712
+ metadata: {
713
+ promptSegmentKind: "toolDeclarations",
714
+ toolMaterialType: "policy",
715
+ mcpPlusServerIds: mcpPlusServers.map((server) => server.serverId),
716
+ },
717
+ }];
718
+ }
719
+ function mergeStoredSkillIndexIntoMcpPlusPlan(plan, skillIndexByServerId) {
720
+ return {
721
+ servers: plan.servers.map((server) => {
722
+ if (server.mode !== "mcp-plus")
723
+ return server;
724
+ const storedSkillIndex = skillIndexByServerId[server.serverId] ?? [];
725
+ if (storedSkillIndex.length === 0)
726
+ return server;
727
+ const existingSkillIds = new Set(server.surface.sidecar.skillIndex.map((skill) => skill.id));
728
+ const mergedSkillIndex = [
729
+ ...server.surface.sidecar.skillIndex,
730
+ ...storedSkillIndex.filter((skill) => !existingSkillIds.has(skill.id)),
731
+ ];
732
+ return {
733
+ ...server,
734
+ surface: {
735
+ ...server.surface,
736
+ sidecar: {
737
+ ...server.surface.sidecar,
738
+ skillIndex: mergedSkillIndex,
739
+ },
740
+ },
741
+ };
742
+ }),
743
+ };
744
+ }
745
+ async function discoverRuntimeMcpDynamicSurface(manifest, executor, mcpPlusRuntime) {
746
+ const profiles = buildMcpServerProfilesFromManifest(manifest);
747
+ if (profiles.length === 0 || executor.mcp?.listTools === undefined) {
748
+ return { tools: [], toolDeclarationPreludeMaterials: [] };
749
+ }
590
750
  const inventory = {};
591
751
  for (const profile of profiles) {
592
- const listed = await executor.mcp.listTools({ serverId: profile.serverId });
593
- if (listed?.ok !== true)
594
- continue;
595
- const rawTools = Array.isArray(listed.output?.tools) ? listed.output.tools : [];
752
+ const rawTools = [];
753
+ let cursor;
754
+ for (let page = 0; page < 100; page += 1) {
755
+ const listed = await executor.mcp.listTools({ serverId: profile.serverId, ...(cursor === undefined ? {} : { cursor }) });
756
+ if (listed?.ok !== true)
757
+ break;
758
+ if (Array.isArray(listed.output?.tools))
759
+ rawTools.push(...listed.output.tools);
760
+ const nextCursor = typeof listed.output?.nextCursor === "string" ? listed.output.nextCursor : undefined;
761
+ if (nextCursor === undefined || nextCursor === cursor)
762
+ break;
763
+ cursor = nextCursor;
764
+ }
596
765
  inventory[profile.serverId] = rawTools
597
766
  .map(normalizeMcpNativeToolDeclaration)
598
767
  .filter((tool) => tool !== undefined);
599
768
  }
600
769
  await mcpPlusRuntime?.setNativeInventory(inventory);
601
- return planMcpHarnessExposure(manifest, inventory, await mcpPlusRuntime?.exposureStateByServerId(manifest) ?? {}, await mcpPlusRuntime?.learnedProfilesByServerId(manifest) ?? {}).servers.flatMap((server) => [...server.dynamicToolSpecs]);
770
+ const plan = planMcpHarnessExposure(manifest, inventory, await mcpPlusRuntime?.exposureStateByServerId(manifest) ?? {}, await mcpPlusRuntime?.learnedProfilesByServerId(manifest) ?? {});
771
+ const promptPlan = mergeStoredSkillIndexIntoMcpPlusPlan(plan, await mcpPlusRuntime?.skillIndexByServerId(manifest) ?? {});
772
+ return {
773
+ tools: plan.servers.flatMap((server) => [...server.dynamicToolSpecs]),
774
+ toolDeclarationPreludeMaterials: renderMcpPlusNativePrelude(promptPlan),
775
+ };
776
+ }
777
+ async function discoverRuntimeSkillIndexMaterials(input) {
778
+ const skillModule = skillPlaneModuleFrom(input.manifest.harness);
779
+ if (skillModule === undefined)
780
+ return [];
781
+ const includeScopes = skillModule.indexPolicy.includeScopes;
782
+ function scopeMatchesIndexPolicy(head) {
783
+ const scopes = includeScopes;
784
+ if (scopes.length === 0)
785
+ return true;
786
+ return head.scope !== undefined && scopes.includes(head.scope);
787
+ }
788
+ const bySkillId = new Map();
789
+ for (const head of await loadSkillHeadsFromSources(skillModule.sources, { workspaceRoot: input.workspaceRoot })) {
790
+ if (scopeMatchesIndexPolicy(head) && !bySkillId.has(head.skillId)) {
791
+ bySkillId.set(head.skillId, head);
792
+ }
793
+ }
794
+ for (const head of await input.store.listHeads({ scopes: skillModule.indexPolicy.includeScopes })) {
795
+ if (!bySkillId.has(head.skillId)) {
796
+ bySkillId.set(head.skillId, head);
797
+ }
798
+ }
799
+ const heads = [...bySkillId.values()].slice(0, skillModule.indexPolicy.maxHeads);
800
+ return heads.length === 0 ? [] : [renderSkillIndexMaterial(heads)];
602
801
  }
603
802
  function providerToolMappings(manifest) {
604
803
  return createProviderToolMappings(manifest.harness.tools);
605
804
  }
805
+ async function shutdownRuntimeOwnedExecutor(executor, events) {
806
+ const shutdown = executor?.mcp?.__praxisRuntimeOwnedShutdown;
807
+ if (typeof shutdown !== "function")
808
+ return;
809
+ try {
810
+ const result = await shutdown({ reason: "runtime.runManifest.finished" });
811
+ events.push(result?.ok === false ? "runtime.mcp.shutdown.failed" : "runtime.mcp.shutdown.completed");
812
+ }
813
+ catch {
814
+ events.push("runtime.mcp.shutdown.failed");
815
+ }
816
+ }
606
817
  function providerToolSchemaFamilyForModel(model) {
607
818
  if (model.provider === "anthropic" || model.endpointShape === "messages")
608
819
  return "anthropicMessages";
@@ -2578,6 +2789,8 @@ async function buildPromptPackAndLower(input) {
2578
2789
  budget: contextWindowTokens === undefined ? undefined : { contextWindowTokens },
2579
2790
  sessionSummary: input.sessionSummary,
2580
2791
  conversationWindow: input.conversationWindow,
2792
+ toolDeclarationPreludeMaterials: input.toolDeclarationPreludeMaterials,
2793
+ skillIndexMaterials: input.skillIndexMaterials,
2581
2794
  projectContextGovernanceMaterials: input.projectContextGovernanceMaterials?.map((material) => ({
2582
2795
  id: material.id,
2583
2796
  kind: "runtime",
@@ -3926,194 +4139,168 @@ export class PraxisRuntimeKernel {
3926
4139
  events.push(runtimeEvent.type);
3927
4140
  },
3928
4141
  });
3929
- const modelCaller = {
3930
- kind: "application",
3931
- id: "praxis-runtime-kernel",
3932
- sessionId,
3933
- };
3934
- const mcpPlusRuntime = createRuntimeMcpPlusController({
3935
- sessionId,
3936
- projectId: runtimeMcpPlusProjectId({
3937
- explicit: options.mcpPlus?.projectId,
4142
+ const runtimeOwnedExecutor = options.executor === undefined ? executor : undefined;
4143
+ try {
4144
+ const modelCaller = {
4145
+ kind: "application",
4146
+ id: "praxis-runtime-kernel",
4147
+ sessionId,
4148
+ };
4149
+ const mcpPlusRuntime = createRuntimeMcpPlusController({
4150
+ sessionId,
4151
+ projectId: runtimeMcpPlusProjectId({
4152
+ explicit: options.mcpPlus?.projectId,
4153
+ workspaceRoot: toolWorkspaceRoot,
4154
+ }),
4155
+ profileStore: options.mcpPlus?.profileStore ?? createFileMcpPlusProfileStore(path.join(toolWorkspaceRoot, ".rax_workspace", "mcp-plus")),
4156
+ overlayStore: options.mcpPlus?.overlayStore ?? createInMemoryMcpPlusOverlayStore(),
4157
+ skillStore: options.mcpPlus?.skillStore ?? createFileMcpPlusSkillStore(path.join(toolWorkspaceRoot, ".rax_workspace", "mcp-plus")),
4158
+ reprofileConsecutiveIndexedCalls: options.mcpPlus?.reprofileConsecutiveIndexedCalls ?? 6,
4159
+ });
4160
+ const maxModelTurns = manifest.harness.loop.maxModelTurns ?? 2;
4161
+ const maxToolCalls = manifest.harness.loop.maxToolCalls ?? 4;
4162
+ let toolMappings = [];
4163
+ let toolDeclarationPreludeMaterials = [];
4164
+ const skillIndexMaterials = await discoverRuntimeSkillIndexMaterials({
4165
+ manifest,
3938
4166
  workspaceRoot: toolWorkspaceRoot,
3939
- }),
3940
- profileStore: options.mcpPlus?.profileStore ?? createFileMcpPlusProfileStore(path.join(toolWorkspaceRoot, ".rax_workspace", "mcp-plus")),
3941
- overlayStore: options.mcpPlus?.overlayStore ?? createInMemoryMcpPlusOverlayStore(),
3942
- skillStore: options.mcpPlus?.skillStore ?? createFileMcpPlusSkillStore(path.join(toolWorkspaceRoot, ".rax_workspace", "mcp-plus")),
3943
- reprofileConsecutiveIndexedCalls: options.mcpPlus?.reprofileConsecutiveIndexedCalls ?? 6,
3944
- });
3945
- const maxModelTurns = manifest.harness.loop.maxModelTurns ?? 2;
3946
- const maxToolCalls = manifest.harness.loop.maxToolCalls ?? 4;
3947
- let toolMappings = [];
3948
- async function refreshRuntimeMcpTools(reason) {
3949
- manifest = withRuntimeHarnessToolLayer(runtimeMcpBaseManifest, await discoverRuntimeMcpDynamicTools(runtimeMcpBaseManifest, executor, mcpPlusRuntime), reason);
3950
- toolMappings = providerToolMappings(manifest);
3951
- }
3952
- await refreshRuntimeMcpTools("session.checkpoint.start");
3953
- const providerFamily = providerToolSchemaFamilyForModel(manifest.model);
3954
- let toolContextSelection = {
3955
- families: normalizedSelection(options.toolContextSelection?.families),
3956
- groups: normalizedSelection(options.toolContextSelection?.groups),
3957
- toolIds: normalizedSelection(options.toolContextSelection?.toolIds),
3958
- };
3959
- const providerResponseOutputItems = [];
3960
- let previousProviderResponse = options.previousProviderResponse;
3961
- let toolContextHeatState = createBaseToolContextHeatState({
3962
- agentId: manifest.identity.id,
3963
- sessionId,
3964
- usage: options.toolContextUsage,
3965
- updatedAt: createdAt,
3966
- });
3967
- let compactSessionSummary = options.sessionSummary;
3968
- let compactConversationWindow = options.conversationWindow;
3969
- let preCompactGovernanceProjectContextMaterials = [];
3970
- const compactInputBudgetTokens = manifestCompactInputBudgetTokens(manifest, options.compactContextWindowTokens);
3971
- const compactExecutor = options.compactExecutor ?? createRuntimeFallbackCompactExecutor();
3972
- const preCompactGovernanceExecutor = options.preCompactGovernanceExecutor
3973
- ?? createNoopPreCompactGovernanceExecutor();
3974
- const preCompactGovernanceEnabled = options.preCompactGovernanceEnabled ?? options.preCompactGovernanceExecutor !== undefined;
3975
- let previousModelCacheDebug;
3976
- let runnerError;
3977
- const runnerResult = await runMainLoopEngine({
3978
- sessionId,
3979
- recorder: {
3980
- recordEvent: async (coreEvent) => {
3981
- await store.appendEvent(event(sessionId, `event:mainLoopEngine:${coreEvent.eventId}`, `runtime.mainLoop.${coreEvent.name}`, coreEvent.createdAt, coreEvent.payload));
4167
+ store: options.skillPlane?.store ?? createFileSkillPlaneStore(path.join(toolWorkspaceRoot, ".rax_workspace", "skills")),
4168
+ });
4169
+ async function refreshRuntimeMcpTools(reason) {
4170
+ const dynamicSurface = await discoverRuntimeMcpDynamicSurface(runtimeMcpBaseManifest, executor, mcpPlusRuntime);
4171
+ manifest = withRuntimeHarnessToolLayer(runtimeMcpBaseManifest, dynamicSurface.tools, reason);
4172
+ toolDeclarationPreludeMaterials = dynamicSurface.toolDeclarationPreludeMaterials;
4173
+ toolMappings = providerToolMappings(manifest);
4174
+ }
4175
+ await refreshRuntimeMcpTools("session.checkpoint.start");
4176
+ const providerFamily = providerToolSchemaFamilyForModel(manifest.model);
4177
+ let toolContextSelection = {
4178
+ families: normalizedSelection(options.toolContextSelection?.families),
4179
+ groups: normalizedSelection(options.toolContextSelection?.groups),
4180
+ toolIds: normalizedSelection(options.toolContextSelection?.toolIds),
4181
+ };
4182
+ const providerResponseOutputItems = [];
4183
+ let previousProviderResponse = options.previousProviderResponse;
4184
+ let toolContextHeatState = createBaseToolContextHeatState({
4185
+ agentId: manifest.identity.id,
4186
+ sessionId,
4187
+ usage: options.toolContextUsage,
4188
+ updatedAt: createdAt,
4189
+ });
4190
+ let compactSessionSummary = options.sessionSummary;
4191
+ let compactConversationWindow = options.conversationWindow;
4192
+ let preCompactGovernanceProjectContextMaterials = [];
4193
+ const compactInputBudgetTokens = manifestCompactInputBudgetTokens(manifest, options.compactContextWindowTokens);
4194
+ const compactExecutor = options.compactExecutor ?? createRuntimeFallbackCompactExecutor();
4195
+ const preCompactGovernanceExecutor = options.preCompactGovernanceExecutor
4196
+ ?? createNoopPreCompactGovernanceExecutor();
4197
+ const preCompactGovernanceEnabled = options.preCompactGovernanceEnabled ?? options.preCompactGovernanceExecutor !== undefined;
4198
+ let previousModelCacheDebug;
4199
+ let runnerError;
4200
+ const runnerResult = await runMainLoopEngine({
4201
+ sessionId,
4202
+ recorder: {
4203
+ recordEvent: async (coreEvent) => {
4204
+ await store.appendEvent(event(sessionId, `event:mainLoopEngine:${coreEvent.eventId}`, `runtime.mainLoop.${coreEvent.name}`, coreEvent.createdAt, coreEvent.payload));
4205
+ },
4206
+ recordStep: async (stepRecord) => {
4207
+ await recordMainLoopStep({
4208
+ store,
4209
+ sessionId,
4210
+ createdAt: now(),
4211
+ events,
4212
+ mainLoopSteps,
4213
+ step: stepRecord,
4214
+ });
4215
+ },
4216
+ recordTurnState: async (turnState) => {
4217
+ await store.appendState(state(sessionId, `state:mainLoopEngine:${turnState.turnId}:${turnState.revision}`, turnState.phase, turnState.updatedAt, {
4218
+ turnId: turnState.turnId,
4219
+ turnIndex: turnState.turnIndex,
4220
+ revision: turnState.revision,
4221
+ resumeToken: turnState.resumeToken,
4222
+ budgetUsage: turnState.budgetUsage,
4223
+ pendingInputQueueSize: turnState.pendingInputQueue.length,
4224
+ observationRefs: turnState.observationRefs,
4225
+ }));
4226
+ },
3982
4227
  },
3983
- recordStep: async (stepRecord) => {
3984
- await recordMainLoopStep({
3985
- store,
4228
+ interruptSignal: options.interruptSignal,
4229
+ maxModelTurns,
4230
+ maxToolCalls,
4231
+ prepareTurn: async (turn) => {
4232
+ await store.appendState(state(sessionId, `state:model:${turn + 1}`, "model", now(), { turn }));
4233
+ const stepBase = turn * 20 + 2;
4234
+ let prompt = await buildPromptPackAndLower({
4235
+ runtimeId,
3986
4236
  sessionId,
3987
- createdAt: now(),
4237
+ manifest,
4238
+ task: input.input.normalizedText,
4239
+ turnIndex: turn,
4240
+ startStepIndex: stepBase,
4241
+ workspaceRoot: toolWorkspaceRoot,
4242
+ allowedRoots: toolAllowedRoots,
4243
+ now: now(),
4244
+ modelCaller,
4245
+ toolMappings,
4246
+ observations,
3988
4247
  events,
3989
- mainLoopSteps,
3990
- step: stepRecord,
4248
+ contextWindowTokens: compactInputBudgetTokens,
4249
+ sessionSummary: compactSessionSummary,
4250
+ conversationWindow: compactConversationWindow,
4251
+ toolDeclarationPreludeMaterials,
4252
+ skillIndexMaterials,
4253
+ projectContextGovernanceMaterials: preCompactGovernanceProjectContextMaterials,
4254
+ toolContextSelection,
4255
+ toolContextUsage: toolContextHeatState.usage,
3991
4256
  });
3992
- },
3993
- recordTurnState: async (turnState) => {
3994
- await store.appendState(state(sessionId, `state:mainLoopEngine:${turnState.turnId}:${turnState.revision}`, turnState.phase, turnState.updatedAt, {
3995
- turnId: turnState.turnId,
3996
- turnIndex: turnState.turnIndex,
3997
- revision: turnState.revision,
3998
- resumeToken: turnState.resumeToken,
3999
- budgetUsage: turnState.budgetUsage,
4000
- pendingInputQueueSize: turnState.pendingInputQueue.length,
4001
- observationRefs: turnState.observationRefs,
4002
- }));
4003
- },
4004
- },
4005
- interruptSignal: options.interruptSignal,
4006
- maxModelTurns,
4007
- maxToolCalls,
4008
- prepareTurn: async (turn) => {
4009
- await store.appendState(state(sessionId, `state:model:${turn + 1}`, "model", now(), { turn }));
4010
- const stepBase = turn * 20 + 2;
4011
- let prompt = await buildPromptPackAndLower({
4012
- runtimeId,
4013
- sessionId,
4014
- manifest,
4015
- task: input.input.normalizedText,
4016
- turnIndex: turn,
4017
- startStepIndex: stepBase,
4018
- workspaceRoot: toolWorkspaceRoot,
4019
- allowedRoots: toolAllowedRoots,
4020
- now: now(),
4021
- modelCaller,
4022
- toolMappings,
4023
- observations,
4024
- events,
4025
- contextWindowTokens: compactInputBudgetTokens,
4026
- sessionSummary: compactSessionSummary,
4027
- conversationWindow: compactConversationWindow,
4028
- projectContextGovernanceMaterials: preCompactGovernanceProjectContextMaterials,
4029
- toolContextSelection,
4030
- toolContextUsage: toolContextHeatState.usage,
4031
- });
4032
- toolContextSelection = { families: [], groups: [], toolIds: [] };
4033
- events.push(...prompt.events);
4034
- if (!prompt.ok) {
4035
- await recordKernelError({ store, sessionId, errorId: `error:prompt:${turn + 1}`, error: prompt.error, createdAt: now() });
4036
- await recordMainLoopStep({
4037
- store,
4038
- sessionId,
4039
- createdAt: now(),
4040
- events,
4041
- mainLoopSteps,
4042
- step: createMainLoopStepRecord({
4257
+ toolContextSelection = { families: [], groups: [], toolIds: [] };
4258
+ events.push(...prompt.events);
4259
+ if (!prompt.ok) {
4260
+ await recordKernelError({ store, sessionId, errorId: `error:prompt:${turn + 1}`, error: prompt.error, createdAt: now() });
4261
+ await recordMainLoopStep({
4262
+ store,
4043
4263
  sessionId,
4044
- turnIndex: turn,
4045
- stepIndex: stepBase,
4046
- actionPrimitive: "assemblePromptPack",
4047
- status: "failed",
4048
- inputRefs: ["runtime.input.normalized", ...observations.map((observation) => observation.observationId)],
4264
+ createdAt: now(),
4265
+ events,
4266
+ mainLoopSteps,
4267
+ step: createMainLoopStepRecord({
4268
+ sessionId,
4269
+ turnIndex: turn,
4270
+ stepIndex: stepBase,
4271
+ actionPrimitive: "assemblePromptPack",
4272
+ status: "failed",
4273
+ inputRefs: ["runtime.input.normalized", ...observations.map((observation) => observation.observationId)],
4274
+ error: {
4275
+ code: prompt.error.code,
4276
+ message: prompt.error.message,
4277
+ boundary: "prompt",
4278
+ publicSafe: true,
4279
+ },
4280
+ now: now(),
4281
+ }),
4282
+ });
4283
+ return {
4284
+ ok: false,
4049
4285
  error: {
4050
4286
  code: prompt.error.code,
4051
4287
  message: prompt.error.message,
4052
4288
  boundary: "prompt",
4053
4289
  publicSafe: true,
4054
4290
  },
4055
- now: now(),
4056
- }),
4057
- });
4058
- return {
4059
- ok: false,
4060
- error: {
4061
- code: prompt.error.code,
4062
- message: prompt.error.message,
4063
- boundary: "prompt",
4064
- publicSafe: true,
4065
- },
4066
- events: prompt.events,
4067
- };
4068
- }
4069
- for (const turnStep of prompt.turnRecord.stepRecords) {
4070
- await recordMainLoopStep({
4071
- store,
4072
- sessionId,
4073
- createdAt: now(),
4074
- events,
4075
- mainLoopSteps,
4076
- step: turnStep,
4077
- });
4078
- }
4079
- await recordMainLoopStep({
4080
- store,
4081
- sessionId,
4082
- createdAt: now(),
4083
- events,
4084
- mainLoopSteps,
4085
- step: createMainLoopStepRecord({
4086
- sessionId,
4087
- turnIndex: turn,
4088
- stepIndex: stepBase + 3,
4089
- actionPrimitive: "lowerPrompt",
4090
- status: "completed",
4091
- inputRefs: [prompt.promptPackId],
4092
- outputRefs: [prompt.loweredPrompt.loweringId],
4093
- promptPackRef: prompt.promptPackId,
4094
- loweredPromptRef: prompt.loweredPrompt.loweringId,
4095
- now: now(),
4096
- metadata: {
4097
- materialRefs: prompt.loweredPrompt.materialRefs,
4098
- targetCapability: prompt.loweredPrompt.target.capabilityId,
4099
- },
4100
- }),
4101
- });
4102
- const contextWindowTokens = compactInputBudgetTokens;
4103
- const promptBoundaryEvents = [...prompt.events];
4104
- if (contextWindowTokens !== undefined) {
4105
- const compactDecision = decideTurnBoundaryCompact({
4106
- trigger: observations.length > 0 ? "toolLoopBoundary" : "turnBoundary",
4107
- estimatedNextPromptTokens: prompt.promptPack.totalEstimatedTokens,
4108
- contextWindowTokens,
4109
- thresholdRatio: options.compactThresholdRatio,
4110
- });
4111
- const compactEvent = compactDecision.shouldCompact
4112
- ? "runtime.contextCompact.thresholdReached"
4113
- : "runtime.contextCompact.thresholdNotReached";
4114
- events.push(compactEvent);
4115
- promptBoundaryEvents.push(compactEvent);
4116
- await store.appendEvent(event(sessionId, `event:contextCompact:decision:${turn + 1}`, "runtime.contextCompact.thresholdDecision", now(), compactDecision));
4291
+ events: prompt.events,
4292
+ };
4293
+ }
4294
+ for (const turnStep of prompt.turnRecord.stepRecords) {
4295
+ await recordMainLoopStep({
4296
+ store,
4297
+ sessionId,
4298
+ createdAt: now(),
4299
+ events,
4300
+ mainLoopSteps,
4301
+ step: turnStep,
4302
+ });
4303
+ }
4117
4304
  await recordMainLoopStep({
4118
4305
  store,
4119
4306
  sessionId,
@@ -4123,921 +4310,694 @@ export class PraxisRuntimeKernel {
4123
4310
  step: createMainLoopStepRecord({
4124
4311
  sessionId,
4125
4312
  turnIndex: turn,
4126
- stepIndex: stepBase + 4,
4127
- actionPrimitive: "updateSummaryStateEvent",
4128
- status: compactDecision.shouldCompact ? "planned" : "completed",
4313
+ stepIndex: stepBase + 3,
4314
+ actionPrimitive: "lowerPrompt",
4315
+ status: "completed",
4129
4316
  inputRefs: [prompt.promptPackId],
4130
- outputRefs: [],
4317
+ outputRefs: [prompt.loweredPrompt.loweringId],
4131
4318
  promptPackRef: prompt.promptPackId,
4319
+ loweredPromptRef: prompt.loweredPrompt.loweringId,
4132
4320
  now: now(),
4133
4321
  metadata: {
4134
- decision: compactDecision,
4135
- note: compactDecision.shouldCompact
4136
- ? "compact should run after this boundary through the configured CompactExecutor"
4137
- : "compact threshold not reached at this boundary",
4322
+ materialRefs: prompt.loweredPrompt.materialRefs,
4323
+ targetCapability: prompt.loweredPrompt.target.capabilityId,
4138
4324
  },
4139
4325
  }),
4140
4326
  });
4141
- if (compactDecision.shouldCompact) {
4142
- let preCompactGovernanceResult;
4143
- let preCompactGovernanceRecord;
4144
- if (preCompactGovernanceEnabled) {
4145
- const governancePacket = buildPreCompactGovernancePacket({
4146
- runtimeId,
4327
+ const contextWindowTokens = compactInputBudgetTokens;
4328
+ const promptBoundaryEvents = [...prompt.events];
4329
+ if (contextWindowTokens !== undefined) {
4330
+ const compactDecision = decideTurnBoundaryCompact({
4331
+ trigger: observations.length > 0 ? "toolLoopBoundary" : "turnBoundary",
4332
+ estimatedNextPromptTokens: prompt.promptPack.totalEstimatedTokens,
4333
+ contextWindowTokens,
4334
+ thresholdRatio: options.compactThresholdRatio,
4335
+ });
4336
+ const compactEvent = compactDecision.shouldCompact
4337
+ ? "runtime.contextCompact.thresholdReached"
4338
+ : "runtime.contextCompact.thresholdNotReached";
4339
+ events.push(compactEvent);
4340
+ promptBoundaryEvents.push(compactEvent);
4341
+ await store.appendEvent(event(sessionId, `event:contextCompact:decision:${turn + 1}`, "runtime.contextCompact.thresholdDecision", now(), compactDecision));
4342
+ await recordMainLoopStep({
4343
+ store,
4344
+ sessionId,
4345
+ createdAt: now(),
4346
+ events,
4347
+ mainLoopSteps,
4348
+ step: createMainLoopStepRecord({
4147
4349
  sessionId,
4148
4350
  turnIndex: turn,
4149
- trigger: compactDecision.trigger,
4150
- currentUserTurnText: input.input.normalizedText,
4151
- promptPackId: prompt.promptPackId,
4152
- promptPack: prompt.promptPack,
4153
- compactDecision,
4154
- });
4155
- const governance = await preCompactGovernanceExecutor.govern({
4156
- packet: governancePacket,
4351
+ stepIndex: stepBase + 4,
4352
+ actionPrimitive: "updateSummaryStateEvent",
4353
+ status: compactDecision.shouldCompact ? "planned" : "completed",
4354
+ inputRefs: [prompt.promptPackId],
4355
+ outputRefs: [],
4356
+ promptPackRef: prompt.promptPackId,
4157
4357
  now: now(),
4158
4358
  metadata: {
4159
- promptPackId: prompt.promptPackId,
4160
- compactTrigger: compactDecision.trigger,
4359
+ decision: compactDecision,
4360
+ note: compactDecision.shouldCompact
4361
+ ? "compact should run after this boundary through the configured CompactExecutor"
4362
+ : "compact threshold not reached at this boundary",
4161
4363
  },
4162
- });
4163
- preCompactGovernanceRecord = governance.record;
4164
- events.push(...governance.events);
4165
- promptBoundaryEvents.push(...governance.events);
4166
- await store.appendEvent(event(sessionId, `event:preCompactGovernance:${turn + 1}`, "runtime.preCompactGovernance.result", now(), governance.record));
4167
- await store.appendState(state(sessionId, `state:preCompactGovernance:${turn + 1}`, governance.ok ? "completed" : "failed", now(), {
4168
- record: governance.record,
4169
- }));
4170
- await recordMainLoopStep({
4171
- store,
4172
- sessionId,
4173
- createdAt: now(),
4174
- events,
4175
- mainLoopSteps,
4176
- step: createMainLoopStepRecord({
4364
+ }),
4365
+ });
4366
+ if (compactDecision.shouldCompact) {
4367
+ let preCompactGovernanceResult;
4368
+ let preCompactGovernanceRecord;
4369
+ if (preCompactGovernanceEnabled) {
4370
+ const governancePacket = buildPreCompactGovernancePacket({
4371
+ runtimeId,
4177
4372
  sessionId,
4178
4373
  turnIndex: turn,
4179
- stepIndex: stepBase + 5,
4180
- actionPrimitive: "updateSummaryStateEvent",
4181
- status: governance.ok ? "completed" : "failed",
4182
- inputRefs: packetMaterialRefs(governancePacket),
4183
- outputRefs: [governance.record.governanceId],
4184
- promptPackRef: prompt.promptPackId,
4374
+ trigger: compactDecision.trigger,
4375
+ currentUserTurnText: input.input.normalizedText,
4376
+ promptPackId: prompt.promptPackId,
4377
+ promptPack: prompt.promptPack,
4378
+ compactDecision,
4379
+ });
4380
+ const governance = await preCompactGovernanceExecutor.govern({
4381
+ packet: governancePacket,
4185
4382
  now: now(),
4186
4383
  metadata: {
4187
- governanceRecord: governance.record,
4188
- note: governance.ok
4189
- ? "preCompactGovernance completed before compactExecutor"
4190
- : "preCompactGovernance failed or skipped; continuing normal compact",
4384
+ promptPackId: prompt.promptPackId,
4385
+ compactTrigger: compactDecision.trigger,
4191
4386
  },
4192
- }),
4193
- });
4194
- if (governance.ok) {
4195
- preCompactGovernanceResult = governance.result;
4196
- preCompactGovernanceProjectContextMaterials = projectContextMaterialsFromGovernance({
4197
- governanceResult: governance.result,
4198
- governanceRecord: governance.record,
4199
4387
  });
4200
- }
4201
- }
4202
- const compactMaterials = compactRequestMaterials({
4203
- promptPack: prompt.promptPack,
4204
- governanceResult: preCompactGovernanceResult,
4205
- projectContextGovernanceMaterials: preCompactGovernanceProjectContextMaterials,
4206
- });
4207
- const compactResult = await compactExecutor.compact({
4208
- sessionId,
4209
- trigger: compactDecision.trigger,
4210
- materialRefs: compactMaterials.map((material) => material.id),
4211
- materials: compactMaterials,
4212
- currentUserTurnText: input.input.normalizedText,
4213
- estimatedTokens: prompt.promptPack.totalEstimatedTokens,
4214
- contextWindowTokens,
4215
- thresholdRatio: compactDecision.thresholdRatio,
4216
- now: now(),
4217
- metadata: {
4218
- promptPackId: prompt.promptPackId,
4219
- turnIndex: turn,
4220
- preCompactGovernanceRecord: preCompactGovernanceRecord ?? null,
4221
- preCompactGovernanceResult: preCompactGovernanceResult ?? null,
4222
- },
4223
- });
4224
- events.push(...compactResult.events);
4225
- promptBoundaryEvents.push(...compactResult.events);
4226
- if (compactResult.ok) {
4227
- compactSessionSummary = {
4228
- summaryId: compactResult.record.after.sessionSummaryRef,
4229
- text: governedSessionSummaryText({
4230
- compactSummaryText: compactResult.sessionSummaryText,
4231
- governanceResult: preCompactGovernanceResult,
4232
- }),
4233
- compactedUntilTurnId: `${sessionId}:turn:${turn}`,
4234
- updatedAt: compactResult.record.createdAt,
4235
- metadata: {
4236
- compactId: compactResult.record.compactId,
4237
- executor: compactResult.record.executor,
4238
- preCompactGovernanceId: preCompactGovernanceRecord?.governanceId ?? "",
4239
- },
4240
- };
4241
- compactConversationWindow = compactResult.recentConversationText.trim().length === 0
4242
- ? []
4243
- : [{
4244
- messageId: compactResult.record.after.recentConversationRefs[0] ?? `${compactResult.record.compactId}:recentConversation`,
4245
- role: "runtime-summary",
4246
- text: compactResult.recentConversationText,
4247
- createdAt: compactResult.record.createdAt,
4248
- metadata: {
4249
- compactId: compactResult.record.compactId,
4250
- },
4251
- }];
4252
- await store.appendState(state(sessionId, `state:contextCompact:${turn + 1}`, "summarizing", now(), {
4253
- decision: compactDecision,
4254
- record: compactResult.record,
4255
- }));
4256
- const rebuiltPrompt = await buildPromptPackAndLower({
4257
- runtimeId,
4258
- sessionId,
4259
- manifest,
4260
- task: input.input.normalizedText,
4261
- turnIndex: turn,
4262
- startStepIndex: stepBase + 6,
4263
- workspaceRoot: toolWorkspaceRoot,
4264
- allowedRoots: toolAllowedRoots,
4265
- now: now(),
4266
- modelCaller,
4267
- toolMappings,
4268
- observations,
4269
- events,
4270
- sessionSummary: compactSessionSummary,
4271
- conversationWindow: compactConversationWindow,
4272
- projectContextGovernanceMaterials: preCompactGovernanceProjectContextMaterials,
4273
- toolContextSelection,
4274
- toolContextUsage: toolContextHeatState.usage,
4275
- });
4276
- events.push(...rebuiltPrompt.events);
4277
- promptBoundaryEvents.push(...rebuiltPrompt.events);
4278
- if (!rebuiltPrompt.ok) {
4279
- return {
4280
- ok: false,
4281
- error: {
4282
- code: rebuiltPrompt.error.code,
4283
- message: rebuiltPrompt.error.message,
4284
- boundary: "prompt",
4285
- publicSafe: true,
4286
- },
4287
- events: rebuiltPrompt.events,
4288
- };
4289
- }
4290
- for (const turnStep of rebuiltPrompt.turnRecord.stepRecords) {
4388
+ preCompactGovernanceRecord = governance.record;
4389
+ events.push(...governance.events);
4390
+ promptBoundaryEvents.push(...governance.events);
4391
+ await store.appendEvent(event(sessionId, `event:preCompactGovernance:${turn + 1}`, "runtime.preCompactGovernance.result", now(), governance.record));
4392
+ await store.appendState(state(sessionId, `state:preCompactGovernance:${turn + 1}`, governance.ok ? "completed" : "failed", now(), {
4393
+ record: governance.record,
4394
+ }));
4291
4395
  await recordMainLoopStep({
4292
4396
  store,
4293
4397
  sessionId,
4294
4398
  createdAt: now(),
4295
4399
  events,
4296
4400
  mainLoopSteps,
4297
- step: turnStep,
4401
+ step: createMainLoopStepRecord({
4402
+ sessionId,
4403
+ turnIndex: turn,
4404
+ stepIndex: stepBase + 5,
4405
+ actionPrimitive: "updateSummaryStateEvent",
4406
+ status: governance.ok ? "completed" : "failed",
4407
+ inputRefs: packetMaterialRefs(governancePacket),
4408
+ outputRefs: [governance.record.governanceId],
4409
+ promptPackRef: prompt.promptPackId,
4410
+ now: now(),
4411
+ metadata: {
4412
+ governanceRecord: governance.record,
4413
+ note: governance.ok
4414
+ ? "preCompactGovernance completed before compactExecutor"
4415
+ : "preCompactGovernance failed or skipped; continuing normal compact",
4416
+ },
4417
+ }),
4298
4418
  });
4419
+ if (governance.ok) {
4420
+ preCompactGovernanceResult = governance.result;
4421
+ preCompactGovernanceProjectContextMaterials = projectContextMaterialsFromGovernance({
4422
+ governanceResult: governance.result,
4423
+ governanceRecord: governance.record,
4424
+ });
4425
+ }
4299
4426
  }
4300
- await recordMainLoopStep({
4301
- store,
4427
+ const compactMaterials = compactRequestMaterials({
4428
+ promptPack: prompt.promptPack,
4429
+ governanceResult: preCompactGovernanceResult,
4430
+ projectContextGovernanceMaterials: preCompactGovernanceProjectContextMaterials,
4431
+ });
4432
+ const compactResult = await compactExecutor.compact({
4302
4433
  sessionId,
4303
- createdAt: now(),
4304
- events,
4305
- mainLoopSteps,
4306
- step: createMainLoopStepRecord({
4307
- sessionId,
4434
+ trigger: compactDecision.trigger,
4435
+ materialRefs: compactMaterials.map((material) => material.id),
4436
+ materials: compactMaterials,
4437
+ currentUserTurnText: input.input.normalizedText,
4438
+ estimatedTokens: prompt.promptPack.totalEstimatedTokens,
4439
+ contextWindowTokens,
4440
+ thresholdRatio: compactDecision.thresholdRatio,
4441
+ now: now(),
4442
+ metadata: {
4443
+ promptPackId: prompt.promptPackId,
4308
4444
  turnIndex: turn,
4309
- stepIndex: stepBase + 8,
4310
- actionPrimitive: "lowerPrompt",
4311
- status: "completed",
4312
- inputRefs: [rebuiltPrompt.promptPackId],
4313
- outputRefs: [rebuiltPrompt.loweredPrompt.loweringId],
4314
- promptPackRef: rebuiltPrompt.promptPackId,
4315
- loweredPromptRef: rebuiltPrompt.loweredPrompt.loweringId,
4316
- now: now(),
4445
+ preCompactGovernanceRecord: preCompactGovernanceRecord ?? null,
4446
+ preCompactGovernanceResult: preCompactGovernanceResult ?? null,
4447
+ },
4448
+ });
4449
+ events.push(...compactResult.events);
4450
+ promptBoundaryEvents.push(...compactResult.events);
4451
+ if (compactResult.ok) {
4452
+ compactSessionSummary = {
4453
+ summaryId: compactResult.record.after.sessionSummaryRef,
4454
+ text: governedSessionSummaryText({
4455
+ compactSummaryText: compactResult.sessionSummaryText,
4456
+ governanceResult: preCompactGovernanceResult,
4457
+ }),
4458
+ compactedUntilTurnId: `${sessionId}:turn:${turn}`,
4459
+ updatedAt: compactResult.record.createdAt,
4317
4460
  metadata: {
4318
- materialRefs: rebuiltPrompt.loweredPrompt.materialRefs,
4319
- targetCapability: rebuiltPrompt.loweredPrompt.target.capabilityId,
4320
- compactRecordRef: compactResult.record.compactId,
4461
+ compactId: compactResult.record.compactId,
4462
+ executor: compactResult.record.executor,
4463
+ preCompactGovernanceId: preCompactGovernanceRecord?.governanceId ?? "",
4321
4464
  },
4322
- }),
4323
- });
4324
- prompt = rebuiltPrompt;
4325
- await recordMainLoopStep({
4326
- store,
4327
- sessionId,
4328
- createdAt: now(),
4329
- events,
4330
- mainLoopSteps,
4331
- step: createMainLoopStepRecord({
4465
+ };
4466
+ compactConversationWindow = compactResult.recentConversationText.trim().length === 0
4467
+ ? []
4468
+ : [{
4469
+ messageId: compactResult.record.after.recentConversationRefs[0] ?? `${compactResult.record.compactId}:recentConversation`,
4470
+ role: "runtime-summary",
4471
+ text: compactResult.recentConversationText,
4472
+ createdAt: compactResult.record.createdAt,
4473
+ metadata: {
4474
+ compactId: compactResult.record.compactId,
4475
+ },
4476
+ }];
4477
+ await store.appendState(state(sessionId, `state:contextCompact:${turn + 1}`, "summarizing", now(), {
4478
+ decision: compactDecision,
4479
+ record: compactResult.record,
4480
+ }));
4481
+ const rebuiltPrompt = await buildPromptPackAndLower({
4482
+ runtimeId,
4332
4483
  sessionId,
4484
+ manifest,
4485
+ task: input.input.normalizedText,
4333
4486
  turnIndex: turn,
4334
- stepIndex: stepBase + 9,
4335
- actionPrimitive: "updateSummaryStateEvent",
4336
- status: "completed",
4337
- inputRefs: [compactResult.record.compactId],
4338
- outputRefs: [rebuiltPrompt.promptPackId],
4339
- promptPackRef: rebuiltPrompt.promptPackId,
4487
+ startStepIndex: stepBase + 6,
4488
+ workspaceRoot: toolWorkspaceRoot,
4489
+ allowedRoots: toolAllowedRoots,
4340
4490
  now: now(),
4341
- metadata: {
4342
- decision: compactDecision,
4343
- compactRecord: compactResult.record,
4344
- rebuiltPromptPackTokens: rebuiltPrompt.promptPack.totalEstimatedTokens,
4345
- },
4346
- }),
4347
- });
4348
- }
4349
- else {
4350
- runnerError = kernelError("PROMPT_PACK_FAILED", compactResult.error.message, "runtime-state");
4351
- const compactError = {
4352
- code: compactResult.error.code,
4353
- message: compactResult.error.message,
4354
- boundary: "prompt",
4355
- publicSafe: true,
4356
- };
4357
- const compactStepError = {
4358
- code: compactResult.error.code,
4359
- message: compactResult.error.message,
4360
- boundary: "runtime-state",
4361
- publicSafe: true,
4362
- };
4363
- await recordMainLoopStep({
4364
- store,
4365
- sessionId,
4366
- createdAt: now(),
4367
- events,
4368
- mainLoopSteps,
4369
- step: createMainLoopStepRecord({
4491
+ modelCaller,
4492
+ toolMappings,
4493
+ observations,
4494
+ events,
4495
+ sessionSummary: compactSessionSummary,
4496
+ conversationWindow: compactConversationWindow,
4497
+ toolDeclarationPreludeMaterials,
4498
+ skillIndexMaterials,
4499
+ projectContextGovernanceMaterials: preCompactGovernanceProjectContextMaterials,
4500
+ toolContextSelection,
4501
+ toolContextUsage: toolContextHeatState.usage,
4502
+ });
4503
+ events.push(...rebuiltPrompt.events);
4504
+ promptBoundaryEvents.push(...rebuiltPrompt.events);
4505
+ if (!rebuiltPrompt.ok) {
4506
+ return {
4507
+ ok: false,
4508
+ error: {
4509
+ code: rebuiltPrompt.error.code,
4510
+ message: rebuiltPrompt.error.message,
4511
+ boundary: "prompt",
4512
+ publicSafe: true,
4513
+ },
4514
+ events: rebuiltPrompt.events,
4515
+ };
4516
+ }
4517
+ for (const turnStep of rebuiltPrompt.turnRecord.stepRecords) {
4518
+ await recordMainLoopStep({
4519
+ store,
4520
+ sessionId,
4521
+ createdAt: now(),
4522
+ events,
4523
+ mainLoopSteps,
4524
+ step: turnStep,
4525
+ });
4526
+ }
4527
+ await recordMainLoopStep({
4528
+ store,
4370
4529
  sessionId,
4371
- turnIndex: turn,
4372
- stepIndex: stepBase + 5,
4373
- actionPrimitive: "updateSummaryStateEvent",
4374
- status: "failed",
4375
- inputRefs: [prompt.promptPackId],
4376
- outputRefs: [],
4377
- promptPackRef: prompt.promptPackId,
4378
- now: now(),
4379
- error: compactStepError,
4380
- }),
4381
- });
4382
- return {
4383
- ok: false,
4384
- error: compactError,
4385
- events: promptBoundaryEvents,
4386
- };
4530
+ createdAt: now(),
4531
+ events,
4532
+ mainLoopSteps,
4533
+ step: createMainLoopStepRecord({
4534
+ sessionId,
4535
+ turnIndex: turn,
4536
+ stepIndex: stepBase + 8,
4537
+ actionPrimitive: "lowerPrompt",
4538
+ status: "completed",
4539
+ inputRefs: [rebuiltPrompt.promptPackId],
4540
+ outputRefs: [rebuiltPrompt.loweredPrompt.loweringId],
4541
+ promptPackRef: rebuiltPrompt.promptPackId,
4542
+ loweredPromptRef: rebuiltPrompt.loweredPrompt.loweringId,
4543
+ now: now(),
4544
+ metadata: {
4545
+ materialRefs: rebuiltPrompt.loweredPrompt.materialRefs,
4546
+ targetCapability: rebuiltPrompt.loweredPrompt.target.capabilityId,
4547
+ compactRecordRef: compactResult.record.compactId,
4548
+ },
4549
+ }),
4550
+ });
4551
+ prompt = rebuiltPrompt;
4552
+ await recordMainLoopStep({
4553
+ store,
4554
+ sessionId,
4555
+ createdAt: now(),
4556
+ events,
4557
+ mainLoopSteps,
4558
+ step: createMainLoopStepRecord({
4559
+ sessionId,
4560
+ turnIndex: turn,
4561
+ stepIndex: stepBase + 9,
4562
+ actionPrimitive: "updateSummaryStateEvent",
4563
+ status: "completed",
4564
+ inputRefs: [compactResult.record.compactId],
4565
+ outputRefs: [rebuiltPrompt.promptPackId],
4566
+ promptPackRef: rebuiltPrompt.promptPackId,
4567
+ now: now(),
4568
+ metadata: {
4569
+ decision: compactDecision,
4570
+ compactRecord: compactResult.record,
4571
+ rebuiltPromptPackTokens: rebuiltPrompt.promptPack.totalEstimatedTokens,
4572
+ },
4573
+ }),
4574
+ });
4575
+ }
4576
+ else {
4577
+ runnerError = kernelError("PROMPT_PACK_FAILED", compactResult.error.message, "runtime-state");
4578
+ const compactError = {
4579
+ code: compactResult.error.code,
4580
+ message: compactResult.error.message,
4581
+ boundary: "prompt",
4582
+ publicSafe: true,
4583
+ };
4584
+ const compactStepError = {
4585
+ code: compactResult.error.code,
4586
+ message: compactResult.error.message,
4587
+ boundary: "runtime-state",
4588
+ publicSafe: true,
4589
+ };
4590
+ await recordMainLoopStep({
4591
+ store,
4592
+ sessionId,
4593
+ createdAt: now(),
4594
+ events,
4595
+ mainLoopSteps,
4596
+ step: createMainLoopStepRecord({
4597
+ sessionId,
4598
+ turnIndex: turn,
4599
+ stepIndex: stepBase + 5,
4600
+ actionPrimitive: "updateSummaryStateEvent",
4601
+ status: "failed",
4602
+ inputRefs: [prompt.promptPackId],
4603
+ outputRefs: [],
4604
+ promptPackRef: prompt.promptPackId,
4605
+ now: now(),
4606
+ error: compactStepError,
4607
+ }),
4608
+ });
4609
+ return {
4610
+ ok: false,
4611
+ error: compactError,
4612
+ events: promptBoundaryEvents,
4613
+ };
4614
+ }
4387
4615
  }
4388
4616
  }
4389
- }
4390
- return { prompt, events: promptBoundaryEvents };
4391
- },
4392
- invokeModel: async (turn, prompt) => {
4393
- const stepBase = turn * 20 + 2;
4394
- const modelInvocationId = `${sessionId}:model:${turn + 1}`;
4395
- const promptCacheKey = stablePromptCacheKey(manifest, sessionId);
4396
- const promptSplit = splitPromptPackForProvider(prompt.promptPack);
4397
- const providerToolResultHistory = providerToolResultHistoryFromObservations(observations);
4398
- const toolResultInputs = providerToolResultHistory.results
4399
- .map((result) => lowerProviderToolResult({ providerFamily, result }));
4400
- const providerBodyCandidate = buildProviderBodyFromPromptPack(manifest, prompt.promptPack, prompt.providerToolBundle, prompt.providerToolBundle.mappings, {
4401
- exposeProviderTools: options.exposeProviderTools,
4402
- observations,
4403
- previousProviderOutputItems: providerResponseOutputItems,
4404
- promptCacheKey,
4405
- });
4406
- const candidateCacheDebug = buildPromptPackCacheDebug({
4407
- promptPack: prompt.promptPack,
4408
- providerBody: providerBodyCandidate,
4409
- promptCacheKey,
4410
- previousProviderOutputItems: providerResponseOutputItems,
4411
- toolResultInputs,
4412
- toolResultBudget: providerToolResultHistory.budget,
4413
- promptSplit,
4414
- });
4415
- const canUsePreviousProviderResponseId = providerResponseOutputItems.length === 0 && toolResultInputs.length === 0;
4416
- const previousProviderResponseId = canUsePreviousProviderResponseId &&
4417
- options.allowPreviousResponseId === true &&
4418
- manifest.model.endpointShape !== "chat_completions" &&
4419
- manifest.model.provider !== "anthropic" &&
4420
- previousProviderResponse !== undefined &&
4421
- previousProviderResponse.stablePrefixHash === candidateCacheDebug.providerBody.cacheShape.stablePrefixHash
4422
- ? previousProviderResponse.responseId
4423
- : undefined;
4424
- const providerBody = previousProviderResponseId === undefined
4425
- ? providerBodyCandidate
4426
- : buildProviderBodyFromPromptPack(manifest, prompt.promptPack, prompt.providerToolBundle, prompt.providerToolBundle.mappings, {
4617
+ return { prompt, events: promptBoundaryEvents };
4618
+ },
4619
+ invokeModel: async (turn, prompt) => {
4620
+ const stepBase = turn * 20 + 2;
4621
+ const modelInvocationId = `${sessionId}:model:${turn + 1}`;
4622
+ const promptCacheKey = stablePromptCacheKey(manifest, sessionId);
4623
+ const promptSplit = splitPromptPackForProvider(prompt.promptPack);
4624
+ const providerToolResultHistory = providerToolResultHistoryFromObservations(observations);
4625
+ const toolResultInputs = providerToolResultHistory.results
4626
+ .map((result) => lowerProviderToolResult({ providerFamily, result }));
4627
+ const providerBodyCandidate = buildProviderBodyFromPromptPack(manifest, prompt.promptPack, prompt.providerToolBundle, prompt.providerToolBundle.mappings, {
4427
4628
  exposeProviderTools: options.exposeProviderTools,
4428
4629
  observations,
4429
4630
  previousProviderOutputItems: providerResponseOutputItems,
4430
- previousProviderResponseId,
4431
4631
  promptCacheKey,
4432
4632
  });
4433
- const cacheDebug = previousProviderResponseId === undefined
4434
- ? candidateCacheDebug
4435
- : buildPromptPackCacheDebug({
4633
+ const candidateCacheDebug = buildPromptPackCacheDebug({
4436
4634
  promptPack: prompt.promptPack,
4437
- providerBody,
4635
+ providerBody: providerBodyCandidate,
4438
4636
  promptCacheKey,
4439
4637
  previousProviderOutputItems: providerResponseOutputItems,
4440
4638
  toolResultInputs,
4441
4639
  toolResultBudget: providerToolResultHistory.budget,
4442
4640
  promptSplit,
4443
4641
  });
4444
- await options.onModelCallProgress?.({
4445
- phase: "started",
4446
- invocationId: modelInvocationId,
4447
- turnIndex: turn,
4448
- provider: manifest.model.provider,
4449
- carrierId: manifest.model.carrierId,
4450
- model: manifest.model.model,
4451
- });
4452
- const modelResult = await invokeModelThroughRuntime({
4453
- runtimeId,
4454
- invocationId: modelInvocationId,
4455
- caller: modelCaller,
4456
- loweredPrompt: prompt.loweredPrompt,
4457
- capability: modelInvocationCapabilityForModel(manifest.model),
4458
- carrier: {
4642
+ const canUsePreviousProviderResponseId = providerResponseOutputItems.length === 0 && toolResultInputs.length === 0;
4643
+ const previousProviderResponseId = canUsePreviousProviderResponseId &&
4644
+ options.allowPreviousResponseId === true &&
4645
+ manifest.model.endpointShape !== "chat_completions" &&
4646
+ manifest.model.provider !== "anthropic" &&
4647
+ previousProviderResponse !== undefined &&
4648
+ previousProviderResponse.stablePrefixHash === candidateCacheDebug.providerBody.cacheShape.stablePrefixHash
4649
+ ? previousProviderResponse.responseId
4650
+ : undefined;
4651
+ const providerBody = previousProviderResponseId === undefined
4652
+ ? providerBodyCandidate
4653
+ : buildProviderBodyFromPromptPack(manifest, prompt.promptPack, prompt.providerToolBundle, prompt.providerToolBundle.mappings, {
4654
+ exposeProviderTools: options.exposeProviderTools,
4655
+ observations,
4656
+ previousProviderOutputItems: providerResponseOutputItems,
4657
+ previousProviderResponseId,
4658
+ promptCacheKey,
4659
+ });
4660
+ const cacheDebug = previousProviderResponseId === undefined
4661
+ ? candidateCacheDebug
4662
+ : buildPromptPackCacheDebug({
4663
+ promptPack: prompt.promptPack,
4664
+ providerBody,
4665
+ promptCacheKey,
4666
+ previousProviderOutputItems: providerResponseOutputItems,
4667
+ toolResultInputs,
4668
+ toolResultBudget: providerToolResultHistory.budget,
4669
+ promptSplit,
4670
+ });
4671
+ await options.onModelCallProgress?.({
4672
+ phase: "started",
4673
+ invocationId: modelInvocationId,
4674
+ turnIndex: turn,
4675
+ provider: manifest.model.provider,
4459
4676
  carrierId: manifest.model.carrierId,
4677
+ model: manifest.model.model,
4678
+ });
4679
+ const modelResult = await invokeModelThroughRuntime({
4680
+ runtimeId,
4681
+ invocationId: modelInvocationId,
4682
+ caller: modelCaller,
4683
+ loweredPrompt: prompt.loweredPrompt,
4684
+ capability: modelInvocationCapabilityForModel(manifest.model),
4685
+ carrier: {
4686
+ carrierId: manifest.model.carrierId,
4687
+ provider: manifest.model.provider,
4688
+ endpointShape: manifest.model.endpointShape,
4689
+ baseURL: manifest.model.baseURL,
4690
+ metadata: manifest.model.metadata,
4691
+ },
4692
+ mode: "single",
4693
+ dryRun,
4694
+ allowProviderCall: options.allowProviderCall ?? manifest.harness.policy.allowProviderCall ?? !dryRun,
4695
+ auth: options.auth,
4696
+ runtimeAuthResolver: options.runtimeAuthResolver,
4697
+ authSelection,
4698
+ providerCaller: options.providerCaller,
4699
+ modelClient: options.modelClient,
4700
+ openaiResponsesCaller: options.openaiResponsesCaller,
4701
+ openaiChatCompletionsCaller: options.openaiChatCompletionsCaller,
4702
+ anthropicMessagesCaller: options.anthropicMessagesCaller,
4703
+ geminiGenerateContentTransport: options.geminiGenerateContentTransport,
4704
+ providerBody,
4705
+ governance: { accepted: true },
4706
+ contract: { accepted: true },
4707
+ clientName: manifest.model.clientName,
4708
+ clientVersion: manifest.model.clientVersion,
4709
+ signal: options.interruptSignal,
4710
+ });
4711
+ const modelUsage = modelResult.ok && modelResult.usage
4712
+ ? {
4713
+ inputTokens: modelResult.usage.inputTokens,
4714
+ outputTokens: modelResult.usage.outputTokens,
4715
+ thinkingTokens: "reasoningTokens" in modelResult.usage ? modelResult.usage.reasoningTokens : undefined,
4716
+ totalTokens: modelResult.usage.totalTokens,
4717
+ cachedInputTokens: "cachedInputTokens" in modelResult.usage ? modelResult.usage.cachedInputTokens : undefined,
4718
+ source: modelResult.usage.source,
4719
+ estimated: modelResult.usage.estimated ?? false,
4720
+ }
4721
+ : undefined;
4722
+ const providerRouting = modelResult.ok
4723
+ ? providerRoutingDebug(providerResponseHeadersForKernel(modelResult.providerResult))
4724
+ : undefined;
4725
+ const providerResponseId = modelResult.ok ? extractOpenAIResponseId(modelResult.raw) : undefined;
4726
+ const observedCacheDebug = cacheDebugWithPreviousComparison(cacheDebugWithObservedUsage(cacheDebug, modelUsage), previousModelCacheDebug);
4727
+ previousModelCacheDebug = observedCacheDebug;
4728
+ await options.onModelCallProgress?.({
4729
+ phase: modelResult.ok ? "completed" : "failed",
4730
+ invocationId: modelInvocationId,
4731
+ turnIndex: turn,
4460
4732
  provider: manifest.model.provider,
4461
- endpointShape: manifest.model.endpointShape,
4462
- baseURL: manifest.model.baseURL,
4463
- metadata: manifest.model.metadata,
4464
- },
4465
- mode: "single",
4466
- dryRun,
4467
- allowProviderCall: options.allowProviderCall ?? manifest.harness.policy.allowProviderCall ?? !dryRun,
4468
- auth: options.auth,
4469
- runtimeAuthResolver: options.runtimeAuthResolver,
4470
- authSelection,
4471
- providerCaller: options.providerCaller,
4472
- modelClient: options.modelClient,
4473
- openaiResponsesCaller: options.openaiResponsesCaller,
4474
- openaiChatCompletionsCaller: options.openaiChatCompletionsCaller,
4475
- anthropicMessagesCaller: options.anthropicMessagesCaller,
4476
- geminiGenerateContentTransport: options.geminiGenerateContentTransport,
4477
- providerBody,
4478
- governance: { accepted: true },
4479
- contract: { accepted: true },
4480
- clientName: manifest.model.clientName,
4481
- clientVersion: manifest.model.clientVersion,
4482
- signal: options.interruptSignal,
4483
- });
4484
- const modelUsage = modelResult.ok && modelResult.usage
4485
- ? {
4486
- inputTokens: modelResult.usage.inputTokens,
4487
- outputTokens: modelResult.usage.outputTokens,
4488
- thinkingTokens: "reasoningTokens" in modelResult.usage ? modelResult.usage.reasoningTokens : undefined,
4489
- totalTokens: modelResult.usage.totalTokens,
4490
- cachedInputTokens: "cachedInputTokens" in modelResult.usage ? modelResult.usage.cachedInputTokens : undefined,
4491
- source: modelResult.usage.source,
4492
- estimated: modelResult.usage.estimated ?? false,
4493
- }
4494
- : undefined;
4495
- const providerRouting = modelResult.ok
4496
- ? providerRoutingDebug(providerResponseHeadersForKernel(modelResult.providerResult))
4497
- : undefined;
4498
- const providerResponseId = modelResult.ok ? extractOpenAIResponseId(modelResult.raw) : undefined;
4499
- const observedCacheDebug = cacheDebugWithPreviousComparison(cacheDebugWithObservedUsage(cacheDebug, modelUsage), previousModelCacheDebug);
4500
- previousModelCacheDebug = observedCacheDebug;
4501
- await options.onModelCallProgress?.({
4502
- phase: modelResult.ok ? "completed" : "failed",
4503
- invocationId: modelInvocationId,
4504
- turnIndex: turn,
4505
- provider: manifest.model.provider,
4506
- carrierId: manifest.model.carrierId,
4507
- model: manifest.model.model,
4508
- ok: modelResult.ok,
4509
- usage: modelUsage,
4510
- providerRouting,
4511
- cacheDebug: observedCacheDebug,
4512
- providerResponseId,
4513
- previousProviderResponseId,
4514
- error: modelResult.ok
4515
- ? undefined
4516
- : kernelError("MODEL_INVOCATION_FAILED", modelResult.error.message, "model"),
4517
- });
4518
- events.push(...modelResult.events);
4519
- modelCalls.push({
4520
- invocationId: modelInvocationId,
4521
- raw: modelResult.ok ? modelResult.raw : null,
4522
- ok: modelResult.ok,
4523
- usage: modelUsage,
4524
- providerRouting,
4525
- cacheDebug: observedCacheDebug,
4526
- providerResponseId,
4527
- previousProviderResponseId,
4528
- });
4529
- await store.appendInvocation(invocation(sessionId, modelInvocationId, "model", manifest.model.carrierId, modelResult.ok, now(), {
4530
- turn,
4531
- promptPackId: prompt.promptPackId,
4532
- loweringId: prompt.loweredPrompt.loweringId,
4533
- }));
4534
- await recordMainLoopStep({
4535
- store,
4536
- sessionId,
4537
- createdAt: now(),
4538
- events,
4539
- mainLoopSteps,
4540
- step: createMainLoopStepRecord({
4733
+ carrierId: manifest.model.carrierId,
4734
+ model: manifest.model.model,
4735
+ ok: modelResult.ok,
4736
+ usage: modelUsage,
4737
+ providerRouting,
4738
+ cacheDebug: observedCacheDebug,
4739
+ providerResponseId,
4740
+ previousProviderResponseId,
4741
+ error: modelResult.ok
4742
+ ? undefined
4743
+ : kernelError("MODEL_INVOCATION_FAILED", modelResult.error.message, "model"),
4744
+ });
4745
+ events.push(...modelResult.events);
4746
+ modelCalls.push({
4747
+ invocationId: modelInvocationId,
4748
+ raw: modelResult.ok ? modelResult.raw : null,
4749
+ ok: modelResult.ok,
4750
+ usage: modelUsage,
4751
+ providerRouting,
4752
+ cacheDebug: observedCacheDebug,
4753
+ providerResponseId,
4754
+ previousProviderResponseId,
4755
+ });
4756
+ await store.appendInvocation(invocation(sessionId, modelInvocationId, "model", manifest.model.carrierId, modelResult.ok, now(), {
4757
+ turn,
4758
+ promptPackId: prompt.promptPackId,
4759
+ loweringId: prompt.loweredPrompt.loweringId,
4760
+ }));
4761
+ await recordMainLoopStep({
4762
+ store,
4541
4763
  sessionId,
4764
+ createdAt: now(),
4765
+ events,
4766
+ mainLoopSteps,
4767
+ step: createMainLoopStepRecord({
4768
+ sessionId,
4769
+ turnIndex: turn,
4770
+ stepIndex: stepBase + 4,
4771
+ actionPrimitive: "invokeModel",
4772
+ status: modelResult.ok ? "completed" : "failed",
4773
+ inputRefs: [prompt.loweredPrompt.loweringId],
4774
+ outputRefs: [modelInvocationId],
4775
+ modelCallId: modelInvocationId,
4776
+ promptPackRef: prompt.promptPackId,
4777
+ loweredPromptRef: prompt.loweredPrompt.loweringId,
4778
+ error: modelResult.ok ? undefined : {
4779
+ code: modelResult.error.code,
4780
+ message: modelResult.error.message,
4781
+ boundary: "model",
4782
+ publicSafe: true,
4783
+ },
4784
+ now: now(),
4785
+ metadata: { turn },
4786
+ }),
4787
+ });
4788
+ await recordHandoffPlan({
4789
+ store,
4790
+ sessionId,
4791
+ createdAt: now(),
4792
+ events,
4793
+ mainLoopSteps,
4542
4794
  turnIndex: turn,
4543
- stepIndex: stepBase + 4,
4544
- actionPrimitive: "invokeModel",
4545
- status: modelResult.ok ? "completed" : "failed",
4546
- inputRefs: [prompt.loweredPrompt.loweringId],
4547
- outputRefs: [modelInvocationId],
4548
- modelCallId: modelInvocationId,
4795
+ startStepIndex: stepBase + 20,
4796
+ tickKind: "model-only",
4549
4797
  promptPackRef: prompt.promptPackId,
4550
4798
  loweredPromptRef: prompt.loweredPrompt.loweringId,
4551
- error: modelResult.ok ? undefined : {
4552
- code: modelResult.error.code,
4553
- message: modelResult.error.message,
4554
- boundary: "model",
4555
- publicSafe: true,
4556
- },
4557
- now: now(),
4558
- metadata: { turn },
4559
- }),
4560
- });
4561
- await recordHandoffPlan({
4562
- store,
4563
- sessionId,
4564
- createdAt: now(),
4565
- events,
4566
- mainLoopSteps,
4567
- turnIndex: turn,
4568
- startStepIndex: stepBase + 20,
4569
- tickKind: "model-only",
4570
- promptPackRef: prompt.promptPackId,
4571
- loweredPromptRef: prompt.loweredPrompt.loweringId,
4572
- modelCallId: modelInvocationId,
4573
- inputRefs: [prompt.promptPackId, prompt.loweredPrompt.loweringId],
4574
- outputRefs: [modelInvocationId],
4575
- });
4576
- if (!modelResult.ok) {
4577
- const interrupted = options.interruptSignal?.aborted === true;
4578
- const error = interrupted
4579
- ? kernelError("MAIN_LOOP_INTERRUPTED", modelResult.error.message, "runtime-state")
4580
- : kernelError("MODEL_INVOCATION_FAILED", modelResult.error.message, "model");
4581
- runnerError = error;
4582
- await recordKernelError({ store, sessionId, errorId: `error:model:${turn + 1}`, error, createdAt: now(), metadata: { modelInvocationId } });
4799
+ modelCallId: modelInvocationId,
4800
+ inputRefs: [prompt.promptPackId, prompt.loweredPrompt.loweringId],
4801
+ outputRefs: [modelInvocationId],
4802
+ });
4803
+ if (!modelResult.ok) {
4804
+ const interrupted = options.interruptSignal?.aborted === true;
4805
+ const error = interrupted
4806
+ ? kernelError("MAIN_LOOP_INTERRUPTED", modelResult.error.message, "runtime-state")
4807
+ : kernelError("MODEL_INVOCATION_FAILED", modelResult.error.message, "model");
4808
+ runnerError = error;
4809
+ await recordKernelError({ store, sessionId, errorId: `error:model:${turn + 1}`, error, createdAt: now(), metadata: { modelInvocationId } });
4810
+ return {
4811
+ ok: false,
4812
+ modelCallId: modelInvocationId,
4813
+ error: {
4814
+ code: error.code,
4815
+ message: error.message,
4816
+ boundary: "model",
4817
+ publicSafe: true,
4818
+ },
4819
+ events: modelResult.events,
4820
+ };
4821
+ }
4822
+ providerResponseOutputItems.push(...extractProviderOutputItems(modelResult.raw, providerFamily));
4823
+ if (providerResponseId !== undefined) {
4824
+ previousProviderResponse = {
4825
+ responseId: providerResponseId,
4826
+ stablePrefixHash: observedCacheDebug.providerBody.cacheShape.stablePrefixHash,
4827
+ };
4828
+ }
4583
4829
  return {
4584
- ok: false,
4830
+ ok: true,
4585
4831
  modelCallId: modelInvocationId,
4586
- error: {
4587
- code: error.code,
4588
- message: error.message,
4589
- boundary: "model",
4590
- publicSafe: true,
4591
- },
4832
+ raw: modelResult.raw,
4833
+ usage: modelUsage === undefined
4834
+ ? undefined
4835
+ : {
4836
+ inputTokens: modelUsage.inputTokens,
4837
+ outputTokens: modelUsage.outputTokens,
4838
+ totalTokens: modelUsage.totalTokens,
4839
+ providerRaw: modelResult.usage,
4840
+ },
4592
4841
  events: modelResult.events,
4593
4842
  };
4594
- }
4595
- providerResponseOutputItems.push(...extractProviderOutputItems(modelResult.raw, providerFamily));
4596
- if (providerResponseId !== undefined) {
4597
- previousProviderResponse = {
4598
- responseId: providerResponseId,
4599
- stablePrefixHash: observedCacheDebug.providerBody.cacheShape.stablePrefixHash,
4600
- };
4601
- }
4602
- return {
4603
- ok: true,
4604
- modelCallId: modelInvocationId,
4605
- raw: modelResult.raw,
4606
- usage: modelUsage === undefined
4607
- ? undefined
4608
- : {
4609
- inputTokens: modelUsage.inputTokens,
4610
- outputTokens: modelUsage.outputTokens,
4611
- totalTokens: modelUsage.totalTokens,
4612
- providerRaw: modelResult.usage,
4613
- },
4614
- events: modelResult.events,
4615
- };
4616
- },
4617
- interpretDecision: async (turn, model, prompt) => {
4618
- const stepBase = turn * 20 + 2;
4619
- const decisionResult = interpretModelDecision({
4620
- raw: model.raw,
4621
- sessionId,
4622
- turnIndex: turn,
4623
- providerFamily,
4624
- providerToolMappings: toolMappings,
4625
- providerRawRef: model.modelCallId,
4626
- });
4627
- events.push(...decisionResult.events);
4628
- await recordMainLoopStep({
4629
- store,
4630
- sessionId,
4631
- createdAt: now(),
4632
- events,
4633
- mainLoopSteps,
4634
- step: createMainLoopStepRecord({
4843
+ },
4844
+ interpretDecision: async (turn, model, prompt) => {
4845
+ const stepBase = turn * 20 + 2;
4846
+ const decisionResult = interpretModelDecision({
4847
+ raw: model.raw,
4635
4848
  sessionId,
4636
4849
  turnIndex: turn,
4637
- stepIndex: stepBase + 5,
4638
- actionPrimitive: "interpretModelDecision",
4639
- status: decisionResult.ok ? "completed" : "failed",
4640
- inputRefs: [model.modelCallId],
4641
- outputRefs: decisionResult.ok ? decisionResult.decisions.map((decision) => decision.decisionId) : [],
4642
- modelCallId: model.modelCallId,
4643
- promptPackRef: prompt.promptPackId,
4644
- error: decisionResult.ok ? undefined : {
4645
- code: decisionResult.error.code,
4646
- message: decisionResult.error.message,
4647
- boundary: "model",
4648
- publicSafe: true,
4649
- },
4650
- now: now(),
4651
- metadata: {
4652
- decisionKinds: decisionResult.ok ? decisionResult.decisions.map((decision) => decision.kind) : [],
4653
- },
4654
- }),
4655
- });
4656
- if (!decisionResult.ok) {
4657
- const error = kernelError("MODEL_DECISION_FAILED", decisionResult.error.message, "model");
4658
- runnerError = error;
4659
- await recordKernelError({ store, sessionId, errorId: `error:modelDecision:${turn + 1}`, error, createdAt: now(), metadata: { modelInvocationId: model.modelCallId } });
4660
- return {
4661
- ok: false,
4662
- error: {
4663
- code: error.code,
4664
- message: error.message,
4665
- boundary: "model-decision",
4666
- publicSafe: true,
4667
- },
4668
- events: decisionResult.events,
4669
- };
4670
- }
4671
- return { ok: true, decisions: decisionResult.decisions, events: decisionResult.events };
4672
- },
4673
- acceptFinalOutput: async ({ turnIndex: turn, decisionIndex, decision }) => {
4674
- const finalAcceptance = decideMainLoopFinalAcceptance({
4675
- finalOutput: decision.finalOutput ?? "",
4676
- fatalFailureRefs: runnerError === undefined ? [] : [runnerError.code],
4677
- });
4678
- await recordMainLoopStep({
4679
- store,
4680
- sessionId,
4681
- createdAt: now(),
4682
- events,
4683
- mainLoopSteps,
4684
- step: createMainLoopStepRecord({
4850
+ providerFamily,
4851
+ providerToolMappings: toolMappings,
4852
+ providerRawRef: model.modelCallId,
4853
+ });
4854
+ events.push(...decisionResult.events);
4855
+ await recordMainLoopStep({
4856
+ store,
4685
4857
  sessionId,
4686
- turnIndex: turn,
4687
- stepIndex: turn * 20 + 60 + decisionIndex,
4688
- actionPrimitive: "adjudicateDecision",
4689
- status: finalAcceptance.canBreak ? "completed" : "failed",
4690
- inputRefs: [decision.decisionId],
4691
- outputRefs: finalAcceptance.canBreak ? ["runtime.final.accepted"] : finalAcceptance.blockingRefs,
4692
- error: finalAcceptance.canBreak ? undefined : {
4693
- code: "FINAL_OUTPUT_REJECTED",
4694
- message: finalAcceptance.reason,
4695
- boundary: "output",
4696
- publicSafe: true,
4697
- },
4698
- now: now(),
4699
- metadata: {
4700
- decisionKind: decision.kind,
4701
- finalAcceptanceKind: finalAcceptance.kind,
4702
- canBreak: finalAcceptance.canBreak,
4703
- },
4704
- }),
4705
- });
4706
- if (!finalAcceptance.canBreak) {
4707
- return {
4708
- ok: false,
4709
- error: {
4710
- code: "FINAL_OUTPUT_REJECTED",
4711
- message: finalAcceptance.reason,
4712
- boundary: "output",
4713
- publicSafe: true,
4714
- },
4715
- events: ["agentCore.execution.mainLoop.runner.finalOutputRejected"],
4716
- };
4717
- }
4718
- return {
4719
- ok: true,
4720
- finalOutput: finalAcceptance.finalOutput ?? "",
4721
- events: ["agentCore.execution.mainLoop.runner.finalOutputAccepted"],
4722
- };
4723
- },
4724
- handleContinue: async ({ turnIndex: turn, decisionIndex, decision }) => {
4725
- const expansion = decision.toolContextExpansion;
4726
- if (expansion === undefined) {
4727
- return {
4728
- ok: true,
4729
- continueLoop: true,
4730
- events: ["agentCore.execution.mainLoop.runner.continue"],
4731
- };
4732
- }
4733
- if (expansion.targetKind === "family" && expansion.family !== undefined && !toolContextSelection.families.includes(expansion.family)) {
4734
- toolContextSelection.families.push(expansion.family);
4735
- }
4736
- if (expansion.targetKind === "group" && expansion.family !== undefined && expansion.group !== undefined) {
4737
- const groupId = `${expansion.family}/${expansion.group}`;
4738
- if (!toolContextSelection.groups.includes(groupId))
4739
- toolContextSelection.groups.push(groupId);
4740
- }
4741
- if (expansion.targetKind === "tool" && expansion.toolId !== undefined && !toolContextSelection.toolIds.includes(expansion.toolId)) {
4742
- toolContextSelection.toolIds.push(expansion.toolId);
4743
- }
4744
- const providerCallId = typeof decision.metadata.callId === "string" && decision.metadata.callId.trim().length > 0
4745
- ? decision.metadata.callId.trim()
4746
- : undefined;
4747
- if (providerCallId !== undefined) {
4748
- observations.push(createObservationMaterial({
4749
- observationId: `${sessionId}:observation:${providerCallId}:tool-context-expanded`,
4750
- source: "runtime",
4751
- status: "completed",
4752
- title: "BaseTool context expanded",
4753
- summary: `Expanded ${expansion.targetKind} BaseTool context for the next turn.`,
4754
- refs: [providerCallId, decision.decisionId],
4755
- payload: {
4756
- expanded: expansion,
4757
- selection: {
4758
- families: [...toolContextSelection.families],
4759
- groups: [...toolContextSelection.groups],
4760
- toolIds: [...toolContextSelection.toolIds],
4858
+ createdAt: now(),
4859
+ events,
4860
+ mainLoopSteps,
4861
+ step: createMainLoopStepRecord({
4862
+ sessionId,
4863
+ turnIndex: turn,
4864
+ stepIndex: stepBase + 5,
4865
+ actionPrimitive: "interpretModelDecision",
4866
+ status: decisionResult.ok ? "completed" : "failed",
4867
+ inputRefs: [model.modelCallId],
4868
+ outputRefs: decisionResult.ok ? decisionResult.decisions.map((decision) => decision.decisionId) : [],
4869
+ modelCallId: model.modelCallId,
4870
+ promptPackRef: prompt.promptPackId,
4871
+ error: decisionResult.ok ? undefined : {
4872
+ code: decisionResult.error.code,
4873
+ message: decisionResult.error.message,
4874
+ boundary: "model",
4875
+ publicSafe: true,
4876
+ },
4877
+ now: now(),
4878
+ metadata: {
4879
+ decisionKinds: decisionResult.ok ? decisionResult.decisions.map((decision) => decision.kind) : [],
4761
4880
  },
4762
- },
4763
- trustLevel: "runtimeFact",
4764
- metadata: metadataRecord({
4765
- toolCallId: providerCallId,
4766
- toolId: "praxis_expand_tool_context",
4767
- providerToolName: "praxis_expand_tool_context",
4768
- observationStatus: "completed",
4769
- runtimeDecision: "expandToolContext",
4770
4881
  }),
4771
- }));
4772
- }
4773
- const stepBase = turn * 20 + 2;
4774
- await recordMainLoopStep({
4775
- store,
4776
- sessionId,
4777
- createdAt: now(),
4778
- events,
4779
- mainLoopSteps,
4780
- step: createMainLoopStepRecord({
4781
- sessionId,
4782
- turnIndex: turn,
4783
- stepIndex: stepBase + 6 + decisionIndex,
4784
- actionPrimitive: "emitEvent",
4785
- status: "completed",
4786
- inputRefs: [decision.decisionId],
4787
- outputRefs: [
4788
- ...(expansion.family === undefined ? [] : [`baseToolContext.family:${expansion.family}`]),
4789
- ...(expansion.group === undefined || expansion.family === undefined ? [] : [`baseToolContext.group:${expansion.family}/${expansion.group}`]),
4790
- ...(expansion.toolId === undefined ? [] : [`baseToolContext.tool:${expansion.toolId}`]),
4791
- ],
4792
- now: now(),
4793
- metadata: {
4794
- runtimeDecision: "expandToolContext",
4795
- expansion,
4796
- reason: expansion.reason ?? "model requested folded BaseTool documentation",
4797
- selection: {
4798
- families: [...toolContextSelection.families],
4799
- groups: [...toolContextSelection.groups],
4800
- toolIds: [...toolContextSelection.toolIds],
4882
+ });
4883
+ if (!decisionResult.ok) {
4884
+ const error = kernelError("MODEL_DECISION_FAILED", decisionResult.error.message, "model");
4885
+ runnerError = error;
4886
+ await recordKernelError({ store, sessionId, errorId: `error:modelDecision:${turn + 1}`, error, createdAt: now(), metadata: { modelInvocationId: model.modelCallId } });
4887
+ return {
4888
+ ok: false,
4889
+ error: {
4890
+ code: error.code,
4891
+ message: error.message,
4892
+ boundary: "model-decision",
4893
+ publicSafe: true,
4801
4894
  },
4802
- },
4803
- }),
4804
- });
4805
- return {
4806
- ok: true,
4807
- continueLoop: true,
4808
- events: ["agentCore.execution.mainLoop.runner.expandToolContext"],
4809
- };
4810
- },
4811
- handleFailure: async ({ turnIndex: turn, decisionIndex, decision }) => {
4812
- const stepBase = turn * 20 + 2;
4813
- const error = kernelError("MODEL_DECISION_FAILED", decision.failure?.message ?? "model decision requested failure", "model");
4814
- runnerError = error;
4815
- await recordMainLoopStep({
4816
- store,
4817
- sessionId,
4818
- createdAt: now(),
4819
- events,
4820
- mainLoopSteps,
4821
- step: createMainLoopStepRecord({
4822
- sessionId,
4823
- turnIndex: turn,
4824
- stepIndex: stepBase + 6 + decisionIndex,
4825
- actionPrimitive: "fail",
4826
- status: "failed",
4827
- inputRefs: [decision.decisionId],
4828
- error: {
4829
- code: decision.failure?.code ?? "MODEL_DECISION_FAILED",
4830
- message: decision.failure?.message ?? "model decision requested failure",
4831
- boundary: "model",
4832
- publicSafe: true,
4833
- },
4834
- now: now(),
4835
- }),
4836
- });
4837
- await recordKernelError({
4838
- store,
4839
- sessionId,
4840
- errorId: `error:modelDecisionFail:${turn + 1}:${decisionIndex}`,
4841
- error,
4842
- createdAt: now(),
4843
- metadata: {
4844
- decisionId: decision.decisionId,
4845
- providerRawRef: decision.metadata.providerRawRef,
4846
- },
4847
- });
4848
- return {
4849
- ok: false,
4850
- error: {
4851
- code: error.code,
4852
- message: error.message,
4853
- boundary: "model-decision",
4854
- publicSafe: true,
4855
- },
4856
- events: ["agentCore.execution.mainLoop.runner.fail"],
4857
- };
4858
- },
4859
- handleApproval: async ({ turnIndex: turn, decisionIndex, decision }) => {
4860
- const stepBase = turn * 20 + 2;
4861
- const approval = await requestRuntimeApproval({
4862
- runtimeId,
4863
- sessionId,
4864
- approvalId: `${decision.decisionId}:approval`,
4865
- source: "model",
4866
- reason: decision.approvalRequest?.reason ?? "model requested approval",
4867
- requestedScopes: decision.approvalRequest?.requestedScopes ?? [],
4868
- riskLevel: decision.approvalRequest?.riskLevel,
4869
- resolver: options.approvalResolver,
4870
- store,
4871
- now,
4872
- metadata: {
4873
- decisionId: decision.decisionId,
4874
- modelCallId: `${sessionId}:model:${turn + 1}`,
4875
- },
4876
- });
4877
- events.push(...approval.events);
4878
- await recordHandoffPlan({
4879
- store,
4880
- sessionId,
4881
- createdAt: now(),
4882
- events,
4883
- mainLoopSteps,
4884
- turnIndex: turn,
4885
- startStepIndex: stepBase + 30 + decisionIndex * 10,
4886
- tickKind: "approval-wait",
4887
- inputRefs: [decision.decisionId],
4888
- outputRefs: [approval.envelope.approvalId],
4889
- });
4890
- await recordMainLoopStep({
4891
- store,
4892
- sessionId,
4893
- createdAt: now(),
4894
- events,
4895
- mainLoopSteps,
4896
- step: createMainLoopStepRecord({
4895
+ events: decisionResult.events,
4896
+ };
4897
+ }
4898
+ return { ok: true, decisions: decisionResult.decisions, events: decisionResult.events };
4899
+ },
4900
+ acceptFinalOutput: async ({ turnIndex: turn, decisionIndex, decision }) => {
4901
+ const finalAcceptance = decideMainLoopFinalAcceptance({
4902
+ finalOutput: decision.finalOutput ?? "",
4903
+ fatalFailureRefs: runnerError === undefined ? [] : [runnerError.code],
4904
+ });
4905
+ await recordMainLoopStep({
4906
+ store,
4897
4907
  sessionId,
4898
- turnIndex: turn,
4899
- stepIndex: stepBase + 6 + decisionIndex,
4900
- actionPrimitive: "requestApproval",
4901
- status: "waitingApproval",
4902
- inputRefs: [decision.decisionId],
4903
- outputRefs: decision.approvalRequest?.requestedScopes ?? [],
4904
- now: now(),
4905
- metadata: {
4906
- reason: decision.approvalRequest?.reason ?? "model requested approval",
4907
- riskLevel: decision.approvalRequest?.riskLevel ?? "unknown",
4908
- approvalId: approval.envelope.approvalId,
4909
- approvalStatus: approval.status,
4910
- },
4911
- }),
4912
- });
4913
- if (approval.status === "approved") {
4908
+ createdAt: now(),
4909
+ events,
4910
+ mainLoopSteps,
4911
+ step: createMainLoopStepRecord({
4912
+ sessionId,
4913
+ turnIndex: turn,
4914
+ stepIndex: turn * 20 + 60 + decisionIndex,
4915
+ actionPrimitive: "adjudicateDecision",
4916
+ status: finalAcceptance.canBreak ? "completed" : "failed",
4917
+ inputRefs: [decision.decisionId],
4918
+ outputRefs: finalAcceptance.canBreak ? ["runtime.final.accepted"] : finalAcceptance.blockingRefs,
4919
+ error: finalAcceptance.canBreak ? undefined : {
4920
+ code: "FINAL_OUTPUT_REJECTED",
4921
+ message: finalAcceptance.reason,
4922
+ boundary: "output",
4923
+ publicSafe: true,
4924
+ },
4925
+ now: now(),
4926
+ metadata: {
4927
+ decisionKind: decision.kind,
4928
+ finalAcceptanceKind: finalAcceptance.kind,
4929
+ canBreak: finalAcceptance.canBreak,
4930
+ },
4931
+ }),
4932
+ });
4933
+ if (!finalAcceptance.canBreak) {
4934
+ return {
4935
+ ok: false,
4936
+ error: {
4937
+ code: "FINAL_OUTPUT_REJECTED",
4938
+ message: finalAcceptance.reason,
4939
+ boundary: "output",
4940
+ publicSafe: true,
4941
+ },
4942
+ events: ["agentCore.execution.mainLoop.runner.finalOutputRejected"],
4943
+ };
4944
+ }
4945
+ return {
4946
+ ok: true,
4947
+ finalOutput: finalAcceptance.finalOutput ?? "",
4948
+ events: ["agentCore.execution.mainLoop.runner.finalOutputAccepted"],
4949
+ };
4950
+ },
4951
+ handleContinue: async ({ turnIndex: turn, decisionIndex, decision }) => {
4952
+ const expansion = decision.toolContextExpansion;
4953
+ if (expansion === undefined) {
4954
+ return {
4955
+ ok: true,
4956
+ continueLoop: true,
4957
+ events: ["agentCore.execution.mainLoop.runner.continue"],
4958
+ };
4959
+ }
4960
+ if (expansion.targetKind === "family" && expansion.family !== undefined && !toolContextSelection.families.includes(expansion.family)) {
4961
+ toolContextSelection.families.push(expansion.family);
4962
+ }
4963
+ if (expansion.targetKind === "group" && expansion.family !== undefined && expansion.group !== undefined) {
4964
+ const groupId = `${expansion.family}/${expansion.group}`;
4965
+ if (!toolContextSelection.groups.includes(groupId))
4966
+ toolContextSelection.groups.push(groupId);
4967
+ }
4968
+ if (expansion.targetKind === "tool" && expansion.toolId !== undefined && !toolContextSelection.toolIds.includes(expansion.toolId)) {
4969
+ toolContextSelection.toolIds.push(expansion.toolId);
4970
+ }
4914
4971
  const providerCallId = typeof decision.metadata.callId === "string" && decision.metadata.callId.trim().length > 0
4915
4972
  ? decision.metadata.callId.trim()
4916
4973
  : undefined;
4917
4974
  if (providerCallId !== undefined) {
4918
4975
  observations.push(createObservationMaterial({
4919
- observationId: `${sessionId}:observation:${providerCallId}:approval`,
4976
+ observationId: `${sessionId}:observation:${providerCallId}:tool-context-expanded`,
4920
4977
  source: "runtime",
4921
4978
  status: "completed",
4922
- title: "Runtime approval resolved",
4923
- summary: approval.reason ?? "model approval request was approved",
4924
- refs: [providerCallId, decision.decisionId, approval.envelope.approvalId],
4979
+ title: "BaseTool context expanded",
4980
+ summary: `Expanded ${expansion.targetKind} BaseTool context for the next turn.`,
4981
+ refs: [providerCallId, decision.decisionId],
4925
4982
  payload: {
4926
- status: approval.status,
4927
- reason: approval.reason,
4928
- approvalId: approval.envelope.approvalId,
4929
- requestedScopes: approval.envelope.requestedScopes,
4930
- riskLevel: approval.envelope.riskLevel,
4983
+ expanded: expansion,
4984
+ selection: {
4985
+ families: [...toolContextSelection.families],
4986
+ groups: [...toolContextSelection.groups],
4987
+ toolIds: [...toolContextSelection.toolIds],
4988
+ },
4931
4989
  },
4932
- trustLevel: "runtimeFact",
4933
- metadata: metadataRecord({
4934
- toolCallId: providerCallId,
4935
- toolId: "praxis.request.approval",
4936
- providerToolName: "praxis_request_approval",
4937
- observationStatus: "completed",
4938
- runtimeDecision: "requestApproval",
4939
- approvalId: approval.envelope.approvalId,
4940
- approvalStatus: approval.status,
4941
- }),
4942
- }));
4943
- }
4944
- return { ok: true, continueLoop: true, events: approval.events };
4945
- }
4946
- const error = kernelError("APPROVAL_REQUIRED", decision.approvalRequest?.reason ?? "model requested approval", "tool");
4947
- runnerError = error;
4948
- await recordKernelError({
4949
- store,
4950
- sessionId,
4951
- errorId: `error:approval:${approval.envelope.approvalId}`,
4952
- error,
4953
- createdAt: now(),
4954
- metadata: {
4955
- approvalId: approval.envelope.approvalId,
4956
- approvalStatus: approval.status,
4957
- decisionId: decision.decisionId,
4958
- },
4959
- });
4960
- return {
4961
- ok: false,
4962
- error: {
4963
- code: error.code,
4964
- message: error.message,
4965
- boundary: "approval",
4966
- publicSafe: true,
4967
- },
4968
- events: approval.events,
4969
- };
4970
- },
4971
- handleToolCall: async ({ turnIndex: turn, decisionIndex, decision }) => {
4972
- const stepBase = turn * 20 + 2;
4973
- if (decision.toolCall === undefined) {
4974
- return {
4975
- ok: false,
4976
- error: {
4977
- code: "MISSING_TOOL_CALL",
4978
- message: "toolCall decision is missing tool call payload",
4979
- boundary: "tool",
4980
- publicSafe: true,
4981
- },
4982
- events: ["agentCore.execution.mainLoop.runner.toolCallRejected"],
4983
- };
4984
- }
4985
- const preambleText = decision.preambleText?.trim();
4986
- if (preambleText) {
4987
- await options.onTextDelta?.(preambleText, {
4988
- source: "model_tool_preamble",
4989
- decisionId: decision.decisionId,
4990
- toolCallId: decision.toolCall.callId,
4991
- toolId: decision.toolCall.toolId,
4992
- });
4993
- }
4994
- await recordHandoffPlan({
4995
- store,
4996
- sessionId,
4997
- createdAt: now(),
4998
- events,
4999
- mainLoopSteps,
5000
- turnIndex: turn,
5001
- startStepIndex: stepBase + 40 + decisionIndex * 10,
5002
- tickKind: "tool-call",
5003
- toolCallId: decision.toolCall.callId,
5004
- inputRefs: [decision.decisionId],
5005
- });
5006
- await store.appendState(state(sessionId, `state:tool:${decision.toolCall.callId}`, "tool", now(), {
5007
- toolId: decision.toolCall.toolId,
5008
- providerToolName: decision.toolCall.providerToolName,
5009
- }));
5010
- await options.onToolCallProgress?.({
5011
- phase: "started",
5012
- callId: decision.toolCall.callId,
5013
- toolId: decision.toolCall.toolId,
5014
- providerToolName: decision.toolCall.providerToolName,
5015
- arguments: decision.toolCall.arguments,
5016
- });
5017
- const readCacheKey = fileReadCacheKey(decision.toolCall.toolId, decision.toolCall.arguments);
5018
- const cachedRead = readCacheKey === undefined ? undefined : sameTurnFileReadCache.get(readCacheKey);
5019
- if (cachedRead?.fullFileRead === true) {
5020
- const reused = duplicateFileReadRecord({
5021
- sessionId,
5022
- toolCallId: decision.toolCall.callId,
5023
- toolId: decision.toolCall.toolId,
5024
- providerToolName: decision.toolCall.providerToolName,
5025
- args: decision.toolCall.arguments,
5026
- previousCallId: cachedRead.callId,
5027
- now: now(),
5028
- });
5029
- await options.onToolCallProgress?.({
5030
- phase: "completed",
5031
- providerToolName: decision.toolCall.providerToolName,
5032
- record: reused.record,
5033
- });
5034
- toolCalls.push(reused.record);
5035
- observations.push(reused.observation);
5036
- await store.appendInvocation(invocation(sessionId, reused.record.callId, "tool", reused.record.toolId, true, now(), {
5037
- ok: true,
5038
- decisionId: decision.decisionId,
5039
- duplicateOfToolCallId: cachedRead.callId,
5040
- }));
4990
+ trustLevel: "runtimeFact",
4991
+ metadata: metadataRecord({
4992
+ toolCallId: providerCallId,
4993
+ toolId: "praxis_expand_tool_context",
4994
+ providerToolName: "praxis_expand_tool_context",
4995
+ observationStatus: "completed",
4996
+ runtimeDecision: "expandToolContext",
4997
+ }),
4998
+ }));
4999
+ }
5000
+ const stepBase = turn * 20 + 2;
5041
5001
  await recordMainLoopStep({
5042
5002
  store,
5043
5003
  sessionId,
@@ -5048,47 +5008,37 @@ export class PraxisRuntimeKernel {
5048
5008
  sessionId,
5049
5009
  turnIndex: turn,
5050
5010
  stepIndex: stepBase + 6 + decisionIndex,
5051
- actionPrimitive: "invokeBaseTool",
5011
+ actionPrimitive: "emitEvent",
5052
5012
  status: "completed",
5053
5013
  inputRefs: [decision.decisionId],
5054
- outputRefs: [reused.record.callId],
5055
- toolCallId: reused.record.callId,
5056
- observationRefs: [reused.observation.observationId],
5014
+ outputRefs: [
5015
+ ...(expansion.family === undefined ? [] : [`baseToolContext.family:${expansion.family}`]),
5016
+ ...(expansion.group === undefined || expansion.family === undefined ? [] : [`baseToolContext.group:${expansion.family}/${expansion.group}`]),
5017
+ ...(expansion.toolId === undefined ? [] : [`baseToolContext.tool:${expansion.toolId}`]),
5018
+ ],
5057
5019
  now: now(),
5058
5020
  metadata: {
5059
- toolId: reused.record.toolId,
5060
- providerToolName: decision.toolCall.providerToolName ?? "",
5061
- duplicateObservationReuse: true,
5062
- duplicateOfToolCallId: cachedRead.callId,
5021
+ runtimeDecision: "expandToolContext",
5022
+ expansion,
5023
+ reason: expansion.reason ?? "model requested folded BaseTool documentation",
5024
+ selection: {
5025
+ families: [...toolContextSelection.families],
5026
+ groups: [...toolContextSelection.groups],
5027
+ toolIds: [...toolContextSelection.toolIds],
5028
+ },
5063
5029
  },
5064
5030
  }),
5065
5031
  });
5066
- return { ok: true, continueLoop: true, events: ["runtime.baseTool.fileRead.duplicateObservationReused"] };
5067
- }
5068
- const shellCacheKey = shellRunCacheKey(decision.toolCall.toolId, decision.toolCall.arguments);
5069
- const cachedShell = shellCacheKey === undefined ? undefined : sameTurnShellRunCache.get(shellCacheKey);
5070
- if (cachedShell !== undefined) {
5071
- const reused = duplicateShellRunRecord({
5072
- sessionId,
5073
- toolCallId: decision.toolCall.callId,
5074
- toolId: decision.toolCall.toolId,
5075
- providerToolName: decision.toolCall.providerToolName,
5076
- args: decision.toolCall.arguments,
5077
- previousCallId: cachedShell.callId,
5078
- now: now(),
5079
- });
5080
- await options.onToolCallProgress?.({
5081
- phase: "completed",
5082
- providerToolName: decision.toolCall.providerToolName,
5083
- record: reused.record,
5084
- });
5085
- toolCalls.push(reused.record);
5086
- observations.push(reused.observation);
5087
- await store.appendInvocation(invocation(sessionId, reused.record.callId, "tool", reused.record.toolId, true, now(), {
5032
+ return {
5088
5033
  ok: true,
5089
- decisionId: decision.decisionId,
5090
- duplicateOfToolCallId: cachedShell.callId,
5091
- }));
5034
+ continueLoop: true,
5035
+ events: ["agentCore.execution.mainLoop.runner.expandToolContext"],
5036
+ };
5037
+ },
5038
+ handleFailure: async ({ turnIndex: turn, decisionIndex, decision }) => {
5039
+ const stepBase = turn * 20 + 2;
5040
+ const error = kernelError("MODEL_DECISION_FAILED", decision.failure?.message ?? "model decision requested failure", "model");
5041
+ runnerError = error;
5092
5042
  await recordMainLoopStep({
5093
5043
  store,
5094
5044
  sessionId,
@@ -5099,424 +5049,743 @@ export class PraxisRuntimeKernel {
5099
5049
  sessionId,
5100
5050
  turnIndex: turn,
5101
5051
  stepIndex: stepBase + 6 + decisionIndex,
5102
- actionPrimitive: "invokeBaseTool",
5103
- status: "completed",
5052
+ actionPrimitive: "fail",
5053
+ status: "failed",
5104
5054
  inputRefs: [decision.decisionId],
5105
- outputRefs: [reused.record.callId],
5106
- toolCallId: reused.record.callId,
5107
- observationRefs: [reused.observation.observationId],
5108
- now: now(),
5109
- metadata: {
5110
- toolId: reused.record.toolId,
5111
- providerToolName: decision.toolCall.providerToolName ?? "",
5112
- duplicateObservationReuse: true,
5113
- duplicateOfToolCallId: cachedShell.callId,
5055
+ error: {
5056
+ code: decision.failure?.code ?? "MODEL_DECISION_FAILED",
5057
+ message: decision.failure?.message ?? "model decision requested failure",
5058
+ boundary: "model",
5059
+ publicSafe: true,
5114
5060
  },
5061
+ now: now(),
5115
5062
  }),
5116
5063
  });
5117
- return { ok: true, continueLoop: true, events: ["runtime.baseTool.shellRun.duplicateObservationReused"] };
5118
- }
5119
- const executed = await executeBaseToolDecision({
5120
- runtimeId,
5121
- sessionId,
5122
- manifest,
5123
- executor,
5124
- toolCallId: decision.toolCall.callId,
5125
- toolId: decision.toolCall.toolId,
5126
- providerToolName: decision.toolCall.providerToolName,
5127
- args: decision.toolCall.arguments,
5128
- workspaceRoot: toolWorkspaceRoot,
5129
- allowedRoots: toolAllowedRoots,
5130
- allowToolExecution: options.allowToolExecution,
5131
- store,
5132
- approvalResolver: options.approvalResolver,
5133
- agentReviewResolver: options.agentReviewResolver,
5134
- mcpPlusRuntime,
5135
- preparedSandbox: sandboxPrepared.sandbox,
5136
- dependencyRuntime: options.baseToolDependencyRuntime,
5137
- now,
5138
- events,
5139
- });
5140
- await options.onToolCallProgress?.({
5141
- phase: executed.record.ok ? "completed" : "failed",
5142
- providerToolName: decision.toolCall.providerToolName,
5143
- record: executed.record,
5144
- });
5145
- toolCalls.push(executed.record);
5146
- observations.push(executed.observation);
5147
- const mcpPlusToolCallUpdated = await mcpPlusRuntime.recordToolCall({
5148
- manifest,
5149
- toolId: executed.record.toolId,
5150
- ok: executed.record.ok,
5151
- now: now(),
5152
- });
5153
- if (executed.record.ok && (mcpPlusToolCallUpdated ||
5154
- manifest.harness.tools.find((tool) => tool.toolId === executed.record.toolId)?.metadata?.toolProviderKind === "mcp-plus-control")) {
5155
- await refreshRuntimeMcpTools("session.checkpoint.after_mcp_tool");
5156
- }
5157
- if (invalidatesFileReadCache(executed.record.toolId)) {
5158
- sameTurnFileReadCache.clear();
5159
- }
5160
- else if (readCacheKey !== undefined && executed.record.ok) {
5161
- const output = isRecord(executed.record.output) ? executed.record.output : undefined;
5162
- const truncated = output?.truncated === true;
5163
- sameTurnFileReadCache.set(readCacheKey, {
5164
- callId: executed.record.callId,
5165
- fullFileRead: !truncated,
5166
- });
5167
- }
5168
- if (shellCacheKey !== undefined && executed.record.ok) {
5169
- sameTurnShellRunCache.set(shellCacheKey, { callId: executed.record.callId });
5170
- }
5171
- if (invalidatesShellRunCache(executed.record.toolId)) {
5172
- sameTurnShellRunCache.clear();
5173
- }
5174
- toolContextHeatState = applyBaseToolContextUsage(toolContextHeatState, [{ toolId: executed.record.toolId }], now());
5175
- await store.appendState(state(sessionId, `state:toolContextHeat:${executed.record.callId}`, "toolContextHeat", now(), {
5176
- agentId: toolContextHeatState.agentId,
5177
- usage: toolContextHeatState.usage,
5178
- }));
5179
- await store.appendInvocation(invocation(sessionId, executed.record.callId, "tool", executed.record.toolId, executed.record.ok, now(), {
5180
- ok: executed.record.ok,
5181
- decisionId: decision.decisionId,
5182
- }));
5183
- await recordMainLoopStep({
5184
- store,
5185
- sessionId,
5186
- createdAt: now(),
5187
- events,
5188
- mainLoopSteps,
5189
- step: createMainLoopStepRecord({
5190
- sessionId,
5191
- turnIndex: turn,
5192
- stepIndex: stepBase + 6 + decisionIndex,
5193
- actionPrimitive: "invokeBaseTool",
5194
- status: executed.record.ok ? "completed" : (isRecord(executed.record.error) && executed.record.error.code === "APPROVAL_REQUIRED" ? "waitingApproval" : "failed"),
5195
- inputRefs: [decision.decisionId],
5196
- outputRefs: [executed.record.callId],
5197
- toolCallId: executed.record.callId,
5198
- observationRefs: [executed.observation.observationId],
5199
- error: executed.record.ok ? undefined : {
5200
- code: "TOOL_INVOCATION_FAILED",
5201
- message: `tool invocation failed: ${executed.record.toolId}`,
5202
- boundary: "tool",
5203
- publicSafe: true,
5204
- },
5205
- now: now(),
5206
- metadata: {
5207
- toolId: executed.record.toolId,
5208
- providerToolName: decision.toolCall.providerToolName ?? "",
5209
- },
5210
- }),
5211
- });
5212
- if (!executed.record.ok) {
5213
- const approvalRequired = isRecord(executed.record.error) && executed.record.error.code === "APPROVAL_REQUIRED";
5214
- const error = approvalRequired
5215
- ? kernelError("APPROVAL_REQUIRED", `tool invocation requires approval: ${executed.record.toolId}`, "tool")
5216
- : kernelError("TOOL_INVOCATION_FAILED", `tool invocation failed: ${executed.record.toolId}`, "tool");
5217
5064
  await recordKernelError({
5218
5065
  store,
5219
5066
  sessionId,
5220
- errorId: `error:tool:${executed.record.callId}`,
5067
+ errorId: `error:modelDecisionFail:${turn + 1}:${decisionIndex}`,
5221
5068
  error,
5222
5069
  createdAt: now(),
5223
5070
  metadata: {
5224
- toolCallId: executed.record.callId,
5225
- toolId: executed.record.toolId,
5226
- approvalRequired,
5071
+ decisionId: decision.decisionId,
5072
+ providerRawRef: decision.metadata.providerRawRef,
5227
5073
  },
5228
5074
  });
5229
- if (!approvalRequired) {
5230
- return {
5231
- ok: true,
5232
- continueLoop: true,
5233
- events: [...executed.events, "agentCore.execution.mainLoop.runner.toolFailureObservation"],
5234
- };
5235
- }
5236
- runnerError = error;
5237
5075
  return {
5238
5076
  ok: false,
5239
5077
  error: {
5240
5078
  code: error.code,
5241
5079
  message: error.message,
5242
- boundary: approvalRequired ? "approval" : "tool",
5080
+ boundary: "model-decision",
5243
5081
  publicSafe: true,
5244
5082
  },
5245
- events: executed.events,
5083
+ events: ["agentCore.execution.mainLoop.runner.fail"],
5246
5084
  };
5247
- }
5248
- return { ok: true, continueLoop: true, events: executed.events };
5249
- },
5250
- handleEphemeralProcedure: async ({ turnIndex: turn, decisionIndex, decision }) => {
5251
- const stepBase = turn * 20 + 2;
5252
- if (decision.ephemeralProcedurePlan === undefined) {
5085
+ },
5086
+ handleApproval: async ({ turnIndex: turn, decisionIndex, decision }) => {
5087
+ const stepBase = turn * 20 + 2;
5088
+ const approval = await requestRuntimeApproval({
5089
+ runtimeId,
5090
+ sessionId,
5091
+ approvalId: `${decision.decisionId}:approval`,
5092
+ source: "model",
5093
+ reason: decision.approvalRequest?.reason ?? "model requested approval",
5094
+ requestedScopes: decision.approvalRequest?.requestedScopes ?? [],
5095
+ riskLevel: decision.approvalRequest?.riskLevel,
5096
+ resolver: options.approvalResolver,
5097
+ store,
5098
+ now,
5099
+ metadata: {
5100
+ decisionId: decision.decisionId,
5101
+ modelCallId: `${sessionId}:model:${turn + 1}`,
5102
+ },
5103
+ });
5104
+ events.push(...approval.events);
5105
+ await recordHandoffPlan({
5106
+ store,
5107
+ sessionId,
5108
+ createdAt: now(),
5109
+ events,
5110
+ mainLoopSteps,
5111
+ turnIndex: turn,
5112
+ startStepIndex: stepBase + 30 + decisionIndex * 10,
5113
+ tickKind: "approval-wait",
5114
+ inputRefs: [decision.decisionId],
5115
+ outputRefs: [approval.envelope.approvalId],
5116
+ });
5117
+ await recordMainLoopStep({
5118
+ store,
5119
+ sessionId,
5120
+ createdAt: now(),
5121
+ events,
5122
+ mainLoopSteps,
5123
+ step: createMainLoopStepRecord({
5124
+ sessionId,
5125
+ turnIndex: turn,
5126
+ stepIndex: stepBase + 6 + decisionIndex,
5127
+ actionPrimitive: "requestApproval",
5128
+ status: "waitingApproval",
5129
+ inputRefs: [decision.decisionId],
5130
+ outputRefs: decision.approvalRequest?.requestedScopes ?? [],
5131
+ now: now(),
5132
+ metadata: {
5133
+ reason: decision.approvalRequest?.reason ?? "model requested approval",
5134
+ riskLevel: decision.approvalRequest?.riskLevel ?? "unknown",
5135
+ approvalId: approval.envelope.approvalId,
5136
+ approvalStatus: approval.status,
5137
+ },
5138
+ }),
5139
+ });
5140
+ if (approval.status === "approved") {
5141
+ const providerCallId = typeof decision.metadata.callId === "string" && decision.metadata.callId.trim().length > 0
5142
+ ? decision.metadata.callId.trim()
5143
+ : undefined;
5144
+ if (providerCallId !== undefined) {
5145
+ observations.push(createObservationMaterial({
5146
+ observationId: `${sessionId}:observation:${providerCallId}:approval`,
5147
+ source: "runtime",
5148
+ status: "completed",
5149
+ title: "Runtime approval resolved",
5150
+ summary: approval.reason ?? "model approval request was approved",
5151
+ refs: [providerCallId, decision.decisionId, approval.envelope.approvalId],
5152
+ payload: {
5153
+ status: approval.status,
5154
+ reason: approval.reason,
5155
+ approvalId: approval.envelope.approvalId,
5156
+ requestedScopes: approval.envelope.requestedScopes,
5157
+ riskLevel: approval.envelope.riskLevel,
5158
+ },
5159
+ trustLevel: "runtimeFact",
5160
+ metadata: metadataRecord({
5161
+ toolCallId: providerCallId,
5162
+ toolId: "praxis.request.approval",
5163
+ providerToolName: "praxis_request_approval",
5164
+ observationStatus: "completed",
5165
+ runtimeDecision: "requestApproval",
5166
+ approvalId: approval.envelope.approvalId,
5167
+ approvalStatus: approval.status,
5168
+ }),
5169
+ }));
5170
+ }
5171
+ return { ok: true, continueLoop: true, events: approval.events };
5172
+ }
5173
+ const error = kernelError("APPROVAL_REQUIRED", decision.approvalRequest?.reason ?? "model requested approval", "tool");
5174
+ runnerError = error;
5175
+ await recordKernelError({
5176
+ store,
5177
+ sessionId,
5178
+ errorId: `error:approval:${approval.envelope.approvalId}`,
5179
+ error,
5180
+ createdAt: now(),
5181
+ metadata: {
5182
+ approvalId: approval.envelope.approvalId,
5183
+ approvalStatus: approval.status,
5184
+ decisionId: decision.decisionId,
5185
+ },
5186
+ });
5253
5187
  return {
5254
5188
  ok: false,
5255
5189
  error: {
5256
- code: "MISSING_EPHEMERAL_PROCEDURE_PLAN",
5257
- message: "ephemeralProcedurePlan decision is missing a procedure plan",
5258
- boundary: "procedure",
5190
+ code: error.code,
5191
+ message: error.message,
5192
+ boundary: "approval",
5259
5193
  publicSafe: true,
5260
5194
  },
5261
- events: ["agentCore.execution.mainLoop.runner.procedureRejected"],
5195
+ events: approval.events,
5262
5196
  };
5263
- }
5264
- const procedureCreatedAt = now();
5265
- await recordHandoffPlan({
5266
- store,
5267
- sessionId,
5268
- createdAt: procedureCreatedAt,
5269
- events,
5270
- mainLoopSteps,
5271
- turnIndex: turn,
5272
- startStepIndex: stepBase + 50 + decisionIndex * 10,
5273
- tickKind: "ephemeral-procedure",
5274
- procedureId: decision.ephemeralProcedurePlan.procedureId,
5275
- inputRefs: [decision.decisionId],
5276
- });
5277
- await store.appendProcedure({
5278
- sessionId,
5279
- procedureId: decision.ephemeralProcedurePlan.procedureId,
5280
- status: decision.ephemeralProcedurePlan.approval.required ? "waitingApproval" : "running",
5281
- createdAt: procedureCreatedAt,
5282
- summary: {
5283
- decisionId: decision.decisionId,
5284
- executionMode: decision.ephemeralProcedurePlan.executionMode,
5285
- requiredBaseTools: decision.ephemeralProcedurePlan.requiredBaseTools,
5286
- riskLevel: decision.ephemeralProcedurePlan.riskLevel,
5287
- },
5288
- });
5289
- await store.appendInvocation(invocation(sessionId, decision.ephemeralProcedurePlan.procedureId, "procedure", decision.ephemeralProcedurePlan.purpose, true, now(), {
5290
- decisionId: decision.decisionId,
5291
- status: "planned",
5292
- }));
5293
- const procedureResult = await executeEphemeralProcedure({
5294
- runtimeId,
5295
- sessionId,
5296
- manifest,
5297
- executor,
5298
- plan: decision.ephemeralProcedurePlan,
5299
- workspaceRoot: toolWorkspaceRoot,
5300
- allowedRoots: toolAllowedRoots,
5301
- allowToolExecution: options.allowToolExecution,
5302
- store,
5303
- approvalResolver: options.approvalResolver,
5304
- agentReviewResolver: options.agentReviewResolver,
5305
- preparedSandbox: sandboxPrepared.sandbox,
5306
- dependencyRuntime: options.baseToolDependencyRuntime,
5307
- onToolCallProgress: options.onToolCallProgress,
5308
- interruptSignal: options.interruptSignal,
5309
- now,
5310
- events,
5311
- });
5312
- await store.appendProcedure({
5313
- sessionId,
5314
- procedureId: decision.ephemeralProcedurePlan.procedureId,
5315
- status: procedureResult.ok ? "completed" : (procedureResult.error?.code === "APPROVAL_REQUIRED" ? "waitingApproval" : "failed"),
5316
- createdAt: procedureCreatedAt,
5317
- updatedAt: now(),
5318
- summary: {
5197
+ },
5198
+ handleToolCall: async ({ turnIndex: turn, decisionIndex, decision }) => {
5199
+ const stepBase = turn * 20 + 2;
5200
+ if (decision.toolCall === undefined) {
5201
+ return {
5202
+ ok: false,
5203
+ error: {
5204
+ code: "MISSING_TOOL_CALL",
5205
+ message: "toolCall decision is missing tool call payload",
5206
+ boundary: "tool",
5207
+ publicSafe: true,
5208
+ },
5209
+ events: ["agentCore.execution.mainLoop.runner.toolCallRejected"],
5210
+ };
5211
+ }
5212
+ const preambleText = decision.preambleText?.trim();
5213
+ if (preambleText) {
5214
+ await options.onTextDelta?.(preambleText, {
5215
+ source: "model_tool_preamble",
5216
+ decisionId: decision.decisionId,
5217
+ toolCallId: decision.toolCall.callId,
5218
+ toolId: decision.toolCall.toolId,
5219
+ });
5220
+ }
5221
+ await recordHandoffPlan({
5222
+ store,
5223
+ sessionId,
5224
+ createdAt: now(),
5225
+ events,
5226
+ mainLoopSteps,
5227
+ turnIndex: turn,
5228
+ startStepIndex: stepBase + 40 + decisionIndex * 10,
5229
+ tickKind: "tool-call",
5230
+ toolCallId: decision.toolCall.callId,
5231
+ inputRefs: [decision.decisionId],
5232
+ });
5233
+ await store.appendState(state(sessionId, `state:tool:${decision.toolCall.callId}`, "tool", now(), {
5234
+ toolId: decision.toolCall.toolId,
5235
+ providerToolName: decision.toolCall.providerToolName,
5236
+ }));
5237
+ await options.onToolCallProgress?.({
5238
+ phase: "started",
5239
+ callId: decision.toolCall.callId,
5240
+ toolId: decision.toolCall.toolId,
5241
+ providerToolName: decision.toolCall.providerToolName,
5242
+ arguments: decision.toolCall.arguments,
5243
+ });
5244
+ const readCacheKey = fileReadCacheKey(decision.toolCall.toolId, decision.toolCall.arguments);
5245
+ const cachedRead = readCacheKey === undefined ? undefined : sameTurnFileReadCache.get(readCacheKey);
5246
+ if (cachedRead?.fullFileRead === true) {
5247
+ const reused = duplicateFileReadRecord({
5248
+ sessionId,
5249
+ toolCallId: decision.toolCall.callId,
5250
+ toolId: decision.toolCall.toolId,
5251
+ providerToolName: decision.toolCall.providerToolName,
5252
+ args: decision.toolCall.arguments,
5253
+ previousCallId: cachedRead.callId,
5254
+ now: now(),
5255
+ });
5256
+ await options.onToolCallProgress?.({
5257
+ phase: "completed",
5258
+ providerToolName: decision.toolCall.providerToolName,
5259
+ record: reused.record,
5260
+ });
5261
+ toolCalls.push(reused.record);
5262
+ observations.push(reused.observation);
5263
+ await store.appendInvocation(invocation(sessionId, reused.record.callId, "tool", reused.record.toolId, true, now(), {
5264
+ ok: true,
5265
+ decisionId: decision.decisionId,
5266
+ duplicateOfToolCallId: cachedRead.callId,
5267
+ }));
5268
+ await recordMainLoopStep({
5269
+ store,
5270
+ sessionId,
5271
+ createdAt: now(),
5272
+ events,
5273
+ mainLoopSteps,
5274
+ step: createMainLoopStepRecord({
5275
+ sessionId,
5276
+ turnIndex: turn,
5277
+ stepIndex: stepBase + 6 + decisionIndex,
5278
+ actionPrimitive: "invokeBaseTool",
5279
+ status: "completed",
5280
+ inputRefs: [decision.decisionId],
5281
+ outputRefs: [reused.record.callId],
5282
+ toolCallId: reused.record.callId,
5283
+ observationRefs: [reused.observation.observationId],
5284
+ now: now(),
5285
+ metadata: {
5286
+ toolId: reused.record.toolId,
5287
+ providerToolName: decision.toolCall.providerToolName ?? "",
5288
+ duplicateObservationReuse: true,
5289
+ duplicateOfToolCallId: cachedRead.callId,
5290
+ },
5291
+ }),
5292
+ });
5293
+ return { ok: true, continueLoop: true, events: ["runtime.baseTool.fileRead.duplicateObservationReused"] };
5294
+ }
5295
+ const shellCacheKey = shellRunCacheKey(decision.toolCall.toolId, decision.toolCall.arguments);
5296
+ const cachedShell = shellCacheKey === undefined ? undefined : sameTurnShellRunCache.get(shellCacheKey);
5297
+ if (cachedShell !== undefined) {
5298
+ const reused = duplicateShellRunRecord({
5299
+ sessionId,
5300
+ toolCallId: decision.toolCall.callId,
5301
+ toolId: decision.toolCall.toolId,
5302
+ providerToolName: decision.toolCall.providerToolName,
5303
+ args: decision.toolCall.arguments,
5304
+ previousCallId: cachedShell.callId,
5305
+ now: now(),
5306
+ });
5307
+ await options.onToolCallProgress?.({
5308
+ phase: "completed",
5309
+ providerToolName: decision.toolCall.providerToolName,
5310
+ record: reused.record,
5311
+ });
5312
+ toolCalls.push(reused.record);
5313
+ observations.push(reused.observation);
5314
+ await store.appendInvocation(invocation(sessionId, reused.record.callId, "tool", reused.record.toolId, true, now(), {
5315
+ ok: true,
5316
+ decisionId: decision.decisionId,
5317
+ duplicateOfToolCallId: cachedShell.callId,
5318
+ }));
5319
+ await recordMainLoopStep({
5320
+ store,
5321
+ sessionId,
5322
+ createdAt: now(),
5323
+ events,
5324
+ mainLoopSteps,
5325
+ step: createMainLoopStepRecord({
5326
+ sessionId,
5327
+ turnIndex: turn,
5328
+ stepIndex: stepBase + 6 + decisionIndex,
5329
+ actionPrimitive: "invokeBaseTool",
5330
+ status: "completed",
5331
+ inputRefs: [decision.decisionId],
5332
+ outputRefs: [reused.record.callId],
5333
+ toolCallId: reused.record.callId,
5334
+ observationRefs: [reused.observation.observationId],
5335
+ now: now(),
5336
+ metadata: {
5337
+ toolId: reused.record.toolId,
5338
+ providerToolName: decision.toolCall.providerToolName ?? "",
5339
+ duplicateObservationReuse: true,
5340
+ duplicateOfToolCallId: cachedShell.callId,
5341
+ },
5342
+ }),
5343
+ });
5344
+ return { ok: true, continueLoop: true, events: ["runtime.baseTool.shellRun.duplicateObservationReused"] };
5345
+ }
5346
+ const executed = await executeBaseToolDecision({
5347
+ runtimeId,
5348
+ sessionId,
5349
+ manifest,
5350
+ executor,
5351
+ toolCallId: decision.toolCall.callId,
5352
+ toolId: decision.toolCall.toolId,
5353
+ providerToolName: decision.toolCall.providerToolName,
5354
+ args: decision.toolCall.arguments,
5355
+ workspaceRoot: toolWorkspaceRoot,
5356
+ allowedRoots: toolAllowedRoots,
5357
+ allowToolExecution: options.allowToolExecution,
5358
+ store,
5359
+ approvalResolver: options.approvalResolver,
5360
+ agentReviewResolver: options.agentReviewResolver,
5361
+ mcpPlusRuntime,
5362
+ preparedSandbox: sandboxPrepared.sandbox,
5363
+ dependencyRuntime: options.baseToolDependencyRuntime,
5364
+ now,
5365
+ events,
5366
+ });
5367
+ await options.onToolCallProgress?.({
5368
+ phase: executed.record.ok ? "completed" : "failed",
5369
+ providerToolName: decision.toolCall.providerToolName,
5370
+ record: executed.record,
5371
+ });
5372
+ toolCalls.push(executed.record);
5373
+ observations.push(executed.observation);
5374
+ const mcpPlusToolCallUpdated = await mcpPlusRuntime.recordToolCall({
5375
+ manifest,
5376
+ toolId: executed.record.toolId,
5377
+ ok: executed.record.ok,
5378
+ now: now(),
5379
+ });
5380
+ if (executed.record.ok && (mcpPlusToolCallUpdated ||
5381
+ manifest.harness.tools.find((tool) => tool.toolId === executed.record.toolId)?.metadata?.toolProviderKind === "mcp-plus-control")) {
5382
+ await refreshRuntimeMcpTools("session.checkpoint.after_mcp_tool");
5383
+ }
5384
+ if (invalidatesFileReadCache(executed.record.toolId)) {
5385
+ sameTurnFileReadCache.clear();
5386
+ }
5387
+ else if (readCacheKey !== undefined && executed.record.ok) {
5388
+ const output = isRecord(executed.record.output) ? executed.record.output : undefined;
5389
+ const truncated = output?.truncated === true;
5390
+ sameTurnFileReadCache.set(readCacheKey, {
5391
+ callId: executed.record.callId,
5392
+ fullFileRead: !truncated,
5393
+ });
5394
+ }
5395
+ if (shellCacheKey !== undefined && executed.record.ok) {
5396
+ sameTurnShellRunCache.set(shellCacheKey, { callId: executed.record.callId });
5397
+ }
5398
+ if (invalidatesShellRunCache(executed.record.toolId)) {
5399
+ sameTurnShellRunCache.clear();
5400
+ }
5401
+ toolContextHeatState = applyBaseToolContextUsage(toolContextHeatState, [{ toolId: executed.record.toolId }], now());
5402
+ await store.appendState(state(sessionId, `state:toolContextHeat:${executed.record.callId}`, "toolContextHeat", now(), {
5403
+ agentId: toolContextHeatState.agentId,
5404
+ usage: toolContextHeatState.usage,
5405
+ }));
5406
+ await store.appendInvocation(invocation(sessionId, executed.record.callId, "tool", executed.record.toolId, executed.record.ok, now(), {
5407
+ ok: executed.record.ok,
5319
5408
  decisionId: decision.decisionId,
5320
- executionMode: decision.ephemeralProcedurePlan.executionMode,
5321
- requiredBaseTools: decision.ephemeralProcedurePlan.requiredBaseTools,
5322
- recordCount: procedureResult.records.length,
5323
- observationCount: procedureResult.observations.length,
5324
- errorCode: procedureResult.error?.code,
5325
- },
5326
- });
5327
- toolCalls.push(...procedureResult.records);
5328
- observations.push(...procedureResult.observations);
5329
- const providerCallId = typeof decision.metadata.callId === "string" && decision.metadata.callId.trim().length > 0
5330
- ? decision.metadata.callId.trim()
5331
- : undefined;
5332
- if (providerCallId !== undefined) {
5333
- observations.push(createObservationMaterial({
5334
- observationId: `${sessionId}:observation:${providerCallId}:ephemeral-procedure`,
5335
- source: "ephemeralProcedure",
5336
- status: procedureResult.ok ? "completed" : (procedureResult.error?.code === "APPROVAL_REQUIRED" ? "waitingApproval" : "failed"),
5337
- title: `EphemeralProcedure ${decision.ephemeralProcedurePlan.procedureId}`,
5338
- summary: procedureResult.ok
5339
- ? "ephemeral procedure completed"
5340
- : procedureResult.error?.message ?? "ephemeral procedure failed",
5341
- refs: [providerCallId, decision.decisionId, decision.ephemeralProcedurePlan.procedureId],
5342
- payload: {
5343
- ok: procedureResult.ok,
5344
- procedureId: decision.ephemeralProcedurePlan.procedureId,
5345
- recordCount: procedureResult.records.length,
5346
- observationCount: procedureResult.observations.length,
5347
- error: procedureResult.error,
5348
- },
5349
- metadata: metadataRecord({
5350
- toolCallId: providerCallId,
5351
- toolId: "praxis_ephemeral_procedure",
5352
- providerToolName: "praxis_ephemeral_procedure",
5353
- observationStatus: procedureResult.ok ? "completed" : "failed",
5354
- procedureId: decision.ephemeralProcedurePlan.procedureId,
5409
+ }));
5410
+ await recordMainLoopStep({
5411
+ store,
5412
+ sessionId,
5413
+ createdAt: now(),
5414
+ events,
5415
+ mainLoopSteps,
5416
+ step: createMainLoopStepRecord({
5417
+ sessionId,
5418
+ turnIndex: turn,
5419
+ stepIndex: stepBase + 6 + decisionIndex,
5420
+ actionPrimitive: "invokeBaseTool",
5421
+ status: executed.record.ok ? "completed" : (isRecord(executed.record.error) && executed.record.error.code === "APPROVAL_REQUIRED" ? "waitingApproval" : "failed"),
5422
+ inputRefs: [decision.decisionId],
5423
+ outputRefs: [executed.record.callId],
5424
+ toolCallId: executed.record.callId,
5425
+ observationRefs: [executed.observation.observationId],
5426
+ error: executed.record.ok ? undefined : {
5427
+ code: "TOOL_INVOCATION_FAILED",
5428
+ message: `tool invocation failed: ${executed.record.toolId}`,
5429
+ boundary: "tool",
5430
+ publicSafe: true,
5431
+ },
5432
+ now: now(),
5433
+ metadata: {
5434
+ toolId: executed.record.toolId,
5435
+ providerToolName: decision.toolCall.providerToolName ?? "",
5436
+ },
5355
5437
  }),
5356
- }));
5357
- }
5358
- if (procedureResult.records.length > 0) {
5359
- toolContextHeatState = applyBaseToolContextUsage(toolContextHeatState, procedureResult.records.map((record) => ({ toolId: record.toolId })), now());
5360
- await store.appendState(state(sessionId, `state:toolContextHeat:${decision.ephemeralProcedurePlan.procedureId}`, "toolContextHeat", now(), {
5361
- agentId: toolContextHeatState.agentId,
5438
+ });
5439
+ if (!executed.record.ok) {
5440
+ const approvalRequired = isRecord(executed.record.error) && executed.record.error.code === "APPROVAL_REQUIRED";
5441
+ const error = approvalRequired
5442
+ ? kernelError("APPROVAL_REQUIRED", `tool invocation requires approval: ${executed.record.toolId}`, "tool")
5443
+ : kernelError("TOOL_INVOCATION_FAILED", `tool invocation failed: ${executed.record.toolId}`, "tool");
5444
+ await recordKernelError({
5445
+ store,
5446
+ sessionId,
5447
+ errorId: `error:tool:${executed.record.callId}`,
5448
+ error,
5449
+ createdAt: now(),
5450
+ metadata: {
5451
+ toolCallId: executed.record.callId,
5452
+ toolId: executed.record.toolId,
5453
+ approvalRequired,
5454
+ },
5455
+ });
5456
+ if (!approvalRequired) {
5457
+ return {
5458
+ ok: true,
5459
+ continueLoop: true,
5460
+ events: [...executed.events, "agentCore.execution.mainLoop.runner.toolFailureObservation"],
5461
+ };
5462
+ }
5463
+ runnerError = error;
5464
+ return {
5465
+ ok: false,
5466
+ error: {
5467
+ code: error.code,
5468
+ message: error.message,
5469
+ boundary: approvalRequired ? "approval" : "tool",
5470
+ publicSafe: true,
5471
+ },
5472
+ events: executed.events,
5473
+ };
5474
+ }
5475
+ return { ok: true, continueLoop: true, events: executed.events };
5476
+ },
5477
+ handleEphemeralProcedure: async ({ turnIndex: turn, decisionIndex, decision }) => {
5478
+ const stepBase = turn * 20 + 2;
5479
+ if (decision.ephemeralProcedurePlan === undefined) {
5480
+ return {
5481
+ ok: false,
5482
+ error: {
5483
+ code: "MISSING_EPHEMERAL_PROCEDURE_PLAN",
5484
+ message: "ephemeralProcedurePlan decision is missing a procedure plan",
5485
+ boundary: "procedure",
5486
+ publicSafe: true,
5487
+ },
5488
+ events: ["agentCore.execution.mainLoop.runner.procedureRejected"],
5489
+ };
5490
+ }
5491
+ const procedureCreatedAt = now();
5492
+ await recordHandoffPlan({
5493
+ store,
5494
+ sessionId,
5495
+ createdAt: procedureCreatedAt,
5496
+ events,
5497
+ mainLoopSteps,
5498
+ turnIndex: turn,
5499
+ startStepIndex: stepBase + 50 + decisionIndex * 10,
5500
+ tickKind: "ephemeral-procedure",
5362
5501
  procedureId: decision.ephemeralProcedurePlan.procedureId,
5363
- usage: toolContextHeatState.usage,
5364
- }));
5365
- }
5366
- for (const record of procedureResult.records) {
5367
- await store.appendInvocation(invocation(sessionId, record.callId, "tool", record.toolId, record.ok, now(), {
5368
- ok: record.ok,
5502
+ inputRefs: [decision.decisionId],
5503
+ });
5504
+ await store.appendProcedure({
5505
+ sessionId,
5369
5506
  procedureId: decision.ephemeralProcedurePlan.procedureId,
5507
+ status: decision.ephemeralProcedurePlan.approval.required ? "waitingApproval" : "running",
5508
+ createdAt: procedureCreatedAt,
5509
+ summary: {
5510
+ decisionId: decision.decisionId,
5511
+ executionMode: decision.ephemeralProcedurePlan.executionMode,
5512
+ requiredBaseTools: decision.ephemeralProcedurePlan.requiredBaseTools,
5513
+ riskLevel: decision.ephemeralProcedurePlan.riskLevel,
5514
+ },
5515
+ });
5516
+ await store.appendInvocation(invocation(sessionId, decision.ephemeralProcedurePlan.procedureId, "procedure", decision.ephemeralProcedurePlan.purpose, true, now(), {
5370
5517
  decisionId: decision.decisionId,
5518
+ status: "planned",
5371
5519
  }));
5372
- }
5373
- await recordMainLoopStep({
5374
- store,
5375
- sessionId,
5376
- createdAt: now(),
5377
- events,
5378
- mainLoopSteps,
5379
- step: createMainLoopStepRecord({
5520
+ const procedureResult = await executeEphemeralProcedure({
5521
+ runtimeId,
5522
+ sessionId,
5523
+ manifest,
5524
+ executor,
5525
+ plan: decision.ephemeralProcedurePlan,
5526
+ workspaceRoot: toolWorkspaceRoot,
5527
+ allowedRoots: toolAllowedRoots,
5528
+ allowToolExecution: options.allowToolExecution,
5529
+ store,
5530
+ approvalResolver: options.approvalResolver,
5531
+ agentReviewResolver: options.agentReviewResolver,
5532
+ preparedSandbox: sandboxPrepared.sandbox,
5533
+ dependencyRuntime: options.baseToolDependencyRuntime,
5534
+ onToolCallProgress: options.onToolCallProgress,
5535
+ interruptSignal: options.interruptSignal,
5536
+ now,
5537
+ events,
5538
+ });
5539
+ await store.appendProcedure({
5380
5540
  sessionId,
5381
- turnIndex: turn,
5382
- stepIndex: stepBase + 6 + decisionIndex,
5383
- actionPrimitive: "executeEphemeralProcedure",
5384
- status: procedureResult.ok ? "completed" : (procedureResult.error?.code === "APPROVAL_REQUIRED" ? "waitingApproval" : "failed"),
5385
- inputRefs: [decision.decisionId],
5386
- outputRefs: procedureResult.records.map((record) => record.callId),
5387
5541
  procedureId: decision.ephemeralProcedurePlan.procedureId,
5388
- observationRefs: procedureResult.observations.map((observation) => observation.observationId),
5389
- error: procedureResult.ok || procedureResult.error === undefined ? undefined : {
5390
- code: procedureResult.error.code,
5391
- message: procedureResult.error.message,
5392
- boundary: "procedure",
5393
- publicSafe: true,
5394
- },
5395
- now: now(),
5396
- metadata: {
5542
+ status: procedureResult.ok ? "completed" : (procedureResult.error?.code === "APPROVAL_REQUIRED" ? "waitingApproval" : "failed"),
5543
+ createdAt: procedureCreatedAt,
5544
+ updatedAt: now(),
5545
+ summary: {
5546
+ decisionId: decision.decisionId,
5397
5547
  executionMode: decision.ephemeralProcedurePlan.executionMode,
5398
5548
  requiredBaseTools: decision.ephemeralProcedurePlan.requiredBaseTools,
5549
+ recordCount: procedureResult.records.length,
5550
+ observationCount: procedureResult.observations.length,
5551
+ errorCode: procedureResult.error?.code,
5399
5552
  },
5400
- }),
5401
- });
5402
- if (!procedureResult.ok) {
5403
- const error = procedureResult.error ?? kernelError("PROCEDURE_INVOCATION_FAILED", "procedure invocation failed", "tool");
5404
- await recordKernelError({
5553
+ });
5554
+ toolCalls.push(...procedureResult.records);
5555
+ observations.push(...procedureResult.observations);
5556
+ const providerCallId = typeof decision.metadata.callId === "string" && decision.metadata.callId.trim().length > 0
5557
+ ? decision.metadata.callId.trim()
5558
+ : undefined;
5559
+ if (providerCallId !== undefined) {
5560
+ observations.push(createObservationMaterial({
5561
+ observationId: `${sessionId}:observation:${providerCallId}:ephemeral-procedure`,
5562
+ source: "ephemeralProcedure",
5563
+ status: procedureResult.ok ? "completed" : (procedureResult.error?.code === "APPROVAL_REQUIRED" ? "waitingApproval" : "failed"),
5564
+ title: `EphemeralProcedure ${decision.ephemeralProcedurePlan.procedureId}`,
5565
+ summary: procedureResult.ok
5566
+ ? "ephemeral procedure completed"
5567
+ : procedureResult.error?.message ?? "ephemeral procedure failed",
5568
+ refs: [providerCallId, decision.decisionId, decision.ephemeralProcedurePlan.procedureId],
5569
+ payload: {
5570
+ ok: procedureResult.ok,
5571
+ procedureId: decision.ephemeralProcedurePlan.procedureId,
5572
+ recordCount: procedureResult.records.length,
5573
+ observationCount: procedureResult.observations.length,
5574
+ error: procedureResult.error,
5575
+ },
5576
+ metadata: metadataRecord({
5577
+ toolCallId: providerCallId,
5578
+ toolId: "praxis_ephemeral_procedure",
5579
+ providerToolName: "praxis_ephemeral_procedure",
5580
+ observationStatus: procedureResult.ok ? "completed" : "failed",
5581
+ procedureId: decision.ephemeralProcedurePlan.procedureId,
5582
+ }),
5583
+ }));
5584
+ }
5585
+ if (procedureResult.records.length > 0) {
5586
+ toolContextHeatState = applyBaseToolContextUsage(toolContextHeatState, procedureResult.records.map((record) => ({ toolId: record.toolId })), now());
5587
+ await store.appendState(state(sessionId, `state:toolContextHeat:${decision.ephemeralProcedurePlan.procedureId}`, "toolContextHeat", now(), {
5588
+ agentId: toolContextHeatState.agentId,
5589
+ procedureId: decision.ephemeralProcedurePlan.procedureId,
5590
+ usage: toolContextHeatState.usage,
5591
+ }));
5592
+ }
5593
+ for (const record of procedureResult.records) {
5594
+ await store.appendInvocation(invocation(sessionId, record.callId, "tool", record.toolId, record.ok, now(), {
5595
+ ok: record.ok,
5596
+ procedureId: decision.ephemeralProcedurePlan.procedureId,
5597
+ decisionId: decision.decisionId,
5598
+ }));
5599
+ }
5600
+ await recordMainLoopStep({
5405
5601
  store,
5406
5602
  sessionId,
5407
- errorId: `error:procedure:${decision.ephemeralProcedurePlan.procedureId}`,
5408
- error,
5409
5603
  createdAt: now(),
5410
- metadata: {
5604
+ events,
5605
+ mainLoopSteps,
5606
+ step: createMainLoopStepRecord({
5607
+ sessionId,
5608
+ turnIndex: turn,
5609
+ stepIndex: stepBase + 6 + decisionIndex,
5610
+ actionPrimitive: "executeEphemeralProcedure",
5611
+ status: procedureResult.ok ? "completed" : (procedureResult.error?.code === "APPROVAL_REQUIRED" ? "waitingApproval" : "failed"),
5612
+ inputRefs: [decision.decisionId],
5613
+ outputRefs: procedureResult.records.map((record) => record.callId),
5411
5614
  procedureId: decision.ephemeralProcedurePlan.procedureId,
5412
- decisionId: decision.decisionId,
5413
- approvalRequired: error.code === "APPROVAL_REQUIRED",
5414
- },
5615
+ observationRefs: procedureResult.observations.map((observation) => observation.observationId),
5616
+ error: procedureResult.ok || procedureResult.error === undefined ? undefined : {
5617
+ code: procedureResult.error.code,
5618
+ message: procedureResult.error.message,
5619
+ boundary: "procedure",
5620
+ publicSafe: true,
5621
+ },
5622
+ now: now(),
5623
+ metadata: {
5624
+ executionMode: decision.ephemeralProcedurePlan.executionMode,
5625
+ requiredBaseTools: decision.ephemeralProcedurePlan.requiredBaseTools,
5626
+ },
5627
+ }),
5415
5628
  });
5416
- if (error.code === "MAIN_LOOP_INTERRUPTED") {
5629
+ if (!procedureResult.ok) {
5630
+ const error = procedureResult.error ?? kernelError("PROCEDURE_INVOCATION_FAILED", "procedure invocation failed", "tool");
5631
+ await recordKernelError({
5632
+ store,
5633
+ sessionId,
5634
+ errorId: `error:procedure:${decision.ephemeralProcedurePlan.procedureId}`,
5635
+ error,
5636
+ createdAt: now(),
5637
+ metadata: {
5638
+ procedureId: decision.ephemeralProcedurePlan.procedureId,
5639
+ decisionId: decision.decisionId,
5640
+ approvalRequired: error.code === "APPROVAL_REQUIRED",
5641
+ },
5642
+ });
5643
+ if (error.code === "MAIN_LOOP_INTERRUPTED") {
5644
+ runnerError = error;
5645
+ return {
5646
+ ok: false,
5647
+ error: {
5648
+ code: error.code,
5649
+ message: error.message,
5650
+ boundary: "model",
5651
+ publicSafe: true,
5652
+ },
5653
+ events: [],
5654
+ };
5655
+ }
5656
+ if (error.code !== "APPROVAL_REQUIRED") {
5657
+ return {
5658
+ ok: true,
5659
+ continueLoop: true,
5660
+ events: ["agentCore.execution.mainLoop.runner.procedureFailureObservation"],
5661
+ };
5662
+ }
5417
5663
  runnerError = error;
5418
5664
  return {
5419
5665
  ok: false,
5420
5666
  error: {
5421
5667
  code: error.code,
5422
5668
  message: error.message,
5423
- boundary: "model",
5669
+ boundary: error.code === "APPROVAL_REQUIRED" ? "approval" : "procedure",
5424
5670
  publicSafe: true,
5425
5671
  },
5426
5672
  events: [],
5427
5673
  };
5428
5674
  }
5429
- if (error.code !== "APPROVAL_REQUIRED") {
5430
- return {
5431
- ok: true,
5432
- continueLoop: true,
5433
- events: ["agentCore.execution.mainLoop.runner.procedureFailureObservation"],
5434
- };
5435
- }
5436
- runnerError = error;
5437
- return {
5438
- ok: false,
5439
- error: {
5440
- code: error.code,
5441
- message: error.message,
5442
- boundary: error.code === "APPROVAL_REQUIRED" ? "approval" : "procedure",
5443
- publicSafe: true,
5444
- },
5445
- events: [],
5446
- };
5447
- }
5448
- return { ok: true, continueLoop: true, events: [] };
5449
- },
5450
- onModelDryRun: async () => ({
5451
- ok: true,
5452
- finalOutput: "PraxisRuntimeKernel dry-run completed.",
5453
- events: ["agentCore.execution.mainLoop.runner.dryRunFinal"],
5454
- }),
5455
- onNoFinalOutput: async (input) => ({
5456
- ok: true,
5457
- finalOutput: input.reason === "tool_call_limit"
5458
- ? "PraxisRuntimeKernel reached the tool call limit before a final answer."
5459
- : input.reason === "no_continuation"
5460
- ? "PraxisRuntimeKernel stopped without a final answer."
5461
- : "PraxisRuntimeKernel reached the model turn limit before a final answer.",
5462
- events: [`agentCore.execution.mainLoop.runner.noFinalOutput.${input.reason}`],
5463
- }),
5464
- });
5465
- events.push(...runnerResult.events);
5466
- if (!runnerResult.ok) {
5467
- const interrupted = runnerResult.error.code === "MAIN_LOOP_INTERRUPTED";
5468
- const fallbackCode = interrupted
5469
- ? "MAIN_LOOP_INTERRUPTED"
5470
- : runnerResult.error.boundary === "prompt"
5471
- ? "PROMPT_PACK_FAILED"
5675
+ return { ok: true, continueLoop: true, events: [] };
5676
+ },
5677
+ onModelDryRun: async () => ({
5678
+ ok: true,
5679
+ finalOutput: "PraxisRuntimeKernel dry-run completed.",
5680
+ events: ["agentCore.execution.mainLoop.runner.dryRunFinal"],
5681
+ }),
5682
+ onNoFinalOutput: async (input) => ({
5683
+ ok: true,
5684
+ finalOutput: input.reason === "tool_call_limit"
5685
+ ? "PraxisRuntimeKernel reached the tool call limit before a final answer."
5686
+ : input.reason === "no_continuation"
5687
+ ? "PraxisRuntimeKernel stopped without a final answer."
5688
+ : "PraxisRuntimeKernel reached the model turn limit before a final answer.",
5689
+ events: [`agentCore.execution.mainLoop.runner.noFinalOutput.${input.reason}`],
5690
+ }),
5691
+ });
5692
+ events.push(...runnerResult.events);
5693
+ if (!runnerResult.ok) {
5694
+ const interrupted = runnerResult.error.code === "MAIN_LOOP_INTERRUPTED";
5695
+ const fallbackCode = interrupted
5696
+ ? "MAIN_LOOP_INTERRUPTED"
5697
+ : runnerResult.error.boundary === "prompt"
5698
+ ? "PROMPT_PACK_FAILED"
5699
+ : runnerResult.error.boundary === "model" || runnerResult.error.boundary === "model-decision"
5700
+ ? "MODEL_DECISION_FAILED"
5701
+ : runnerResult.error.boundary === "tool"
5702
+ ? "TOOL_INVOCATION_FAILED"
5703
+ : runnerResult.error.boundary === "procedure"
5704
+ ? "PROCEDURE_INVOCATION_FAILED"
5705
+ : runnerResult.error.boundary === "approval"
5706
+ ? "APPROVAL_REQUIRED"
5707
+ : "TEXT_OUTPUT_REJECTED";
5708
+ const fallbackBoundary = interrupted
5709
+ ? "runtime-state"
5472
5710
  : runnerResult.error.boundary === "model" || runnerResult.error.boundary === "model-decision"
5473
- ? "MODEL_DECISION_FAILED"
5474
- : runnerResult.error.boundary === "tool"
5475
- ? "TOOL_INVOCATION_FAILED"
5476
- : runnerResult.error.boundary === "procedure"
5477
- ? "PROCEDURE_INVOCATION_FAILED"
5478
- : runnerResult.error.boundary === "approval"
5479
- ? "APPROVAL_REQUIRED"
5480
- : "TEXT_OUTPUT_REJECTED";
5481
- const fallbackBoundary = interrupted
5482
- ? "runtime-state"
5483
- : runnerResult.error.boundary === "model" || runnerResult.error.boundary === "model-decision"
5484
- ? "model"
5485
- : runnerResult.error.boundary === "tool" || runnerResult.error.boundary === "procedure" || runnerResult.error.boundary === "approval"
5486
- ? "tool"
5487
- : runnerResult.error.boundary === "output"
5488
- ? "io"
5489
- : "runtime-state";
5490
- const error = runnerError ?? kernelError(fallbackCode, runnerResult.error.message, fallbackBoundary);
5491
- await store.updateSessionStatus(sessionId, interrupted ? "interrupted" : error.code === "APPROVAL_REQUIRED" ? "waitingApproval" : "failed");
5492
- const snapshot = await store.readSession(sessionId);
5493
- return {
5494
- ok: false,
5711
+ ? "model"
5712
+ : runnerResult.error.boundary === "tool" || runnerResult.error.boundary === "procedure" || runnerResult.error.boundary === "approval"
5713
+ ? "tool"
5714
+ : runnerResult.error.boundary === "output"
5715
+ ? "io"
5716
+ : "runtime-state";
5717
+ const error = runnerError ?? kernelError(fallbackCode, runnerResult.error.message, fallbackBoundary);
5718
+ await store.updateSessionStatus(sessionId, interrupted ? "interrupted" : error.code === "APPROVAL_REQUIRED" ? "waitingApproval" : "failed");
5719
+ const snapshot = await store.readSession(sessionId);
5720
+ return {
5721
+ ok: false,
5722
+ runtimeId,
5723
+ sessionId,
5724
+ manifest,
5725
+ error,
5726
+ mainLoopSteps,
5727
+ events,
5728
+ state: snapshot,
5729
+ };
5730
+ }
5731
+ const finalOutput = runnerResult.finalOutput;
5732
+ const output = exposeTextOutput({
5733
+ outputId: `${sessionId}:output:final`,
5495
5734
  runtimeId,
5496
5735
  sessionId,
5497
- manifest,
5498
- error,
5499
- mainLoopSteps,
5500
- events,
5501
- state: snapshot,
5502
- };
5503
- }
5504
- const finalOutput = runnerResult.finalOutput;
5505
- const output = exposeTextOutput({
5506
- outputId: `${sessionId}:output:final`,
5507
- runtimeId,
5508
- sessionId,
5509
- source: "model",
5510
- stage: "final",
5511
- text: finalOutput,
5512
- governance: { accepted: true },
5513
- contract: { accepted: true },
5514
- });
5515
- events.push(...output.events);
5516
- const outputStepIndex = Math.max(0, ...mainLoopSteps.map((step) => step.stepIndex)) + 1;
5517
- if (!output.ok) {
5518
- const error = kernelError("TEXT_OUTPUT_REJECTED", output.error.message, "io");
5519
- await store.updateSessionStatus(sessionId, "failed");
5736
+ source: "model",
5737
+ stage: "final",
5738
+ text: finalOutput,
5739
+ governance: { accepted: true },
5740
+ contract: { accepted: true },
5741
+ });
5742
+ events.push(...output.events);
5743
+ const outputStepIndex = Math.max(0, ...mainLoopSteps.map((step) => step.stepIndex)) + 1;
5744
+ if (!output.ok) {
5745
+ const error = kernelError("TEXT_OUTPUT_REJECTED", output.error.message, "io");
5746
+ await store.updateSessionStatus(sessionId, "failed");
5747
+ await recordMainLoopStep({
5748
+ store,
5749
+ sessionId,
5750
+ createdAt: now(),
5751
+ events,
5752
+ mainLoopSteps,
5753
+ step: createMainLoopStepRecord({
5754
+ sessionId,
5755
+ turnIndex: maxModelTurns,
5756
+ stepIndex: outputStepIndex,
5757
+ actionPrimitive: "exposeOutput",
5758
+ status: "failed",
5759
+ inputRefs: ["runtime.output.final"],
5760
+ error: {
5761
+ code: output.error.code,
5762
+ message: output.error.message,
5763
+ boundary: "output",
5764
+ publicSafe: true,
5765
+ },
5766
+ now: now(),
5767
+ }),
5768
+ });
5769
+ await recordKernelError({
5770
+ store,
5771
+ sessionId,
5772
+ errorId: "error:output:final",
5773
+ error,
5774
+ createdAt: now(),
5775
+ metadata: { outputId: `${sessionId}:output:final` },
5776
+ });
5777
+ const snapshot = await store.readSession(sessionId);
5778
+ return {
5779
+ ok: false,
5780
+ runtimeId,
5781
+ sessionId,
5782
+ manifest,
5783
+ error,
5784
+ mainLoopSteps,
5785
+ events,
5786
+ state: snapshot,
5787
+ };
5788
+ }
5520
5789
  await recordMainLoopStep({
5521
5790
  store,
5522
5791
  sessionId,
@@ -5528,71 +5797,33 @@ export class PraxisRuntimeKernel {
5528
5797
  turnIndex: maxModelTurns,
5529
5798
  stepIndex: outputStepIndex,
5530
5799
  actionPrimitive: "exposeOutput",
5531
- status: "failed",
5800
+ status: "completed",
5532
5801
  inputRefs: ["runtime.output.final"],
5533
- error: {
5534
- code: output.error.code,
5535
- message: output.error.message,
5536
- boundary: "output",
5537
- publicSafe: true,
5538
- },
5802
+ outputRefs: [output.exposed.outputId],
5539
5803
  now: now(),
5540
5804
  }),
5541
5805
  });
5542
- await recordKernelError({
5543
- store,
5544
- sessionId,
5545
- errorId: "error:output:final",
5546
- error,
5547
- createdAt: now(),
5548
- metadata: { outputId: `${sessionId}:output:final` },
5549
- });
5550
- const snapshot = await store.readSession(sessionId);
5806
+ await store.appendEvent(event(sessionId, "event:output.final", "runtime.output.final", now(), {
5807
+ outputId: output.exposed.outputId,
5808
+ }));
5809
+ await store.appendState(state(sessionId, "state:completed", "completed", now()));
5810
+ await store.updateSessionStatus(sessionId, "completed");
5551
5811
  return {
5552
- ok: false,
5812
+ ok: true,
5553
5813
  runtimeId,
5554
5814
  sessionId,
5555
5815
  manifest,
5556
- error,
5816
+ finalOutput,
5817
+ modelCalls,
5818
+ toolCalls,
5557
5819
  mainLoopSteps,
5558
5820
  events,
5559
- state: snapshot,
5821
+ state: await store.readSession(sessionId),
5560
5822
  };
5561
5823
  }
5562
- await recordMainLoopStep({
5563
- store,
5564
- sessionId,
5565
- createdAt: now(),
5566
- events,
5567
- mainLoopSteps,
5568
- step: createMainLoopStepRecord({
5569
- sessionId,
5570
- turnIndex: maxModelTurns,
5571
- stepIndex: outputStepIndex,
5572
- actionPrimitive: "exposeOutput",
5573
- status: "completed",
5574
- inputRefs: ["runtime.output.final"],
5575
- outputRefs: [output.exposed.outputId],
5576
- now: now(),
5577
- }),
5578
- });
5579
- await store.appendEvent(event(sessionId, "event:output.final", "runtime.output.final", now(), {
5580
- outputId: output.exposed.outputId,
5581
- }));
5582
- await store.appendState(state(sessionId, "state:completed", "completed", now()));
5583
- await store.updateSessionStatus(sessionId, "completed");
5584
- return {
5585
- ok: true,
5586
- runtimeId,
5587
- sessionId,
5588
- manifest,
5589
- finalOutput,
5590
- modelCalls,
5591
- toolCalls,
5592
- mainLoopSteps,
5593
- events,
5594
- state: await store.readSession(sessionId),
5595
- };
5824
+ finally {
5825
+ await shutdownRuntimeOwnedExecutor(runtimeOwnedExecutor, events);
5826
+ }
5596
5827
  }
5597
5828
  }
5598
5829
  export function createPraxisRuntimeKernel(options = {}) {