@botbotgo/agent-harness 0.0.346 → 0.0.347

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.
@@ -17,6 +17,7 @@ export type RequestResult = {
17
17
  artifacts?: ArtifactRecord[];
18
18
  metadata?: Record<string, unknown>;
19
19
  };
20
+ export type TerminalExecutionStatus = "completed" | "blocked" | "failed" | "refused";
20
21
  export type UpstreamRuntimeEvent = unknown;
21
22
  export type UpstreamRuntimeEventItem = {
22
23
  sessionId: string;
@@ -203,6 +203,9 @@ export type CompiledBuiltinToolsConfig = {
203
203
  todos?: boolean;
204
204
  modelExposed?: boolean | string[];
205
205
  };
206
+ export type CompiledExecutionContract = {
207
+ requiresPlan?: boolean;
208
+ };
206
209
  export type LangChainAgentParams = {
207
210
  model: CompiledModel;
208
211
  tools: CompiledTool[];
@@ -287,6 +290,7 @@ export type CompiledAgentBinding = {
287
290
  resilience?: Record<string, unknown>;
288
291
  governance?: Record<string, unknown>;
289
292
  observability?: Record<string, unknown>;
293
+ executionContract?: CompiledExecutionContract;
290
294
  deepagent?: {
291
295
  description?: string;
292
296
  passthrough?: Record<string, unknown>;
@@ -1,2 +1,2 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.346";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.347";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.346";
1
+ export const AGENT_HARNESS_VERSION = "0.0.347";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
@@ -0,0 +1,8 @@
1
+ The delegated task failed. You are the routing/delegation parent agent, so you must not switch into local execution or start a new local plan.
2
+
3
+ Your next response has only two valid forms:
4
+
5
+ 1. Call the `task` tool again, preserving the user's original request and delegating to the same specialist or another explicit specialist whose configured responsibility clearly matches the original request.
6
+ 2. Return a final blocker report to the user explaining that delegated execution failed.
7
+
8
+ Do not call local execution tools, repository tools, web tools, shell tools, or `write_todos` from the parent agent after this delegated failure. Do not invent a new topic or downgrade the original request. If you continue execution, it must be through `task`.
@@ -1,4 +1,5 @@
1
1
  import { extractVisibleOutput, isToolCallRecoveryFailure, isRetrySafeInvalidToolSelectionError, resolveMissingPlanRecoveryInstruction, resolveExecutionWithoutToolEvidenceTextInstruction, shouldValidateExecutionWithoutToolEvidence, resolveToolCallRecoveryInstruction, sanitizeVisibleText, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, INVALID_TOOL_SELECTION_RECOVERY_INSTRUCTION, } from "../../parsing/output-parsing.js";
2
+ import { DELEGATED_TASK_FAILURE_RECOVERY_INSTRUCTION } from "../../prompts/runtime-prompts.js";
2
3
  import { buildInvocationRequest } from "../model/invocation-request.js";
3
4
  import { buildRawModelMessages } from "../model/message-assembly.js";
4
5
  import { projectRuntimeStreamEvent, createStreamEventProjectionState } from "../stream-event-projection.js";
@@ -55,6 +56,18 @@ function hasSuccessfulNonTodoToolEvidence(executedToolResults) {
55
56
  function hasSuccessfulTaskToolEvidence(executedToolResults) {
56
57
  return executedToolResults.some((item) => item.isError !== true && item.toolName === "task");
57
58
  }
59
+ function requiresPlanEvidence(binding) {
60
+ return binding.harnessRuntime?.executionContract?.requiresPlan === true;
61
+ }
62
+ function hasParentLocalToolExecutionAfterDelegationFailure(originalEvidence, executedToolResults) {
63
+ return originalEvidence.hasFailedTaskDelegation
64
+ && executedToolResults.some((item) => item.toolName !== "task");
65
+ }
66
+ function isDelegationFailureFinalReport(originalEvidence, executedToolResults, visibleOutput) {
67
+ return originalEvidence.hasFailedTaskDelegation
68
+ && executedToolResults.length === 0
69
+ && visibleOutput.trim().length > 0;
70
+ }
58
71
  function buildExecutionRecoveryEvidence(params) {
59
72
  const { projectionState, executedToolResults = [] } = params;
60
73
  return {
@@ -65,7 +78,8 @@ function buildExecutionRecoveryEvidence(params) {
65
78
  hasIncompletePlanState: projectionState.hasIncompletePlanState || hasIncompletePlanStateInExecutedToolResults(executedToolResults),
66
79
  hasPlanStateEvidence: projectionState.sawPlanState || hasIncompletePlanStateInExecutedToolResults(executedToolResults),
67
80
  hasOpenTaskDelegation: projectionState.openTaskDelegations > 0,
68
- hasFailedTaskDelegation: projectionState.hasFailedTaskDelegation,
81
+ hasFailedTaskDelegation: projectionState.hasFailedTaskDelegation
82
+ || executedToolResults.some((item) => item.toolName === "task" && item.isError === true),
69
83
  hasDelegatedAgentWithConfiguredTools: projectionState.sawDelegatedAgentWithConfiguredTools,
70
84
  hasDelegatedExecutionToolEvidence: projectionState.emittedDelegatedExecutionToolResult,
71
85
  hasOnlyPlaceholderTaskCompletion: projectionState.emittedSuccessfulTaskResult
@@ -98,6 +112,9 @@ function resolveStreamedRuntimeFailureRecoveryInstruction(output, evidence) {
98
112
  return hasExecutionEvidence ? null : EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION;
99
113
  }
100
114
  function resolveDelegatedExecutionRecoveryInstruction(evidence) {
115
+ if (evidence.hasFailedTaskDelegation) {
116
+ return DELEGATED_TASK_FAILURE_RECOVERY_INSTRUCTION;
117
+ }
101
118
  if (hasMissingDelegatedFindings(evidence)
102
119
  || (evidence.hasOpenTaskDelegation
103
120
  && evidence.hasDelegatedAgentWithConfiguredTools
@@ -400,6 +417,7 @@ export async function* streamRuntimeExecution(options) {
400
417
  ? resolveMissingPlanRecoveryInstruction({
401
418
  request,
402
419
  assistantText: terminalVisibleOutput,
420
+ requiresPlan: requiresPlanEvidence(options.binding),
403
421
  hasPlanStateEvidence: terminalExecutionEvidence.hasPlanStateEvidence,
404
422
  hasWriteTodosEvidence: terminalExecutionEvidence.hasPlanStateEvidence,
405
423
  hasToolResultEvidence: terminalExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence,
@@ -443,8 +461,9 @@ export async function* streamRuntimeExecution(options) {
443
461
  throw error;
444
462
  }
445
463
  const streamedExecutionEvidence = buildExecutionRecoveryEvidence({ projectionState });
446
- const delegatedExecutionRecoveryInstruction = !emittedUnsafeStreamSideEffects
447
- ? resolveDelegatedExecutionRecoveryInstruction(streamedExecutionEvidence)
464
+ const streamedDelegatedRecoveryInstruction = resolveDelegatedExecutionRecoveryInstruction(streamedExecutionEvidence);
465
+ const delegatedExecutionRecoveryInstruction = !emittedUnsafeStreamSideEffects || streamedDelegatedRecoveryInstruction
466
+ ? streamedDelegatedRecoveryInstruction
448
467
  : null;
449
468
  if (hasUnresolvedExecution(streamedExecutionEvidence) && !delegatedExecutionRecoveryInstruction) {
450
469
  throw createUnresolvedExecutionError(streamedExecutionEvidence);
@@ -453,6 +472,7 @@ export async function* streamRuntimeExecution(options) {
453
472
  ? resolveExecutionWithoutToolEvidenceTextInstruction(request, projectionState.emittedOutput, false, {
454
473
  ...streamedExecutionEvidence,
455
474
  hasMissingDelegatedExecutionEvidence: hasMissingDelegatedExecutionEvidence(streamedExecutionEvidence),
475
+ requiresPlan: requiresPlanEvidence(options.binding),
456
476
  })
457
477
  : null;
458
478
  const streamedRuntimeFailureRecoveryInstruction = projectionState.emittedOutput
@@ -462,6 +482,7 @@ export async function* streamRuntimeExecution(options) {
462
482
  ? resolveMissingPlanRecoveryInstruction({
463
483
  request,
464
484
  assistantText: projectionState.emittedOutput,
485
+ requiresPlan: requiresPlanEvidence(options.binding),
465
486
  hasPlanStateEvidence: streamedExecutionEvidence.hasPlanStateEvidence,
466
487
  hasWriteTodosEvidence: streamedExecutionEvidence.hasPlanStateEvidence,
467
488
  hasToolResultEvidence: streamedExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence,
@@ -484,17 +505,22 @@ export async function* streamRuntimeExecution(options) {
484
505
  projectionState: createStreamEventProjectionState(),
485
506
  executedToolResults,
486
507
  });
508
+ if (hasParentLocalToolExecutionAfterDelegationFailure(originalExecutionEvidence, executedToolResults)) {
509
+ throw new ExecutionReconciliationError("Agent attempted parent-local tool execution after delegated task failure; it must report a blocker or re-delegate with task.");
510
+ }
487
511
  const retriedVisibleOutput = retried.output ? toVisibleContent(retried.output) : "";
512
+ const retriedIsDelegationFailureFinalReport = isDelegationFailureFinalReport(originalExecutionEvidence, executedToolResults, retriedVisibleOutput);
488
513
  const retriedCarriesExecutionEvidence = retriedExecutionEvidence.hasToolResultEvidence
489
514
  || retriedExecutionEvidence.hasOpenTaskDelegation
490
515
  || retriedExecutionEvidence.hasDelegatedExecutionToolEvidence;
491
- const retriedHasUnresolvedExecution = hasUnresolvedExecution(retriedExecutionEvidence)
492
- || hasMissingDelegatedExecutionEvidence(retriedExecutionEvidence)
493
- || hasMissingDelegatedFindings(retriedExecutionEvidence)
494
- || (!retriedCarriesExecutionEvidence
495
- && (hasUnresolvedExecution(originalExecutionEvidence)
496
- || hasMissingDelegatedExecutionEvidence(originalExecutionEvidence)
497
- || hasMissingDelegatedFindings(originalExecutionEvidence)));
516
+ const retriedHasUnresolvedExecution = !retriedIsDelegationFailureFinalReport
517
+ && (hasUnresolvedExecution(retriedExecutionEvidence)
518
+ || hasMissingDelegatedExecutionEvidence(retriedExecutionEvidence)
519
+ || hasMissingDelegatedFindings(retriedExecutionEvidence)
520
+ || (!retriedCarriesExecutionEvidence
521
+ && (hasUnresolvedExecution(originalExecutionEvidence)
522
+ || hasMissingDelegatedExecutionEvidence(originalExecutionEvidence)
523
+ || hasMissingDelegatedFindings(originalExecutionEvidence))));
498
524
  const effectiveRecoveryEvidence = retriedCarriesExecutionEvidence
499
525
  ? retriedExecutionEvidence
500
526
  : {
@@ -666,12 +692,14 @@ export async function* streamRuntimeExecution(options) {
666
692
  ? resolveExecutionWithoutToolEvidenceTextInstruction(request, result.output, false, {
667
693
  ...invokeExecutionEvidence,
668
694
  hasMissingDelegatedExecutionEvidence: hasMissingDelegatedExecutionEvidence(invokeExecutionEvidence),
695
+ requiresPlan: requiresPlanEvidence(options.binding),
669
696
  })
670
697
  : resolveDelegatedExecutionRecoveryInstruction(invokeExecutionEvidence);
671
698
  const invokeFallbackMissingPlanRecoveryInstruction = !hasUnresolvedExecution(invokeExecutionEvidence) && !invokeFallbackRecoveryInstruction
672
699
  ? resolveMissingPlanRecoveryInstruction({
673
700
  request,
674
701
  assistantText: typeof result.output === "string" ? result.output : "",
702
+ requiresPlan: requiresPlanEvidence(options.binding),
675
703
  hasPlanStateEvidence: invokeExecutionEvidence.hasPlanStateEvidence,
676
704
  hasWriteTodosEvidence: invokeExecutionEvidence.hasPlanStateEvidence,
677
705
  hasToolResultEvidence: invokeExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence,
@@ -688,17 +716,22 @@ export async function* streamRuntimeExecution(options) {
688
716
  projectionState: createStreamEventProjectionState(),
689
717
  executedToolResults: recoveredToolResults,
690
718
  });
719
+ if (hasParentLocalToolExecutionAfterDelegationFailure(originalExecutionEvidence, recoveredToolResults)) {
720
+ throw new ExecutionReconciliationError("Agent attempted parent-local tool execution after delegated task failure; it must report a blocker or re-delegate with task.");
721
+ }
691
722
  const recoveredVisibleOutput = recovered.output ? toVisibleContent(recovered.output) : "";
723
+ const recoveredIsDelegationFailureFinalReport = isDelegationFailureFinalReport(originalExecutionEvidence, recoveredToolResults, recoveredVisibleOutput);
692
724
  const recoveredCarriesExecutionEvidence = recoveredExecutionEvidence.hasToolResultEvidence
693
725
  || recoveredExecutionEvidence.hasOpenTaskDelegation
694
726
  || recoveredExecutionEvidence.hasDelegatedExecutionToolEvidence;
695
- const recoveredHasUnresolvedExecution = hasUnresolvedExecution(recoveredExecutionEvidence)
696
- || hasMissingDelegatedExecutionEvidence(recoveredExecutionEvidence)
697
- || hasMissingDelegatedFindings(recoveredExecutionEvidence)
698
- || (!recoveredCarriesExecutionEvidence
699
- && (hasUnresolvedExecution(originalExecutionEvidence)
700
- || hasMissingDelegatedExecutionEvidence(originalExecutionEvidence)
701
- || hasMissingDelegatedFindings(originalExecutionEvidence)));
727
+ const recoveredHasUnresolvedExecution = !recoveredIsDelegationFailureFinalReport
728
+ && (hasUnresolvedExecution(recoveredExecutionEvidence)
729
+ || hasMissingDelegatedExecutionEvidence(recoveredExecutionEvidence)
730
+ || hasMissingDelegatedFindings(recoveredExecutionEvidence)
731
+ || (!recoveredCarriesExecutionEvidence
732
+ && (hasUnresolvedExecution(originalExecutionEvidence)
733
+ || hasMissingDelegatedExecutionEvidence(originalExecutionEvidence)
734
+ || hasMissingDelegatedFindings(originalExecutionEvidence))));
702
735
  const effectiveRecoveredEvidence = recoveredCarriesExecutionEvidence
703
736
  ? recoveredExecutionEvidence
704
737
  : {
@@ -3,6 +3,7 @@ import { salvageFunctionLikeToolCall } from "../parsing/output-tool-args.js";
3
3
  import { buildStateSnapshot } from "./model/message-assembly.js";
4
4
  import { asRecord } from "./tool/resolved-tool.js";
5
5
  import { renderToolFailure } from "../support/harness-support.js";
6
+ import { mapTerminalStatusToRequestState, readTerminalExecutionStatus } from "./terminal-status.js";
6
7
  function looksLikeLeakedToolCallText(value) {
7
8
  const normalized = sanitizeVisibleText(value).trim();
8
9
  if (!normalized) {
@@ -168,6 +169,29 @@ function extractDeterministicToolFailureReport(executedToolResults) {
168
169
  "- none",
169
170
  ].join("\n");
170
171
  }
172
+ function hasEmptyFinalMessage(result) {
173
+ const messages = Array.isArray(result.messages) ? result.messages : [];
174
+ const lastMessage = messages.at(-1);
175
+ if (!lastMessage || typeof lastMessage !== "object") {
176
+ return false;
177
+ }
178
+ const direct = lastMessage;
179
+ return direct.content === "" || direct.kwargs?.content === "" || direct.lc_kwargs?.content === "";
180
+ }
181
+ function hasFinalMessageToolCalls(result) {
182
+ const messages = Array.isArray(result.messages) ? result.messages : [];
183
+ const lastMessage = messages.at(-1);
184
+ if (!lastMessage || typeof lastMessage !== "object") {
185
+ return false;
186
+ }
187
+ const direct = lastMessage;
188
+ return Array.isArray(direct.tool_calls) && direct.tool_calls.length > 0
189
+ || Array.isArray(direct.invalid_tool_calls) && direct.invalid_tool_calls.length > 0
190
+ || Array.isArray(direct.kwargs?.tool_calls) && direct.kwargs.tool_calls.length > 0
191
+ || Array.isArray(direct.kwargs?.invalid_tool_calls) && direct.kwargs.invalid_tool_calls.length > 0
192
+ || Array.isArray(direct.lc_kwargs?.tool_calls) && direct.lc_kwargs.tool_calls.length > 0
193
+ || Array.isArray(direct.lc_kwargs?.invalid_tool_calls) && direct.lc_kwargs.invalid_tool_calls.length > 0;
194
+ }
171
195
  export function resolveDeterministicFinalOutput(params) {
172
196
  const visibleOutput = params.visibleOutput ?? "";
173
197
  const toolFallback = params.toolFallback ?? "";
@@ -178,6 +202,9 @@ export function resolveDeterministicFinalOutput(params) {
178
202
  const deterministicFailureReport = extractDeterministicToolFailureReport(executedToolResults);
179
203
  const delegatedTaskOutput = extractLatestSuccessfulTaskResultText(executedToolResults);
180
204
  const successfulToolOutput = extractLatestSuccessfulNonTodoToolResultText(executedToolResults);
205
+ if (sanitizedVisibleOutput && deterministicFailureReport && hasDelegationBlocker(executedToolResults) && !successfulToolOutput) {
206
+ return deterministicFailureReport;
207
+ }
181
208
  if (sanitizedVisibleOutput && successfulToolOutput && hasDelegationBlocker(executedToolResults)) {
182
209
  return deterministicFailureReport || delegatedTaskOutput || successfulToolOutput;
183
210
  }
@@ -215,9 +242,26 @@ export function finalizeRequestResult(params) {
215
242
  const visibleOutput = extractedOutput && !isLikelyToolArgsObject(tryParseJson(extractedOutput)) ? extractedOutput : "";
216
243
  const emptyAssistantMessageFailure = extractEmptyAssistantMessageFailure(result);
217
244
  const toolFallback = extractToolFallbackContext(result);
245
+ const outputContent = extractOutputContent(result);
246
+ const contentBlocks = extractContentBlocks(result);
247
+ const structuredResponse = result.structuredResponse;
248
+ const structuredTerminalStatus = readTerminalExecutionStatus(structuredResponse) ?? readTerminalExecutionStatus(result);
249
+ const files = asRecord(result.files);
218
250
  if (!visibleOutput && !toolFallback && emptyAssistantMessageFailure) {
219
251
  throw new Error(emptyAssistantMessageFailure);
220
252
  }
253
+ if (!visibleOutput
254
+ && !toolFallback
255
+ && interruptContent === undefined
256
+ && outputContent === undefined
257
+ && contentBlocks.length === 0
258
+ && structuredResponse === undefined
259
+ && !files
260
+ && executedToolResults.length === 0
261
+ && hasEmptyFinalMessage(result)
262
+ && !hasFinalMessageToolCalls(result)) {
263
+ throw new Error("empty_final_output");
264
+ }
221
265
  const serializedResult = JSON.stringify(result, null, 2);
222
266
  const output = resolveDeterministicFinalOutput({
223
267
  visibleOutput,
@@ -226,17 +270,16 @@ export function finalizeRequestResult(params) {
226
270
  })
227
271
  || (containsLikelySkillDocument(result) ? "" : serializedResult);
228
272
  const finalMessageText = sanitizeVisibleText(output);
229
- const outputContent = extractOutputContent(result);
230
- const contentBlocks = extractContentBlocks(result);
231
- const structuredResponse = result.structuredResponse;
232
- const files = asRecord(result.files);
273
+ const terminalStatus = structuredTerminalStatus ?? readTerminalExecutionStatus(finalMessageText);
233
274
  const stateSnapshot = buildStateSnapshot(result);
234
275
  const memoryCandidates = executedToolResults.flatMap((toolResult) => toolResult.memoryCandidates ?? []);
235
276
  return {
236
277
  sessionId,
237
278
  requestId,
238
279
  agentId: bindingAgentId,
239
- state: Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0 ? "waiting_for_approval" : "completed",
280
+ state: Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0
281
+ ? "waiting_for_approval"
282
+ : mapTerminalStatusToRequestState(terminalStatus),
240
283
  interruptContent,
241
284
  output: finalMessageText,
242
285
  finalMessageText,
@@ -247,6 +290,7 @@ export function finalizeRequestResult(params) {
247
290
  ...(executedToolResults.length > 0 ? { executedToolResults } : {}),
248
291
  ...(memoryCandidates.length > 0 ? { memoryCandidates } : {}),
249
292
  ...(structuredResponse !== undefined ? { structuredResponse } : {}),
293
+ ...(terminalStatus ? { terminalStatus } : {}),
250
294
  ...(outputContent !== undefined ? { outputContent } : {}),
251
295
  ...(contentBlocks.length > 0 ? { contentBlocks } : {}),
252
296
  ...(files ? { files } : {}),
@@ -43,6 +43,9 @@ function hasNonTodoToolEvidence(executedToolResults) {
43
43
  function hasPlanStateEvidence(executedToolResults) {
44
44
  return executedToolResults.some((item) => item.toolName === "write_todos" || item.toolName === "read_todos" || readPlanStateSummary(item.output) !== null);
45
45
  }
46
+ function requiresPlanEvidence(binding) {
47
+ return binding.harnessRuntime.executionContract?.requiresPlan === true;
48
+ }
46
49
  function extractLatestUserInput(request) {
47
50
  const typedRequest = request;
48
51
  const messages = Array.isArray(typedRequest.messages) ? typedRequest.messages : [];
@@ -87,6 +90,7 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
87
90
  hasToolResultEvidence: hasExecutionBeyondTodoPlanning,
88
91
  hasPlanStateEvidence: hasPlanStateEvidence(executedToolResults),
89
92
  hasIncompletePlanState: hasExecutionBeyondTodoPlanning && hasIncompletePlanState,
93
+ requiresPlan: requiresPlanEvidence(binding),
90
94
  })
91
95
  : hasIncompletePlanState && hasExecutionBeyondTodoPlanning
92
96
  ? AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION
@@ -102,6 +106,7 @@ export async function runLocalToolInvocationLoop({ binding, request, primaryTool
102
106
  }
103
107
  const missingPlanRecoveryInstruction = resolveMissingPlanRecoveryInstruction({
104
108
  request: activeRequest,
109
+ requiresPlan: requiresPlanEvidence(binding),
105
110
  hasPlanStateEvidence: hasPlanStateEvidence(executedToolResults),
106
111
  hasWriteTodosEvidence: executedToolResults.some((item) => item.toolName === "write_todos"),
107
112
  hasToolResultEvidence: executedToolResults.length > 0 || toolCalls.length > 0,
@@ -350,7 +350,9 @@ export function projectRuntimeStreamEvent(params) {
350
350
  ? state.lastCompletedTaskDelegationFindings
351
351
  : "";
352
352
  const effectiveToolOutput = salvagedTaskErrorFindings || toolResult.output;
353
- const effectiveToolIsError = salvagedTaskErrorFindings ? false : toolResult.isError;
353
+ const effectiveToolIsError = salvagedTaskErrorFindings
354
+ ? false
355
+ : toolResult.isError === true;
354
356
  const isSuccessfulTaskResult = toolResult.toolName === "task" && effectiveToolIsError !== true;
355
357
  const isDelegatedExecutionTool = (isDelegatedAgentEvent || state.openToolCapableTaskDelegations > 0)
356
358
  && toolResult.toolName !== "write_todos"
@@ -0,0 +1,4 @@
1
+ import type { RequestState, TerminalExecutionStatus } from "../../contracts/types.js";
2
+ export declare function readTerminalExecutionStatus(value: unknown): TerminalExecutionStatus | null;
3
+ export declare function mapTerminalStatusToRequestState(status: TerminalExecutionStatus | null): RequestState;
4
+ export declare function mapTerminalStatusToPlanItemStatus(status: TerminalExecutionStatus): "completed" | "failed";
@@ -0,0 +1,67 @@
1
+ const TERMINAL_STATUSES = new Set(["completed", "blocked", "failed", "refused"]);
2
+ function normalizeTerminalStatus(value) {
3
+ if (typeof value !== "string") {
4
+ return null;
5
+ }
6
+ const normalized = value.trim().toLowerCase();
7
+ return TERMINAL_STATUSES.has(normalized)
8
+ ? normalized
9
+ : null;
10
+ }
11
+ function readStatusLine(value) {
12
+ for (const line of value.split("\n")) {
13
+ const [key, ...rest] = line.split(":");
14
+ if (key?.trim().toLowerCase() !== "status") {
15
+ continue;
16
+ }
17
+ const statusValue = rest.join(":").trim().split(/\s+/)[0];
18
+ const status = normalizeTerminalStatus(statusValue);
19
+ if (status) {
20
+ return status;
21
+ }
22
+ }
23
+ return null;
24
+ }
25
+ export function readTerminalExecutionStatus(value) {
26
+ const direct = normalizeTerminalStatus(value);
27
+ if (direct) {
28
+ return direct;
29
+ }
30
+ if (typeof value === "string") {
31
+ try {
32
+ return readTerminalExecutionStatus(JSON.parse(value));
33
+ }
34
+ catch {
35
+ return readStatusLine(value);
36
+ }
37
+ }
38
+ if (typeof value !== "object" || value === null) {
39
+ return null;
40
+ }
41
+ if (Array.isArray(value)) {
42
+ for (let index = value.length - 1; index >= 0; index -= 1) {
43
+ const status = readTerminalExecutionStatus(value[index]);
44
+ if (status) {
45
+ return status;
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+ const typed = value;
51
+ return (readTerminalExecutionStatus(typed.status)
52
+ ?? readTerminalExecutionStatus(typed.structuredResponse)
53
+ ?? readTerminalExecutionStatus(typed.messages)
54
+ ?? readTerminalExecutionStatus(typed.content)
55
+ ?? readTerminalExecutionStatus(typed.kwargs?.content)
56
+ ?? readTerminalExecutionStatus(typed.lc_kwargs?.content)
57
+ ?? readTerminalExecutionStatus(typed.output)
58
+ ?? readTerminalExecutionStatus(typed.data));
59
+ }
60
+ export function mapTerminalStatusToRequestState(status) {
61
+ return status === "blocked" || status === "failed" || status === "refused"
62
+ ? "failed"
63
+ : "completed";
64
+ }
65
+ export function mapTerminalStatusToPlanItemStatus(status) {
66
+ return status === "completed" ? "completed" : "failed";
67
+ }
@@ -1,9 +1,10 @@
1
1
  import path from "node:path";
2
- import { GENERAL_PURPOSE_SUBAGENT, createAsyncSubAgentMiddleware, createDeepAgent, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSummarizationMiddleware, createSubAgentMiddleware, FilesystemBackend, StateBackend, } from "deepagents";
2
+ import { createAsyncSubAgentMiddleware, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSummarizationMiddleware, createSubAgentMiddleware, FilesystemBackend, StateBackend, } from "deepagents";
3
3
  import { createAgent, humanInTheLoopMiddleware, todoListMiddleware } from "langchain";
4
4
  import { wrapResolvedModel, } from "./parsing/output-parsing.js";
5
- import { AGENT_INTERRUPT_SENTINEL_PREFIX, buildDeepAgentCreateParams, buildLangChainCreateParams, DEFAULT_DEEPAGENT_RECURSION_LIMIT, materializeModelExposedBuiltinMiddlewareTools, resolveLangChainInvocationConfig, resolveRunnableCheckpointer, resolveRunnableInterruptOn, shouldAttachDeepAgentBackend, shouldAttachDeepAgentCheckpointer, shouldAttachDeepAgentStore, } from "./agent-runtime-assembly.js";
5
+ import { AGENT_INTERRUPT_SENTINEL_PREFIX, buildDeepAgentCreateParams, buildDeepAgentSystemPromptWithCapabilityHierarchy, buildLangChainCreateParams, DEFAULT_DEEPAGENT_RECURSION_LIMIT, materializeModelExposedBuiltinMiddlewareTools, resolveLangChainInvocationConfig, resolveRunnableCheckpointer, resolveRunnableInterruptOn, shouldAttachDeepAgentBackend, shouldAttachDeepAgentCheckpointer, shouldAttachDeepAgentStore, } from "./agent-runtime-assembly.js";
6
6
  import { resolveDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
7
+ import { EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION } from "./prompts/runtime-prompts.js";
7
8
  import { buildToolNameMapping, } from "./adapter/tool/tool-name-mapping.js";
8
9
  import { executeRequestInvocation } from "./adapter/flow/invocation-flow.js";
9
10
  import { streamRuntimeExecution } from "./adapter/flow/stream-runtime.js";
@@ -21,8 +22,22 @@ export { buildAuthOmittingFetch, normalizeOpenAICompatibleInit } from "./adapter
21
22
  export { buildToolNameMapping, createModelFacingToolNameCandidates, createModelFacingToolNameLookupCandidates, resolveModelFacingToolName, sanitizeToolNameForModel, } from "./adapter/tool/tool-name-mapping.js";
22
23
  export { computeRemainingTimeoutMs, isRetryableProviderError, resolveBindingTimeout, resolveProviderRetryPolicy, resolveStreamIdleTimeout, resolveTimeoutMs, } from "./adapter/resilience.js";
23
24
  import { getBindingAdapterKind, getBindingBuiltinToolsConfig, getBindingDeepAgentSubagents, getBindingExecutionParams, getBindingExecutionKind, getBindingFilesystemConfig, getBindingMemorySources, getBindingPrimaryModel, getBindingSkills, getBindingToolCount, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
25
+ class DelegatedExecutionNoToolEvidenceError extends Error {
26
+ constructor(agentId) {
27
+ super(`Delegated agent ${agentId} completed without tool execution evidence.`);
28
+ this.name = "DelegatedExecutionNoToolEvidenceError";
29
+ }
30
+ }
31
+ function hasDelegatedExecutionToolEvidence(result) {
32
+ const executedToolResults = Array.isArray(result.metadata?.executedToolResults)
33
+ ? result.metadata.executedToolResults
34
+ : [];
35
+ return executedToolResults.some((toolResult) => (toolResult.isError !== true
36
+ && toolResult.toolName !== "write_todos"
37
+ && toolResult.toolName !== "read_todos"));
38
+ }
24
39
  function shouldUseConfigurableDeepAgentAssembly(binding) {
25
- return getBindingBuiltinToolsConfig(binding) !== undefined;
40
+ return getBindingExecutionKind(binding) === "deepagent";
26
41
  }
27
42
  export class AgentRuntimeAdapter {
28
43
  options;
@@ -319,9 +334,18 @@ export class AgentRuntimeAdapter {
319
334
  const childSessionId = `${sessionId}:delegated:${resolvedSubagent.name}`;
320
335
  const childRequestId = `${requestId}:delegated:${resolvedSubagent.name}:${Date.now().toString(36)}`;
321
336
  try {
322
- const result = await this.invoke(targetBinding, requestText, childSessionId, childRequestId, undefined, [], {
337
+ const invokeOptions = {
323
338
  ...(typeof config?.context === "object" && config.context ? { context: config.context } : {}),
324
- });
339
+ };
340
+ const runDelegatedRequest = (text, requestSuffix = "") => this.invoke(targetBinding, text, childSessionId, `${childRequestId}${requestSuffix}`, undefined, [], invokeOptions);
341
+ let result = await runDelegatedRequest(requestText);
342
+ const targetRequiresExecutionToolEvidence = getBindingPrimaryTools(targetBinding).length > 0;
343
+ if (targetRequiresExecutionToolEvidence && !hasDelegatedExecutionToolEvidence(result)) {
344
+ result = await runDelegatedRequest([requestText, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":tool-evidence-retry");
345
+ if (!hasDelegatedExecutionToolEvidence(result)) {
346
+ throw new DelegatedExecutionNoToolEvidenceError(targetBinding.agent.id);
347
+ }
348
+ }
325
349
  return wrapRequestResultAsSubagentResponse({
326
350
  output: result.output,
327
351
  structuredResponse: result.structuredResponse,
@@ -331,9 +355,7 @@ export class AgentRuntimeAdapter {
331
355
  const message = error instanceof Error && error.message.trim().length > 0
332
356
  ? error.message.trim()
333
357
  : "delegated execution failed";
334
- return wrapRequestResultAsSubagentResponse({
335
- output: `Blocked: ${message}`,
336
- });
358
+ throw new Error(message);
337
359
  }
338
360
  },
339
361
  },
@@ -422,18 +444,6 @@ export class AgentRuntimeAdapter {
422
444
  ownerId: binding.agent.id,
423
445
  skillPaths: getBindingSkills(binding),
424
446
  }) ?? [];
425
- const deepAgentConfig = buildDeepAgentCreateParams({
426
- binding,
427
- resolvedModel,
428
- resolvedTools: [...resolvedTools, ...builtinMiddlewareTools],
429
- resolvedMiddleware,
430
- resolvedSubagents,
431
- resolvedCheckpointer,
432
- resolvedStore,
433
- resolvedBackend,
434
- resolvedInterruptOn,
435
- resolvedSkills,
436
- });
437
447
  if (shouldUseConfigurableDeepAgentAssembly(binding)) {
438
448
  return this.createConfigurableDeepAgentRunnable(binding, {
439
449
  resolvedModel,
@@ -441,36 +451,33 @@ export class AgentRuntimeAdapter {
441
451
  resolvedMiddleware,
442
452
  resolvedSubagents,
443
453
  resolvedInterruptOn,
454
+ resolvedCheckpointer,
455
+ resolvedStore,
444
456
  resolvedBackend,
445
457
  resolvedSkills,
446
458
  });
447
459
  }
448
- return createDeepAgent(deepAgentConfig);
460
+ throw new Error(`Agent ${binding.agent.id} has no supported deepagent assembly path`);
449
461
  }
450
462
  createConfigurableDeepAgentRunnable(binding, input) {
451
463
  const builtinTools = getBindingBuiltinToolsConfig(binding) ?? {};
452
464
  const backend = (input.resolvedBackend ?? new StateBackend({}));
453
465
  const inlineSubagents = input.resolvedSubagents.filter((subagent) => !("graphId" in subagent));
454
466
  const asyncSubagents = input.resolvedSubagents.filter((subagent) => "graphId" in subagent);
455
- const subagents = inlineSubagents.some((subagent) => subagent.name === GENERAL_PURPOSE_SUBAGENT.name)
456
- ? inlineSubagents
457
- : [{
458
- ...GENERAL_PURPOSE_SUBAGENT,
459
- model: input.resolvedModel,
460
- tools: input.resolvedTools,
461
- skills: input.resolvedSkills,
462
- }, ...inlineSubagents];
467
+ const subagents = inlineSubagents;
463
468
  const middleware = [
464
469
  ...(builtinTools.todos === false ? [] : [todoListMiddleware()]),
465
470
  ...(input.resolvedSkills.length > 0 ? [createSkillsMiddleware({ backend, sources: input.resolvedSkills })] : []),
466
471
  ...(builtinTools.filesystem === false ? [] : [createFilesystemMiddleware({ backend })]),
467
- createSubAgentMiddleware({
468
- defaultModel: input.resolvedModel,
469
- defaultTools: input.resolvedTools,
470
- defaultInterruptOn: input.resolvedInterruptOn,
471
- subagents: subagents,
472
- generalPurposeAgent: false,
473
- }),
472
+ ...(subagents.length > 0
473
+ ? [createSubAgentMiddleware({
474
+ defaultModel: input.resolvedModel,
475
+ defaultTools: input.resolvedTools,
476
+ defaultInterruptOn: input.resolvedInterruptOn,
477
+ subagents: subagents,
478
+ generalPurposeAgent: false,
479
+ })]
480
+ : []),
474
481
  createSummarizationMiddleware({
475
482
  model: input.resolvedModel,
476
483
  backend,
@@ -487,10 +494,17 @@ export class AgentRuntimeAdapter {
487
494
  : undefined;
488
495
  return createAgent({
489
496
  model: input.resolvedModel,
490
- systemPrompt: getBindingSystemPrompt(binding),
497
+ systemPrompt: buildDeepAgentSystemPromptWithCapabilityHierarchy({
498
+ systemPrompt: getBindingSystemPrompt(binding),
499
+ subagents: input.resolvedSubagents,
500
+ skills: input.resolvedSkills,
501
+ tools: getBindingPrimaryTools(binding),
502
+ }),
491
503
  tools: input.resolvedTools,
492
504
  middleware: middleware,
493
505
  name: binding.agent.id,
506
+ ...(input.resolvedCheckpointer !== undefined ? { checkpointer: input.resolvedCheckpointer } : {}),
507
+ ...(input.resolvedStore !== undefined ? { store: input.resolvedStore } : {}),
494
508
  ...(responseFormat !== undefined ? { responseFormat: responseFormat } : {}),
495
509
  });
496
510
  }
@@ -8,6 +8,16 @@ export declare function materializeModelExposedBuiltinMiddlewareTools(input: {
8
8
  explicitToolNames?: string[];
9
9
  modelExposed?: boolean | string[];
10
10
  }): unknown[];
11
+ export declare function buildDeepAgentSystemPromptWithCapabilityHierarchy(input: {
12
+ systemPrompt?: unknown;
13
+ subagents: Array<Pick<UpstreamSubagentConfig, "name" | "description"> | Pick<CompiledAsyncSubAgent, "name" | "description">>;
14
+ skills?: string[];
15
+ tools?: Array<{
16
+ name: string;
17
+ description?: string;
18
+ }>;
19
+ }): unknown;
20
+ export declare const buildDeepAgentSystemPromptWithSubagentCatalog: typeof buildDeepAgentSystemPromptWithCapabilityHierarchy;
11
21
  export declare function resolveRunnableCheckpointer(options: RuntimeAdapterOptions, binding: CompiledAgentBinding): unknown;
12
22
  export declare function resolveRunnableInterruptOn(binding: CompiledAgentBinding): Record<string, {
13
23
  allowedDecisions: import("./adapter/tool/interrupt-policy.js").InterruptDecision[];
@@ -2,6 +2,7 @@ import { MemorySaver } from "@langchain/langgraph";
2
2
  import { UPSTREAM_REQUEST_CONFIG_KEY, UPSTREAM_SESSION_CONFIG_KEY } from "./adapter/upstream-configurable-keys.js";
3
3
  import { asStructuredExecutableTool } from "./adapter/tool/resolved-tool.js";
4
4
  import { compileInterruptOn } from "./adapter/tool/interrupt-policy.js";
5
+ import { readSkillMetadata } from "./skills/skill-metadata.js";
5
6
  import { getBindingBackendConfig, getBindingExecutionKind, getBindingExecutionParams, getBindingInterruptCompatibilityRules, getBindingMemorySources, getBindingMiddlewareConfigs, getBindingPrimaryTools, getBindingSkills, getBindingStoreConfig, } from "./support/compiled-binding.js";
6
7
  export const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
7
8
  export const DEFAULT_DEEPAGENT_RECURSION_LIMIT = 100;
@@ -37,6 +38,67 @@ export function materializeModelExposedBuiltinMiddlewareTools(input) {
37
38
  }
38
39
  return tools;
39
40
  }
41
+ function formatCapabilityLine(item) {
42
+ const description = typeof item.description === "string" && item.description.length > 0
43
+ ? `: ${item.description}`
44
+ : "";
45
+ return `- ${JSON.stringify(item.name)}${description}`;
46
+ }
47
+ function buildSkillCatalog(skillPaths) {
48
+ return skillPaths.map((skillPath) => {
49
+ const metadata = readSkillMetadata(skillPath);
50
+ return {
51
+ name: metadata.name,
52
+ ...(metadata.description ? { description: metadata.description } : {}),
53
+ };
54
+ });
55
+ }
56
+ export function buildDeepAgentSystemPromptWithCapabilityHierarchy(input) {
57
+ const basePrompt = typeof input.systemPrompt === "string" ? input.systemPrompt : undefined;
58
+ const skills = buildSkillCatalog(input.skills ?? []);
59
+ const tools = input.tools ?? [];
60
+ if (input.subagents.length === 0 && skills.length === 0 && tools.length === 0) {
61
+ return input.systemPrompt;
62
+ }
63
+ const catalogPrompt = [
64
+ "Capability selection hierarchy:",
65
+ "1. If the current request fits an available subagent, delegate with the task tool before using local skills or raw tools.",
66
+ "2. If you are the selected agent and an available skill fits the request, read and follow that skill before calling raw tools.",
67
+ "3. Use raw tools as execution primitives for the selected agent or skill, or when no listed skill applies.",
68
+ "Keep each selection inside the selected capability's described responsibility boundary.",
69
+ "If no listed subagent, skill, or tool can responsibly handle the request, do not invent a path. Return a terminal response with status \"refused\" and explain the missing capability.",
70
+ "If a selected capability cannot complete after using its available tools, return a terminal response with status \"blocked\" or \"failed\" and include the blocker evidence.",
71
+ ...(input.subagents.length > 0
72
+ ? [
73
+ "",
74
+ "Available subagents for task delegation:",
75
+ ...input.subagents.map(formatCapabilityLine),
76
+ "",
77
+ "When using the task tool, set subagent_type to exactly one of the listed subagent names. Do not create, translate, alias, or modify subagent names.",
78
+ ]
79
+ : [
80
+ "",
81
+ "No configured specialist subagents are available for this agent. Do not use task delegation for role-internal work; select a skill first, then tools.",
82
+ ]),
83
+ ...(skills.length > 0
84
+ ? [
85
+ "",
86
+ "Available skills for this agent:",
87
+ ...skills.map(formatCapabilityLine),
88
+ ]
89
+ : []),
90
+ ...(tools.length > 0
91
+ ? [
92
+ "",
93
+ "Raw tools available to this agent:",
94
+ ...tools.map(formatCapabilityLine),
95
+ ]
96
+ : []),
97
+ "",
98
+ ].join("\n");
99
+ return [basePrompt, catalogPrompt].filter((part) => typeof part === "string" && part.length > 0).join("\n\n");
100
+ }
101
+ export const buildDeepAgentSystemPromptWithSubagentCatalog = buildDeepAgentSystemPromptWithCapabilityHierarchy;
40
102
  export function resolveRunnableCheckpointer(options, binding) {
41
103
  return options.checkpointerResolver ? options.checkpointerResolver(binding) : new MemorySaver();
42
104
  }
@@ -146,6 +208,12 @@ export function buildDeepAgentCreateParams(input) {
146
208
  ]);
147
209
  return {
148
210
  ...upstreamParams,
211
+ systemPrompt: buildDeepAgentSystemPromptWithSubagentCatalog({
212
+ systemPrompt: upstreamParams.systemPrompt,
213
+ subagents: input.resolvedSubagents,
214
+ skills: input.resolvedSkills,
215
+ tools: getBindingPrimaryTools(input.binding),
216
+ }),
149
217
  skills: input.resolvedSkills,
150
218
  model: input.resolvedModel,
151
219
  tools: input.resolvedTools,
@@ -2,6 +2,7 @@ import { resolveDeterministicFinalOutput, } from "../../adapter/invocation-resul
2
2
  import { AGENT_INTERRUPT_SENTINEL_PREFIX, RuntimeOperationTimeoutError } from "../../agent-runtime-adapter.js";
3
3
  import { ExecutionReconciliationError } from "../../adapter/flow/stream-runtime.js";
4
4
  import { buildRequestPlanState, summarizeBuiltinWriteTodosArgs } from "../../adapter/runtime-adapter-support.js";
5
+ import { mapTerminalStatusToPlanItemStatus, mapTerminalStatusToRequestState, readTerminalExecutionStatus, } from "../../adapter/terminal-status.js";
5
6
  import { sanitizeVisibleText } from "../../parsing/output-parsing.js";
6
7
  import { describeRuntimeError, renderRuntimeFailure, renderToolFailure } from "../../support/harness-support.js";
7
8
  import { getBindingPrimaryModel } from "../../support/compiled-binding.js";
@@ -37,27 +38,6 @@ function planStateHasActiveItems(planState) {
37
38
  }
38
39
  return planState.summary.pending > 0 || planState.summary.inProgress > 0;
39
40
  }
40
- function readTerminalStructuredStatus(value) {
41
- if (typeof value === "string") {
42
- try {
43
- return readTerminalStructuredStatus(JSON.parse(value));
44
- }
45
- catch {
46
- return /^\s*Status:\s*completed\b/im.test(value) ? "completed" : null;
47
- }
48
- }
49
- if (typeof value !== "object" || value === null) {
50
- return null;
51
- }
52
- const typed = value;
53
- if (typed.status === "completed") {
54
- return typed.status;
55
- }
56
- return (readTerminalStructuredStatus(typed.structuredResponse)
57
- ?? readTerminalStructuredStatus(typed.content)
58
- ?? readTerminalStructuredStatus(typed.output)
59
- ?? readTerminalStructuredStatus(typed.data));
60
- }
61
41
  function isSubstantiveTerminalAssistantOutput(value) {
62
42
  const normalized = sanitizeVisibleText(value).trim();
63
43
  if (normalized.length < 80) {
@@ -898,10 +878,10 @@ export async function* streamHarnessRun(options) {
898
878
  }
899
879
  }
900
880
  const terminalStructuredStatus = normalizedChunk.toolName === "task"
901
- ? readTerminalStructuredStatus(normalizedChunk.output)
881
+ ? readTerminalExecutionStatus(normalizedChunk.output)
902
882
  : null;
903
883
  if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
904
- const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
884
+ const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, mapTerminalStatusToPlanItemStatus(terminalStructuredStatus), new Date().toISOString());
905
885
  const signature = buildPlanStateSignature(reconciledPlanState);
906
886
  if (signature !== lastPlanStateSignature) {
907
887
  const previousPlanState = currentPlanState;
@@ -1005,9 +985,9 @@ export async function* streamHarnessRun(options) {
1005
985
  }
1006
986
  }
1007
987
  currentPlanState = await refreshPlanStateFromPersistence(options, currentPlanState);
1008
- const terminalStructuredStatus = readTerminalStructuredStatus(actual.structuredResponse);
988
+ const terminalStructuredStatus = readTerminalExecutionStatus(actual.structuredResponse);
1009
989
  if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
1010
- const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
990
+ const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, mapTerminalStatusToPlanItemStatus(terminalStructuredStatus), new Date().toISOString());
1011
991
  const signature = buildPlanStateSignature(reconciledPlanState);
1012
992
  if (signature !== lastPlanStateSignature) {
1013
993
  const previousPlanState = currentPlanState;
@@ -1075,8 +1055,10 @@ export async function* streamHarnessRun(options) {
1075
1055
  content: assistantOutput,
1076
1056
  };
1077
1057
  }
1058
+ const terminalStatus = readTerminalExecutionStatus(assistantOutput);
1059
+ const terminalRequestState = mapTerminalStatusToRequestState(terminalStatus);
1078
1060
  await options.appendAssistantMessage(options.sessionId, options.requestId, assistantOutput);
1079
- const completedEvent = await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, "completed", {
1061
+ const completedEvent = await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, terminalRequestState, {
1080
1062
  previousState: "running",
1081
1063
  });
1082
1064
  yield {
@@ -1089,9 +1071,10 @@ export async function* streamHarnessRun(options) {
1089
1071
  sessionId: options.sessionId,
1090
1072
  requestId: options.requestId,
1091
1073
  agentId: currentAgentId,
1092
- state: "completed",
1074
+ state: terminalRequestState,
1093
1075
  output: assistantOutput,
1094
1076
  finalMessageText: assistantOutput,
1077
+ ...(terminalStatus ? { metadata: { terminalStatus } } : {}),
1095
1078
  },
1096
1079
  };
1097
1080
  }
@@ -1101,9 +1084,9 @@ export async function* streamHarnessRun(options) {
1101
1084
  executedToolResults,
1102
1085
  });
1103
1086
  if (!assistantOutput && sawSuccessfulToolResult && deterministicToolEvidenceOutput) {
1104
- const terminalStructuredStatus = readTerminalStructuredStatus(deterministicToolEvidenceOutput);
1087
+ const terminalStructuredStatus = readTerminalExecutionStatus(deterministicToolEvidenceOutput);
1105
1088
  if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
1106
- const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, terminalStructuredStatus, new Date().toISOString());
1089
+ const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, mapTerminalStatusToPlanItemStatus(terminalStructuredStatus), new Date().toISOString());
1107
1090
  const signature = buildPlanStateSignature(reconciledPlanState);
1108
1091
  if (signature !== lastPlanStateSignature) {
1109
1092
  const previousPlanState = currentPlanState;
@@ -1137,7 +1120,9 @@ export async function* streamHarnessRun(options) {
1137
1120
  agentId: currentAgentId,
1138
1121
  content: deterministicToolEvidenceOutput,
1139
1122
  };
1140
- const completedEvent = await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, "completed", {
1123
+ const terminalStatus = readTerminalExecutionStatus(deterministicToolEvidenceOutput);
1124
+ const terminalRequestState = mapTerminalStatusToRequestState(terminalStatus);
1125
+ const completedEvent = await options.setRequestStateAndEmit(options.sessionId, options.requestId, 6, terminalRequestState, {
1141
1126
  previousState: "running",
1142
1127
  });
1143
1128
  yield {
@@ -1150,11 +1135,12 @@ export async function* streamHarnessRun(options) {
1150
1135
  sessionId: options.sessionId,
1151
1136
  requestId: options.requestId,
1152
1137
  agentId: currentAgentId,
1153
- state: "completed",
1138
+ state: terminalRequestState,
1154
1139
  output: deterministicToolEvidenceOutput,
1155
1140
  finalMessageText: deterministicToolEvidenceOutput,
1156
1141
  metadata: {
1157
1142
  executedToolResults,
1143
+ ...(terminalStatus ? { terminalStatus } : {}),
1158
1144
  },
1159
1145
  },
1160
1146
  };
@@ -7,10 +7,10 @@ export declare function isRepairableWriteTodosEmptyFailure(error: unknown): bool
7
7
  export declare function isToolCallRecoveryFailure(error: unknown): boolean;
8
8
  export declare function isRetrySafeInvalidToolSelectionError(value: unknown): boolean;
9
9
  export declare function shouldValidateExecutionWithoutToolEvidence(request: unknown): boolean;
10
- export declare function shouldRequireVisibleTodoPlan(request: unknown): boolean;
11
10
  export declare function resolveMissingPlanRecoveryInstruction(params: {
12
11
  request: unknown;
13
12
  assistantText?: string;
13
+ requiresPlan?: boolean;
14
14
  hasPlanStateEvidence?: boolean;
15
15
  hasWriteTodosEvidence?: boolean;
16
16
  hasToolResultEvidence?: boolean;
@@ -23,6 +23,7 @@ export declare function resolveExecutionWithoutToolEvidenceTextInstruction(reque
23
23
  hasPlanStateEvidence?: boolean;
24
24
  hasOpenTaskDelegation?: boolean;
25
25
  hasMissingDelegatedExecutionEvidence?: boolean;
26
+ requiresPlan?: boolean;
26
27
  }): string | null;
27
28
  export declare function resolveToolCallRecoveryInstruction(error: unknown): string | null;
28
29
  export declare function appendToolRecoveryInstruction(input: unknown, instruction: string): unknown;
@@ -111,34 +111,10 @@ export function shouldValidateExecutionWithoutToolEvidence(request) {
111
111
  }
112
112
  return readSystemInstructionText(request).length > 0;
113
113
  }
114
- export function shouldRequireVisibleTodoPlan(request) {
115
- const userText = readLatestUserRequestText(request).toLowerCase();
116
- if (!userText) {
117
- return false;
118
- }
119
- return [
120
- "investigate",
121
- "investigation",
122
- "issue",
123
- "issues",
124
- "rca",
125
- "root cause",
126
- "go deeper",
127
- "deep research",
128
- "debug",
129
- "排查",
130
- "调查",
131
- "问题",
132
- "根因",
133
- "故障",
134
- "集群",
135
- "cluster",
136
- ].some((keyword) => userText.includes(keyword));
137
- }
138
114
  export function resolveMissingPlanRecoveryInstruction(params) {
139
115
  const hasPlanEvidence = params.hasWriteTodosEvidence === true
140
116
  || params.hasPlanStateEvidence === true;
141
- if (!shouldRequireVisibleTodoPlan(params.request) || hasPlanEvidence) {
117
+ if (params.requiresPlan !== true || hasPlanEvidence) {
142
118
  return null;
143
119
  }
144
120
  if (params.hasToolResultEvidence === true) {
@@ -158,6 +134,7 @@ export function resolveExecutionWithoutToolEvidenceTextInstruction(request, assi
158
134
  const missingPlanRecoveryInstruction = resolveMissingPlanRecoveryInstruction({
159
135
  request,
160
136
  assistantText: normalizedText,
137
+ requiresPlan: resultEvidence.requiresPlan,
161
138
  hasWriteTodosEvidence: resultEvidence.hasWriteTodosEvidence,
162
139
  hasPlanStateEvidence: resultEvidence.hasIncompletePlanState === true || resultEvidence.hasPlanStateEvidence === true,
163
140
  hasToolResultEvidence: resultEvidence.hasToolResultEvidence,
@@ -11,6 +11,7 @@ export declare const WRITE_TODOS_REQUIRED_PLAN_INSTRUCTION: string;
11
11
  export declare const EXECUTION_WITH_TOOL_EVIDENCE_INSTRUCTION: string;
12
12
  export declare const EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION: string;
13
13
  export declare const AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION: string;
14
+ export declare const DELEGATED_TASK_FAILURE_RECOVERY_INSTRUCTION: string;
14
15
  export declare const INTERNAL_RUNTIME_SPILL_PATH_INSTRUCTION: string;
15
16
  export declare const WORKSPACE_RELATIVE_PATH_INSTRUCTION: string;
16
17
  export declare function renderDurableMemoryContextPrompt(memoryContext: string): string;
@@ -14,6 +14,7 @@ export const WRITE_TODOS_REQUIRED_PLAN_INSTRUCTION = readRuntimePrompt("write-to
14
14
  export const EXECUTION_WITH_TOOL_EVIDENCE_INSTRUCTION = readRuntimePrompt("execution-with-tool-evidence");
15
15
  export const EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION = readRuntimePrompt("execution-with-tool-evidence-retry");
16
16
  export const AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION = readRuntimePrompt("autonomous-investigation-recovery");
17
+ export const DELEGATED_TASK_FAILURE_RECOVERY_INSTRUCTION = readRuntimePrompt("delegated-task-failure-recovery");
17
18
  export const INTERNAL_RUNTIME_SPILL_PATH_INSTRUCTION = readRuntimePrompt("internal-runtime-spill-path");
18
19
  export const WORKSPACE_RELATIVE_PATH_INSTRUCTION = readRuntimePrompt("workspace-relative-path");
19
20
  export function renderDurableMemoryContextPrompt(memoryContext) {
@@ -199,6 +199,15 @@ function resolveResponseFormat(agent) {
199
199
  function resolveContextSchema(agent) {
200
200
  return getAgentExecutionConfigValue(agent, "contextSchema");
201
201
  }
202
+ function resolveExecutionContract(agent) {
203
+ const value = getAgentExecutionObject(agent, "executionContract");
204
+ if (!value) {
205
+ return undefined;
206
+ }
207
+ return {
208
+ ...(value.requiresPlan === true ? { requiresPlan: true } : {}),
209
+ };
210
+ }
202
211
  function resolveCompiledMiddleware(agent, models) {
203
212
  const middleware = getAgentExecutionConfigValue(agent, "middleware");
204
213
  return compileMiddlewareConfigs(middleware, models, agent.id);
@@ -399,6 +408,7 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
399
408
  : undefined;
400
409
  const runtimeGovernanceDefaults = asObject(runtimeDefaults?.governance);
401
410
  const runtimeObservabilityDefaults = asObject(runtimeDefaults?.observability);
411
+ const executionContract = resolveExecutionContract(agent);
402
412
  const compiledFilesystemConfig = agent.executionMode === "langchain-v1"
403
413
  ? mergeConfigObjects(runtimeFilesystemDefaults, getAgentExecutionObject(agent, "filesystem", { executionMode: "langchain-v1" }))
404
414
  : undefined;
@@ -417,6 +427,7 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
417
427
  resilience,
418
428
  ...(runtimeGovernanceDefaults ? { governance: runtimeGovernanceDefaults } : {}),
419
429
  ...(runtimeObservabilityDefaults ? { observability: runtimeObservabilityDefaults } : {}),
430
+ ...(executionContract ? { executionContract } : {}),
420
431
  ...(agent.executionMode === "deepagent"
421
432
  ? {
422
433
  deepagent: {
@@ -1,8 +1,11 @@
1
- import { readFileSync } from "node:fs";
2
1
  import path from "node:path";
3
2
  import { validateSkillMetadata } from "../runtime/skills/skill-metadata.js";
4
3
  import { getAgentExecutionConfigValue } from "./support/agent-execution-config.js";
5
4
  import { resolvePromptValue } from "./support/workspace-ref-utils.js";
5
+ const FORBIDDEN_GENERAL_PURPOSE_SUBAGENT_NAME = "general-purpose";
6
+ const FRAMEWORK_AGENT_TOOL_NAMES = new Set(["task"]);
7
+ const FRAMEWORK_EXECUTION_TOOL_NAMES = new Set(["write_todos", "read_todos"]);
8
+ const TERMINAL_STATUS_VALUES = new Set(["completed", "blocked", "failed", "refused"]);
6
9
  function normalizeMode(mode) {
7
10
  if (mode === "warn" || mode === "error") {
8
11
  return mode;
@@ -29,7 +32,76 @@ function isWorkspaceOwnedPath(candidate, roots) {
29
32
  function addIssue(issues, code, message) {
30
33
  issues.push({ code, message });
31
34
  }
32
- function validateAgentContract(agent, referencedSubagentIds, issues) {
35
+ function stripRefPrefix(value, prefix) {
36
+ return value.startsWith(prefix) ? value.slice(prefix.length) : value;
37
+ }
38
+ function resolveRefId(value) {
39
+ return stripRefPrefix(stripRefPrefix(value, "agent/"), "tool/");
40
+ }
41
+ function readBuiltinToolsConfig(agent) {
42
+ const value = getAgentExecutionConfigValue(agent, "builtinTools");
43
+ return typeof value === "object" && value && !Array.isArray(value)
44
+ ? value
45
+ : undefined;
46
+ }
47
+ function readExecutionContractConfig(agent) {
48
+ const value = getAgentExecutionConfigValue(agent, "executionContract");
49
+ return typeof value === "object" && value && !Array.isArray(value)
50
+ ? value
51
+ : undefined;
52
+ }
53
+ function collectAgentToolNames(agent, tools, ownsDelegation) {
54
+ const names = new Set(FRAMEWORK_EXECUTION_TOOL_NAMES);
55
+ if (ownsDelegation) {
56
+ for (const toolName of FRAMEWORK_AGENT_TOOL_NAMES) {
57
+ names.add(toolName);
58
+ }
59
+ }
60
+ for (const ref of agent.toolRefs) {
61
+ const tool = tools.get(resolveRefId(ref));
62
+ if (tool) {
63
+ names.add(tool.id);
64
+ names.add(tool.name);
65
+ }
66
+ names.add(resolveRefId(ref));
67
+ }
68
+ for (const binding of agent.toolBindings ?? []) {
69
+ const tool = tools.get(resolveRefId(binding.ref));
70
+ if (tool) {
71
+ names.add(tool.id);
72
+ names.add(tool.name);
73
+ }
74
+ names.add(resolveRefId(binding.ref));
75
+ }
76
+ for (const tool of agent.inlineTools ?? []) {
77
+ names.add(tool.id);
78
+ names.add(tool.name);
79
+ }
80
+ return names;
81
+ }
82
+ function hasDuplicateValues(values) {
83
+ return new Set(values).size !== values.length;
84
+ }
85
+ function readObject(value) {
86
+ return typeof value === "object" && value !== null && !Array.isArray(value)
87
+ ? value
88
+ : undefined;
89
+ }
90
+ function validateResponseFormatTerminalStatus(agent, responseFormat, issues) {
91
+ const schema = readObject(responseFormat);
92
+ const properties = readObject(schema?.properties);
93
+ const statusProperty = readObject(properties?.status);
94
+ const required = Array.isArray(schema?.required) ? schema.required : [];
95
+ if (!statusProperty || !required.includes("status")) {
96
+ addIssue(issues, "agent.response_format.missing_terminal_status", `Agent ${agent.id} responseFormat must require a status field so parents can distinguish completed, blocked, failed, and refused terminal states.`);
97
+ return;
98
+ }
99
+ const statusEnum = Array.isArray(statusProperty.enum) ? statusProperty.enum : [];
100
+ if (!Array.from(TERMINAL_STATUS_VALUES).every((value) => statusEnum.includes(value))) {
101
+ addIssue(issues, "agent.response_format.incomplete_terminal_status_enum", `Agent ${agent.id} responseFormat status enum must include completed, blocked, failed, and refused.`);
102
+ }
103
+ }
104
+ function validateAgentContract(agent, referencedSubagentIds, tools, issues) {
33
105
  const description = agent.description.trim();
34
106
  const systemPrompt = resolvePromptValue(getAgentExecutionConfigValue(agent, "systemPrompt"), path.dirname(agent.sourcePath));
35
107
  const ownsDelegation = agent.subagentRefs.length > 0 || agent.subagentPathRefs.length > 0 || (agent.asyncSubagents?.length ?? 0) > 0;
@@ -38,45 +110,72 @@ function validateAgentContract(agent, referencedSubagentIds, issues) {
38
110
  || (agent.toolBindings?.length ?? 0) > 0
39
111
  || (agent.inlineTools?.length ?? 0) > 0;
40
112
  const responseFormat = getAgentExecutionConfigValue(agent, "responseFormat");
113
+ const builtinTools = readBuiltinToolsConfig(agent);
114
+ const executionContract = readExecutionContractConfig(agent);
115
+ const localSubagentNames = [
116
+ ...agent.subagentRefs.map(resolveRefId),
117
+ ...(agent.asyncSubagents ?? []).map((subagent) => subagent.name),
118
+ ];
119
+ if (agent.id === FORBIDDEN_GENERAL_PURPOSE_SUBAGENT_NAME) {
120
+ addIssue(issues, "agent.general_purpose.forbidden", `Agent ${agent.id} uses the reserved general-purpose subagent name. Define explicit specialists with narrow responsibilities instead.`);
121
+ }
122
+ for (const asyncSubagent of agent.asyncSubagents ?? []) {
123
+ if (asyncSubagent.name === FORBIDDEN_GENERAL_PURPOSE_SUBAGENT_NAME) {
124
+ addIssue(issues, "agent.general_purpose.forbidden", `Agent ${agent.id} defines async subagent ${asyncSubagent.name}. Define explicit specialists with narrow responsibilities instead.`);
125
+ }
126
+ }
127
+ if (localSubagentNames.includes(FORBIDDEN_GENERAL_PURPOSE_SUBAGENT_NAME)) {
128
+ addIssue(issues, "agent.general_purpose.forbidden", `Agent ${agent.id} references reserved subagent name ${FORBIDDEN_GENERAL_PURPOSE_SUBAGENT_NAME}. Define explicit specialists with narrow responsibilities instead.`);
129
+ }
130
+ if (hasDuplicateValues(localSubagentNames)) {
131
+ addIssue(issues, "agent.subagent.duplicate_name", `Agent ${agent.id} exposes duplicate subagent names. Each delegated capability must have one stable owner.`);
132
+ }
41
133
  if (description.length < 24) {
42
134
  addIssue(issues, "agent.description.too_short", `Agent ${agent.id} should use a more specific description that explains when it should be used.`);
43
135
  }
136
+ if (executionContract?.requiresPlan === true && builtinTools?.todos === false) {
137
+ addIssue(issues, "agent.execution_contract.plan_without_todos", `Agent ${agent.id} requires plan evidence but disables todo tools. Enable todo tools or remove config.executionContract.requiresPlan.`);
138
+ }
44
139
  if (ownsDelegation) {
140
+ if (hasTools) {
141
+ addIssue(issues, "agent.orchestrator.mixed_tool_surface", `Delegating agent ${agent.id} defines both subagents and direct tools. Keep routing agents focused on delegation, and move execution tools to specialist agents.`);
142
+ }
143
+ if (builtinTools?.modelExposed !== false) {
144
+ addIssue(issues, "agent.orchestrator.model_exposed_builtins", `Delegating agent ${agent.id} should set config.builtinTools.modelExposed: false so raw built-in tools do not compete with specialist routing.`);
145
+ }
45
146
  if (!systemPrompt?.trim()) {
46
147
  addIssue(issues, "agent.orchestrator.missing_prompt", `Delegating agent ${agent.id} should define a systemPrompt that explains decomposition, delegation, synthesis, and stop conditions.`);
47
148
  }
48
- if (!/(delegate|delegation|subagent|decompose|synthesi|answer directly|parallel)/i.test(description)) {
49
- addIssue(issues, "agent.orchestrator.description_boundary", `Delegating agent ${agent.id} description should make its delegation boundary explicit, for example when it should answer directly versus delegate.`);
50
- }
51
149
  }
52
150
  if (isSubagent) {
53
151
  if (!systemPrompt?.trim()) {
54
152
  addIssue(issues, "agent.subagent.missing_prompt", `Subagent ${agent.id} should define a systemPrompt that makes its operating boundary and output contract explicit.`);
55
153
  }
56
- if (!/(use this when|when the task|for .*?(analysis|research|search|debug|review|triage|inspection|extraction|comparison|validation|implementation))/i.test(description)) {
57
- addIssue(issues, "agent.subagent.description_trigger", `Subagent ${agent.id} description should clarify when it should be delegated to and what narrow task class it owns.`);
58
- }
59
154
  if (agent.executionMode === "deepagent" && hasTools && responseFormat === undefined) {
60
155
  addIssue(issues, "agent.subagent.deepagent.missing_response_format", `DeepAgents subagent ${agent.id} exposes tools, so it should define config.responseFormat to guarantee a stable task result for its parent agent.`);
61
156
  }
157
+ if (agent.executionMode === "deepagent" && hasTools && responseFormat !== undefined) {
158
+ validateResponseFormatTerminalStatus(agent, responseFormat, issues);
159
+ }
160
+ if (hasTools && agent.skillPathRefs.length === 0) {
161
+ addIssue(issues, "agent.subagent.tools_without_skills", `Subagent ${agent.id} exposes execution tools but no skills. Add skills that describe tool-selection workflows and boundaries.`);
162
+ }
163
+ }
164
+ const toolNames = collectAgentToolNames(agent, tools, ownsDelegation);
165
+ for (const skillPath of agent.skillPathRefs) {
166
+ const metadata = validateSkillMetadata(skillPath);
167
+ for (const allowedTool of metadata.allowedTools ?? []) {
168
+ if (!toolNames.has(allowedTool)) {
169
+ addIssue(issues, "agent.skill.allowed_tool_unavailable", `Agent ${agent.id} attaches skill ${metadata.name}, but that skill allows tool ${allowedTool} which is not available to the agent.`);
170
+ }
171
+ }
62
172
  }
63
- }
64
- function stripFrontmatter(document) {
65
- return document.replace(/^---\s*\n[\s\S]*?\n---\s*(?:\n|$)/, "");
66
173
  }
67
174
  function validateSkillContract(skillRoot, issues) {
68
175
  const metadata = validateSkillMetadata(skillRoot);
69
- const document = readFileSync(path.join(skillRoot, "SKILL.md"), "utf8");
70
- const body = stripFrontmatter(document);
71
176
  const skillName = metadata.name || path.basename(skillRoot);
72
- if (!/(Use this skill when|Use this when)/i.test(body)) {
73
- addIssue(issues, "skill.missing_trigger", `Skill ${skillName} should explain when it should be used, preferably with a clear "Use this skill when..." trigger.`);
74
- }
75
- if (!/(## Workflow|^## Workflow|^\d+\.\s)/m.test(body)) {
76
- addIssue(issues, "skill.missing_workflow", `Skill ${skillName} should define an explicit workflow instead of only background prose.`);
77
- }
78
- if (!/(## Rules|Do not|Output|Caveat|Caveats)/i.test(body)) {
79
- addIssue(issues, "skill.missing_boundaries", `Skill ${skillName} should include execution boundaries such as rules, non-goals, caveats, or output expectations.`);
177
+ if (!metadata.description?.trim()) {
178
+ addIssue(issues, "skill.description.missing", `Skill ${skillName} must define a frontmatter description so agents can compare its boundary without reading the whole document.`);
80
179
  }
81
180
  }
82
181
  function validateToolContract(tool, issues) {
@@ -85,9 +184,6 @@ function validateToolContract(tool, issues) {
85
184
  addIssue(issues, "tool.description.too_short", `Tool ${tool.id} should use a more specific description that explains invocation boundaries and argument expectations.`);
86
185
  return;
87
186
  }
88
- if (!/(Use this when|Do not use|Before calling)/i.test(description)) {
89
- addIssue(issues, "tool.description.missing_boundary", `Tool ${tool.id} description should describe when to call it and, ideally, when not to call it or what must be true before calling it.`);
90
- }
91
187
  }
92
188
  export function validateFrameworkContracts(input) {
93
189
  const mode = normalizeMode(input.mode);
@@ -95,12 +191,12 @@ export function validateFrameworkContracts(input) {
95
191
  return;
96
192
  }
97
193
  const issues = [];
98
- const referencedSubagentIds = new Set(input.agents.flatMap((agent) => agent.subagentRefs.map((ref) => ref.replace(/^agent\//, ""))));
194
+ const referencedSubagentIds = new Set(input.agents.flatMap((agent) => agent.subagentRefs.map(resolveRefId)));
99
195
  for (const agent of input.agents) {
100
196
  if (!isWorkspaceOwnedPath(agent.sourcePath, input.ownedRoots)) {
101
197
  continue;
102
198
  }
103
- validateAgentContract(agent, referencedSubagentIds, issues);
199
+ validateAgentContract(agent, referencedSubagentIds, input.tools, issues);
104
200
  }
105
201
  for (const [skillName, skillRoot] of input.skillRegistry) {
106
202
  if (!isWorkspaceOwnedPath(skillRoot, input.ownedRoots)) {
@@ -29,6 +29,7 @@ const CONSUMED_AGENT_CONFIG_KEYS = [
29
29
  "filesystem",
30
30
  "builtinTools",
31
31
  "interactionMode",
32
+ "executionContract",
32
33
  ];
33
34
  const NON_AGENT_CONFIG_ITEM_KEYS = [
34
35
  "id",
@@ -65,6 +66,7 @@ const MIGRATED_AGENT_CONFIG_KEYS = [
65
66
  "filesystem",
66
67
  "builtinTools",
67
68
  "interactionMode",
69
+ "executionContract",
68
70
  ];
69
71
  function normalizeAgentItemForMerge(item) {
70
72
  const normalized = { ...item };
@@ -267,6 +269,7 @@ function readSharedAgentConfig(config) {
267
269
  ...(config.includeAgentName === "inline" ? { includeAgentName: "inline" } : {}),
268
270
  ...(config.version === "v1" || config.version === "v2" ? { version: config.version } : {}),
269
271
  ...(typeof config.filesystem === "object" && config.filesystem ? { filesystem: config.filesystem } : {}),
272
+ ...(typeof config.executionContract === "object" && config.executionContract ? { executionContract: config.executionContract } : {}),
270
273
  ...(backend ? { backend } : {}),
271
274
  ...(store ? { store } : {}),
272
275
  ...(middleware ? { middleware } : {}),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.346",
3
+ "version": "0.0.347",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",