@botbotgo/agent-harness 0.0.349 → 0.0.351

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.
@@ -1,2 +1,2 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.349";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.351";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.349";
1
+ export const AGENT_HARNESS_VERSION = "0.0.351";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
@@ -0,0 +1,8 @@
1
+ A previous response ended without delegating work through the task tool.
2
+
3
+ This agent has configured subagents and no direct execution skills or raw tools for local specialist work. Your next response must do exactly one of these:
4
+
5
+ 1. If the request fits any listed subagent description, call the task tool with the exact configured subagent_type and preserve the user's original request in the task description.
6
+ 2. If no listed subagent can responsibly handle the request, return a final refusal that says the configured subagents do not cover the request.
7
+
8
+ Do not answer locally, do not merely announce that you will delegate, and do not invent or mention tools that are not listed.
@@ -4,6 +4,197 @@ import { finalizeRequestResult } from "../invocation-result.js";
4
4
  import { invokeRuntimeWithLocalTools } from "./invoke-runtime.js";
5
5
  import { buildInvocationRequest } from "../model/invocation-request.js";
6
6
  import { UPSTREAM_REQUEST_CONFIG_KEY, UPSTREAM_SESSION_CONFIG_KEY } from "../upstream-configurable-keys.js";
7
+ import { extractVisibleOutput, tryParseJson } from "../../parsing/output-parsing.js";
8
+ function readBindingExecutionParams(binding) {
9
+ const params = binding.execution?.params ?? binding.deepAgentParams ?? binding.langchainAgentParams;
10
+ return {
11
+ tools: Array.isArray(params?.tools) ? params.tools : [],
12
+ subagents: Array.isArray(params?.subagents)
13
+ ? (params.subagents ?? [])
14
+ : [],
15
+ };
16
+ }
17
+ function isDelegationOnlyBinding(binding) {
18
+ const { tools, subagents } = readBindingExecutionParams(binding);
19
+ const agent = binding.agent;
20
+ const configuredSubagents = [
21
+ ...subagents,
22
+ ...(agent?.subagentRefs ?? []),
23
+ ...(agent?.subagentPathRefs ?? []),
24
+ ...(agent?.asyncSubagents ?? []),
25
+ ];
26
+ const configuredTools = [
27
+ ...tools,
28
+ ...(agent?.toolRefs ?? []),
29
+ ...(agent?.toolBindings ?? []),
30
+ ];
31
+ const skillRefs = agent?.skillPathRefs ?? [];
32
+ return configuredSubagents.length > 0 && configuredTools.length === 0 && skillRefs.length === 0;
33
+ }
34
+ function hasTaskDelegationEvidence(executedToolResults) {
35
+ return executedToolResults.some((item) => item.toolName === "task");
36
+ }
37
+ function hasNativeTaskDelegationIntent(value) {
38
+ if (typeof value !== "object" || value === null) {
39
+ return false;
40
+ }
41
+ if (Array.isArray(value)) {
42
+ return value.some((item) => hasNativeTaskDelegationIntent(item));
43
+ }
44
+ const typed = value;
45
+ if (typed.name === "task") {
46
+ return true;
47
+ }
48
+ return hasNativeTaskDelegationIntent(typed.tool_calls) || hasNativeTaskDelegationIntent(typed.messages);
49
+ }
50
+ function readStructuredToolCall(value) {
51
+ const text = typeof value === "string" ? value.trim() : "";
52
+ const parsed = text ? (tryParseJson(text) ?? extractFirstJsonObject(text)) : value;
53
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
54
+ return null;
55
+ }
56
+ const typed = parsed;
57
+ const nameCandidate = typed.name ?? typed.tool;
58
+ const name = typeof nameCandidate === "string" ? nameCandidate.trim() : "";
59
+ if (!name) {
60
+ return null;
61
+ }
62
+ const argsCandidate = typed.arguments ?? typed.args ?? typed.parameters ?? {};
63
+ const args = typeof argsCandidate === "object" && argsCandidate !== null && !Array.isArray(argsCandidate)
64
+ ? argsCandidate
65
+ : {};
66
+ return { name, args };
67
+ }
68
+ function extractFirstJsonObject(text) {
69
+ let start = -1;
70
+ let depth = 0;
71
+ let inString = false;
72
+ let escaped = false;
73
+ for (let index = 0; index < text.length; index += 1) {
74
+ const char = text[index];
75
+ if (start < 0) {
76
+ if (char === "{") {
77
+ start = index;
78
+ depth = 1;
79
+ }
80
+ continue;
81
+ }
82
+ if (escaped) {
83
+ escaped = false;
84
+ continue;
85
+ }
86
+ if (char === "\\") {
87
+ escaped = inString;
88
+ continue;
89
+ }
90
+ if (char === "\"") {
91
+ inString = !inString;
92
+ continue;
93
+ }
94
+ if (inString) {
95
+ continue;
96
+ }
97
+ if (char === "{") {
98
+ depth += 1;
99
+ continue;
100
+ }
101
+ if (char === "}") {
102
+ depth -= 1;
103
+ if (depth === 0) {
104
+ const parsed = tryParseJson(text.slice(start, index + 1));
105
+ if (parsed) {
106
+ return parsed;
107
+ }
108
+ start = -1;
109
+ }
110
+ }
111
+ }
112
+ return null;
113
+ }
114
+ async function replayStructuredTaskToolCall(input) {
115
+ if (hasTaskDelegationEvidence(input.invocation.executedToolResults)) {
116
+ return input.invocation;
117
+ }
118
+ const visibleOutput = extractVisibleOutput(input.invocation.result);
119
+ const toolCall = readStructuredToolCall(visibleOutput);
120
+ if (toolCall?.name !== "task") {
121
+ return input.invocation;
122
+ }
123
+ const taskTool = input.builtinExecutableTools.get("task");
124
+ if (!taskTool) {
125
+ return input.invocation;
126
+ }
127
+ try {
128
+ const output = await taskTool.invoke(toolCall.args, input.toolRuntimeContext);
129
+ return {
130
+ result: { output },
131
+ executedToolResults: [
132
+ ...input.invocation.executedToolResults,
133
+ { toolName: "task", output },
134
+ ],
135
+ };
136
+ }
137
+ catch (error) {
138
+ const output = error instanceof Error ? error.message : String(error);
139
+ return {
140
+ result: { output },
141
+ executedToolResults: [
142
+ ...input.invocation.executedToolResults,
143
+ { toolName: "task", output, isError: true },
144
+ ],
145
+ };
146
+ }
147
+ }
148
+ function formatDelegationSubagentCatalog(binding) {
149
+ const { subagents } = readBindingExecutionParams(binding);
150
+ return subagents
151
+ .map((subagent) => {
152
+ if (typeof subagent !== "object" || subagent === null) {
153
+ return "";
154
+ }
155
+ const typed = subagent;
156
+ const name = typeof typed.name === "string" ? typed.name.trim() : "";
157
+ if (!name) {
158
+ return "";
159
+ }
160
+ const description = typeof typed.description === "string" ? typed.description.trim() : "";
161
+ return description ? `- ${name}: ${description}` : `- ${name}`;
162
+ })
163
+ .filter(Boolean)
164
+ .join("\n");
165
+ }
166
+ function buildDelegationOnlyRecoveryInstruction(binding, input) {
167
+ const subagentCatalog = formatDelegationSubagentCatalog(binding) || "(none)";
168
+ return [
169
+ "/no_think",
170
+ "Runtime correction: this agent is delegation-only.",
171
+ "Return exactly one JSON object and no prose.",
172
+ "Use this shape when a listed subagent fits:",
173
+ "{\"name\":\"task\",\"arguments\":{\"subagent_type\":\"<listed name>\",\"description\":\"<original request>\"}}",
174
+ "Use this shape only when no listed subagent fits:",
175
+ "{\"status\":\"refused\",\"reason\":\"No configured subagent can handle the request.\"}",
176
+ "Listed subagents:",
177
+ subagentCatalog,
178
+ "Original request:",
179
+ JSON.stringify(input),
180
+ ].join("\n");
181
+ }
182
+ function appendUserRecoveryInstruction(input, instruction) {
183
+ const content = [
184
+ "Runtime correction:",
185
+ instruction,
186
+ ].join("\n\n");
187
+ if (Array.isArray(input)) {
188
+ return [...input, { role: "user", content }];
189
+ }
190
+ if (typeof input === "object" && input && Array.isArray(input.messages)) {
191
+ return {
192
+ ...input,
193
+ messages: [...(input.messages), { role: "user", content }],
194
+ };
195
+ }
196
+ return input;
197
+ }
7
198
  export async function executeRequestInvocation(options) {
8
199
  const history = options.history ?? [];
9
200
  const invokeOptions = options.invokeOptions ?? {};
@@ -24,9 +215,9 @@ export async function executeRequestInvocation(options) {
24
215
  },
25
216
  });
