@botbotgo/agent-harness 0.0.353 → 0.0.355

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.353";
2
- export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.355";
2
+ export declare const AGENT_HARNESS_RELEASE_DATE = "2026-04-25";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.353";
2
- export const AGENT_HARNESS_RELEASE_DATE = "2026-04-24";
1
+ export const AGENT_HARNESS_VERSION = "0.0.355";
2
+ export const AGENT_HARNESS_RELEASE_DATE = "2026-04-25";
@@ -1 +1,10 @@
1
- Do not stop at a plan or ask the user to choose the next obvious diagnostic step when the request is for deep investigation, root-cause analysis, or step-by-step execution. Do not ask for more background, scope, logs, or environment details until you have first exhausted the context and tools already available in this runtime. Start from the current workspace, shell, and attached runtime/tool context by default, then continue the investigation yourself with the next concrete tool call. If the user explicitly asked for a plan, or if the task is clearly non-trivial and multi-step, call write_todos first with concrete investigation/execution steps before any other tool call or final answer. If the todo board already exists, do not restart planning and do not repeat the same clarification request; your next response must continue execution with a concrete diagnostic tool call. When prior tool results or triage evidence already exist, your next response must contain tool calls only and must advance the investigation from that evidence. Then keep executing the next diagnostic steps until you can explain the likely causes, impact, and recommended next actions. Ask a blocking clarification question only after the available evidence is genuinely insufficient to continue.
1
+ Do not stop at a plan or ask the user to choose the next obvious diagnostic step when the request is for deep investigation, root-cause analysis, or step-by-step execution. Do not ask for more background, scope, logs, or environment details until you have first exhausted the context and tools already available in this runtime.
2
+
3
+ If no concrete execution has happened yet, start from the current workspace, shell, and attached runtime/tool context by default, then continue the investigation yourself with the next concrete tool call. If the user explicitly asked for a plan, or if the task is clearly non-trivial and multi-step, call write_todos first with concrete investigation/execution steps before any other tool call or final answer.
4
+
5
+ If a todo board already exists, do not restart planning and do not repeat the same clarification request. Use the current todo board and prior tool results to choose one of these terminally useful actions:
6
+ - If more evidence is genuinely needed and an available tool can get it, make the next concrete tool call.
7
+ - If the existing evidence is enough to answer, update the todo board to completed or blocked as appropriate, then provide the final answer grounded in the tool results.
8
+ - If the available tools cannot resolve the remaining work, update the todo board to blocked or failed and provide a blocker report with the evidence.
9
+
10
+ Never print a tool-call JSON object, function call, or tool name as prose when you intend to use a tool. Actually call the tool. Ask a blocking clarification question only after the available evidence is genuinely insufficient to continue.
@@ -11,3 +11,9 @@ export declare function resolveDeepAgentSkillSourcePaths(options: {
11
11
  ownerId: string;
12
12
  skillPaths?: string[];
13
13
  }): string[] | undefined;
14
+ export declare function resolveDeepAgentSkillSourceRootPaths(options: {
15
+ workspaceRoot?: string;
16
+ runtimeRoot?: string;
17
+ ownerId: string;
18
+ skillPaths?: string[];
19
+ }): string[] | undefined;
@@ -1,4 +1,5 @@
1
1
  import path from "node:path";
