@botbotgo/agent-harness 0.0.133 → 0.0.135

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,11 +1,13 @@
1
1
  import { existsSync, mkdirSync, readdirSync } from "node:fs";
2
+ import { spawn } from "node:child_process";
2
3
  import { createRequire } from "node:module";
3
4
  import path from "node:path";
4
5
  import { stat } from "node:fs/promises";
5
6
  import { readFile } from "node:fs/promises";
6
7
  import { fileURLToPath, pathToFileURL } from "node:url";
7
8
  import { CompositeBackend, LocalShellBackend, StateBackend, StoreBackend } from "deepagents";
8
- import { getBindingBackendConfig } from "../runtime/support/compiled-binding.js";
9
+ import { getBindingBackendConfig, getBindingExecutionView, getBindingPrimaryModel } from "../runtime/support/compiled-binding.js";
10
+ import { resolveCompiledEmbeddingModelRef } from "../runtime/support/embedding-models.js";
9
11
  import { createRuntimeEnv } from "../runtime/support/runtime-env.js";
10
12
  import { isSupportedToolModulePath, loadToolModuleDefinition } from "../tool-modules.js";
11
13
  import { createMcpToolResolver, } from "./mcp-tool-support.js";
@@ -273,6 +275,142 @@ function createWorkspaceProviderResolvers(workspace, factory) {
273
275
  .map((provider) => factory(provider))
274
276
  .filter((resolver) => Boolean(resolver));
275
277
  }