26
217
  const builtinExecutableTools = await options.resolveBuiltinMiddlewareTools(options.binding, invokeOptions);
27
- const localOrUpstreamInvocation = await invokeRuntimeWithLocalTools({
218
+ const invokeOnce = (activeRequest) => invokeRuntimeWithLocalTools({
28
219
  binding: options.binding,
29
- request,
220
+ request: activeRequest,
30
221
  resumePayload: options.resumePayload,
31
222
  primaryTools,
32
223
  defersToUpstreamHitlExecution,
@@ -36,6 +227,20 @@ export async function executeRequestInvocation(options) {
36
227
  callRuntimeWithToolParseRecovery: options.callRuntimeWithToolParseRecovery,
37
228
  toolRuntimeContext: invokeOptions.toolRuntimeContext,
38
229
  });
230
+ let localOrUpstreamInvocation = await invokeOnce(request);
231
+ if (options.resumePayload === undefined
232
+ && isDelegationOnlyBinding(options.binding)
233
+ && !hasTaskDelegationEvidence(localOrUpstreamInvocation.executedToolResults)
234
+ && !hasNativeTaskDelegationIntent(localOrUpstreamInvocation.result)) {
235
+ const delegationRecoveryInstruction = buildDelegationOnlyRecoveryInstruction(options.binding, options.input);
236
+ const recoveredRequest = appendUserRecoveryInstruction(request, delegationRecoveryInstruction);
237
+ const recoveredInvocation = await invokeOnce(recoveredRequest);
238
+ localOrUpstreamInvocation = await replayStructuredTaskToolCall({
239
+ invocation: recoveredInvocation,
240
+ builtinExecutableTools: builtinExecutableTools,
241
+ toolRuntimeContext: invokeOptions.toolRuntimeContext,
242
+ });
243
+ }
39
244
  const result = localOrUpstreamInvocation.result;
40
245
  const executedToolResults = [...localOrUpstreamInvocation.executedToolResults];
41
246
  if (!result) {
@@ -1,5 +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
+ import { DELEGATED_TASK_FAILURE_RECOVERY_INSTRUCTION, DELEGATION_ONLY_RECOVERY_INSTRUCTION, } from "../../prompts/runtime-prompts.js";
3
3
  import { buildInvocationRequest } from "../model/invocation-request.js";
4
4
  import { buildRawModelMessages } from "../model/message-assembly.js";
5
5
  import { projectRuntimeStreamEvent, createStreamEventProjectionState } from "../stream-event-projection.js";
@@ -85,6 +85,7 @@ function buildExecutionRecoveryEvidence(params) {
85
85
  hasOnlyPlaceholderTaskCompletion: projectionState.emittedSuccessfulTaskResult
86
86
  && projectionState.emittedPlaceholderTaskResult
87
87
  && !projectionState.emittedDelegatedTerminalOutput,
88
+ hasSuccessfulTaskToolEvidence: projectionState.emittedSuccessfulTaskResult || hasSuccessfulTaskToolEvidence(executedToolResults),
88
89
  };
89
90
  }
90
91
  function hasUnresolvedExecution(evidence) {
@@ -98,6 +99,45 @@ function hasMissingDelegatedExecutionEvidence(evidence) {
98
99
  function hasMissingDelegatedFindings(evidence) {
99
100
  return evidence.hasDelegatedAgentWithConfiguredTools && evidence.hasOnlyPlaceholderTaskCompletion;
100
101
  }
102
+ function readBindingExecutionParams(binding) {
103
+ const params = binding.execution?.params ?? binding.deepAgentParams ?? binding.langchainAgentParams;
104
+ return {
105
+ tools: Array.isArray(params?.tools) ? params.tools : [],
106
+ subagents: Array.isArray(params?.subagents)
107
+ ? (params.subagents ?? [])
108
+ : [],
109
+ };
110
+ }
111
+ function isDelegationOnlyBinding(binding) {
112
+ const { tools, subagents } = readBindingExecutionParams(binding);
113
+ const agent = binding.agent;
114
+ const configuredSubagents = [
115
+ ...subagents,
116
+ ...(agent?.subagentRefs ?? []),
117
+ ...(agent?.subagentPathRefs ?? []),
118
+ ...(agent?.asyncSubagents ?? []),
119
+ ];
120
+ const configuredTools = [
121
+ ...tools,
122
+ ...(agent?.toolRefs ?? []),
123
+ ...(agent?.toolBindings ?? []),
124
+ ];
125
+ const skillRefs = agent?.skillPathRefs ?? [];
126
+ return configuredSubagents.length > 0 && configuredTools.length === 0 && skillRefs.length === 0;
127
+ }
128
+ function hasDelegationEvidence(evidence) {
129
+ return (evidence.hasSuccessfulTaskToolEvidence
130
+ || evidence.hasOpenTaskDelegation
131
+ || evidence.hasFailedTaskDelegation
132
+ || evidence.hasDelegatedAgentWithConfiguredTools
133
+ || evidence.hasDelegatedExecutionToolEvidence);
134
+ }
135
+ function resolveDelegationOnlyRecoveryInstruction(binding, evidence) {
136
+ if (!isDelegationOnlyBinding(binding) || hasDelegationEvidence(evidence)) {
137
+ return null;
138
+ }
139
+ return DELEGATION_ONLY_RECOVERY_INSTRUCTION;
140
+ }
101
141
  function isRuntimeFailureOutput(value) {
102
142
  return value.trim().startsWith("runtime_error=");
103
143
  }
@@ -328,7 +368,7 @@ export async function* streamRuntimeExecution(options) {
328
368
  });
329
369
  throw error;
330
370
  }
331
- if (!options.forceInvokeFallback && typeof runnable.streamEvents === "function") {
371
+ if (!options.forceInvokeFallback && !isDelegationOnlyBinding(options.binding) && typeof runnable.streamEvents === "function") {
332
372
  const streamEventsStart = startProfileStep({
333
373
  id: "profile:agent:stream-events-start",
334
374
  kind: "agent",
@@ -423,13 +463,15 @@ export async function* streamRuntimeExecution(options) {
423
463
  hasToolResultEvidence: terminalExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence,
424
464
  })
425
465
  : null;
466
+ const terminalDelegationOnlyRecoveryInstruction = resolveDelegationOnlyRecoveryInstruction(options.binding, terminalExecutionEvidence);
426
467
  if (!shouldDeferStreamContent()
427
468
  && !terminalExecutionEvidence.hasIncompletePlanState
428
469
  && !terminalExecutionEvidence.hasFailedTaskDelegation
429
470
  && !terminalExecutionEvidence.hasOpenTaskDelegation
430
471
  && !hasMissingDelegatedExecutionEvidence(terminalExecutionEvidence)
431
472
  && !hasMissingDelegatedFindings(terminalExecutionEvidence)
432
- && !terminalMissingPlanRecoveryInstruction) {
473
+ && !terminalMissingPlanRecoveryInstruction
474
+ && !terminalDelegationOnlyRecoveryInstruction) {
433
475
  if (deferredStreamContent.length > 0) {
434
476
  yield* flushDeferredStreamContent();
435
477
  }
@@ -462,6 +504,7 @@ export async function* streamRuntimeExecution(options) {
462
504
  }
463
505
  const streamedExecutionEvidence = buildExecutionRecoveryEvidence({ projectionState });
464
506
  const streamedDelegatedRecoveryInstruction = resolveDelegatedExecutionRecoveryInstruction(streamedExecutionEvidence);
507
+ const streamedDelegationOnlyRecoveryInstruction = resolveDelegationOnlyRecoveryInstruction(options.binding, streamedExecutionEvidence);
465
508
  const delegatedExecutionRecoveryInstruction = !emittedUnsafeStreamSideEffects || streamedDelegatedRecoveryInstruction
466
509
  ? streamedDelegatedRecoveryInstruction
467
510
  : null;
@@ -493,6 +536,7 @@ export async function* streamRuntimeExecution(options) {
493
536
  : delegatedExecutionRecoveryInstruction
494
537
  ?? streamedRuntimeFailureRecoveryInstruction
495
538
  ?? missingPlanRecoveryInstruction
539
+ ?? streamedDelegationOnlyRecoveryInstruction
496
540
  ?? executionWithoutToolEvidenceInstruction;
497
541
  if (retryInstruction) {
498
542
  let retried;
@@ -565,7 +609,10 @@ export async function* streamRuntimeExecution(options) {
565
609
  return;
566
610
  }
567
611
  }
568
- if (!options.forceInvokeFallback && options.isLangChainBinding(options.binding) && typeof runnable.stream === "function") {
612
+ if (!options.forceInvokeFallback
613
+ && options.isLangChainBinding(options.binding)
614
+ && !isDelegationOnlyBinding(options.binding)
615
+ && typeof runnable.stream === "function") {
569
616
  const streamStart = startProfileStep({
570
617
  id: "profile:agent:stream-start",
571
618
  kind: "agent",
@@ -705,7 +752,9 @@ export async function* streamRuntimeExecution(options) {
705
752
  hasToolResultEvidence: invokeExecutionEvidence.hasSuccessfulNonTodoToolResultEvidence,
706
753
  })
707
754
  : null;
755
+ const invokeFallbackDelegationOnlyRecoveryInstruction = resolveDelegationOnlyRecoveryInstruction(options.binding, invokeExecutionEvidence);
708
756
  const effectiveInvokeFallbackRecoveryInstruction = invokeFallbackMissingPlanRecoveryInstruction
757
+ ?? invokeFallbackDelegationOnlyRecoveryInstruction
709
758
  ?? invokeFallbackRecoveryInstruction;
710
759
  if (effectiveInvokeFallbackRecoveryInstruction) {
711
760
  const recovered = await options.invoke(options.applyToolRecoveryInstruction(options.binding, effectiveInvokeFallbackRecoveryInstruction), options.input, options.sessionId, options.runtimeOptions.requestId ?? options.sessionId, undefined, options.history, options.runtimeOptions);
@@ -9,7 +9,7 @@ import { resolveDeclaredMiddleware } from "./tool/declared-middleware.js";
9
9
  import { UPSTREAM_SESSION_CONFIG_KEY } from "./upstream-configurable-keys.js";
10
10
  import { bindingHasLangChainSubagentSupport, bindingHasMiddlewareKind, getBindingBuiltinToolsConfig, getBindingExecutionKind, getBindingGeneralPurposeAgent, getBindingDeepAgentSubagents, getBindingInterruptCompatibilityRules, getBindingMiddlewareConfigs, getBindingMemorySources, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSkills, getBindingSubagents, getBindingTaskDescription, isDeepAgentBinding, isLangChainBinding, } from "../support/compiled-binding.js";
11
11
  import { materializeDeepAgentSkillSourcePaths } from "./compat/deepagent-compat.js";
12
- import { DEFAULT_SUBAGENT_PROMPT } from "../prompts/runtime-prompts.js";
12
+ import { DEFAULT_SUBAGENT_PROMPT, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, } from "../prompts/runtime-prompts.js";
13
13
  import { createContextHygieneMiddleware } from "./middleware/context-hygiene.js";
14
14
  const INVALID_TOOL_MESSAGE_BLOCK_TYPES = new Set(["tool_use", "thinking", "redacted_thinking"]);
15
15
  function extractDeepAgentTaskContent(result) {
@@ -32,6 +32,92 @@ function extractDeepAgentTaskContent(result) {
32
32
  }
33
33
  return undefined;
34
34
  }
35
+ function readMessageType(message) {
36
+ if (typeof message !== "object" || message === null) {
37
+ return "";
38
+ }
39
+ if (typeof message._getType === "function") {
40
+ const typeName = message._getType();
41
+ return typeof typeName === "string" ? typeName : "";
42
+ }
43
+ const typed = message;
44
+ if (typeof typed.type === "string") {
45
+ return typed.type;
46
+ }
47
+ const kwargs = typeof typed.kwargs === "object" && typed.kwargs !== null
48
+ ? typed.kwargs
49
+ : undefined;
50
+ if (typeof kwargs?.type === "string") {
51
+ return kwargs.type;
52
+ }
53
+ const lcKwargs = typeof typed.lc_kwargs === "object" && typed.lc_kwargs !== null
54
+ ? typed.lc_kwargs
55
+ : undefined;
56
+ return typeof lcKwargs?.type === "string" ? lcKwargs.type : "";
57
+ }
58
+ function readMessageName(message) {
59
+ if (typeof message !== "object" || message === null) {
60
+ return "";
61
+ }
62
+ const typed = message;
63
+ if (typeof typed.name === "string") {
64
+ return typed.name;
65
+ }
66
+ const kwargs = typeof typed.kwargs === "object" && typed.kwargs !== null
67
+ ? typed.kwargs
68
+ : undefined;
69
+ if (typeof kwargs?.name === "string") {
70
+ return kwargs.name;
71
+ }
72
+ const lcKwargs = typeof typed.lc_kwargs === "object" && typed.lc_kwargs !== null
73
+ ? typed.lc_kwargs
74
+ : undefined;
75
+ return typeof lcKwargs?.name === "string" ? lcKwargs.name : "";
76
+ }
77
+ function readMessages(result) {
78
+ if (typeof result !== "object" || result === null) {
79
+ return [];
80
+ }
81
+ const messages = result.messages;
82
+ return Array.isArray(messages) ? messages : [];
83
+ }
84
+ function readToolNames(tools) {
85
+ if (!Array.isArray(tools)) {
86
+ return new Set();
87
+ }
88
+ return new Set(tools
89
+ .map((tool) => {
90
+ if (typeof tool !== "object" || tool === null) {
91
+ return "";
92
+ }
93
+ const name = tool.name;
94
+ return typeof name === "string" ? name : "";
95
+ })
96
+ .filter((name) => name.length > 0));
97
+ }
98
+ function hasSubagentExecutionToolEvidence(result, resolvedTools, configuredTools) {
99
+ const requiredToolNames = new Set([
100
+ ...readToolNames(configuredTools),
101
+ ...readToolNames(resolvedTools),
102
+ ]);
103
+ if (requiredToolNames.size === 0) {
104
+ return true;
105
+ }
106
+ for (const message of readMessages(result)) {
107
+ const typeName = readMessageType(message);
108
+ if (typeName !== "tool" && typeName !== "ToolMessage") {
109
+ continue;
110
+ }
111
+ const name = readMessageName(message);
112
+ if (name === "write_todos" || name === "read_todos") {
113
+ continue;
114
+ }
115
+ if (requiredToolNames.has(name)) {
116
+ return true;
117
+ }
118
+ }
119
+ return false;
120
+ }
35
121
  export function extractSubagentRequestText(state) {
36
122
  if (!isRecord(state)) {
37
123
  return "";
@@ -257,6 +343,7 @@ export async function invokeBuiltinTaskTool(input) {
257
343
  const builtinBackend = input.resolveBuiltinMiddlewareBackend(input.binding, input.options);
258
344
  const resolvedSubagents = await input.resolveSubagents(compiledSubagents, input.binding);
259
345
  const selectedSubagent = resolvedSubagents.find((subagent) => subagent.name === subagentType);
346
+ const selectedCompiledSubagent = compiledSubagents.find((subagent) => subagent.name === subagentType);
260
347
  if (!selectedSubagent) {
261
348
  const allowed = resolvedSubagents.map((subagent) => subagent.name);
262
349
  throw new Error(`Error: invoked agent of type ${subagentType}, the only allowed types are ${allowed.map((name) => `\`${name}\``).join(", ")}`);
@@ -282,9 +369,16 @@ export async function invokeBuiltinTaskTool(input) {
282
369
  configurable: { [UPSTREAM_SESSION_CONFIG_KEY]: `${input.binding.agent.id}:builtin-task` },
283
370
  ...(input.options?.context ? { context: input.options.context } : {}),
284
371
  };
285
- const result = await runnable.invoke({
286
- messages: [new HumanMessage({ content: description })],
372
+ const invokeSubagent = (content) => runnable.invoke({
373
+ messages: [new HumanMessage({ content })],
287
374
  }, invokeConfig);
375
+ let result = await invokeSubagent(description);
376
+ if (!hasSubagentExecutionToolEvidence(result, resolvedSubagentTools, selectedCompiledSubagent?.tools)) {
377
+ result = await invokeSubagent([description, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"));
378
+ if (!hasSubagentExecutionToolEvidence(result, resolvedSubagentTools, selectedCompiledSubagent?.tools)) {
379
+ throw new Error(`Delegated agent ${selectedSubagent.name} completed without tool execution evidence.`);
380
+ }
381
+ }
288
382
  const structuredResponse = typeof result === "object" && result !== null && "structuredResponse" in result
289
383
  ? result.structuredResponse
290
384
  : undefined;
@@ -315,11 +409,23 @@ export async function invokeBuiltinTaskTool(input) {
315
409
  }
316
410
  export async function resolveBuiltinMiddlewareTools(input) {
317
411
  const backend = input.resolveBuiltinMiddlewareBackend(input.binding, input.options);
412
+ const params = input.binding.execution?.params ?? input.binding.deepAgentParams ?? input.binding.langchainAgentParams;
413
+ const configuredSubagents = [
414
+ ...(Array.isArray(params?.subagents)
415
+ ? (params.subagents ?? [])
416
+ : []),
417
+ ...(input.binding.agent.subagentRefs ?? []),
418
+ ...(input.binding.agent.subagentPathRefs ?? []),
419
+ ...(input.binding.agent.asyncSubagents ?? []),
420
+ ];
421
+ const includeTaskTool = configuredSubagents.length > 0;
318
422
  const tools = (await createBuiltinMiddlewareTools(backend, {
319
- includeTaskTool: false,
423
+ includeTaskTool,
320
424
  workspaceRoot: input.binding.harnessRuntime.workspaceRoot,
321
425
  toolRuntimeContext: input.options?.toolRuntimeContext,
322
- invokeTaskTool: undefined,
426
+ invokeTaskTool: includeTaskTool
427
+ ? (toolInput) => input.invokeBuiltinTaskTool(input.binding, toolInput, input.options)
428
+ : undefined,
323
429
  }));
324
430
  const builtinTools = getBindingBuiltinToolsConfig(input.binding) ?? {};
325
431
  if (builtinTools.todos === false) {
@@ -299,6 +299,10 @@ function extractToolCallPayload(text) {
299
299
  if (direct) {
300
300
  return direct;
301
301
  }
302
+ const firstJsonObject = extractFirstJsonObjectPayload(trimmed);
303
+ if (firstJsonObject) {
304
+ return firstJsonObject;
305
+ }
302
306
  const fenced = extractFencePayload(trimmed);
303
307
  if (fenced) {
304
308
  const parsed = tryParseJson(fenced);
@@ -326,6 +330,52 @@ function extractToolCallPayload(text) {
326
330
  }
327
331
  return null;
328
332
  }
333
+ function extractFirstJsonObjectPayload(text) {
334
+ let start = -1;
335
+ let depth = 0;
336
+ let inString = false;
337
+ let escaped = false;
338
+ for (let index = 0; index < text.length; index += 1) {
339
+ const char = text[index];
340
+ if (start < 0) {
341
+ if (char === "{") {
342
+ start = index;
343
+ depth = 1;
344
+ }
345
+ continue;
346
+ }
347
+ if (escaped) {
348
+ escaped = false;
349
+ continue;
350
+ }
351
+ if (char === "\\") {
352
+ escaped = inString;
353
+ continue;
354
+ }
355
+ if (char === "\"") {
356
+ inString = !inString;
357
+ continue;
358
+ }
359
+ if (inString) {
360
+ continue;
361
+ }
362
+ if (char === "{") {
363
+ depth += 1;
364
+ continue;
365
+ }
366
+ if (char === "}") {
367
+ depth -= 1;
368
+ if (depth === 0) {
369
+ const parsed = tryParseJson(text.slice(start, index + 1));
370
+ if (parsed) {
371
+ return parsed;
372
+ }
373
+ start = -1;
374
+ }
375
+ }
376
+ }
377
+ return null;
378
+ }
329
379
  function extractFencePayload(text) {
330
380
  const start = text.indexOf("```");
331
381
  if (start < 0) {
@@ -41,6 +41,21 @@ function isEmptyFinalAiMessageError(error) {
41
41
  const message = error instanceof Error ? error.message : String(error);
42
42
  return message.toLowerCase().startsWith("empty_final_ai_message:");
43
43
  }
44
+ function isRetryableHttpStatus(status) {
45
+ return typeof status === "number" && Number.isInteger(status) && status >= 500 && status <= 599;
46
+ }
47
+ function hasRetryableHttpStatus(error, seen = new Set()) {
48
+ if (typeof error !== "object" || error === null || seen.has(error)) {
49
+ return false;
50
+ }
51
+ seen.add(error);
52
+ const typed = error;
53
+ return isRetryableHttpStatus(typed.status)
54
+ || isRetryableHttpStatus(typed.statusCode)
55
+ || isRetryableHttpStatus(typed.status_code)
56
+ || isRetryableHttpStatus(typed.code)
57
+ || hasRetryableHttpStatus(typed.cause, seen);
58
+ }
44
59
  export function resolveProviderRetryPolicy(binding) {
45
60
  const resilience = typeof binding.harnessRuntime.resilience === "object" && binding.harnessRuntime.resilience
46
61
  ? binding.harnessRuntime.resilience
@@ -71,6 +86,9 @@ export function isRetryableProviderError(binding, error) {
71
86
  if (isEmptyFinalAiMessageError(error)) {
72
87
  return true;
73
88
  }
89
+ if (hasRetryableHttpStatus(error)) {
90
+ return true;
91
+ }
74
92
  const message = error instanceof Error ? error.message : String(error);
75
93
  const normalized = message.toLowerCase();
76
94
  const { retryableMessages } = resolveProviderRetryPolicy(binding);
@@ -58,6 +58,7 @@ export function buildDeepAgentSystemPromptWithCapabilityHierarchy(input) {
58
58
  const skills = buildSkillCatalog(input.skills ?? []);
59
59
  const tools = input.tools ?? [];
60
60
  const hasNoConfiguredCapabilities = input.subagents.length === 0 && skills.length === 0 && tools.length === 0;
61
+ const isDelegationOnly = input.subagents.length > 0 && skills.length === 0 && tools.length === 0;
61
62
  const catalogPrompt = [
62
63
  "Capability selection hierarchy:",
63
64
  "1. If the current request fits an available subagent, delegate with the task tool before using local skills or raw tools.",
@@ -109,6 +110,16 @@ export function buildDeepAgentSystemPromptWithCapabilityHierarchy(input) {
109
110
  "The only valid next step is for the workspace owner to attach an appropriate subagent, skill, or tool and retry.",
110
111
  ]
111
112
  : []),
113
+ ...(isDelegationOnly
114
+ ? [
115
+ "",
116
+ "Delegation-only mode:",
117
+ "This agent has specialist subagents but no direct execution skills or raw tools.",
118
+ "For any request that fits a listed subagent, the first assistant response must be a task tool call only.",
119
+ "Do not write a natural-language promise such as saying you will use a subagent. Actually call the task tool.",
120
+ "If no listed subagent fits, return a final refusal instead of answering locally.",
121
+ ]
122
+ : []),
112
123
  "",
113
124
  ].join("\n");
114
125
  return [basePrompt, catalogPrompt].filter((part) => typeof part === "string" && part.length > 0).join("\n\n");
@@ -12,6 +12,7 @@ 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
14
  export declare const DELEGATED_TASK_FAILURE_RECOVERY_INSTRUCTION: string;
15
+ export declare const DELEGATION_ONLY_RECOVERY_INSTRUCTION: string;
15
16
  export declare const INTERNAL_RUNTIME_SPILL_PATH_INSTRUCTION: string;
16
17
  export declare const WORKSPACE_RELATIVE_PATH_INSTRUCTION: string;
17
18
  export declare function renderDurableMemoryContextPrompt(memoryContext: string): string;
@@ -15,6 +15,7 @@ export const EXECUTION_WITH_TOOL_EVIDENCE_INSTRUCTION = readRuntimePrompt("execu
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
17
  export const DELEGATED_TASK_FAILURE_RECOVERY_INSTRUCTION = readRuntimePrompt("delegated-task-failure-recovery");
18
+ export const DELEGATION_ONLY_RECOVERY_INSTRUCTION = readRuntimePrompt("delegation-only-recovery");
18
19
  export const INTERNAL_RUNTIME_SPILL_PATH_INSTRUCTION = readRuntimePrompt("internal-runtime-spill-path");
19
20
  export const WORKSPACE_RELATIVE_PATH_INSTRUCTION = readRuntimePrompt("workspace-relative-path");
20
21
  export function renderDurableMemoryContextPrompt(memoryContext) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.349",
3
+ "version": "0.0.351",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",