@praxis-ai/praxis 0.1.4 → 0.1.5

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