2
+ import { existsSync } from "node:fs";
2
3
  export function relativizeDeepAgentSkillSourcePaths(workspaceRoot, skillPaths) {
3
4
  if (!workspaceRoot || !skillPaths) {
4
5
  return skillPaths;
@@ -24,3 +25,18 @@ export function resolveDeepAgentSkillSourcePaths(options) {
24
25
  }
25
26
  return relativizeDeepAgentSkillSourcePaths(workspaceRoot, skillPaths) ?? skillPaths;
26
27
  }
28
+ export function resolveDeepAgentSkillSourceRootPaths(options) {
29
+ const { workspaceRoot, skillPaths } = options;
30
+ if (!skillPaths) {
31
+ return skillPaths;
32
+ }
33
+ const sourceRoots = Array.from(new Set(skillPaths.map((skillPath) => {
34
+ const absolutePath = path.isAbsolute(skillPath) || !workspaceRoot
35
+ ? skillPath
36
+ : path.resolve(workspaceRoot, skillPath);
37
+ return existsSync(path.join(absolutePath, "SKILL.md"))
38
+ ? path.dirname(absolutePath)
39
+ : absolutePath;
40
+ })));
41
+ return relativizeDeepAgentSkillSourcePaths(workspaceRoot, sourceRoots) ?? sourceRoots;
42
+ }
@@ -4,9 +4,10 @@ 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";
7
+ import { appendToolRecoveryInstruction, extractVisibleOutput, tryParseJson } from "../../parsing/output-parsing.js";
8
8
  import { salvageJsonToolCalls } from "../../parsing/output-tool-args.js";
9
9
  import { isEmptyFinalAiMessageError } from "../resilience.js";
10
+ import { AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION } from "../../prompts/runtime-prompts.js";
10
11
  function readBindingExecutionParams(binding) {
11
12
  const params = binding.execution?.params ?? binding.deepAgentParams ?? binding.langchainAgentParams;
12
13
  return {
@@ -36,6 +37,29 @@ function isDelegationOnlyBinding(binding) {
36
37
  function hasTaskDelegationEvidence(executedToolResults) {
37
38
  return executedToolResults.some((item) => item.toolName === "task");
38
39
  }
40
+ function hasIncompleteTodos(value) {
41
+ if (!Array.isArray(value)) {
42
+ return false;
43
+ }
44
+ return value.some((todo) => {
45
+ if (typeof todo !== "object" || todo === null) {
46
+ return false;
47
+ }
48
+ const status = typeof todo.status === "string"
49
+ ? todo.status.trim().toLowerCase()
50
+ : "";
51
+ return status === "pending" || status === "in_progress";
52
+ });
53
+ }
54
+ function hasIncompleteUpstreamPlan(value) {
55
+ if (typeof value !== "object" || value === null) {
56
+ return false;
57
+ }
58
+ const typed = value;
59
+ return hasIncompleteTodos(typed.todos)
60
+ || hasIncompleteTodos(typed.stateSnapshot?.todos)
61
+ || hasIncompleteTodos(typed.metadata?.stateSnapshot?.todos);
62
+ }
39
63
  function hasNativeTaskDelegationIntent(value) {
40
64
  if (typeof value !== "object" || value === null) {
41
65
  return false;
@@ -285,11 +309,25 @@ export async function executeRequestInvocation(options) {
285
309
  toolRuntimeContext: invokeOptions.toolRuntimeContext,
286
310
  });
287
311
  }
288
- const result = localOrUpstreamInvocation.result;
312
+ let result = localOrUpstreamInvocation.result;
289
313
  const executedToolResults = [...localOrUpstreamInvocation.executedToolResults];
290
314
  if (!result) {
291
315
  throw new Error("Agent invocation returned no result");
292
316
  }
317
+ if (options.resumePayload === undefined
318
+ && options.binding.harnessRuntime.executionContract?.requiresPlan === true
319
+ && hasIncompleteUpstreamPlan(result)
320
+ && !extractVisibleOutput(result).trim()) {
321
+ const messages = Array.isArray(result.messages)
322
+ ? result.messages
323
+ : undefined;
324
+ const recoveryBase = messages ? { messages } : request;
325
+ const recoveredRequest = appendToolRecoveryInstruction(recoveryBase, AUTONOMOUS_INVESTIGATION_RECOVERY_INSTRUCTION);
326
+ const recoveredInvocation = await invokeOnce(recoveredRequest);
327
+ localOrUpstreamInvocation = recoveredInvocation;
328
+ result = recoveredInvocation.result;
329
+ executedToolResults.splice(0, executedToolResults.length, ...recoveredInvocation.executedToolResults);
330
+ }
293
331
  try {
294
332
  return finalizeRequestResult({
295
333
  bindingAgentId: options.binding.agent.id,
@@ -57,6 +57,17 @@ function looksLikeToolBlocker(value) {
57
57
  || /invalid tool call/iu.test(normalized)
58
58
  || /tool call.*schema/iu.test(normalized);
59
59
  }
60
+ function isSubstantiveFinalOutput(value) {
61
+ const normalized = sanitizeVisibleText(value).trim();
62
+ if (normalized.length < 80) {
63
+ return false;
64
+ }
65
+ return !looksLikeLeakedToolCallText(normalized)
66
+ && !looksLikeToolBlocker(normalized)
67
+ && !looksLikeClarificationQuestion(normalized)
68
+ && !looksLikeNonEvidenceApology(normalized)
69
+ && !isLowSignalStructuredCompletion(normalized);
70
+ }
60
71
  function normalizeToolOutputText(output) {
61
72
  const directText = typeof output === "string"
62
73
  ? sanitizeVisibleText(output).trim()
@@ -343,18 +354,30 @@ export function finalizeRequestResult(params) {
343
354
  && !hasFinalMessageToolCalls(result)) {
344
355
  throw new Error("empty_final_output");
345
356
  }
357
+ const stateSnapshot = buildStateSnapshot(result);
358
+ const hasIncompleteRequiredPlan = binding?.harnessRuntime?.executionContract?.requiresPlan === true
359
+ && hasIncompleteStateSnapshotPlan(stateSnapshot);
346
360
  const serializedResult = JSON.stringify(result, null, 2);
347
- const output = resolveDeterministicFinalOutput({
361
+ let output = resolveDeterministicFinalOutput({
348
362
  visibleOutput,
349
363
  toolFallback,
350
364
  executedToolResults: allExecutedToolResults,
351
365
  })
352
366
  || (containsLikelySkillDocument(result) ? "" : serializedResult);
367
+ const preliminaryTerminalStatus = readTerminalExecutionStatus(output);
368
+ const hasMissingRequiredFinalAnswer = binding?.harnessRuntime?.executionContract?.requiresPlan === true
369
+ && !visibleOutput
370
+ && !preliminaryTerminalStatus
371
+ && allExecutedToolResults.some((toolResult) => toolResult.isError !== true && toolResult.toolName !== "write_todos" && toolResult.toolName !== "read_todos");
372
+ if (hasIncompleteRequiredPlan && !visibleOutput) {
373
+ output = "runtime_error=Agent ended while required plan still had unfinished work.";
374
+ }
375
+ else if (hasMissingRequiredFinalAnswer) {
376
+ output = "runtime_error=Agent ended after tool execution without producing a final answer.";
377
+ }
353
378
  const finalMessageText = sanitizeVisibleText(output);
354
379
  const terminalStatus = structuredTerminalStatus ?? readTerminalExecutionStatus(finalMessageText);
355
- const stateSnapshot = buildStateSnapshot(result);
356
- const hasIncompleteRequiredPlan = binding?.harnessRuntime?.executionContract?.requiresPlan === true
357
- && hasIncompleteStateSnapshotPlan(stateSnapshot);
380
+ const hasSubstantiveFinalOutput = Boolean(visibleOutput) && isSubstantiveFinalOutput(finalMessageText);
358
381
  const hasTerminalToolBlocker = looksLikeToolBlocker(finalMessageText);
359
382
  const memoryCandidates = allExecutedToolResults.flatMap((toolResult) => toolResult.memoryCandidates ?? []);
360
383
  return {
@@ -363,7 +386,7 @@ export function finalizeRequestResult(params) {
363
386
  agentId: bindingAgentId,
364
387
  state: Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0
365
388
  ? "waiting_for_approval"
366
- : hasIncompleteRequiredPlan
389
+ : (hasIncompleteRequiredPlan && !hasSubstantiveFinalOutput) || hasMissingRequiredFinalAnswer
367
390
  ? "failed"
368
391
  : hasTerminalToolBlocker
369
392
  ? "failed"
@@ -23,6 +23,7 @@ const PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER = [
23
23
  "If a TOOL_RESULT is already present for the requested work, do not repeat that tool call; answer normally.",
24
24
  'Shape: {"name":"tool_name","arguments":{}}',
25
25
  ].join("\n");
26
+ const NO_THINK_CONTROL_TOKEN = "/no_think";
26
27
  function readModelText(value) {
27
28
  if (typeof value === "string") {
28
29
  return value.trim();
@@ -455,16 +456,21 @@ function formatBoundToolInstruction(tool) {
455
456
  `Arguments JSON schema: ${JSON.stringify(schema)}`,
456
457
  ].filter(Boolean).join("\n");
457
458
  }
458
- function withPromptedJsonToolPrompt(input, tools) {
459
+ function withPromptedJsonToolPrompt(input, tools, options = {}) {
459
460
  const toolInstructions = tools.map((tool) => formatBoundToolInstruction(tool)).filter((value) => Boolean(value));
460
461
  if (toolInstructions.length === 0) {
461
462
  return stringifyNodeLlamaCppInput(input);
462
463
  }
463
464
  const systemContent = `${NODE_LLAMA_CPP_TOOL_CALL_INSTRUCTION}\n\n${toolInstructions.join("\n\n")}`;
464
465
  const prompt = stringifyNodeLlamaCppInput(input);
465
- return [systemContent, prompt, PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER].filter(Boolean).join("\n\n");
466
+ return [
467
+ options.suppressThinking ? NO_THINK_CONTROL_TOKEN : "",
468
+ systemContent,
469
+ prompt,
470
+ PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER,
471
+ ].filter(Boolean).join("\n\n");
466
472
  }
467
- function createPromptedJsonToolBindableModel(model, boundTools = []) {
473
+ function createPromptedJsonToolBindableModel(model, boundTools = [], options = {}) {
468
474
  return new Proxy(model, {
469
475
  has(target, prop) {
470
476
  if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "withConfig") {
@@ -474,11 +480,11 @@ function createPromptedJsonToolBindableModel(model, boundTools = []) {
474
480
  },
475
481
  get(target, prop, receiver) {
476
482
  if (prop === "bindTools") {
477
- return (tools) => createPromptedJsonToolBindableModel(target, tools);
483
+ return (tools) => createPromptedJsonToolBindableModel(target, tools, options);
478
484
  }
479
485
  if (prop === "invoke") {
480
486
  return async (input, config) => {
481
- const rawResult = await target.invoke(boundTools.length > 0 ? withPromptedJsonToolPrompt(input, boundTools) : input, config);
487
+ const rawResult = await target.invoke(boundTools.length > 0 ? withPromptedJsonToolPrompt(input, boundTools, options) : input, config);
482
488
  if (boundTools.length === 0) {
483
489
  return rawResult;
484
490
  }
@@ -510,7 +516,7 @@ function createPromptedJsonToolBindableModel(model, boundTools = []) {
510
516
  };
511
517
  }
512
518
  if (prop === "withConfig" && typeof target.withConfig === "function") {
513
- return (config) => createPromptedJsonToolBindableModel(target.withConfig(config), boundTools);
519
+ return (config) => createPromptedJsonToolBindableModel(target.withConfig(config), boundTools, options);
514
520
  }
515
521
  const member = Reflect.get(target, prop, receiver);
516
522
  return typeof member === "function" ? member.bind(target) : member;
@@ -559,7 +565,7 @@ export async function createResolvedModel(model, modelResolver) {
559
565
  const { toolCallingMode, ...init } = model.init ?? {};
560
566
  const resolved = new ChatOllama({ model: model.model, ...init });
561
567
  if (toolCallingMode === "prompted-json") {
562
- return createPromptedJsonToolBindableModel(resolved);
568
+ return createPromptedJsonToolBindableModel(resolved, [], { suppressThinking: init.think === false });
563
569
  }
564
570
  return createProviderToolMessageCompatModel(resolved);
565
571
  }
@@ -42,6 +42,9 @@ export function isEmptyFinalAiMessageError(error) {
42
42
  const message = error instanceof Error ? error.message : String(error);
43
43
  return message.toLowerCase().startsWith("empty_final_ai_message:");
44
44
  }
45
+ function isRuntimeOperationTimeoutError(error) {
46
+ return typeof error === "object" && error !== null && error.name === "RuntimeOperationTimeoutError";
47
+ }
45
48
  function isRetryableHttpStatus(status) {
46
49
  return typeof status === "number" && Number.isInteger(status) && status >= 500 && status <= 599;
47
50
  }
@@ -84,6 +87,9 @@ export function resolveProviderRetryPolicy(binding) {
84
87
  };
85
88
  }
86
89
  export function isRetryableProviderError(binding, error) {
90
+ if (isRuntimeOperationTimeoutError(error)) {
91
+ return false;
92
+ }
87
93
  if (isEmptyFinalAiMessageError(error)) {
88
94
  return true;
89
95
  }
@@ -2,7 +2,7 @@ import type { CompiledAgentBinding, MessageContent, RequestResult, RuntimeAdapte
2
2
  import { type RuntimeStreamChunk } from "./parsing/stream-event-parsing.js";
3
3
  import { AGENT_INTERRUPT_SENTINEL_PREFIX, buildDeepAgentCreateParams, buildLangChainCreateParams, DEFAULT_DEEPAGENT_RECURSION_LIMIT, materializeModelExposedBuiltinMiddlewareTools, resolveLangChainInvocationConfig, resolveRunnableCheckpointer, resolveRunnableInterruptOn } from "./agent-runtime-assembly.js";
4
4
  import { RuntimeOperationTimeoutError } from "./adapter/runtime-shell.js";
5
- export { materializeDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourcePaths, relativizeDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
5
+ export { materializeDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourceRootPaths, relativizeDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
6
6
  export { buildAuthOmittingFetch, normalizeOpenAICompatibleInit } from "./adapter/compat/openai-compatible.js";
7
7
  export { buildToolNameMapping, createModelFacingToolNameCandidates, createModelFacingToolNameLookupCandidates, resolveModelFacingToolName, sanitizeToolNameForModel, } from "./adapter/tool/tool-name-mapping.js";
8
8
  export { computeRemainingTimeoutMs, isRetryableProviderError, resolveBindingTimeout, resolveProviderRetryPolicy, resolveStreamIdleTimeout, resolveTimeoutMs, } from "./adapter/resilience.js";
@@ -57,6 +57,7 @@ export declare class AgentRuntimeAdapter {
57
57
  files?: Record<string, unknown>;
58
58
  memoryContext?: string;
59
59
  }): Promise<RequestResult>;
60
+ private tryDelegateWithCompactRouter;
60
61
  stream(binding: CompiledAgentBinding, input: MessageContent, sessionId: string, history?: TranscriptMessage[], options?: {
61
62
  context?: Record<string, unknown>;
62
63
  state?: Record<string, unknown>;
@@ -1,9 +1,10 @@
1
1
  import path from "node:path";
2
2
  import { createAsyncSubAgentMiddleware, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSummarizationMiddleware, createSubAgentMiddleware, FilesystemBackend, StateBackend, } from "deepagents";
3
3
  import { createAgent, humanInTheLoopMiddleware, todoListMiddleware } from "langchain";
4
- import { wrapResolvedModel, } from "./parsing/output-parsing.js";
4
+ import { sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
5
+ import { extractMessageText } from "../utils/message-content.js";
5
6
  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
- import { resolveDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
7
+ import { resolveDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourceRootPaths, } from "./adapter/compat/deepagent-compat.js";
7
8
  import { EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION } from "./prompts/runtime-prompts.js";
8
9
  import { buildToolNameMapping, } from "./adapter/tool/tool-name-mapping.js";
9
10
  import { executeRequestInvocation } from "./adapter/flow/invocation-flow.js";
@@ -17,11 +18,11 @@ import { resolveAdapterTools } from "./adapter/tool-resolution.js";
17
18
  import { resolveRuntimeStreamExecutionContext, } from "./adapter/flow/execution-context.js";
18
19
  import { isRetryableProviderError } from "./adapter/resilience.js";
19
20
  import { UPSTREAM_REQUEST_CONFIG_KEY, UPSTREAM_SESSION_CONFIG_KEY } from "./adapter/upstream-configurable-keys.js";
20
- export { materializeDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourcePaths, relativizeDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
21
+ export { materializeDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourcePaths, resolveDeepAgentSkillSourceRootPaths, relativizeDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
21
22
  export { buildAuthOmittingFetch, normalizeOpenAICompatibleInit } from "./adapter/compat/openai-compatible.js";
22
23
  export { buildToolNameMapping, createModelFacingToolNameCandidates, createModelFacingToolNameLookupCandidates, resolveModelFacingToolName, sanitizeToolNameForModel, } from "./adapter/tool/tool-name-mapping.js";
23
24
  export { computeRemainingTimeoutMs, isRetryableProviderError, resolveBindingTimeout, resolveProviderRetryPolicy, resolveStreamIdleTimeout, resolveTimeoutMs, } from "./adapter/resilience.js";
24
- import { getBindingAdapterKind, getBindingBuiltinToolsConfig, getBindingDeepAgentSubagents, getBindingExecutionParams, getBindingExecutionKind, getBindingFilesystemConfig, getBindingMemorySources, getBindingPrimaryModel, getBindingSkills, getBindingToolCount, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
25
+ import { getBindingAdapterKind, getBindingBuiltinToolsConfig, getBindingDeepAgentSubagents, getBindingExecutionParams, getBindingExecutionKind, getBindingFilesystemConfig, getBindingMemorySources, getBindingPrimaryModel, getBindingSkills, getBindingSubagents, getBindingToolCount, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
25
26
  class DelegatedExecutionNoToolEvidenceError extends Error {
26
27
  constructor(agentId) {
27
28
  super(`Delegated agent ${agentId} completed without tool execution evidence.`);
@@ -39,6 +40,77 @@ function hasDelegatedExecutionToolEvidence(result) {
39
40
  function shouldUseConfigurableDeepAgentAssembly(binding) {
40
41
  return getBindingExecutionKind(binding) === "deepagent";
41
42
  }
43
+ function readModelText(value) {
44
+ if (typeof value === "string") {
45
+ return value.trim();
46
+ }
47
+ if (typeof value !== "object" || value === null) {
48
+ return "";
49
+ }
50
+ const content = value.content;
51
+ if (typeof content === "string") {
52
+ return content.trim();
53
+ }
54
+ if (Array.isArray(content)) {
55
+ return content
56
+ .map((part) => {
57
+ if (typeof part === "string")
58
+ return part;
59
+ if (typeof part === "object" && part !== null && typeof part.text === "string") {
60
+ return part.text;
61
+ }
62
+ return "";
63
+ })
64
+ .join("")
65
+ .trim();
66
+ }
67
+ return "";
68
+ }
69
+ function parseFirstJsonObject(value) {
70
+ let depth = 0;
71
+ let start = -1;
72
+ let inString = false;
73
+ let escaping = false;
74
+ for (let index = 0; index < value.length; index += 1) {
75
+ const char = value[index];
76
+ if (inString) {
77
+ if (escaping) {
78
+ escaping = false;
79
+ }
80
+ else if (char === "\\") {
81
+ escaping = true;
82
+ }
83
+ else if (char === "\"") {
84
+ inString = false;
85
+ }
86
+ continue;
87
+ }
88
+ if (char === "\"") {
89
+ inString = true;
90
+ continue;
91
+ }
92
+ if (char === "{") {
93
+ if (depth === 0) {
94
+ start = index;
95
+ }
96
+ depth += 1;
97
+ continue;
98
+ }
99
+ if (char === "}" && depth > 0) {
100
+ depth -= 1;
101
+ if (depth === 0 && start >= 0) {
102
+ return tryParseJson(value.slice(start, index + 1));
103
+ }
104
+ }
105
+ }
106
+ return null;
107
+ }
108
+ function isDelegationOnlyDeepAgentBinding(binding) {
109
+ return isDeepAgentBinding(binding)
110
+ && getBindingSubagents(binding).length > 0
111
+ && getBindingPrimaryTools(binding).length === 0
112
+ && getBindingSkills(binding).length === 0;
113
+ }
42
114
  export class AgentRuntimeAdapter {
43
115
  options;
44
116
  modelCache = new Map();
@@ -437,13 +509,17 @@ export class AgentRuntimeAdapter {
437
509
  ? resolveRunnableCheckpointer(this.options, binding)
438
510
  : undefined;
439
511
  const resolvedStore = shouldAttachDeepAgentStore(binding) ? this.options.storeResolver?.(binding) : undefined;
440
- const resolvedBackend = shouldAttachDeepAgentBackend(binding) ? this.options.backendResolver?.(binding) : undefined;
441
512
  const resolvedSkills = resolveDeepAgentSkillSourcePaths({
442
513
  workspaceRoot: binding.harnessRuntime.workspaceRoot,
443
514
  runtimeRoot: binding.harnessRuntime.runtimeRoot,
444
515
  ownerId: binding.agent.id,
445
516
  skillPaths: getBindingSkills(binding),
446
517
  }) ?? [];
518
+ const resolvedBackend = shouldAttachDeepAgentBackend(binding)
519
+ ? (this.options.backendResolver?.(binding) ?? (resolvedSkills.length > 0 ? this.resolveFilesystemBackend(binding, {
520
+ sessionId: options.sessionId ?? options.legacySessionId,
521
+ }) : undefined))
522
+ : undefined;
447
523
  if (shouldUseConfigurableDeepAgentAssembly(binding)) {
448
524
  return this.createConfigurableDeepAgentRunnable(binding, {
449
525
  resolvedModel,
@@ -467,7 +543,15 @@ export class AgentRuntimeAdapter {
467
543
  const subagents = inlineSubagents;
468
544
  const middleware = [
469
545
  ...(builtinTools.todos === false ? [] : [todoListMiddleware()]),
470
- ...(input.resolvedSkills.length > 0 ? [createSkillsMiddleware({ backend, sources: input.resolvedSkills })] : []),
546
+ ...(input.resolvedSkills.length > 0 ? [createSkillsMiddleware({
547
+ backend,
548
+ sources: resolveDeepAgentSkillSourceRootPaths({
549
+ workspaceRoot: binding.harnessRuntime.workspaceRoot,
550
+ runtimeRoot: binding.harnessRuntime.runtimeRoot,
551
+ ownerId: binding.agent.id,
552
+ skillPaths: input.resolvedSkills,
553
+ }) ?? input.resolvedSkills,
554
+ })] : []),
471
555
  ...(builtinTools.filesystem === false ? [] : [createFilesystemMiddleware({ backend })]),
472
556
  ...(subagents.length > 0
473
557
  ? [createSubAgentMiddleware({
@@ -557,6 +641,36 @@ export class AgentRuntimeAdapter {
557
641
  },
558
642
  };
559
643
  }
644
+ const compactDelegation = await this.tryDelegateWithCompactRouter(binding, input, sessionId, requestId, {
645
+ ...options,
646
+ sessionId,
647
+ requestId,
648
+ });
649
+ if (compactDelegation) {
650
+ const output = typeof compactDelegation.toolOutput === "string"
651
+ ? compactDelegation.toolOutput
652
+ : JSON.stringify(compactDelegation.toolOutput);
653
+ const delegatedToolResults = Array.isArray(compactDelegation.delegatedResult?.metadata?.executedToolResults)
654
+ ? compactDelegation.delegatedResult.metadata.executedToolResults
655
+ : [];
656
+ return {
657
+ sessionId,
658
+ requestId,
659
+ agentId: binding.agent.id,
660
+ state: compactDelegation.delegatedResult?.state ?? "completed",
661
+ output: sanitizeVisibleText(output),
662
+ finalMessageText: sanitizeVisibleText(output),
663
+ metadata: {
664
+ executedToolResults: [
665
+ {
666
+ toolName: "task",
667
+ output: compactDelegation.toolOutput,
668
+ },
669
+ ...delegatedToolResults,
670
+ ],
671
+ },
672
+ };
673
+ }
560
674
  const callRuntime = async (activeBinding, activeRequest) => {
561
675
  return this.invokeWithProviderRetry(activeBinding, async () => {
562
676
  const runnable = await this.create(activeBinding, { sessionId });
@@ -612,6 +726,74 @@ export class AgentRuntimeAdapter {
612
726
  return invokeRequest();
613
727
  }
614
728
  }
729
+ async tryDelegateWithCompactRouter(binding, input, sessionId, requestId, options = {}) {
730
+ if (!isDelegationOnlyDeepAgentBinding(binding)) {
731
+ return null;
732
+ }
733
+ if (!this.options.bindingResolver) {
734
+ return null;
735
+ }
736
+ const primaryModel = getBindingPrimaryModel(binding);
737
+ if (!primaryModel) {
738
+ return null;
739
+ }
740
+ const requestText = extractMessageText(input).trim();
741
+ if (!requestText) {
742
+ return null;
743
+ }
744
+ const subagents = getBindingSubagents(binding);
745
+ const subagentCatalog = subagents
746
+ .map((subagent) => `- ${subagent.name}: ${subagent.description}`)
747
+ .join("\n");
748
+ const prompt = [
749
+ primaryModel.init?.think === false ? "/no_think" : "",
750
+ "You are selecting a subagent for a delegation-only agent.",
751
+ "Choose exactly one listed subagent when it can responsibly handle the request.",
752
+ "Return only JSON with this shape:",
753
+ "{\"subagent_type\":\"<listed subagent name>\"}",
754
+ "If no listed subagent can handle the request, return only:",
755
+ "{\"status\":\"refused\",\"reason\":\"No configured subagent can handle the request.\"}",
756
+ "Available subagents:",
757
+ subagentCatalog,
758
+ "User request:",
759
+ requestText,
760
+ ].filter(Boolean).join("\n\n");
761
+ const model = await this.resolveModel(primaryModel);
762
+ if (typeof model.invoke !== "function") {
763
+ return null;
764
+ }
765
+ const raw = await this.invokeWithProviderRetry(binding, () => this.withTimeout(() => model.invoke(prompt, resolveLangChainInvocationConfig(binding, {
766
+ sessionId,
767
+ requestId,
768
+ context: options.context,
769
+ toolRuntimeContext: this.buildFunctionToolRuntimeContext(binding, {
770
+ ...options,
771
+ sessionId,
772
+ requestId,
773
+ }),
774
+ })), resolveBindingTimeout(binding), "delegation router invoke", "invoke"));
775
+ const parsed = parseFirstJsonObject(readModelText(raw));
776
+ if (typeof parsed !== "object" || parsed === null) {
777
+ return null;
778
+ }
779
+ const subagentType = typeof parsed.subagent_type === "string"
780
+ ? parsed.subagent_type
781
+ : "";
782
+ if (!subagents.some((subagent) => subagent.name === subagentType)) {
783
+ return null;
784
+ }
785
+ const selectedBinding = this.options.bindingResolver(subagentType);
786
+ if (!selectedBinding) {
787
+ return null;
788
+ }
789
+ const delegatedResult = await this.invoke(selectedBinding, requestText, sessionId, `${requestId}:${subagentType}`, undefined, [], {
790
+ context: options.context,
791
+ state: options.state,
792
+ files: options.files,
793
+ memoryContext: options.memoryContext,
794
+ });
795
+ return { toolOutput: delegatedResult.output, delegatedResult };
796
+ }
615
797
  async *stream(binding, input, sessionId, history = [], options = {}) {
616
798
  const directListing = await this.tryHandleDirectWorkspaceListing(binding, input, {
617
799
  ...options,
@@ -630,6 +812,25 @@ export class AgentRuntimeAdapter {
630
812
  };
631
813
  return;
632
814
  }
815
+ const compactDelegation = await this.tryDelegateWithCompactRouter(binding, input, sessionId, options.requestId ?? sessionId, {
816
+ ...options,
817
+ sessionId,
818
+ requestId: options.requestId,
819
+ });
820
+ if (compactDelegation) {
821
+ yield {
822
+ kind: "tool-result",
823
+ toolName: "task",
824
+ output: compactDelegation.toolOutput,
825
+ };
826
+ yield {
827
+ kind: "content",
828
+ content: typeof compactDelegation.toolOutput === "string"
829
+ ? compactDelegation.toolOutput
830
+ : JSON.stringify(compactDelegation.toolOutput),
831
+ };
832
+ return;
833
+ }
633
834
  const invokeTimeoutMs = resolveBindingTimeout(binding);
634
835
  const streamIdleTimeoutMs = resolveStreamIdleTimeout(binding);
635
836
  const streamDeadlineAt = invokeTimeoutMs ? Date.now() + invokeTimeoutMs : undefined;
@@ -8,6 +8,7 @@ export declare function materializeModelExposedBuiltinMiddlewareTools(input: {
8
8
  explicitToolNames?: string[];
9
9
  modelExposed?: boolean | string[];
10
10
  }): unknown[];
11
+ export declare function buildRuntimeTemporalContext(now?: Date): string;
11
12
  export declare function buildDeepAgentSystemPromptWithCapabilityHierarchy(input: {
12
13
  systemPrompt?: unknown;
13
14
  subagents: Array<Pick<UpstreamSubagentConfig, "name" | "description"> | Pick<CompiledAsyncSubAgent, "name" | "description">>;
@@ -53,6 +53,21 @@ function buildSkillCatalog(skillPaths) {
53
53
  };
54
54
  });
55
55
  }
56
+ export function buildRuntimeTemporalContext(now = new Date()) {
57
+ const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone || "system local time";
58
+ return [
59
+ "Runtime temporal context:",
60
+ `- Current date/time: ${now.toISOString()}`,
61
+ `- Local time zone: ${timeZone}`,
62
+ "Use the runtime date/time above as authoritative for interpreting current, latest, recent, today, tomorrow, yesterday, and tool-returned dates.",
63
+ "Do not reject tool evidence as future-dated solely because it is newer than the model's training data.",
64
+ ].join("\n");
65
+ }
66
+ function appendRuntimeTemporalContext(systemPrompt) {
67
+ return [typeof systemPrompt === "string" ? systemPrompt : undefined, buildRuntimeTemporalContext()]
68
+ .filter((part) => typeof part === "string" && part.length > 0)
69
+ .join("\n\n");
70
+ }
56
71
  export function buildDeepAgentSystemPromptWithCapabilityHierarchy(input) {
57
72
  const basePrompt = typeof input.systemPrompt === "string" ? input.systemPrompt : undefined;
58
73
  const skills = buildSkillCatalog(input.skills ?? []);
@@ -122,7 +137,7 @@ export function buildDeepAgentSystemPromptWithCapabilityHierarchy(input) {
122
137
  : []),
123
138
  "",
124
139
  ].join("\n");
125
- return [basePrompt, catalogPrompt].filter((part) => typeof part === "string" && part.length > 0).join("\n\n");
140
+ return appendRuntimeTemporalContext([basePrompt, catalogPrompt].filter((part) => typeof part === "string" && part.length > 0).join("\n\n"));
126
141
  }
127
142
  export const buildDeepAgentSystemPromptWithSubagentCatalog = buildDeepAgentSystemPromptWithCapabilityHierarchy;
128
143
  export function resolveRunnableCheckpointer(options, binding) {
@@ -175,7 +190,7 @@ export function buildLangChainCreateParams(input) {
175
190
  ...(legacyPassthrough ?? {}),
176
191
  ...(langchainPassthrough ?? {}),
177
192
  ...(input.passthroughOverride ?? {}),
178
- systemPrompt: input.systemPromptOverride ?? executionParams.systemPrompt,
193
+ systemPrompt: appendRuntimeTemporalContext(input.systemPromptOverride ?? executionParams.systemPrompt),
179
194
  model: input.resolvedModel,
180
195
  tools: input.resolvedTools,
181
196
  middleware: input.resolvedMiddleware,
@@ -517,6 +517,15 @@ function createProfileStepCommentary(step) {
517
517
  if (step.kind === "memory") {
518
518
  return `Checking memory ${name}.`;
519
519
  }
520
+ if (step.kind === "agent" && step.action === "invoke") {
521
+ return "Running model invocation.";
522
+ }
523
+ if (step.kind === "agent" && step.action === "start") {
524
+ return "Starting runtime stream.";
525
+ }
526
+ if (step.kind === "agent" && step.action === "startup") {
527
+ return `Preparing ${name}.`;
528
+ }
520
529
  return null;
521
530
  }
522
531
  function isOpenAICompatibleStreamingCompatibilityError(binding, error) {
@@ -1083,8 +1092,9 @@ export async function* streamHarnessRun(options) {
1083
1092
  visibleOutput: assistantOutput,
1084
1093
  executedToolResults,
1085
1094
  });
1086
- if (!assistantOutput && sawSuccessfulToolResult && deterministicToolEvidenceOutput) {
1087
- const terminalStructuredStatus = readTerminalExecutionStatus(deterministicToolEvidenceOutput);
1095
+ const terminalStructuredStatus = readTerminalExecutionStatus(deterministicToolEvidenceOutput);
1096
+ const canUseDeterministicToolEvidenceOutput = !currentPlanState || !planStateHasActiveItems(currentPlanState) || Boolean(terminalStructuredStatus);
1097
+ if (!assistantOutput && sawSuccessfulToolResult && deterministicToolEvidenceOutput && canUseDeterministicToolEvidenceOutput) {
1088
1098
  if (terminalStructuredStatus && currentPlanState && planStateHasActiveItems(currentPlanState)) {
1089
1099
  const reconciledPlanState = reconcilePlanStateToTerminalStatus(currentPlanState, mapTerminalStatusToPlanItemStatus(terminalStructuredStatus), new Date().toISOString());
1090
1100
  const signature = buildPlanStateSignature(reconciledPlanState);
@@ -59,6 +59,9 @@ function consumeLeadingFunctionLikeToolCall(value) {
59
59
  function stripVisibleFunctionLikeToolCallText(value) {
60
60
  let remaining = value.trim();
61
61
  let removedLeadingCall = false;
62
+ if (salvageJsonToolCalls(remaining).length > 0) {
63
+ return "";
64
+ }
62
65
  while (remaining.length > 0) {
63
66
  const consumed = consumeLeadingFunctionLikeToolCall(remaining);
64
67
  if (!consumed) {
@@ -84,6 +87,7 @@ function stripVisibleFunctionLikeToolCallText(value) {
84
87
  }
85
88
  export function sanitizeVisibleText(value) {
86
89
  return stripVisibleFunctionLikeToolCallText(value
90
+ .replace(/<agent_memory>[\s\S]*?<\/agent_memory>/giu, "")
87
91
  .replace(/[A-Za-z0-9_]*Middleware\.after_model/g, "")
88
92
  .replace(/todoListMiddleware\.after_model/g, "")
89
93
  .replace(/__end__+/g, "")
@@ -205,6 +205,43 @@ function closeJsonContainerSuffix(value) {
205
205
  }
206
206
  return `${trimmed}${stack.reverse().join("")}`;
207
207
  }
208
+ function normalizePythonLikeJson(value) {
209
+ const trimmed = value.trim();
210
+ if (!trimmed.includes("'")) {
211
+ return null;
212
+ }
213
+ let output = "";
214
+ let inSingle = false;
215
+ let inDouble = false;
216
+ let escaping = false;
217
+ for (const char of trimmed) {
218
+ if (escaping) {
219
+ output += char;
220
+ escaping = false;
221
+ continue;
222
+ }
223
+ if (char === "\\") {
224
+ output += char;
225
+ escaping = true;
226
+ continue;
227
+ }
228
+ if (char === "\"" && !inSingle) {
229
+ inDouble = !inDouble;
230
+ output += char;
231
+ continue;
232
+ }
233
+ if (char === "'" && !inDouble) {
234
+ inSingle = !inSingle;
235
+ output += "\"";
236
+ continue;
237
+ }
238
+ output += char;
239
+ }
240
+ if (inSingle || inDouble) {
241
+ return null;
242
+ }
243
+ return output;
244
+ }
208
245
  export function salvageToolArgs(value) {
209
246
  if (typeof value === "object" && value && !Array.isArray(value)) {
210
247
  return value;
@@ -255,6 +292,13 @@ export function salvageJsonToolCalls(value) {
255
292
  if (direct) {
256
293
  return direct;
257
294
  }
295
+ const pythonLike = normalizePythonLikeJson(trimmed);
296
+ if (pythonLike) {
297
+ const parsed = tryParseJson(pythonLike);
298
+ if (parsed) {
299
+ return parsed;
300
+ }
301
+ }
258
302
  const closed = closeJsonContainerSuffix(trimmed);
259
303
  if (closed) {
260
304
  const parsed = tryParseJson(closed);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.353",
3
+ "version": "0.0.355",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",