278
+ function asObject(value) {
279
+ return typeof value === "object" && value !== null ? value : undefined;
280
+ }
281
+ function asStringRecord(value) {
282
+ const record = asObject(value);
283
+ if (!record) {
284
+ return undefined;
285
+ }
286
+ return Object.fromEntries(Object.entries(record).filter((entry) => typeof entry[1] === "string"));
287
+ }
288
+ function asStringArray(value) {
289
+ return Array.isArray(value) ? value.filter((item) => typeof item === "string") : [];
290
+ }
291
+ const FUNCTION_TOOL_SUBPROCESS_RUNNER_SOURCE = `
292
+ import { pathToFileURL } from "node:url";
293
+
294
+ const TOOL_DEFINITION_MARKER = "__agent_harness_tool_definition__";
295
+
296
+ function isToolDefinitionObject(value) {
297
+ return typeof value === "object" && value !== null && value[TOOL_DEFINITION_MARKER] === true;
298
+ }
299
+
300
+ function loadToolDefinition(imported, implementationName) {
301
+ for (const [exportName, value] of Object.entries(imported)) {
302
+ if (exportName === "default" || !isToolDefinitionObject(value)) {
303
+ continue;
304
+ }
305
+ const resolvedName = typeof value.name === "string" && value.name.trim() ? value.name.trim() : exportName;
306
+ if (resolvedName === implementationName) {
307
+ return value;
308
+ }
309
+ }
310
+ throw new Error(\`Tool module must export a tool({...}) definition named \${implementationName}.\`);
311
+ }
312
+
313
+ const [modulePath, implementationName] = process.argv.slice(1);
314
+ const imported = await import(pathToFileURL(modulePath).href);
315
+ const tool = loadToolDefinition(imported, implementationName);
316
+ let stdin = "";
317
+ for await (const chunk of process.stdin) {
318
+ stdin += String(chunk);
319
+ }
320
+ const payload = stdin.trim() ? JSON.parse(stdin) : {};
321
+ const input = tool.schema && typeof tool.schema.parse === "function"
322
+ ? tool.schema.parse(payload.input ?? {})
323
+ : (payload.input ?? {});
324
+ const result = await tool.invoke(input, payload.context ?? {});
325
+ process.stdout.write(typeof result === "string" ? result : JSON.stringify(result));
326
+ `;
327
+ function resolveFunctionToolSubprocessConfig(tool, workspaceRoot, isolatedSourcePath) {
328
+ const execution = asObject(tool.config?.execution);
329
+ if (tool.subprocess !== true) {
330
+ return null;
331
+ }
332
+ const resolvedExecution = execution ?? {};
333
+ const entry = typeof resolvedExecution.entry === "string" && resolvedExecution.entry.trim()
334
+ ? path.resolve(workspaceRoot, resolvedExecution.entry)
335
+ : undefined;
336
+ const command = typeof resolvedExecution.command === "string" && resolvedExecution.command.trim()
337
+ ? resolvedExecution.command
338
+ : entry
339
+ ? process.execPath
340
+ : process.execPath;
341
+ if (!command) {
342
+ throw new Error(`Tool ${tool.id} subprocess execution requires config.execution.command or config.execution.entry`);
343
+ }
344
+ const args = [
345
+ ...(entry ? [entry] : ["--input-type=module", "--eval", FUNCTION_TOOL_SUBPROCESS_RUNNER_SOURCE, isolatedSourcePath, tool.implementationName ?? tool.id]),
346
+ ...asStringArray(resolvedExecution.args),
347
+ ];
348
+ const cwd = typeof resolvedExecution.cwd === "string" && resolvedExecution.cwd.trim()
349
+ ? path.resolve(workspaceRoot, resolvedExecution.cwd)
350
+ : workspaceRoot;
351
+ const timeoutMs = Number.isFinite(resolvedExecution.timeoutMs) ? Number(resolvedExecution.timeoutMs) : undefined;
352
+ return {
353
+ command,
354
+ args,
355
+ cwd,
356
+ env: asStringRecord(resolvedExecution.env),
357
+ timeoutMs,
358
+ };
359
+ }
360
+ async function runFunctionToolInSubprocess(config, input, context) {
361
+ return await new Promise((resolve, reject) => {
362
+ const child = spawn(config.command, config.args, {
363
+ cwd: config.cwd,
364
+ env: {
365
+ ...process.env,
366
+ ...(config.env ?? {}),
367
+ },
368
+ stdio: ["pipe", "pipe", "pipe"],
369
+ });
370
+ let stdout = "";
371
+ let stderr = "";
372
+ let settled = false;
373
+ let timeout;
374
+ const finish = (fn) => {
375
+ if (settled) {
376
+ return;
377
+ }
378
+ settled = true;
379
+ if (timeout) {
380
+ clearTimeout(timeout);
381
+ }
382
+ fn();
383
+ };
384
+ if (config.timeoutMs && config.timeoutMs > 0) {
385
+ timeout = setTimeout(() => {
386
+ child.kill("SIGTERM");
387
+ finish(() => reject(new Error(`Subprocess tool timed out after ${config.timeoutMs}ms`)));
388
+ }, config.timeoutMs);
389
+ }
390
+ child.stdout.setEncoding("utf8");
391
+ child.stderr.setEncoding("utf8");
392
+ child.stdout.on("data", (chunk) => {
393
+ stdout += chunk;
394
+ });
395
+ child.stderr.on("data", (chunk) => {
396
+ stderr += chunk;
397
+ });
398
+ child.on("error", (error) => {
399
+ finish(() => reject(error));
400
+ });
401
+ child.on("close", (code, signal) => {
402
+ finish(() => {
403
+ if (code === 0) {
404
+ resolve(stdout.trim());
405
+ return;
406
+ }
407
+ const message = stderr.trim() || stdout.trim() || `Subprocess tool exited with code ${code ?? "unknown"}${signal ? ` (${signal})` : ""}`;
408
+ reject(new Error(message));
409
+ });
410
+ });
411
+ child.stdin.end(JSON.stringify({ input, context }));
412
+ });
413
+ }
276
414
  async function loadFunctionToolModule(tool) {
277
415
  const cacheKey = `${tool.sourcePath}:${tool.implementationName ?? tool.id}`;
278
416
  const cached = functionToolModuleCache.get(cacheKey);
@@ -285,7 +423,7 @@ async function loadFunctionToolModule(tool) {
285
423
  const imported = await import(pathToFileURL(isolatedSourcePath).href);
286
424
  const implementationName = tool.implementationName ?? tool.id;
287
425
  const loaded = loadToolModuleDefinition(imported, implementationName);
288
- return { invoke: loaded.invoke, schema: loaded.schema, description: loaded.description };
426
+ return { invoke: loaded.invoke, schema: loaded.schema, description: loaded.description, isolatedSourcePath, implementationName };
289
427
  })();
290
428
  functionToolModuleCache.set(cacheKey, loading);
291
429
  return loading;
