@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.
- package/dist/contracts/runtime-requests.d.ts +1 -0
- package/dist/contracts/workspace.d.ts +4 -0
- package/dist/package-version.d.ts +1 -1
- package/dist/package-version.js +1 -1
- package/dist/resources/prompts/runtime/delegated-task-failure-recovery.md +8 -0
- package/dist/runtime/adapter/flow/stream-runtime.js +50 -17
- package/dist/runtime/adapter/invocation-result.js +49 -5
- package/dist/runtime/adapter/local-tool-invocation.js +5 -0
- package/dist/runtime/adapter/stream-event-projection.js +3 -1
- package/dist/runtime/adapter/terminal-status.d.ts +4 -0
- package/dist/runtime/adapter/terminal-status.js +67 -0
- package/dist/runtime/agent-runtime-adapter.js +51 -37
- package/dist/runtime/agent-runtime-assembly.d.ts +10 -0
- package/dist/runtime/agent-runtime-assembly.js +68 -0
- package/dist/runtime/harness/run/stream-run.js +17 -31
- package/dist/runtime/parsing/output-recovery.d.ts +2 -1
- package/dist/runtime/parsing/output-recovery.js +2 -25
- package/dist/runtime/prompts/runtime-prompts.d.ts +1 -0
- package/dist/runtime/prompts/runtime-prompts.js +1 -0
- package/dist/workspace/agent-binding-compiler.js +11 -0
- package/dist/workspace/framework-contract-validation.js +122 -26
- package/dist/workspace/object-loader.js +3 -0
- package/package.json +1 -1
|
@@ -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.
|
|
1
|
+
export declare const AGENT_HARNESS_VERSION = "0.0.347";
|
|
2
2
|
export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
|
package/dist/package-version.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
export const AGENT_HARNESS_VERSION = "0.0.
|
|
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
|
|
447
|
-
|
|
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 =
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
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 =
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
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
|
|
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
|
|
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
|
|
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 {
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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:
|
|
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
|
-
?
|
|
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 =
|
|
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,
|
|
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:
|
|
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 =
|
|
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
|
|
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:
|
|
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 (
|
|
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
|
|
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 (
|
|
73
|
-
addIssue(issues, "skill.
|
|
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(
|
|
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 } : {}),
|