@botbotgo/agent-harness 0.0.346 → 0.0.348

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.
@@ -13,8 +13,16 @@ const NODE_LLAMA_CPP_TOOL_CALL_INSTRUCTION = [
13
13
  "If you need a tool, respond with only one JSON object.",
14
14
  'Use this exact shape: {"name":"tool_name","arguments":{"key":"value"}}',
15
15
  "Do not add markdown, prose, or code fences unless the output is wrapped inside <tool_call>...</tool_call>.",
16
+ "When the latest user message explicitly requests a tool call or provides a tool-call JSON object, call that tool instead of answering locally.",
17
+ "If the conversation already contains TOOL_RESULT for the requested work, answer from that result instead of repeating the same tool call.",
16
18
  "If no tool is needed, answer normally.",
17
19
  ].join("\n");
20
+ const PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER = [
21
+ "Final tool-call rule:",
22
+ "If the correct next step is a tool call, return exactly one JSON object and no prose.",
23
+ "If a TOOL_RESULT is already present for the requested work, do not repeat that tool call; answer normally.",
24
+ 'Shape: {"name":"tool_name","arguments":{}}',
25
+ ].join("\n");
18
26
  function readModelText(value) {
19
27
  if (typeof value === "string") {
20
28
  return value.trim();
@@ -175,6 +183,23 @@ function readToolMessageMetadata(value) {
175
183
  : undefined;
176
184
  return { name, toolCallId };
177
185
  }
186
+ function hasPriorToolResultForToolName(input, toolName) {
187
+ if (!toolName) {
188
+ return false;
189
+ }
190
+ if (Array.isArray(input)) {
191
+ return input.some((message) => {
192
+ if (mapMessageRole(message) !== "TOOL") {
193
+ return false;
194
+ }
195
+ return readToolMessageMetadata(message).name === toolName;
196
+ });
197
+ }
198
+ if (typeof input === "object" && input !== null && Array.isArray(input.messages)) {
199
+ return hasPriorToolResultForToolName(input.messages, toolName);
200
+ }
201
+ return false;
202
+ }
178
203
  function normalizeReadFileToolContent(name, content) {
179
204
  if (name !== "read_file") {
180
205
  return content;
@@ -274,22 +299,75 @@ function extractToolCallPayload(text) {
274
299
  if (direct) {
275
300
  return direct;
276
301
  }
277
- const fenced = trimmed.match(/```(?:json)?\s*([\s\S]*?)```/i)?.[1]?.trim();
302
+ const fenced = extractFencePayload(trimmed);
278
303
  if (fenced) {
279
304
  const parsed = tryParseJson(fenced);
280
305
  if (parsed) {
281
306
  return parsed;
282
307
  }
283
308
  }
284
- const xml = trimmed.match(/<tool_call>\s*([\s\S]*?)\s*<\/tool_call>/i)?.[1]?.trim();
309
+ const xml = extractTaggedContent(trimmed, "tool_call");
285
310
  if (xml) {
286
311
  const parsed = tryParseJson(xml);
287
312
  if (parsed) {
288
313
  return parsed;
289
314
  }
290
315
  }
316
+ const toolCallsXml = extractTaggedContent(trimmed, "tool_calls");
317
+ if (toolCallsXml) {
318
+ const parsed = tryParseJson(toolCallsXml);
319
+ if (parsed) {
320
+ return parsed;
321
+ }
322
+ }
323
+ const codeBlock = extractToolCodePayload(trimmed);
324
+ if (codeBlock) {
325
+ return codeBlock;
326
+ }
291
327
  return null;
292
328
  }
329
+ function extractFencePayload(text) {
330
+ const start = text.indexOf("```");
331
+ if (start < 0) {
332
+ return null;
333
+ }
334
+ const contentStart = text.indexOf("\n", start + 3);
335
+ const bodyStart = contentStart >= 0 ? contentStart + 1 : start + 3;
336
+ const end = text.indexOf("```", bodyStart);
337
+ if (end < 0) {
338
+ return null;
339
+ }
340
+ return text.slice(bodyStart, end).trim();
341
+ }
342
+ function extractTaggedContent(text, tagName) {
343
+ const lowerText = text.toLowerCase();
344
+ const openTag = `<${tagName}>`;
345
+ const closeTag = `</${tagName}>`;
346
+ const start = lowerText.indexOf(openTag);
347
+ if (start < 0) {
348
+ return null;
349
+ }
350
+ const bodyStart = start + openTag.length;
351
+ const end = lowerText.indexOf(closeTag, bodyStart);
352
+ if (end < 0) {
353
+ return null;
354
+ }
355
+ return text.slice(bodyStart, end).trim();
356
+ }
357
+ function extractToolCodePayload(text) {
358
+ const name = extractTaggedContent(text, "tool_code");
359
+ if (!name) {
360
+ return null;
361
+ }
362
+ const rawArgs = extractTaggedContent(text, "tool_args");
363
+ const parsedArgs = rawArgs ? tryParseJson(rawArgs.trim()) : null;
364
+ const args = typeof parsedArgs === "object" && parsedArgs !== null && !Array.isArray(parsedArgs)
365
+ ? parsedArgs
366
+ : Array.isArray(parsedArgs)
367
+ ? { args: parsedArgs }
368
+ : {};
369
+ return { name, arguments: args };
370
+ }
293
371
  function normalizeParsedToolCall(payload) {
294
372
  if (typeof payload !== "object" || payload === null) {
295
373
  return null;
@@ -305,7 +383,9 @@ function normalizeParsedToolCall(payload) {
305
383
  return null;
306
384
  }
307
385
  const argsCandidate = typed.arguments ?? typed.args ?? typed.parameters ?? typed.input ?? functionPayload?.arguments ?? {};
308
- const args = salvageToolArgs(argsCandidate) ?? {};
386
+ const args = Array.isArray(argsCandidate)
387
+ ? { args: argsCandidate }
388
+ : salvageToolArgs(argsCandidate) ?? {};
309
389
  return { name, args };
310
390
  }
311
391
  function formatBoundToolInstruction(tool) {
@@ -325,16 +405,16 @@ function formatBoundToolInstruction(tool) {
325
405
  `Arguments JSON schema: ${JSON.stringify(schema)}`,
326
406
  ].filter(Boolean).join("\n");
327
407
  }
328
- function withNodeLlamaCppToolPrompt(input, tools) {
408
+ function withPromptedJsonToolPrompt(input, tools) {
329
409
  const toolInstructions = tools.map((tool) => formatBoundToolInstruction(tool)).filter((value) => Boolean(value));
330
410
  if (toolInstructions.length === 0) {
331
411
  return stringifyNodeLlamaCppInput(input);
332
412
  }
333
413
  const systemContent = `${NODE_LLAMA_CPP_TOOL_CALL_INSTRUCTION}\n\n${toolInstructions.join("\n\n")}`;
334
414
  const prompt = stringifyNodeLlamaCppInput(input);
335
- return [systemContent, prompt].filter(Boolean).join("\n\n");
415
+ return [systemContent, prompt, PROMPTED_JSON_FINAL_TOOL_CALL_REMINDER].filter(Boolean).join("\n\n");
336
416
  }
337
- function createNodeLlamaCppToolBindableModel(model, boundTools = []) {
417
+ function createPromptedJsonToolBindableModel(model, boundTools = []) {
338
418
  return new Proxy(model, {
339
419
  has(target, prop) {
340
420
  if (prop === "bindTools" || prop === "invoke" || prop === "stream" || prop === "withConfig") {
@@ -344,11 +424,11 @@ function createNodeLlamaCppToolBindableModel(model, boundTools = []) {
344
424
  },
345
425
  get(target, prop, receiver) {
346
426
  if (prop === "bindTools") {
347
- return (tools) => createNodeLlamaCppToolBindableModel(target, tools);
427
+ return (tools) => createPromptedJsonToolBindableModel(target, tools);
348
428
  }
349
429
  if (prop === "invoke") {
350
430
  return async (input, config) => {
351
- const rawResult = await target.invoke(boundTools.length > 0 ? withNodeLlamaCppToolPrompt(input, boundTools) : input, config);
431
+ const rawResult = await target.invoke(boundTools.length > 0 ? withPromptedJsonToolPrompt(input, boundTools) : input, config);
352
432
  if (boundTools.length === 0) {
353
433
  return rawResult;
354
434
  }
@@ -357,6 +437,9 @@ function createNodeLlamaCppToolBindableModel(model, boundTools = []) {
357
437
  if (!parsedToolCall) {
358
438
  return rawResult;
359
439
  }
440
+ if (hasPriorToolResultForToolName(input, parsedToolCall.name)) {
441
+ return rawResult;
442
+ }
360
443
  return new AIMessage({
361
444
  content: "",
362
445
  tool_calls: [{
@@ -377,7 +460,7 @@ function createNodeLlamaCppToolBindableModel(model, boundTools = []) {
377
460
  };
378
461
  }
379
462
  if (prop === "withConfig" && typeof target.withConfig === "function") {
380
- return (config) => createNodeLlamaCppToolBindableModel(target.withConfig(config), boundTools);
463
+ return (config) => createPromptedJsonToolBindableModel(target.withConfig(config), boundTools);
381
464
  }
382
465
  const member = Reflect.get(target, prop, receiver);
383
466
  return typeof member === "function" ? member.bind(target) : member;
@@ -409,7 +492,7 @@ async function createNodeLlamaCppModel(model) {
409
492
  }
410
493
  try {
411
494
  const { ChatLlamaCpp } = await import("@langchain/community/chat_models/llama_cpp");
412
- return createNodeLlamaCppToolBindableModel(await ChatLlamaCpp.initialize({
495
+ return createPromptedJsonToolBindableModel(await ChatLlamaCpp.initialize({
413
496
  ...model.init,
414
497
  modelPath,
415
498
  }));
@@ -423,10 +506,23 @@ export async function createResolvedModel(model, modelResolver) {
423
506
  return modelResolver(model.id);
424
507
  }
425
508
  if (model.provider === "ollama") {
426
- return createProviderToolMessageCompatModel(new ChatOllama({ model: model.model, ...model.init }));
509
+ const { toolCallingMode, ...init } = model.init ?? {};
510
+ const resolved = new ChatOllama({ model: model.model, ...init });
511
+ if (toolCallingMode === "prompted-json") {
512
+ return createPromptedJsonToolBindableModel(resolved);
513
+ }
514
+ return createProviderToolMessageCompatModel(resolved);
427
515
  }
428
516
  if (model.provider === "openai-compatible") {
429
- return createProviderToolMessageCompatModel(new ChatOpenAI({ model: model.model, ...normalizeOpenAICompatibleInit(model.init) }));
517
+ const { toolCallingMode, ...init } = model.init ?? {};
518
+ const resolved = new ChatOpenAI({
519
+ model: model.model,
520
+ ...normalizeOpenAICompatibleInit(init),
521
+ });
522
+ if (toolCallingMode === "prompted-json") {
523
+ return createPromptedJsonToolBindableModel(resolved);
524
+ }
525
+ return createProviderToolMessageCompatModel(resolved);
430
526
  }
431
527
  if (model.provider === "openai") {
432
528
  return createProviderToolMessageCompatModel(new ChatOpenAI({ model: model.model, ...model.init }));
@@ -350,7 +350,9 @@ export function projectRuntimeStreamEvent(params) {
350
350
  ? state.lastCompletedTaskDelegationFindings
351
351
  : "";
352
352
  const effectiveToolOutput = salvagedTaskErrorFindings || toolResult.output;
353
- const effectiveToolIsError = salvagedTaskErrorFindings ? false : toolResult.isError;
353
+ const effectiveToolIsError = salvagedTaskErrorFindings
354
+ ? false
355
+ : toolResult.isError === true;
354
356
  const isSuccessfulTaskResult = toolResult.toolName === "task" && effectiveToolIsError !== true;
355
357
  const isDelegatedExecutionTool = (isDelegatedAgentEvent || state.openToolCapableTaskDelegations > 0)
356
358
  && toolResult.toolName !== "write_todos"
@@ -0,0 +1,4 @@
1
+ import type { RequestState, TerminalExecutionStatus } from "../../contracts/types.js";
2
+ export declare function readTerminalExecutionStatus(value: unknown): TerminalExecutionStatus | null;
3
+ export declare function mapTerminalStatusToRequestState(status: TerminalExecutionStatus | null): RequestState;
4
+ export declare function mapTerminalStatusToPlanItemStatus(status: TerminalExecutionStatus): "completed" | "failed";
@@ -0,0 +1,67 @@
1
+ const TERMINAL_STATUSES = new Set(["completed", "blocked", "failed", "refused"]);
2
+ function normalizeTerminalStatus(value) {
3
+ if (typeof value !== "string") {
4
+ return null;
5
+ }
6
+ const normalized = value.trim().toLowerCase();
7
+ return TERMINAL_STATUSES.has(normalized)
8
+ ? normalized
9
+ : null;
10
+ }
11
+ function readStatusLine(value) {
12
+ for (const line of value.split("\n")) {
13
+ const [key, ...rest] = line.split(":");
14
+ if (key?.trim().toLowerCase() !== "status") {
15
+ continue;
16
+ }
17
+ const statusValue = rest.join(":").trim().split(/\s+/)[0];
18
+ const status = normalizeTerminalStatus(statusValue);
19
+ if (status) {
20
+ return status;
21
+ }
22
+ }
23
+ return null;
24
+ }
25
+ export function readTerminalExecutionStatus(value) {
26
+ const direct = normalizeTerminalStatus(value);
27
+ if (direct) {
28
+ return direct;
29
+ }
30
+ if (typeof value === "string") {
31
+ try {
32
+ return readTerminalExecutionStatus(JSON.parse(value));
33
+ }
34
+ catch {
35
+ return readStatusLine(value);
36
+ }
37
+ }
38
+ if (typeof value !== "object" || value === null) {
39
+ return null;
40
+ }
41
+ if (Array.isArray(value)) {
42
+ for (let index = value.length - 1; index >= 0; index -= 1) {
43
+ const status = readTerminalExecutionStatus(value[index]);
44
+ if (status) {
45
+ return status;
46
+ }
47
+ }
48
+ return null;
49
+ }
50
+ const typed = value;
51
+ return (readTerminalExecutionStatus(typed.status)
52
+ ?? readTerminalExecutionStatus(typed.structuredResponse)
53
+ ?? readTerminalExecutionStatus(typed.messages)
54
+ ?? readTerminalExecutionStatus(typed.content)
55
+ ?? readTerminalExecutionStatus(typed.kwargs?.content)
56
+ ?? readTerminalExecutionStatus(typed.lc_kwargs?.content)
57
+ ?? readTerminalExecutionStatus(typed.output)
58
+ ?? readTerminalExecutionStatus(typed.data));
59
+ }
60
+ export function mapTerminalStatusToRequestState(status) {
61
+ return status === "blocked" || status === "failed" || status === "refused"
62
+ ? "failed"
63
+ : "completed";
64
+ }
65
+ export function mapTerminalStatusToPlanItemStatus(status) {
66
+ return status === "completed" ? "completed" : "failed";
67
+ }
@@ -1,9 +1,10 @@
1
1
  import path from "node:path";
2
- import { GENERAL_PURPOSE_SUBAGENT, createAsyncSubAgentMiddleware, createDeepAgent, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSummarizationMiddleware, createSubAgentMiddleware, FilesystemBackend, StateBackend, } from "deepagents";
2
+ import { createAsyncSubAgentMiddleware, createFilesystemMiddleware, createMemoryMiddleware, createPatchToolCallsMiddleware, createSkillsMiddleware, createSummarizationMiddleware, createSubAgentMiddleware, FilesystemBackend, StateBackend, } from "deepagents";
3
3
  import { createAgent, humanInTheLoopMiddleware, todoListMiddleware } from "langchain";
4
4
  import { wrapResolvedModel, } from "./parsing/output-parsing.js";
5
- import { AGENT_INTERRUPT_SENTINEL_PREFIX, buildDeepAgentCreateParams, buildLangChainCreateParams, DEFAULT_DEEPAGENT_RECURSION_LIMIT, materializeModelExposedBuiltinMiddlewareTools, resolveLangChainInvocationConfig, resolveRunnableCheckpointer, resolveRunnableInterruptOn, shouldAttachDeepAgentBackend, shouldAttachDeepAgentCheckpointer, shouldAttachDeepAgentStore, } from "./agent-runtime-assembly.js";
5
+ import { AGENT_INTERRUPT_SENTINEL_PREFIX, buildDeepAgentCreateParams, buildDeepAgentSystemPromptWithCapabilityHierarchy, buildLangChainCreateParams, DEFAULT_DEEPAGENT_RECURSION_LIMIT, materializeModelExposedBuiltinMiddlewareTools, resolveLangChainInvocationConfig, resolveRunnableCheckpointer, resolveRunnableInterruptOn, shouldAttachDeepAgentBackend, shouldAttachDeepAgentCheckpointer, shouldAttachDeepAgentStore, } from "./agent-runtime-assembly.js";
6
6
  import { resolveDeepAgentSkillSourcePaths, } from "./adapter/compat/deepagent-compat.js";
7
+ import { EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION } from "./prompts/runtime-prompts.js";
7
8
  import { buildToolNameMapping, } from "./adapter/tool/tool-name-mapping.js";
8
9
  import { executeRequestInvocation } from "./adapter/flow/invocation-flow.js";
9
10
  import { streamRuntimeExecution } from "./adapter/flow/stream-runtime.js";
@@ -21,8 +22,22 @@ export { buildAuthOmittingFetch, normalizeOpenAICompatibleInit } from "./adapter
21
22
  export { buildToolNameMapping, createModelFacingToolNameCandidates, createModelFacingToolNameLookupCandidates, resolveModelFacingToolName, sanitizeToolNameForModel, } from "./adapter/tool/tool-name-mapping.js";
22
23
  export { computeRemainingTimeoutMs, isRetryableProviderError, resolveBindingTimeout, resolveProviderRetryPolicy, resolveStreamIdleTimeout, resolveTimeoutMs, } from "./adapter/resilience.js";
23
24
  import { getBindingAdapterKind, getBindingBuiltinToolsConfig, getBindingDeepAgentSubagents, getBindingExecutionParams, getBindingExecutionKind, getBindingFilesystemConfig, getBindingMemorySources, getBindingPrimaryModel, getBindingSkills, getBindingToolCount, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
25
+ class DelegatedExecutionNoToolEvidenceError extends Error {
26
+ constructor(agentId) {
27
+ super(`Delegated agent ${agentId} completed without tool execution evidence.`);
28
+ this.name = "DelegatedExecutionNoToolEvidenceError";
29
+ }
30
+ }
31
+ function hasDelegatedExecutionToolEvidence(result) {
32
+ const executedToolResults = Array.isArray(result.metadata?.executedToolResults)
33
+ ? result.metadata.executedToolResults
34
+ : [];
35
+ return executedToolResults.some((toolResult) => (toolResult.isError !== true
36
+ && toolResult.toolName !== "write_todos"
37
+ && toolResult.toolName !== "read_todos"));
38
+ }
24
39
  function shouldUseConfigurableDeepAgentAssembly(binding) {
25
- return getBindingBuiltinToolsConfig(binding) !== undefined;
40
+ return getBindingExecutionKind(binding) === "deepagent";
26
41
  }
27
42
  export class AgentRuntimeAdapter {
28
43
  options;
@@ -319,9 +334,18 @@ export class AgentRuntimeAdapter {
319
334
  const childSessionId = `${sessionId}:delegated:${resolvedSubagent.name}`;
320
335
  const childRequestId = `${requestId}:delegated:${resolvedSubagent.name}:${Date.now().toString(36)}`;
321
336
  try {
322
- const result = await this.invoke(targetBinding, requestText, childSessionId, childRequestId, undefined, [], {
337
+ const invokeOptions = {
323
338
  ...(typeof config?.context === "object" && config.context ? { context: config.context } : {}),
324
- });
339
+ };
340
+ const runDelegatedRequest = (text, requestSuffix = "") => this.invoke(targetBinding, text, childSessionId, `${childRequestId}${requestSuffix}`, undefined, [], invokeOptions);
341
+ let result = await runDelegatedRequest(requestText);
342
+ const targetRequiresExecutionToolEvidence = getBindingPrimaryTools(targetBinding).length > 0;
343
+ if (targetRequiresExecutionToolEvidence && !hasDelegatedExecutionToolEvidence(result)) {
344
+ result = await runDelegatedRequest([requestText, EXECUTION_WITH_TOOL_EVIDENCE_RETRY_INSTRUCTION].filter(Boolean).join("\n\n"), ":tool-evidence-retry");
345
+ if (!hasDelegatedExecutionToolEvidence(result)) {
346
+ throw new DelegatedExecutionNoToolEvidenceError(targetBinding.agent.id);
347
+ }
348
+ }
325
349
  return wrapRequestResultAsSubagentResponse({
326
350
  output: result.output,
327
351
  structuredResponse: result.structuredResponse,
@@ -331,9 +355,7 @@ export class AgentRuntimeAdapter {
331
355
  const message = error instanceof Error && error.message.trim().length > 0
332
356
  ? error.message.trim()
333
357
  : "delegated execution failed";
334
- return wrapRequestResultAsSubagentResponse({
335
- output: `Blocked: ${message}`,
336
- });
358
+ throw new Error(message);
337
359
  }
338
360
  },
339
361
  },
@@ -422,18 +444,6 @@ export class AgentRuntimeAdapter {
422
444
  ownerId: binding.agent.id,
423
445
  skillPaths: getBindingSkills(binding),
424
446
  }) ?? [];
425
- const deepAgentConfig = buildDeepAgentCreateParams({
426
- binding,
427
- resolvedModel,
428
- resolvedTools: [...resolvedTools, ...builtinMiddlewareTools],
429
- resolvedMiddleware,
430
- resolvedSubagents,
431
- resolvedCheckpointer,
432
- resolvedStore,
433
- resolvedBackend,
434
- resolvedInterruptOn,
435
- resolvedSkills,
436
- });
437
447
  if (shouldUseConfigurableDeepAgentAssembly(binding)) {
438
448
  return this.createConfigurableDeepAgentRunnable(binding, {
439
449
  resolvedModel,
@@ -441,36 +451,33 @@ export class AgentRuntimeAdapter {
441
451
  resolvedMiddleware,
442
452
  resolvedSubagents,
443
453
  resolvedInterruptOn,
454
+ resolvedCheckpointer,
455
+ resolvedStore,
444
456
  resolvedBackend,
445
457
  resolvedSkills,
446
458
  });
447
459
  }
448
- return createDeepAgent(deepAgentConfig);
460
+ throw new Error(`Agent ${binding.agent.id} has no supported deepagent assembly path`);
449
461
  }
450
462
  createConfigurableDeepAgentRunnable(binding, input) {
451
463
  const builtinTools = getBindingBuiltinToolsConfig(binding) ?? {};
452
464
  const backend = (input.resolvedBackend ?? new StateBackend({}));
453
465
  const inlineSubagents = input.resolvedSubagents.filter((subagent) => !("graphId" in subagent));
454
466
  const asyncSubagents = input.resolvedSubagents.filter((subagent) => "graphId" in subagent);
455
- const subagents = inlineSubagents.some((subagent) => subagent.name === GENERAL_PURPOSE_SUBAGENT.name)
456
- ? inlineSubagents
457
- : [{
458
- ...GENERAL_PURPOSE_SUBAGENT,
459
- model: input.resolvedModel,
460
- tools: input.resolvedTools,
461
- skills: input.resolvedSkills,
462
- }, ...inlineSubagents];
467
+ const subagents = inlineSubagents;
463
468
  const middleware = [
464
469
  ...(builtinTools.todos === false ? [] : [todoListMiddleware()]),
465
470
  ...(input.resolvedSkills.length > 0 ? [createSkillsMiddleware({ backend, sources: input.resolvedSkills })] : []),
466
471
  ...(builtinTools.filesystem === false ? [] : [createFilesystemMiddleware({ backend })]),
467
- createSubAgentMiddleware({
468
- defaultModel: input.resolvedModel,
469
- defaultTools: input.resolvedTools,
470
- defaultInterruptOn: input.resolvedInterruptOn,
471
- subagents: subagents,
472
- generalPurposeAgent: false,
473
- }),
472
+ ...(subagents.length > 0
473
+ ? [createSubAgentMiddleware({
474
+ defaultModel: input.resolvedModel,
475
+ defaultTools: input.resolvedTools,
476
+ defaultInterruptOn: input.resolvedInterruptOn,
477
+ subagents: subagents,
478
+ generalPurposeAgent: false,
479
+ })]
480
+ : []),
474
481
  createSummarizationMiddleware({
475
482
  model: input.resolvedModel,
476
483
  backend,
@@ -487,10 +494,17 @@ export class AgentRuntimeAdapter {
487
494
  : undefined;
488
495
  return createAgent({
489
496
  model: input.resolvedModel,
490
- systemPrompt: getBindingSystemPrompt(binding),
497
+ systemPrompt: buildDeepAgentSystemPromptWithCapabilityHierarchy({
498
+ systemPrompt: getBindingSystemPrompt(binding),
499
+ subagents: input.resolvedSubagents,
500
+ skills: input.resolvedSkills,
501
+ tools: getBindingPrimaryTools(binding),
502
+ }),
491
503
  tools: input.resolvedTools,
492
504
  middleware: middleware,
493
505
  name: binding.agent.id,
506
+ ...(input.resolvedCheckpointer !== undefined ? { checkpointer: input.resolvedCheckpointer } : {}),
507
+ ...(input.resolvedStore !== undefined ? { store: input.resolvedStore } : {}),
494
508
  ...(responseFormat !== undefined ? { responseFormat: responseFormat } : {}),
495
509
  });
496
510
  }
@@ -8,6 +8,16 @@ export declare function materializeModelExposedBuiltinMiddlewareTools(input: {
8
8
  explicitToolNames?: string[];
9
9
  modelExposed?: boolean | string[];
10
10
  }): unknown[];
11
+ export declare function buildDeepAgentSystemPromptWithCapabilityHierarchy(input: {
12
+ systemPrompt?: unknown;
13
+ subagents: Array<Pick<UpstreamSubagentConfig, "name" | "description"> | Pick<CompiledAsyncSubAgent, "name" | "description">>;
14
+ skills?: string[];
15
+ tools?: Array<{
16
+ name: string;
17
+ description?: string;
18
+ }>;
19
+ }): unknown;
20
+ export declare const buildDeepAgentSystemPromptWithSubagentCatalog: typeof buildDeepAgentSystemPromptWithCapabilityHierarchy;
11
21
  export declare function resolveRunnableCheckpointer(options: RuntimeAdapterOptions, binding: CompiledAgentBinding): unknown;
12
22
  export declare function resolveRunnableInterruptOn(binding: CompiledAgentBinding): Record<string, {
13
23
  allowedDecisions: import("./adapter/tool/interrupt-policy.js").InterruptDecision[];
@@ -2,6 +2,7 @@ import { MemorySaver } from "@langchain/langgraph";
2
2
  import { UPSTREAM_REQUEST_CONFIG_KEY, UPSTREAM_SESSION_CONFIG_KEY } from "./adapter/upstream-configurable-keys.js";
3
3
  import { asStructuredExecutableTool } from "./adapter/tool/resolved-tool.js";
4
4
  import { compileInterruptOn } from "./adapter/tool/interrupt-policy.js";
5
+ import { readSkillMetadata } from "./skills/skill-metadata.js";
5
6
  import { getBindingBackendConfig, getBindingExecutionKind, getBindingExecutionParams, getBindingInterruptCompatibilityRules, getBindingMemorySources, getBindingMiddlewareConfigs, getBindingPrimaryTools, getBindingSkills, getBindingStoreConfig, } from "./support/compiled-binding.js";
6
7
  export const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
7
8
  export const DEFAULT_DEEPAGENT_RECURSION_LIMIT = 100;
@@ -37,6 +38,67 @@ export function materializeModelExposedBuiltinMiddlewareTools(input) {
37
38
  }
38
39
  return tools;
39
40
  }
41
+ function formatCapabilityLine(item) {
42
+ const description = typeof item.description === "string" && item.description.length > 0
43
+ ? `: ${item.description}`
44
+ : "";
45
+ return `- ${JSON.stringify(item.name)}${description}`;
46
+ }
47
+ function buildSkillCatalog(skillPaths) {
48
+ return skillPaths.map((skillPath) => {
49
+ const metadata = readSkillMetadata(skillPath);
50
+ return {
51
+ name: metadata.name,
52
+ ...(metadata.description ? { description: metadata.description } : {}),
53
+ };
54
+ });
55
+ }
56
+ export function buildDeepAgentSystemPromptWithCapabilityHierarchy(input) {
57
+ const basePrompt = typeof input.systemPrompt === "string" ? input.systemPrompt : undefined;
58
+ const skills = buildSkillCatalog(input.skills ?? []);
59
+ const tools = input.tools ?? [];
60
+ if (input.subagents.length === 0 && skills.length === 0 && tools.length === 0) {
61
+ return input.systemPrompt;
62
+ }
63
+ const catalogPrompt = [
64
+ "Capability selection hierarchy:",
65
+ "1. If the current request fits an available subagent, delegate with the task tool before using local skills or raw tools.",
66
+ "2. If you are the selected agent and an available skill fits the request, read and follow that skill before calling raw tools.",
67
+ "3. Use raw tools as execution primitives for the selected agent or skill, or when no listed skill applies.",
68
+ "Keep each selection inside the selected capability's described responsibility boundary.",
69
+ "If no listed subagent, skill, or tool can responsibly handle the request, do not invent a path. Return a terminal response with status \"refused\" and explain the missing capability.",
70
+ "If a selected capability cannot complete after using its available tools, return a terminal response with status \"blocked\" or \"failed\" and include the blocker evidence.",
71
+ ...(input.subagents.length > 0
72
+ ? [
73
+ "",
74
+ "Available subagents for task delegation:",
75
+ ...input.subagents.map(formatCapabilityLine),
76
+ "",
77
+ "When using the task tool, set subagent_type to exactly one of the listed subagent names. Do not create, translate, alias, or modify subagent names.",
78
+ ]
79
+ : [
80
+ "",
81
+ "No configured specialist subagents are available for this agent. Do not use task delegation for role-internal work; select a skill first, then tools.",
82
+ ]),
83
+ ...(skills.length > 0
84
+ ? [
85
+ "",
86
+ "Available skills for this agent:",
87
+ ...skills.map(formatCapabilityLine),
88
+ ]
89
+ : []),
90
+ ...(tools.length > 0
91
+ ? [
92
+ "",
93
+ "Raw tools available to this agent:",
94
+ ...tools.map(formatCapabilityLine),
95
+ ]
96
+ : []),
97
+ "",
98
+ ].join("\n");
99
+ return [basePrompt, catalogPrompt].filter((part) => typeof part === "string" && part.length > 0).join("\n\n");
100
+ }
101
+ export const buildDeepAgentSystemPromptWithSubagentCatalog = buildDeepAgentSystemPromptWithCapabilityHierarchy;
40
102
  export function resolveRunnableCheckpointer(options, binding) {
41
103
  return options.checkpointerResolver ? options.checkpointerResolver(binding) : new MemorySaver();
42
104
  }
@@ -146,6 +208,12 @@ export function buildDeepAgentCreateParams(input) {
146
208
  ]);
147
209
  return {
148
210
  ...upstreamParams,
211
+ systemPrompt: buildDeepAgentSystemPromptWithSubagentCatalog({
212
+ systemPrompt: upstreamParams.systemPrompt,
213
+ subagents: input.resolvedSubagents,
214
+ skills: input.resolvedSkills,
215
+ tools: getBindingPrimaryTools(input.binding),
216
+ }),
149
217
  skills: input.resolvedSkills,
150
218
  model: input.resolvedModel,
151
219
  tools: input.resolvedTools,