@@ -294,7 +432,7 @@ function createFunctionToolResolver(workspace) {
294
432
  const functionTools = new Map(Array.from(workspace.tools.values())
295
433
  .filter((tool) => tool.type === "function" && isSupportedToolModulePath(tool.sourcePath))
296
434
  .map((tool) => [tool.id, tool]));
297
- return (toolIds) => toolIds.flatMap((toolId) => {
435
+ return (toolIds, binding) => toolIds.flatMap((toolId) => {
298
436
  const tool = functionTools.get(toolId);
299
437
  if (!tool) {
300
438
  return [];
@@ -307,12 +445,63 @@ function createFunctionToolResolver(workspace) {
307
445
  const loaded = await loadFunctionToolModule(tool);
308
446
  const parsedInput = loaded.schema.parse(input ?? {});
309
447
  const toolPackageRoot = await findPackageRoot(tool.sourcePath);
310
- return loaded.invoke(parsedInput, {
448
+ const bindingTool = binding
449
+ ? getBindingExecutionView(binding).primaryTools.find((candidate) => candidate.id === tool.id || candidate.name === tool.name)
450
+ : undefined;
451
+ const effectiveEmbeddingModelRef = bindingTool?.embeddingModelRef ?? tool.embeddingModelRef;
452
+ const effectiveSubprocess = bindingTool?.subprocess ?? tool.subprocess;
453
+ const effectiveConfig = bindingTool?.config ?? tool.config;
454
+ const effectiveHitl = bindingTool?.hitl ?? tool.hitl;
455
+ const effectiveRetryable = bindingTool?.retryable ?? tool.retryable;
456
+ const embeddingModel = effectiveEmbeddingModelRef
457
+ ? resolveCompiledEmbeddingModelRef(workspace, effectiveEmbeddingModelRef)
458
+ : undefined;
459
+ const model = binding ? getBindingPrimaryModel(binding) : undefined;
460
+ const toolContext = {
311
461
  appRoot: workspace.workspaceRoot,
312
462
  toolId: tool.id,
313
463
  toolPath: tool.sourcePath,
314
464
  toolPackageRoot,
315
- });
465
+ embeddingModel,
466
+ model,
467
+ runtime: {
468
+ workspaceRoot: workspace.workspaceRoot,
469
+ runRoot: binding?.harnessRuntime.runRoot,
470
+ },
471
+ agent: binding
472
+ ? {
473
+ id: binding.agent.id,
474
+ executionMode: binding.agent.executionMode,
475
+ description: binding.agent.description,
476
+ sourcePath: binding.agent.sourcePath,
477
+ }
478
+ : undefined,
479
+ tool: {
480
+ id: tool.id,
481
+ name: tool.name,
482
+ type: tool.type,
483
+ description: tool.description,
484
+ sourcePath: tool.sourcePath,
485
+ packageRoot: toolPackageRoot,
486
+ config: effectiveConfig,
487
+ embeddingModelRef: effectiveEmbeddingModelRef,
488
+ inputSchemaRef: tool.inputSchemaRef,
489
+ retryable: effectiveRetryable,
490
+ hitl: effectiveHitl,
491
+ },
492
+ };
493
+ const subprocessConfig = resolveFunctionToolSubprocessConfig({
494
+ ...tool,
495
+ config: effectiveConfig,
496
+ embeddingModelRef: effectiveEmbeddingModelRef,
497
+ hitl: effectiveHitl,
498
+ retryable: effectiveRetryable,
499
+ subprocess: effectiveSubprocess,
500
+ }, workspace.workspaceRoot, loaded.isolatedSourcePath);
501
+ if (subprocessConfig) {
502
+ return runFunctionToolInSubprocess(subprocessConfig, parsedInput, toolContext);
503
+ }
504
+ return loaded.invoke(parsedInput, toolContext);
316
505
  },
317
506
  },
318
507
  ];
@@ -431,7 +620,10 @@ export function createResourceToolResolver(workspace, options = {}) {
431
620
  ];
432
621
  const deduped = new Map();
433
622
  for (const tool of resolved) {
434
- deduped.set(String(tool.name), tool);
623
+ const name = String(tool.name);
624
+ if (!deduped.has(name)) {
625
+ deduped.set(name, tool);
626
+ }
435
627
  }
436
628
  return Array.from(deduped.values());
437
629
  };
@@ -5,7 +5,9 @@ export declare class RuntimeOperationTimeoutError extends Error {
5
5
  readonly stage: "stream" | "invoke";
6
6
  constructor(operation: string, timeoutMs: number, stage?: "stream" | "invoke");
7
7
  }
8
- export declare function invokeWithProviderRetry<T>(binding: CompiledAgentBinding, operation: () => Promise<T>): Promise<T>;
8
+ export declare function invokeWithProviderRetry<T>(binding: CompiledAgentBinding, operation: () => Promise<T>, options?: {
9
+ onRetry?: (attempt: number, error: unknown) => void | Promise<void>;
10
+ }): Promise<T>;
9
11
  export declare function withRuntimeTimeout<T>(producer: () => T | Promise<T>, timeoutMs: number | undefined, operation: string, stage?: "stream" | "invoke"): Promise<T>;
10
12
  export declare function iterateWithTimeout<T>(iterable: AsyncIterable<T>, timeoutMs: number | undefined, operation: string, deadlineAt?: number, deadlineTimeoutMs?: number): AsyncGenerator<T>;
11
13
  export declare function materializeModelStream(streamFactory: (input: unknown, config?: Record<string, unknown>) => Promise<AsyncIterable<unknown>>, input: unknown, config?: Record<string, unknown>): Promise<{
@@ -15,7 +15,7 @@ export class RuntimeOperationTimeoutError extends Error {
15
15
  this.name = "RuntimeOperationTimeoutError";
16
16
  }
17
17
  }
18
- export async function invokeWithProviderRetry(binding, operation) {
18
+ export async function invokeWithProviderRetry(binding, operation, options = {}) {
19
19
  const retryPolicy = resolveProviderRetryPolicy(binding);
20
20
  let lastError;
21
21
  for (let attempt = 1; attempt <= retryPolicy.maxAttempts; attempt += 1) {
@@ -27,6 +27,7 @@ export async function invokeWithProviderRetry(binding, operation) {
27
27
  if (attempt >= retryPolicy.maxAttempts || !isRetryableProviderError(binding, error)) {
28
28
  throw error;
29
29
  }
30
+ await options.onRetry?.(attempt, error);
30
31
  if (retryPolicy.backoffMs > 0) {
31
32
  await sleep(retryPolicy.backoffMs);
32
33
  }
@@ -37,6 +37,7 @@ export function normalizeToolArgsForSchema(args, schema) {
37
37
  const aliasesByExpected = {
38
38
  city: ["location", "locality", "place"],
39
39
  location: ["city", "city_name"],
40
+ query: ["question", "prompt", "text", "request"],
40
41
  };
41
42
  const aliases = aliasesByExpected[expectedKey] ?? [];
42
43
  const aliasKey = aliases.find((candidate) => candidate in args);
@@ -107,6 +107,9 @@ function wrapToolWithDedupe(resolvedTool, compiledTool) {
107
107
  });
108
108
  }
109
109
  function shouldConstrainWorkspacePaths(compiledTool) {
110
+ if (compiledTool.type === "mcp") {
111
+ return false;
112
+ }
110
113
  const normalized = `${compiledTool.name} ${compiledTool.description}`.toLowerCase();
111
114
  if (/(web[_ .-]?search|fetch[_ .-]?url|https?:\/\/|\burl\b)/.test(normalized)) {
112
115
  return false;
@@ -26,6 +26,11 @@ export declare function buildLangChainCreateParams(input: {
26
26
  passthroughOverride?: Record<string, unknown>;
27
27
  systemPromptOverride?: string;
28
28
  }): Record<string, unknown>;
29
+ export declare function resolveLangChainInvocationConfig(binding: CompiledAgentBinding, options: {
30
+ threadId: string;
31
+ runId: string;
32
+ context?: Record<string, unknown>;
33
+ }): Record<string, unknown>;
29
34
  export declare function buildDeepAgentCreateParams(input: {
30
35
  binding: CompiledAgentBinding;
31
36
  resolvedModel: unknown;
@@ -54,6 +59,7 @@ export declare class AgentRuntimeAdapter {
54
59
  private createModelFallbackRunnable;
55
60
  private applyStrictToolJsonInstruction;
56
61
  private resolveModel;
62
+ private invalidateBindingRuntimeCaches;
57
63
  private resolveTools;
58
64
  private getToolNameMapping;
59
65
  private resolveFilesystemBackend;
@@ -89,6 +89,24 @@ export function buildLangChainCreateParams(input) {
89
89
  store: input.resolvedStore,
90
90
  };
91
91
  }
92
+ export function resolveLangChainInvocationConfig(binding, options) {
93
+ const langchainPassthrough = typeof binding.harnessRuntime.langchain?.passthrough === "object" && binding.harnessRuntime.langchain?.passthrough
94
+ ? binding.harnessRuntime.langchain.passthrough
95
+ : undefined;
96
+ const config = {
97
+ configurable: {
98
+ thread_id: options.threadId,
99
+ run_id: options.runId,
100
+ },
101
+ };
102
+ if (typeof langchainPassthrough?.recursionLimit === "number") {
103
+ config.recursionLimit = langchainPassthrough.recursionLimit;
104
+ }
105
+ if (options.context) {
106
+ config.context = options.context;
107
+ }
108
+ return config;
109
+ }
92
110
  export function buildDeepAgentCreateParams(input) {
93
111
  const executionKind = getBindingExecutionKind(input.binding);
94
112
  if (executionKind !== "deepagent" || !getBindingExecutionParams(input.binding)) {
@@ -155,7 +173,11 @@ export class AgentRuntimeAdapter {
155
173
  });
156
174
  }
157
175
  async invokeWithProviderRetry(binding, operation) {
158
- return invokeWithProviderRetryHelper(binding, operation);
176
+ return invokeWithProviderRetryHelper(binding, operation, {
177
+ onRetry: async () => {
178
+ this.invalidateBindingRuntimeCaches(binding);
179
+ },
180
+ });
159
181
  }
160
182
  async withTimeout(producer, timeoutMs, operation, stage = operation.includes("stream") ? "stream" : "invoke") {
161
183
  return withRuntimeTimeout(producer, timeoutMs, operation, stage);
@@ -190,6 +212,10 @@ export class AgentRuntimeAdapter {
190
212
  throw error;
191
213
  }
192
214
  }
215
+ invalidateBindingRuntimeCaches(binding) {
216
+ this.runnableCache.delete(binding);
217
+ this.modelCache.clear();
218
+ }
193
219
  resolveTools(tools, binding) {
194
220
  return resolveAdapterTools({
195
221
  tools,
@@ -418,7 +444,11 @@ export class AgentRuntimeAdapter {
418
444
  const callRuntime = async (activeBinding, activeRequest) => {
419
445
  return this.invokeWithProviderRetry(activeBinding, async () => {
420
446
  const runnable = await this.create(activeBinding);
421
- return (await this.withTimeout(() => runnable.invoke(activeRequest, { configurable: { thread_id: threadId, run_id: runId }, ...(options.context ? { context: options.context } : {}) }), resolveBindingTimeout(activeBinding), "agent invoke", "invoke"));
447
+ return (await this.withTimeout(() => runnable.invoke(activeRequest, resolveLangChainInvocationConfig(activeBinding, {
448
+ threadId,
449
+ runId,
450
+ context: options.context,
451
+ })), resolveBindingTimeout(activeBinding), "agent invoke", "invoke"));
422
452
  });
423
453
  };
424
454
  const callRuntimeWithToolParseRecovery = async (activeRequest) => {
@@ -21,6 +21,7 @@ import { dropPendingRunSlot, enqueuePendingRunSlot } from "./harness/run/run-que
21
21
  import { getDefaultRuntimeEntryAgentId, resolveSelectedAgentId, routeAgentId } from "./harness/run/routing.js";
22
22
  import { resolveStoreFromConfig, } from "./harness/run/resources.js";
23
23
  import { createToolMcpServerFromTools, serveToolsOverStdioFromHarness } from "../mcp.js";
24
+ import { closeMcpClientsForWorkspace } from "../resource/mcp-tool-support.js";
24
25
  import { getBindingRuntimeExecutionMode, } from "./support/compiled-binding.js";
25
26
  import { bindingSupportsRunningReplay, getWorkspaceBinding, resolveWorkspaceAgentTools, } from "./harness/bindings.js";
26
27
  import { describeWorkspaceInventory, getAgentInventoryRecord, listAgentSkills as listWorkspaceAgentSkills, } from "./harness/system/inventory.js";
@@ -584,6 +585,7 @@ export class AgentHarnessRuntime {
584
585
  await Promise.allSettled(Array.from(this.backgroundTasks));
585
586
  await this.threadMemorySync?.close();
586
587
  await this.mem0IngestionSync?.close();
588
+ await closeMcpClientsForWorkspace(this.workspace);
587
589
  this.initialized = false;
588
590
  }
589
591
  async stop() {
@@ -1,4 +1,4 @@
1
- import type { CompiledAgentBinding, CompiledTool, ParsedAgentObject, ParsedModelObject, ParsedToolObject, WorkspaceObject } from "../contracts/types.js";
1
+ import type { CompiledAgentBinding, CompiledTool, ParsedAgentObject, ParsedAgentToolBinding, ParsedModelObject, ParsedToolObject, WorkspaceObject } from "../contracts/types.js";
2
2
  export declare function compileAgentSkills(workspaceRoot: string, agent: ParsedAgentObject, parentSkills?: string[]): string[];
3
- export declare function requireTools(tools: Map<string, ParsedToolObject>, refs: string[], ownerId: string): CompiledTool[];
3
+ export declare function requireTools(tools: Map<string, ParsedToolObject>, bindings: ParsedAgentToolBinding[], ownerId: string): CompiledTool[];
4
4
  export declare function compileBinding(workspaceRoot: string, agent: ParsedAgentObject, agents: Map<string, ParsedAgentObject>, referencedSubagentIds: Set<string>, refs: Map<string, WorkspaceObject | ParsedAgentObject>, models: Map<string, ParsedModelObject>, tools: Map<string, ParsedToolObject>): CompiledAgentBinding;
@@ -74,13 +74,85 @@ function resolveAgentRuntimeName(agent) {
74
74
  }
75
75
  return baseName;
76
76
  }
77
- export function requireTools(tools, refs, ownerId) {
78
- const compiled = refs.flatMap((ref) => {
79
- const targets = resolveToolTargets(tools, ref);
77
+ function asObject(value) {
78
+ return typeof value === "object" && value !== null && !Array.isArray(value)
79
+ ? value
80
+ : undefined;
81
+ }
82
+ function mergeConfigValue(base, override) {
83
+ if (override === undefined) {
84
+ return base;
85
+ }
86
+ if (Array.isArray(base) && Array.isArray(override)) {
87
+ return override;
88
+ }
89
+ if (typeof base === "object" && base && typeof override === "object" && override && !Array.isArray(base) && !Array.isArray(override)) {
90
+ const merged = { ...base };
91
+ for (const [key, value] of Object.entries(override)) {
92
+ merged[key] = key in merged ? mergeConfigValue(merged[key], value) : value;
93
+ }
94
+ return merged;
95
+ }
96
+ return override;
97
+ }
98
+ function mergeConfigObjects(base, override) {
99
+ const merged = mergeConfigValue(base, override);
100
+ return typeof merged === "object" && merged && !Array.isArray(merged)
101
+ ? merged
102
+ : undefined;
103
+ }
104
+ function parseHitlOverride(value) {
105
+ const record = asObject(value);
106
+ if (!record) {
107
+ return undefined;
108
+ }
109
+ const enabled = record.enabled === true;
110
+ const allow = Array.isArray(record.allow)
111
+ ? record.allow.filter((item) => item === "approve" || item === "edit" || item === "reject")
112
+ : undefined;
113
+ return {
114
+ enabled,
115
+ allow: allow && allow.length > 0 ? allow : undefined,
116
+ };
117
+ }
118
+ function applyToolBindingOverrides(tool, binding) {
119
+ const overrides = binding.overrides;
120
+ if (!overrides || Object.keys(overrides).length === 0) {
121
+ return tool;
122
+ }
123
+ const overrideConfig = mergeConfigObjects(asObject(overrides.config), asObject(overrides.execution)
124
+ ? {
125
+ execution: asObject(overrides.execution),
126
+ }
127
+ : undefined);
128
+ return {
129
+ ...tool,
130
+ ...(typeof overrides.name === "string" ? { name: overrides.name } : {}),
131
+ ...(typeof overrides.description === "string" ? { description: overrides.description } : {}),
132
+ ...(typeof overrides.implementationName === "string" ? { implementationName: overrides.implementationName } : {}),
133
+ ...(typeof overrides.inputSchemaRef === "string" ? { inputSchemaRef: overrides.inputSchemaRef } : {}),
134
+ ...(typeof overrides.embeddingModelRef === "string" ? { embeddingModelRef: overrides.embeddingModelRef } : {}),
135
+ ...(typeof overrides.backendOperation === "string" ? { backendOperation: overrides.backendOperation } : {}),
136
+ ...(typeof overrides.mcpRef === "string" ? { mcpRef: overrides.mcpRef } : {}),
137
+ ...(typeof overrides.subprocess === "boolean" ? { subprocess: overrides.subprocess } : {}),
138
+ ...(typeof overrides.retryable === "boolean" ? { retryable: overrides.retryable } : {}),
139
+ ...(overrides.hitl !== undefined ? { hitl: parseHitlOverride(overrides.hitl) } : {}),
140
+ ...(overrideConfig ? { config: mergeConfigObjects(tool.config, overrideConfig) } : {}),
141
+ };
142
+ }
143
+ function getAgentToolBindings(agent) {
144
+ if (agent.toolBindings && agent.toolBindings.length > 0) {
145
+ return agent.toolBindings;
146
+ }
147
+ return agent.toolRefs.map((ref) => ({ ref }));
148
+ }
149
+ export function requireTools(tools, bindings, ownerId) {
150
+ const compiled = bindings.flatMap((binding) => {
151
+ const targets = resolveToolTargets(tools, binding.ref);
80
152
  if (targets.length === 0) {
81
- throw new Error(`Agent ${ownerId} references missing tool ${ref}`);
153
+ throw new Error(`Agent ${ownerId} references missing tool ${binding.ref}`);
82
154
  }
83
- return targets.flatMap((tool) => compileTool(tool, tools));
155
+ return targets.flatMap((tool) => compileTool(applyToolBindingOverrides(tool, binding), tools));
84
156
  });
85
157
  const deduped = new Map();
86
158
  for (const tool of compiled) {
@@ -138,7 +210,7 @@ function compileSubagents(agent, agents, workspaceRoot, models, tools, compiledA
138
210
  function compileExecutionCore(agent, models, tools) {
139
211
  return {
140
212
  model: requireModel(models, agent.modelRef, agent.id),
141
- tools: requireTools(tools, agent.toolRefs, agent.id),
213
+ tools: requireTools(tools, getAgentToolBindings(agent), agent.id),
142
214
  systemPrompt: resolveSystemPrompt(agent),
143
215
  responseFormat: resolveResponseFormat(agent),
144
216
  contextSchema: resolveContextSchema(agent),
@@ -42,6 +42,17 @@ function validateWorkspaceResources(embeddings, mcpServers, models, vectorStores
42
42
  models.forEach((model) => validateModelObject(model, models));
43
43
  vectorStores.forEach((vectorStore) => validateVectorStoreObject(vectorStore));
44
44
  tools.forEach((tool) => validateToolObject(tool, tools));
45
+ tools.forEach((tool) => {
46
+ if (!tool.embeddingModelRef) {
47
+ return;
48
+ }
49
+ const embeddingModelId = tool.embeddingModelRef.startsWith("embedding-model/")
50
+ ? tool.embeddingModelRef.slice("embedding-model/".length)
51
+ : tool.embeddingModelRef;
52
+ if (!embeddings.has(embeddingModelId)) {
53
+ throw new Error(`Tool ${tool.id} references missing embedding model ${tool.embeddingModelRef}`);
54
+ }
55
+ });
45
56
  agents.forEach(validateAgent);
46
57
  validateTopology(agents);
47
58
  tools.forEach((tool) => {
@@ -91,6 +102,11 @@ export async function loadWorkspace(workspaceRoot, options = {}) {
91
102
  loaded.refs.set(`agent/${agent.id}`, agent);
92
103
  }
93
104
  const { embeddings, mcpServers, models, vectorStores, tools } = collectParsedResources(loaded.refs);
105
+ for (const agent of loaded.agents) {
106
+ for (const tool of agent.inlineTools ?? []) {
107
+ tools.set(tool.id, tool);
108
+ }
109
+ }
94
110
  await hydrateAgentMcpTools(loaded.agents, mcpServers, tools);
95
111
  const toolSourceRefs = collectToolSourceRefs(tools, loaded.agents, options);
96
112
  await ensureResourceSources(toolSourceRefs, workspaceRoot);