@botbotgo/agent-harness 0.0.440 → 0.0.442

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.440";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.442";
2
2
  export declare const AGENT_HARNESS_RELEASE_DATE = "2026-05-04";
@@ -1,2 +1,2 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.440";
1
+ export const AGENT_HARNESS_VERSION = "0.0.442";
2
2
  export const AGENT_HARNESS_RELEASE_DATE = "2026-05-04";
@@ -3,6 +3,7 @@ import { buildBindingToolExecutionContext } from "./execution-context.js";
3
3
  import { finalizeRequestResult } from "../invocation-result.js";
4
4
  import { invokeRuntimeWithLocalTools } from "./invoke-runtime.js";
5
5
  import { buildInvocationRequest } from "../model/invocation-request.js";
6
+ import { withPromptedJsonToolPolicy } from "../model/prompted-json-tool-policy.js";
6
7
  import { UPSTREAM_REQUEST_CONFIG_KEY, UPSTREAM_SESSION_CONFIG_KEY } from "../upstream-configurable-keys.js";
7
8
  import { appendToolRecoveryInstruction, extractVisibleOutput, tryParseJson } from "../../parsing/output-parsing.js";
8
9
  import { salvageJsonToolCalls } from "../../parsing/output-tool-args.js";
@@ -311,7 +312,7 @@ export async function executeRequestInvocation(options) {
311
312
  if (options.resumePayload === undefined
312
313
  && options.binding.harnessRuntime.executionContract?.requiresPlan === true
313
314
  && invokeOptions.suppressInitialRequiredPlanInstruction !== true) {
314
- request = appendToolRecoveryInstruction(request, INITIAL_REQUIRED_PLAN_INSTRUCTION);
315
+ request = withPromptedJsonToolPolicy(appendToolRecoveryInstruction(request, INITIAL_REQUIRED_PLAN_INSTRUCTION), "planning");
315
316
  }
316
317
  const { primaryTools, toolNameMapping, executableTools, defersToUpstreamHitlExecution, } = buildBindingToolExecutionContext({
317
318
  binding: options.binding,
@@ -368,7 +369,7 @@ export async function executeRequestInvocation(options) {
368
369
  ? result.messages
369
370
  : undefined;
370
371
  const recoveryBase = messages ? { messages } : request;
371
- const recoveredRequest = appendToolRecoveryInstruction(recoveryBase, WRITE_TODOS_REQUIRED_PLAN_INSTRUCTION);
372
+ const recoveredRequest = withPromptedJsonToolPolicy(appendToolRecoveryInstruction(recoveryBase, WRITE_TODOS_REQUIRED_PLAN_INSTRUCTION), "planning");
372
373
  const recoveredInvocation = await invokeOnce(recoveredRequest);
373
374
  result = recoveredInvocation.result;
374
375
  executedToolResults.splice(0, executedToolResults.length, ...recoveredInvocation.executedToolResults);
@@ -1,6 +1,7 @@
1
1
  import { extractVisibleOutput, isToolCallRecoveryFailure, isRetrySafeInvalidToolSelectionError, appendToolRecoveryInstruction, resolveMissingPlanRecoveryInstruction, resolveExecutionWithoutToolEvidenceTextInstruction, shouldValidateExecutionWithoutToolEvidence, resolveToolCallRecoveryInstruction, sanitizeVisibleText, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION, INVALID_TOOL_SELECTION_RECOVERY_INSTRUCTION, } from "../../parsing/output-parsing.js";
2
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
+ import { PROMPTED_JSON_TOOL_POLICY_KEY, withPromptedJsonToolPolicy } from "../model/prompted-json-tool-policy.js";
4
5
  import { buildRawModelMessages } from "../model/message-assembly.js";
5
6
  import { projectRuntimeStreamEvent, createStreamEventProjectionState } from "../stream-event-projection.js";
6
7
  import { projectTextStreamChunks } from "../stream-text-consumption.js";
@@ -293,6 +294,15 @@ function withSuppressedInitialRequiredPlanInstruction(options) {
293
294
  suppressInitialRequiredPlanInstruction: true,
294
295
  };
295
296
  }
297
+ function withNonPlanningEvidenceToolPolicy(options) {
298
+ return {
299
+ ...options,
300
+ state: {
301
+ ...(typeof options.state === "object" && options.state !== null ? options.state : {}),
302
+ [PROMPTED_JSON_TOOL_POLICY_KEY]: "nonPlanningEvidence",
303
+ },
304
+ };
305
+ }
296
306
  function hasParentLocalToolExecutionAfterDelegationFailure(originalEvidence, executedToolResults) {
297
307
  return originalEvidence.hasFailedTaskDelegation
298
308
  && executedToolResults.some((item) => item.toolName !== "task");
@@ -575,7 +585,7 @@ function projectLocalToolExecutionProfileChunks(executedToolResults, prefix) {
575
585
  export async function* streamRuntimeExecution(options) {
576
586
  let request = buildInvocationRequest(options.binding, options.history, options.input, options.runtimeOptions);
577
587
  if (requiresPlanEvidence(options.binding)) {
578
- request = appendToolRecoveryInstruction(request, buildInitialRequiredPlanInstruction(options.primaryTools));
588
+ request = withPromptedJsonToolPolicy(appendToolRecoveryInstruction(request, buildInitialRequiredPlanInstruction(options.primaryTools)), "planning");
579
589
  }
580
590
  let emittedUnsafeStreamSideEffects = false;
581
591
  const shouldProfile = options.runtimeOptions.profiling === true;
@@ -624,7 +634,11 @@ export async function* streamRuntimeExecution(options) {
624
634
  yield modelStreamStart.chunk;
625
635
  let stream;
626
636
  try {
627
- stream = await options.withTimeout(() => options.langChainStreamModel.stream(buildRawModelMessages(options.binding, options.getSystemPrompt(options.binding), options.history, options.input, options.runtimeOptions.memoryContext)), computeRemainingTimeoutMs(options.streamDeadlineAt, options.invokeTimeoutMs), "model stream start", "stream");
637
+ const rawStreamInput = buildRawModelMessages(options.binding, options.getSystemPrompt(options.binding), options.history, options.input, options.runtimeOptions.memoryContext);
638
+ const streamInput = requiresPlanEvidence(options.binding)
639
+ ? withPromptedJsonToolPolicy(rawStreamInput, "planning")
640
+ : rawStreamInput;
641
+ stream = await options.withTimeout(() => options.langChainStreamModel.stream(streamInput), computeRemainingTimeoutMs(options.streamDeadlineAt, options.invokeTimeoutMs), "model stream start", "stream");
628
642
  if (shouldProfile)
629
643
  yield finishProfileStep({
630
644
  id: "profile:agent:model-stream-start",
@@ -940,7 +954,7 @@ export async function* streamRuntimeExecution(options) {
940
954
  if (earlyStreamRecoveryInstruction) {
941
955
  const earlyRecoveryRuntimeOptions = earlyStreamRecoverySuppressInitialPlan
942
956
  ? {
943
- ...withSuppressedInitialRequiredPlanInstruction(options.runtimeOptions),
957
+ ...withNonPlanningEvidenceToolPolicy(withSuppressedInitialRequiredPlanInstruction(options.runtimeOptions)),
944
958
  externalPlanEvidence: true,
945
959
  ...(earlyStreamExternalPlanEvidenceTools && earlyStreamExternalPlanEvidenceTools.length > 0
946
960
  ? { externalPlanEvidenceTools: earlyStreamExternalPlanEvidenceTools }
@@ -1062,7 +1076,7 @@ export async function* streamRuntimeExecution(options) {
1062
1076
  if (retryInstruction) {
1063
1077
  const retryRuntimeOptions = retryInstruction === streamedIncompletePlanRecoveryInstruction
1064
1078
  || retryInstruction === streamedPrematurePlanCloseRecoveryInstruction
1065
- ? withSuppressedInitialRequiredPlanInstruction(options.runtimeOptions)
1079
+ ? withNonPlanningEvidenceToolPolicy(withSuppressedInitialRequiredPlanInstruction(options.runtimeOptions))
1066
1080
  : options.runtimeOptions;
1067
1081
  let retried;
1068
1082
  retried = await options.invoke(options.applyToolRecoveryInstruction(options.binding, retryInstruction), options.input, options.sessionId, options.runtimeOptions.requestId ?? options.sessionId, undefined, options.history, retryRuntimeOptions);
@@ -1299,7 +1313,7 @@ export async function* streamRuntimeExecution(options) {
1299
1313
  const invokeFallbackRuntimeOptions = effectiveInvokeFallbackRecoveryInstruction === invokeFallbackIncompletePlanRecoveryInstruction
1300
1314
  || effectiveInvokeFallbackRecoveryInstruction === invokeFallbackPlanWithoutEvidenceRecoveryInstruction
1301
1315
  ? {
1302
- ...withSuppressedInitialRequiredPlanInstruction(options.runtimeOptions),
1316
+ ...withNonPlanningEvidenceToolPolicy(withSuppressedInitialRequiredPlanInstruction(options.runtimeOptions)),
1303
1317
  externalPlanEvidence: true,
1304
1318
  }
1305
1319
  : options.runtimeOptions;
@@ -125,42 +125,6 @@ function buildContextualFollowUpInstruction(inputText, hasDurableMemory) {
125
125
  }
126
126
  return "Answer the user's current follow-up directly from the recalled context when it remains relevant. If recalled memory conflicts with the current system prompt, runtime policy, recovery instruction, available tool evidence, or the user's current request, ignore the recalled memory for this turn. If the current user turn corrects, revokes, deletes, or replaces recalled memory, treat the user's latest statement as newer than the recalled memory for this turn. Do not frame the reply as a fresh URL or page summary unless the current user turn explicitly includes a new resource to inspect.";
127
127
  }
128
- function isIncidentFollowUpTurn(inputText) {
129
- const normalized = inputText.trim().toLowerCase();
130
- if (!normalized || hasExplicitResourceReference(normalized)) {
131
- return false;
132
- }
133
- return /(the rca|deep research.*rca|root cause|go deeper|those issues|these issues|that issue|current incident)/i.test(normalized);
134
- }
135
- function findLastAssistantText(history) {
136
- for (let index = history.length - 1; index >= 0; index -= 1) {
137
- const message = history[index];
138
- if (message?.role !== "assistant") {
139
- continue;
140
- }
141
- const text = extractMessageText(message.content).trim();
142
- if (text) {
143
- return compactHistoryText(text);
144
- }
145
- }
146
- return undefined;
147
- }
148
- function buildIncidentFollowUpInstruction(history, inputText) {
149
- if (!isIncidentFollowUpTurn(inputText)) {
150
- return undefined;
151
- }
152
- const priorAssistantText = findLastAssistantText(history);
153
- if (!priorAssistantText) {
154
- return undefined;
155
- }
156
- return [
157
- "Treat the user's current turn as a follow-up on the current incident, not as a generic background-knowledge request.",
158
- "Use the immediately prior assistant findings below as the default incident context unless the user explicitly overrides it.",
159
- "",
160
- "Prior assistant findings:",
161
- priorAssistantText,
162
- ].join("\n");
163
- }
164
128
  export function buildSlashCommandSkillInstruction(binding, input) {
165
129
  const inputText = extractMessageText(input).trim();
166
130
  const match = inputText.match(/^\/([a-z0-9]+(?:-[a-z0-9]+)*)(?:\s+([\s\S]*))?$/i);
@@ -198,7 +162,6 @@ export function buildInvocationRequest(binding, history, input, options = {}) {
198
162
  suppressHistoryTurns: Boolean(memoryInstruction) && !hasExplicitResourceReference(inputText),
199
163
  });
200
164
  const contextualFollowUpInstruction = buildContextualFollowUpInstruction(inputText, Boolean(memoryInstruction));
201
- const incidentFollowUpInstruction = buildIncidentFollowUpInstruction(history, inputText);
202
165
  const conversationLanguage = resolveConversationLanguage(history, inputText);
203
166
  const languageInstruction = !inferMessageLanguage(inputText) && conversationLanguage
204
167
  ? {
@@ -212,7 +175,6 @@ export function buildInvocationRequest(binding, history, input, options = {}) {
212
175
  messages: [
213
176
  ...(memoryInstruction ? [{ role: "system", content: memoryInstruction }] : []),
214
177
  ...(contextualFollowUpInstruction ? [{ role: "system", content: contextualFollowUpInstruction }] : []),
215
- ...(incidentFollowUpInstruction ? [{ role: "system", content: incidentFollowUpInstruction }] : []),
216
178
  ...(languageInstruction ? [{ role: "system", content: languageInstruction }] : []),
217
179
  ...(userInvocableInstruction ? [{ role: "system", content: userInvocableInstruction }] : []),
218
180
  ...messages,
@@ -10,6 +10,7 @@ import { salvageJsonToolCalls } from "../../parsing/output-tool-args.js";
10
10
  import { normalizeModelFacingToolSchema } from "../tool/resolved-tool.js";
11
11
  import { normalizeOpenAICompatibleInit } from "../compat/openai-compatible.js";
12
12
  import { recordPromptedJsonToolCall } from "./prompted-json-tool-call-capture.js";
13
+ import { readPromptedJsonToolPolicy } from "./prompted-json-tool-policy.js";
13
14
  const NODE_LLAMA_CPP_TOOL_CALL_INSTRUCTION = [
14
15
  "Available tools are listed below.",
15
16
  "If you need a tool, respond with only one JSON object.",
@@ -342,8 +343,7 @@ function hasPriorPlanningToolResult(input) {
342
343
  || hasPriorToolResultForToolName(input, "call_read_todos");
343
344
  }
344
345
  function shouldLimitToolsToPlanning(input, boundTools) {
345
- const text = stringifyNodeLlamaCppInput(input);
346
- return text.includes("required visible planning contract")
346
+ return readPromptedJsonToolPolicy(input) === "planning"
347
347
  && !hasPriorToolResultForToolName(input, "write_todos")
348
348
  && !hasPriorToolResultForToolName(input, "tool_call_write_todos")
349
349
  && !hasPriorToolResultForToolName(input, "call_write_todos")
@@ -357,9 +357,7 @@ function selectPlanningToolsForTurn(input, boundTools) {
357
357
  return planningTools.length > 0 ? planningTools : boundTools;
358
358
  }
359
359
  function shouldLimitToolsToNonPlanningEvidence(input, boundTools) {
360
- const text = stringifyNodeLlamaCppInput(input);
361
- const hasNonPlanningEvidenceInstruction = /non[-\s]?planning (?:evidence )?tool call|non[-\s]?TODO evidence tool|Do not call write_todos|Do not call write_todos or read_todos/i.test(text);
362
- return (hasPriorPlanningToolResult(input) || hasNonPlanningEvidenceInstruction)
360
+ return (hasPriorPlanningToolResult(input) || readPromptedJsonToolPolicy(input) === "nonPlanningEvidence")
363
361
  && !hasPriorNonPlanningToolResult(input, boundTools);
364
362
  }
365
363
  function selectNonPlanningToolsForTurn(boundTools) {
@@ -0,0 +1,4 @@
1
+ export declare const PROMPTED_JSON_TOOL_POLICY_KEY = "__agentHarnessPromptedJsonToolPolicy";
2
+ export type PromptedJsonToolPolicy = "planning" | "nonPlanningEvidence";
3
+ export declare function readPromptedJsonToolPolicy(input: unknown): PromptedJsonToolPolicy | undefined;
4
+ export declare function withPromptedJsonToolPolicy<T>(input: T, policy: PromptedJsonToolPolicy): T;
@@ -0,0 +1,22 @@
1
+ export const PROMPTED_JSON_TOOL_POLICY_KEY = "__agentHarnessPromptedJsonToolPolicy";
2
+ export function readPromptedJsonToolPolicy(input) {
3
+ if (typeof input !== "object" || input === null) {
4
+ return undefined;
5
+ }
6
+ const value = input[PROMPTED_JSON_TOOL_POLICY_KEY];
7
+ return value === "planning" || value === "nonPlanningEvidence" ? value : undefined;
8
+ }
9
+ export function withPromptedJsonToolPolicy(input, policy) {
10
+ if (typeof input !== "object" || input === null) {
11
+ return input;
12
+ }
13
+ if (Array.isArray(input)) {
14
+ return Object.assign([...input], {
15
+ [PROMPTED_JSON_TOOL_POLICY_KEY]: policy,
16
+ });
17
+ }
18
+ return {
19
+ ...input,
20
+ [PROMPTED_JSON_TOOL_POLICY_KEY]: policy,
21
+ };
22
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.440",
3
+ "version": "0.0.442",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "license": "MIT",
6
6
  "type": "module",