@botbotgo/agent-harness 0.0.348 → 0.0.350

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.348";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.350";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.348";
1
+ export const AGENT_HARNESS_VERSION = "0.0.350";
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);
@@ -315,11 +315,23 @@ export async function invokeBuiltinTaskTool(input) {
315
315
  }
316
316
  export async function resolveBuiltinMiddlewareTools(input) {
317
317
  const backend = input.resolveBuiltinMiddlewareBackend(input.binding, input.options);
318
+ const params = input.binding.execution?.params ?? input.binding.deepAgentParams ?? input.binding.langchainAgentParams;
319
+ const configuredSubagents = [
320
+ ...(Array.isArray(params?.subagents)
321
+ ? (params.subagents ?? [])
322
+ : []),
323
+ ...(input.binding.agent.subagentRefs ?? []),
324
+ ...(input.binding.agent.subagentPathRefs ?? []),
325
+ ...(input.binding.agent.asyncSubagents ?? []),
326
+ ];
327
+ const includeTaskTool = configuredSubagents.length > 0;
318
328
  const tools = (await createBuiltinMiddlewareTools(backend, {
319
- includeTaskTool: false,
329
+ includeTaskTool,
320
330
  workspaceRoot: input.binding.harnessRuntime.workspaceRoot,
321
331
  toolRuntimeContext: input.options?.toolRuntimeContext,
322
- invokeTaskTool: undefined,
332
+ invokeTaskTool: includeTaskTool
333
+ ? (toolInput) => input.invokeBuiltinTaskTool(input.binding, toolInput, input.options)
334
+ : undefined,
323
335
  }));
324
336
  const builtinTools = getBindingBuiltinToolsConfig(input.binding) ?? {};
325
337
  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);
@@ -57,9 +57,8 @@ export function buildDeepAgentSystemPromptWithCapabilityHierarchy(input) {
57
57
  const basePrompt = typeof input.systemPrompt === "string" ? input.systemPrompt : undefined;
58
58
  const skills = buildSkillCatalog(input.skills ?? []);
59
59
  const tools = input.tools ?? [];
60
- if (input.subagents.length === 0 && skills.length === 0 && tools.length === 0) {
61
- return input.systemPrompt;
62
- }
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;
63
62
  const catalogPrompt = [
64
63
  "Capability selection hierarchy:",
65
64
  "1. If the current request fits an available subagent, delegate with the task tool before using local skills or raw tools.",
@@ -86,13 +85,40 @@ export function buildDeepAgentSystemPromptWithCapabilityHierarchy(input) {
86
85
  "Available skills for this agent:",
87
86
  ...skills.map(formatCapabilityLine),
88
87
  ]
89
- : []),
88
+ : [
89
+ "",
90
+ "Available skills for this agent: none.",
91
+ ]),
90
92
  ...(tools.length > 0
91
93
  ? [
92
94
  "",
93
95
  "Raw tools available to this agent:",
94
96
  ...tools.map(formatCapabilityLine),
95
97
  ]
98
+ : [
99
+ "",
100
+ "Raw tools available to this agent: none.",
101
+ "Do not mention, offer, or claim any raw tool that is not listed here.",
102
+ ]),
103
+ ...(hasNoConfiguredCapabilities
104
+ ? [
105
+ "",
106
+ "No-capability terminal rule:",
107
+ "This agent currently has no configured subagents, skills, or raw tools.",
108
+ "For requests that require execution, retrieval, live data, workspace inspection, or external evidence, return a direct refusal based on the missing configured capability.",
109
+ "Do not propose a tool-based next step, do not name any unlisted tool, and do not ask the user for links so this agent can fetch them.",
110
+ "The only valid next step is for the workspace owner to attach an appropriate subagent, skill, or tool and retry.",
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
+ ]
96
122
  : []),
97
123
  "",
98
124
  ].join("\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.348",
3
+ "version": "0.0.350",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",