@botbotgo/agent-harness 0.0.42 → 0.0.43

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.
package/README.md CHANGED
@@ -15,7 +15,7 @@ What it provides:
15
15
 
16
16
  - a small runtime API centered on `createAgentHarness(...)`, `run(...)`, `subscribe(...)`, inspection methods, and `stop(...)`
17
17
  - YAML-defined runtime assembly for hosts, models, routing, recovery, concurrency, MCP, and maintenance policy
18
- - DeepAgents-first execution with LangChain v1 compatibility
18
+ - backend-adapted execution with current DeepAgents-first defaults and LangChain v1 compatibility
19
19
  - local `resources/tools/` and `resources/skills/` loading
20
20
  - persisted runs, threads, approvals, events, and resumable checkpoints
21
21
 
@@ -66,7 +66,7 @@ try {
66
66
  ## Feature List
67
67
 
68
68
  - Workspace runtime for multi-agent applications
69
- - DeepAgents-first execution with LangChain v1 compatibility
69
+ - Generic runtime contract with backend-adapted execution
70
70
  - YAML-defined host routing
71
71
  - Auto-discovered local tools and SKILL packages
72
72
  - MCP bridge support for agent-declared MCP servers
@@ -107,13 +107,15 @@ const result = await run(runtime, {
107
107
  });
108
108
  ```
109
109
 
110
- Each run creates or continues a persisted thread and returns `threadId`, `runId`, `state`, and `output`.
110
+ Each run creates or continues a persisted thread and returns `threadId`, `runId`, `state`, and the final user-facing output text.
111
111
 
112
- When the upstream LangChain v1 or DeepAgents graph expects extra invocation data, the runtime passes it through directly instead of inventing a parallel harness abstraction:
112
+ The preferred runtime-facing way to pass execution metadata is `invocation`:
113
113
 
114
- - `context` is forwarded as runtime context
115
- - `state` is merged into the invocation input
116
- - `files` is merged into the invocation input for state-backed DeepAgents files, memories, and skills
114
+ - `invocation.context` for request-scoped context
115
+ - `invocation.inputs` for additional structured runtime inputs
116
+ - `invocation.attachments` for attachment-like payloads that the active backend can interpret
117
+
118
+ For compatibility, `context`, `state`, and `files` are still accepted as existing aliases and are normalized into the same runtime invocation envelope.
117
119
 
118
120
  ### Let The Runtime Route
119
121
 
@@ -161,7 +163,7 @@ const approvals = await listApprovals(runtime, { status: "pending" });
161
163
  const approval = approvals[0] ? await getApproval(runtime, approvals[0].approvalId) : null;
162
164
  ```
163
165
 
164
- These methods return runtime-facing records, not raw persistence or backend objects.
166
+ These methods return runtime-facing records, not raw persistence or backend checkpoint objects.
165
167
 
166
168
  ### Bridge MCP Servers Into Agents
167
169
 
@@ -233,9 +235,26 @@ Put stable project context here. Do not use it as mutable long-term memory.
233
235
 
234
236
  ### `config/agents/*.yaml`
235
237
 
236
- Use agent YAML for upstream-facing agent assembly. Common fields include:
238
+ Prefer the generic agent form and declare the current execution backend explicitly:
239
+
240
+ ```yaml
241
+ apiVersion: agent-harness/v1alpha1
242
+ kind: Agent
243
+ metadata:
244
+ name: orchestra
245
+ spec:
246
+ modelRef: model/default
247
+ execution:
248
+ backend: deepagent
249
+ systemPrompt: Coordinate the request.
250
+ ```
251
+
252
+ `kind: DeepAgent` and `kind: LangChainAgent` remain supported as compatibility forms, but `kind: Agent` is the recommended product-facing entry point.
253
+
254
+ Common fields include:
237
255
 
238
256
  - `modelRef`
257
+ - `execution.backend`
239
258
  - `systemPrompt`
240
259
  - `tools`
241
260
  - `skills`
@@ -262,7 +281,7 @@ Keep runtime extension source under `resources/`. Keep tests outside the publish
262
281
 
263
282
  ## Design Notes
264
283
 
265
- - `agent-harness` should express LangChain v1 and DeepAgents concepts as directly as possible
284
+ - `agent-harness` should keep the public runtime contract generic while mapping cleanly onto current backend capabilities
266
285
  - agent-level execution behavior stays upstream
267
286
  - application-level orchestration and lifecycle management stays in the harness
268
287
  - checkpoint resume is treated as a system-managed runtime behavior, not a primary public abstraction
@@ -1,19 +1,20 @@
1
1
  # agent-harness feature: schema version for this declarative config object.
2
2
  apiVersion: agent-harness/v1alpha1
3
3
  # agent-harness feature: object type discriminator.
4
- # Available options today: `DeepAgent`, `LangChainAgent`.
5
- # `LangChainAgent` means this file compiles to the lightweight LangChain v1 execution path
6
- # rather than the upstream `createDeepAgent(...)` runtime.
7
- kind: LangChainAgent
4
+ # Prefer the generic `Agent` form and select the concrete execution backend under `spec.execution`.
5
+ kind: Agent
8
6
  metadata:
9
7
  # agent-harness feature: stable object id used for refs and host-agent selection.
10
8
  name: direct
11
9
  # agent-harness feature: human-readable summary for inventory and UI.
12
10
  description: Manual low-latency host for direct answers and lightweight inventory lookup. Do not use it as the default executor for tool-heavy or specialist-shaped tasks.
13
11
  spec:
14
- # ======================
15
- # LangChainAgent Features
16
- # ======================
12
+ execution:
13
+ # Current backend adapter for this host profile.
14
+ backend: langchain-v1
15
+ # =====================
16
+ # Runtime Agent Features
17
+ # =====================
17
18
  # Shared LangChain v1 / DeepAgents feature: model ref for the underlying LLM used by the
18
19
  # direct-response agent. This should point at a cheap, fast, general-purpose chat model,
19
20
  # because `direct` is intended to be the low-latency path for simple requests.
@@ -1,18 +1,20 @@
1
1
  # agent-harness feature: schema version for this declarative config object.
2
2
  apiVersion: agent-harness/v1alpha1
3
3
  # agent-harness feature: object type discriminator.
4
- # Available options today: `DeepAgent`, `LangChainAgent`.
5
- # `DeepAgent` means this file compiles to `createDeepAgent(...)` rather than the LangChain v1 agent path.
6
- kind: DeepAgent
4
+ # Prefer the generic `Agent` form and select the concrete execution backend under `spec.execution`.
5
+ kind: Agent
7
6
  metadata:
8
7
  # agent-harness feature: stable object id used for refs and default DeepAgents name inference.
9
8
  name: orchestra
10
9
  # agent-harness feature: human-readable summary for inventory and UI.
11
10
  description: Default execution host. Answer directly when possible, use local tools and skills first, and delegate only when a specialist is a better fit. Not a reflex delegation-only planner.
12
11
  spec:
13
- # ===================
14
- # DeepAgents Features
15
- # ===================
12
+ execution:
13
+ # Current backend adapter for this host profile.
14
+ backend: deepagent
15
+ # =====================
16
+ # Runtime Agent Features
17
+ # =====================
16
18
  # DeepAgents aligned feature: model ref for the underlying LLM used by `createDeepAgent(...)`.
17
19
  modelRef: model/default
18
20
  # Shared LangChain v1 / DeepAgents feature: checkpointer config passed into the upstream runtime.
@@ -1,9 +1,14 @@
1
1
  export type ExecutionMode = "deepagent" | "langchain-v1";
2
2
  export declare const AUTO_AGENT_ID = "auto";
3
+ export type RuntimeCapabilities = {
4
+ delegation?: boolean;
5
+ memory?: boolean;
6
+ };
3
7
  export type RunState = "running" | "waiting_for_approval" | "resuming" | "completed" | "failed";
4
8
  export type ParsedAgentObject = {
5
9
  id: string;
6
10
  executionMode: ExecutionMode;
11
+ capabilities?: RuntimeCapabilities;
7
12
  description: string;
8
13
  modelRef: string;
9
14
  runRoot?: string;
@@ -175,6 +180,10 @@ export type CompiledTool = {
175
180
  };
176
181
  export type CompiledAgentBinding = {
177
182
  agent: ParsedAgentObject;
183
+ adapter?: {
184
+ kind: string;
185
+ config: Record<string, unknown>;
186
+ };
178
187
  langchainAgentParams?: LangChainAgentParams;
179
188
  directAgentParams?: LangChainAgentParams;
180
189
  deepAgentParams?: DeepAgentParams;
@@ -182,6 +191,7 @@ export type CompiledAgentBinding = {
182
191
  runRoot: string;
183
192
  workspaceRoot?: string;
184
193
  hostFacing: boolean;
194
+ capabilities?: RuntimeCapabilities;
185
195
  checkpointer?: Record<string, unknown> | boolean;
186
196
  store?: Record<string, unknown>;
187
197
  };
@@ -239,11 +249,14 @@ export type RunResult = {
239
249
  runId: string;
240
250
  state: RunState;
241
251
  output: string;
252
+ finalMessageText?: string;
242
253
  interruptContent?: string;
243
254
  agentId?: string;
244
255
  approvalId?: string;
245
256
  pendingActionId?: string;
246
257
  delegationId?: string;
258
+ artifacts?: ArtifactRecord[];
259
+ metadata?: Record<string, unknown>;
247
260
  };
248
261
  export type RunListeners = {
249
262
  onChunk?: (chunk: string) => void | Promise<void>;
@@ -264,10 +277,17 @@ export type MessageContentPart = {
264
277
  image_url: string;
265
278
  };
266
279
  export type MessageContent = string | MessageContentPart[];
280
+ export type InvocationEnvelope = {
281
+ context?: Record<string, unknown>;
282
+ inputs?: Record<string, unknown>;
283
+ attachments?: Record<string, unknown>;
284
+ capabilities?: Record<string, unknown>;
285
+ };
267
286
  export type RunStartOptions = {
268
287
  agentId?: string;
269
288
  input: MessageContent;
270
289
  threadId?: string;
290
+ invocation?: InvocationEnvelope;
271
291
  context?: Record<string, unknown>;
272
292
  state?: Record<string, unknown>;
273
293
  files?: Record<string, unknown>;
@@ -285,6 +305,9 @@ export type RunOptions = RunStartOptions | RunDecisionOptions;
285
305
  export type HarnessStreamItem = {
286
306
  type: "event";
287
307
  event: HarnessEvent;
308
+ } | {
309
+ type: "result";
310
+ result: RunResult;
288
311
  } | {
289
312
  type: "content";
290
313
  threadId: string;
@@ -322,6 +345,7 @@ export type ThreadRunRecord = {
322
345
  runId: string;
323
346
  agentId: string;
324
347
  executionMode: string;
348
+ adapterKind?: string;
325
349
  createdAt: string;
326
350
  updatedAt: string;
327
351
  state: RunState;
@@ -362,13 +386,15 @@ export type ApprovalRecord = {
362
386
  pendingActionId: string;
363
387
  threadId: string;
364
388
  runId: string;
365
- toolCallId: string;
366
389
  toolName: string;
367
390
  status: "pending" | "approved" | "edited" | "rejected" | "expired";
368
391
  requestedAt: string;
369
392
  resolvedAt: string | null;
370
393
  allowedDecisions: Array<"approve" | "edit" | "reject">;
371
394
  inputPreview: Record<string, unknown>;
395
+ };
396
+ export type InternalApprovalRecord = ApprovalRecord & {
397
+ toolCallId: string;
372
398
  checkpointRef: string;
373
399
  eventRefs: string[];
374
400
  };
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.41";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.42";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.41";
1
+ export const AGENT_HARNESS_VERSION = "0.0.42";
@@ -1,4 +1,4 @@
1
- import type { ApprovalRecord, ArtifactListing, ArtifactRecord, DelegationRecord, HarnessEvent, RunState, ThreadSummary, ThreadRunRecord, TranscriptMessage } from "../contracts/types.js";
1
+ import type { ArtifactListing, ArtifactRecord, DelegationRecord, HarnessEvent, InternalApprovalRecord, RunState, ThreadSummary, ThreadRunRecord, TranscriptMessage } from "../contracts/types.js";
2
2
  type ThreadMeta = {
3
3
  threadId: string;
4
4
  workspaceId: string;
@@ -13,6 +13,7 @@ type RunMeta = {
13
13
  threadId: string;
14
14
  agentId: string;
15
15
  executionMode: string;
16
+ adapterKind?: string;
16
17
  createdAt: string;
17
18
  updatedAt: string;
18
19
  };
@@ -56,6 +57,7 @@ export declare class FilePersistence {
56
57
  runId: string;
57
58
  agentId: string;
58
59
  executionMode: string;
60
+ adapterKind?: string;
59
61
  createdAt: string;
60
62
  }): Promise<void>;
61
63
  setRunState(threadId: string, runId: string, state: RunState, checkpointRef?: string | null): Promise<void>;
@@ -66,14 +68,14 @@ export declare class FilePersistence {
66
68
  getThreadMeta(threadId: string): Promise<ThreadMeta | null>;
67
69
  listThreadRuns(threadId: string): Promise<ThreadRunRecord[]>;
68
70
  listRunEvents(threadId: string, runId: string): Promise<HarnessEvent[]>;
69
- listApprovals(): Promise<ApprovalRecord[]>;
70
- getApproval(approvalId: string): Promise<ApprovalRecord | null>;
71
- getRunApprovals(threadId: string, runId: string): Promise<ApprovalRecord[]>;
71
+ listApprovals(): Promise<InternalApprovalRecord[]>;
72
+ getApproval(approvalId: string): Promise<InternalApprovalRecord | null>;
73
+ getRunApprovals(threadId: string, runId: string): Promise<InternalApprovalRecord[]>;
72
74
  getRunMeta(threadId: string, runId: string): Promise<RunMeta>;
73
75
  getRunLifecycle(threadId: string, runId: string): Promise<Lifecycle>;
74
76
  listDelegations(): Promise<DelegationRecord[]>;
75
- createApproval(record: ApprovalRecord): Promise<void>;
76
- resolveApproval(threadId: string, runId: string, approvalId: string, status: ApprovalRecord["status"]): Promise<ApprovalRecord>;
77
+ createApproval(record: InternalApprovalRecord): Promise<void>;
78
+ resolveApproval(threadId: string, runId: string, approvalId: string, status: InternalApprovalRecord["status"]): Promise<InternalApprovalRecord>;
77
79
  createDelegation(record: DelegationRecord): Promise<void>;
78
80
  updateDelegation(threadId: string, runId: string, delegationId: string, patch: Partial<DelegationRecord>): Promise<DelegationRecord>;
79
81
  createArtifact(threadId: string, runId: string, artifact: ArtifactRecord, content: unknown): Promise<ArtifactRecord>;
@@ -54,6 +54,7 @@ export class FilePersistence {
54
54
  threadId: input.threadId,
55
55
  agentId: input.agentId,
56
56
  executionMode: input.executionMode,
57
+ adapterKind: input.adapterKind ?? input.executionMode,
57
58
  createdAt: input.createdAt,
58
59
  updatedAt: input.createdAt,
59
60
  };
@@ -218,6 +219,7 @@ export class FilePersistence {
218
219
  runId: meta.runId,
219
220
  agentId: meta.agentId,
220
221
  executionMode: meta.executionMode,
222
+ adapterKind: meta.adapterKind ?? meta.executionMode,
221
223
  createdAt: meta.createdAt,
222
224
  updatedAt: meta.updatedAt,
223
225
  state: lifecycle.state,
@@ -27,6 +27,7 @@ export declare class AgentRuntimeAdapter {
27
27
  private buildToolNameMapping;
28
28
  private buildAgentMessages;
29
29
  private buildInvocationRequest;
30
+ private buildStateSnapshot;
30
31
  private buildRawModelMessages;
31
32
  private resolveTools;
32
33
  private normalizeInterruptPolicy;
@@ -12,8 +12,9 @@ import { computeIncrementalOutput, extractAgentStep, extractInterruptPayload, ex
12
12
  import { wrapToolForExecution } from "./tool-hitl.js";
13
13
  import { resolveDeclaredMiddleware } from "./declared-middleware.js";
14
14
  import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
15
+ import { getBindingDeepAgentParams, getBindingInterruptCompatibilityRules, getBindingLangChainParams, getBindingMiddlewareConfigs, getBindingModelInit, getBindingPrimaryModel, getBindingPrimaryTools, getBindingSystemPrompt, isDeepAgentBinding, isLangChainBinding, } from "./support/compiled-binding.js";
15
16
  function countConfiguredTools(binding) {
16
- return binding.langchainAgentParams?.tools.length ?? binding.deepAgentParams?.tools.length ?? 0;
17
+ return getBindingPrimaryTools(binding).length;
17
18
  }
18
19
  const AGENT_INTERRUPT_SENTINEL_PREFIX = "__agent_harness_interrupt__:";
19
20
  const MODEL_SAFE_TOOL_NAME_PATTERN = /^[a-zA-Z0-9_-]+$/;
@@ -142,6 +143,11 @@ function hasCallableToolHandler(value) {
142
143
  const typed = value;
143
144
  return typeof typed.invoke === "function" || typeof typed.call === "function" || typeof typed.func === "function";
144
145
  }
146
+ function asRecord(value) {
147
+ return typeof value === "object" && value !== null && !Array.isArray(value)
148
+ ? { ...value }
149
+ : undefined;
150
+ }
145
151
  function normalizeResolvedToolSchema(resolvedTool) {
146
152
  const schema = resolvedTool.schema;
147
153
  if (schema && typeof schema.parse === "function" && "_def" in schema) {
@@ -172,10 +178,10 @@ export class AgentRuntimeAdapter {
172
178
  this.options = options;
173
179
  }
174
180
  resolveBindingTimeout(binding) {
175
- return resolveTimeoutMs(binding.langchainAgentParams?.model.init.timeout ?? binding.deepAgentParams?.model.init.timeout);
181
+ return resolveTimeoutMs(getBindingModelInit(binding)?.timeout);
176
182
  }
177
183
  resolveStreamIdleTimeout(binding) {
178
- const configuredIdleTimeout = resolveTimeoutMs(binding.langchainAgentParams?.model.init.streamIdleTimeout ?? binding.deepAgentParams?.model.init.streamIdleTimeout);
184
+ const configuredIdleTimeout = resolveTimeoutMs(getBindingModelInit(binding)?.streamIdleTimeout);
179
185
  if (configuredIdleTimeout) {
180
186
  return configuredIdleTimeout;
181
187
  }
@@ -284,35 +290,38 @@ export class AgentRuntimeAdapter {
284
290
  };
285
291
  }
286
292
  applyStrictToolJsonInstruction(binding) {
287
- if (binding.langchainAgentParams) {
293
+ if (isLangChainBinding(binding)) {
294
+ const params = getBindingLangChainParams(binding);
288
295
  return {
289
296
  ...binding,
290
297
  langchainAgentParams: {
291
- ...binding.langchainAgentParams,
292
- systemPrompt: [binding.langchainAgentParams.systemPrompt, STRICT_TOOL_JSON_INSTRUCTION].filter(Boolean).join("\n\n"),
298
+ ...params,
299
+ systemPrompt: [params.systemPrompt, STRICT_TOOL_JSON_INSTRUCTION].filter(Boolean).join("\n\n"),
293
300
  },
294
301
  };
295
302
  }
296
- if (binding.deepAgentParams) {
303
+ if (isDeepAgentBinding(binding)) {
304
+ const params = getBindingDeepAgentParams(binding);
297
305
  return {
298
306
  ...binding,
299
307
  deepAgentParams: {
300
- ...binding.deepAgentParams,
301
- systemPrompt: [binding.deepAgentParams.systemPrompt, STRICT_TOOL_JSON_INSTRUCTION].filter(Boolean).join("\n\n"),
308
+ ...params,
309
+ systemPrompt: [params.systemPrompt, STRICT_TOOL_JSON_INSTRUCTION].filter(Boolean).join("\n\n"),
302
310
  },
303
311
  };
304
312
  }
305
313
  return binding;
306
314
  }
307
315
  async synthesizeDeepAgentAnswer(binding, input, result) {
308
- if (!binding.deepAgentParams) {
316
+ const params = getBindingDeepAgentParams(binding);
317
+ if (!params) {
309
318
  return "";
310
319
  }
311
320
  const toolContext = extractToolFallbackContext(result);
312
321
  if (!toolContext) {
313
322
  return "";
314
323
  }
315
- const model = (await this.resolveModel(binding.deepAgentParams.model));
324
+ const model = (await this.resolveModel(params.model));
316
325
  if (!model?.invoke) {
317
326
  return "";
318
327
  }
@@ -365,6 +374,14 @@ export class AgentRuntimeAdapter {
365
374
  messages: this.buildAgentMessages(history, input),
366
375
  };
367
376
  }
377
+ buildStateSnapshot(result) {
378
+ const snapshot = { ...result };
379
+ delete snapshot.messages;
380
+ delete snapshot.__interrupt__;
381
+ delete snapshot.structuredResponse;
382
+ delete snapshot.files;
383
+ return Object.keys(snapshot).length > 0 ? snapshot : undefined;
384
+ }
368
385
  buildRawModelMessages(systemPrompt, history, input) {
369
386
  const messages = [];
370
387
  if (systemPrompt) {
@@ -424,14 +441,10 @@ export class AgentRuntimeAdapter {
424
441
  return compiled.size > 0 ? Object.fromEntries(compiled.entries()) : undefined;
425
442
  }
426
443
  resolveInterruptOn(binding) {
427
- if (binding.deepAgentParams) {
428
- return this.compileInterruptOn(binding.deepAgentParams.tools, binding.deepAgentParams.interruptOn);
429
- }
430
- return this.compileInterruptOn(binding.langchainAgentParams?.tools ?? [], binding.agent.langchainAgentConfig?.interruptOn);
444
+ return this.compileInterruptOn(getBindingPrimaryTools(binding), getBindingInterruptCompatibilityRules(binding));
431
445
  }
432
446
  async resolveMiddleware(binding, interruptOn) {
433
- const declarativeMiddleware = await resolveDeclaredMiddleware(binding.langchainAgentParams?.middleware ??
434
- binding.deepAgentParams?.middleware, {
447
+ const declarativeMiddleware = await resolveDeclaredMiddleware(getBindingMiddlewareConfigs(binding), {
435
448
  resolveModel: (model) => this.resolveModel(model),
436
449
  resolveCustom: this.options.declaredMiddlewareResolver,
437
450
  binding,
@@ -484,30 +497,31 @@ export class AgentRuntimeAdapter {
484
497
  })));
485
498
  }
486
499
  async create(binding) {
487
- if (binding.langchainAgentParams) {
500
+ if (isLangChainBinding(binding)) {
501
+ const params = getBindingLangChainParams(binding);
488
502
  const interruptOn = this.resolveInterruptOn(binding);
489
- const model = (await this.resolveModel(binding.langchainAgentParams.model));
490
- const tools = this.resolveTools(binding.langchainAgentParams.tools, binding);
503
+ const model = (await this.resolveModel(params.model));
504
+ const tools = this.resolveTools(params.tools, binding);
491
505
  if (tools.length > 0 && typeof model.bindTools !== "function") {
492
- throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${binding.langchainAgentParams.model.id} does not support tool binding.`);
506
+ throw new Error(`Agent ${binding.agent.id} configures ${tools.length} tool(s), but resolved model ${params.model.id} does not support tool binding.`);
493
507
  }
494
508
  return createAgent({
495
509
  model: model,
496
510
  tools: tools,
497
- systemPrompt: binding.langchainAgentParams.systemPrompt,
498
- stateSchema: binding.langchainAgentParams.stateSchema,
499
- responseFormat: binding.langchainAgentParams.responseFormat,
500
- contextSchema: binding.langchainAgentParams.contextSchema,
511
+ systemPrompt: params.systemPrompt,
512
+ stateSchema: params.stateSchema,
513
+ responseFormat: params.responseFormat,
514
+ contextSchema: params.contextSchema,
501
515
  middleware: (await this.resolveMiddleware(binding, interruptOn)),
502
516
  checkpointer: this.resolveCheckpointer(binding),
503
517
  store: this.options.storeResolver?.(binding),
504
- includeAgentName: binding.langchainAgentParams.includeAgentName,
505
- version: binding.langchainAgentParams.version,
506
- name: binding.langchainAgentParams.name,
507
- description: binding.langchainAgentParams.description,
518
+ includeAgentName: params.includeAgentName,
519
+ version: params.version,
520
+ name: params.name,
521
+ description: params.description,
508
522
  });
509
523
  }
510
- const params = binding.deepAgentParams;
524
+ const params = getBindingDeepAgentParams(binding);
511
525
  if (!params) {
512
526
  throw new Error(`Agent ${binding.agent.id} has no runnable params`);
513
527
  }
@@ -532,10 +546,8 @@ export class AgentRuntimeAdapter {
532
546
  return createDeepAgent(deepAgentConfig);
533
547
  }
534
548
  async route(input, primaryBinding, secondaryBinding, options = {}) {
535
- const routeModelConfig = primaryBinding.langchainAgentParams?.model ??
536
- primaryBinding.deepAgentParams?.model ??
537
- secondaryBinding.langchainAgentParams?.model ??
538
- secondaryBinding.deepAgentParams?.model;
549
+ const routeModelConfig = getBindingPrimaryModel(primaryBinding) ??
550
+ getBindingPrimaryModel(secondaryBinding);
539
551
  if (!routeModelConfig) {
540
552
  throw new Error("No router model configuration available");
541
553
  }
@@ -587,13 +599,21 @@ export class AgentRuntimeAdapter {
587
599
  throw new Error(emptyAssistantMessageFailure);
588
600
  }
589
601
  const output = visibleOutput || synthesizedOutput || toolFallback || JSON.stringify(result, null, 2);
602
+ const finalMessageText = sanitizeVisibleText(output);
590
603
  return {
591
604
  threadId,
592
605
  runId,
593
606
  agentId: binding.agent.id,
594
607
  state: Array.isArray(result.__interrupt__) && result.__interrupt__.length > 0 ? "waiting_for_approval" : "completed",
595
608
  interruptContent,
596
- output: sanitizeVisibleText(output),
609
+ output: finalMessageText,
610
+ finalMessageText,
611
+ metadata: {
612
+ ...(result.structuredResponse !== undefined ? { structuredResponse: result.structuredResponse } : {}),
613
+ ...(asRecord(result.files) ? { files: asRecord(result.files) } : {}),
614
+ ...(this.buildStateSnapshot(result) ? { stateSnapshot: this.buildStateSnapshot(result) } : {}),
615
+ upstreamResult: result,
616
+ },
597
617
  };
598
618
  }
599
619
  async *stream(binding, input, threadId, history = [], options = {}) {
@@ -601,9 +621,9 @@ export class AgentRuntimeAdapter {
601
621
  const invokeTimeoutMs = this.resolveBindingTimeout(binding);
602
622
  const streamIdleTimeoutMs = this.resolveStreamIdleTimeout(binding);
603
623
  const streamDeadlineAt = invokeTimeoutMs ? Date.now() + invokeTimeoutMs : undefined;
604
- if (binding.langchainAgentParams) {
605
- const langchainParams = binding.langchainAgentParams;
606
- const resolvedModel = (await this.resolveModel(binding.langchainAgentParams.model));
624
+ if (isLangChainBinding(binding)) {
625
+ const langchainParams = getBindingLangChainParams(binding);
626
+ const resolvedModel = (await this.resolveModel(langchainParams.model));
607
627
  const tools = this.resolveTools(langchainParams.tools, binding);
608
628
  const canUseDirectModelStream = tools.length === 0 || typeof resolvedModel.bindTools !== "function";
609
629
  const model = canUseDirectModelStream
@@ -615,7 +635,7 @@ export class AgentRuntimeAdapter {
615
635
  // agent loop and only adds an extra model round-trip before the runnable path.
616
636
  if (canUseDirectModelStream && typeof model.stream === "function") {
617
637
  let emitted = false;
618
- const stream = await this.withTimeout(() => model.stream(this.buildRawModelMessages(langchainParams.systemPrompt, history, input)), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "model stream start", "stream");
638
+ const stream = await this.withTimeout(() => model.stream(this.buildRawModelMessages(getBindingSystemPrompt(binding), history, input)), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "model stream start", "stream");
619
639
  for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "model stream", streamDeadlineAt, invokeTimeoutMs)) {
620
640
  const delta = readStreamDelta(chunk);
621
641
  if (delta) {
@@ -637,7 +657,7 @@ export class AgentRuntimeAdapter {
637
657
  const request = this.buildInvocationRequest(history, input, options);
638
658
  if (typeof runnable.streamEvents === "function") {
639
659
  const events = await this.withTimeout(() => runnable.streamEvents(request, { configurable: { thread_id: threadId }, version: "v2", ...(options.context ? { context: options.context } : {}) }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent streamEvents start", "stream");
640
- const allowVisibleStreamDeltas = Boolean(binding.langchainAgentParams);
660
+ const allowVisibleStreamDeltas = isLangChainBinding(binding);
641
661
  let emittedOutput = "";
642
662
  let emittedToolError = false;
643
663
  const seenTerminalOutputs = new Set();
@@ -662,7 +682,7 @@ export class AgentRuntimeAdapter {
662
682
  }
663
683
  }
664
684
  }
665
- if (binding.deepAgentParams) {
685
+ if (isDeepAgentBinding(binding)) {
666
686
  const stateStreamOutput = extractStateStreamOutput(event);
667
687
  if (stateStreamOutput) {
668
688
  const nextOutput = computeIncrementalOutput(emittedOutput, sanitizeVisibleText(stateStreamOutput));
@@ -702,7 +722,7 @@ export class AgentRuntimeAdapter {
702
722
  return;
703
723
  }
704
724
  }
705
- if (binding.langchainAgentParams && typeof runnable.stream === "function") {
725
+ if (isLangChainBinding(binding) && typeof runnable.stream === "function") {
706
726
  const stream = await this.withTimeout(() => runnable.stream(request, { configurable: { thread_id: threadId } }), computeRemainingTimeoutMs(streamDeadlineAt, invokeTimeoutMs), "agent stream start", "stream");
707
727
  let emitted = false;
708
728
  for await (const chunk of this.iterateWithTimeout(stream, streamIdleTimeoutMs, "agent stream", streamDeadlineAt, invokeTimeoutMs)) {
@@ -24,6 +24,8 @@ export declare class AgentHarnessRuntime {
24
24
  private readonly concurrencyConfig;
25
25
  private activeRunSlots;
26
26
  private readonly pendingRunSlots;
27
+ private toPublicApprovalRecord;
28
+ private normalizeInvocationEnvelope;
27
29
  private listHostBindings;
28
30
  private defaultRunRoot;
29
31
  private heuristicRoute;
@@ -15,6 +15,7 @@ import { FileBackedStore } from "./store.js";
15
15
  import { CheckpointMaintenanceLoop, discoverCheckpointMaintenanceTargets, readCheckpointMaintenanceConfig, } from "./checkpoint-maintenance.js";
16
16
  import { extractMessageText, normalizeMessageContent } from "../utils/message-content.js";
17
17
  import { createToolMcpServerFromTools, serveToolsOverStdioFromHarness } from "../mcp.js";
18
+ import { getBindingAdapterKind, getBindingPrimaryTools, getBindingStoreConfig } from "./support/compiled-binding.js";
18
19
  export class AgentHarnessRuntime {
19
20
  workspace;
20
21
  runtimeAdapterOptions;
@@ -39,6 +40,19 @@ export class AgentHarnessRuntime {
39
40
  concurrencyConfig;
40
41
  activeRunSlots = 0;
41
42
  pendingRunSlots = [];
43
+ toPublicApprovalRecord(approval) {
44
+ const { toolCallId: _toolCallId, checkpointRef: _checkpointRef, eventRefs: _eventRefs, ...publicApproval } = approval;
45
+ return publicApproval;
46
+ }
47
+ normalizeInvocationEnvelope(options) {
48
+ const invocation = options.invocation;
49
+ return {
50
+ context: invocation?.context ?? options.context,
51
+ state: invocation?.inputs ?? options.state,
52
+ files: invocation?.attachments ?? options.files,
53
+ invocation,
54
+ };
55
+ }
42
56
  listHostBindings() {
43
57
  return inferRoutingBindings(this.workspace).hostBindings;
44
58
  }
@@ -79,7 +93,7 @@ export class AgentHarnessRuntime {
79
93
  return requestedAgentId;
80
94
  }
81
95
  resolveStore(binding) {
82
- const storeConfig = binding?.deepAgentParams?.store ?? binding?.harnessRuntime.store;
96
+ const storeConfig = binding ? getBindingStoreConfig(binding) : undefined;
83
97
  const cacheKey = storeConfig ? JSON.stringify(storeConfig) : undefined;
84
98
  if (!storeConfig) {
85
99
  return this.defaultStore;
@@ -184,7 +198,7 @@ export class AgentHarnessRuntime {
184
198
  if (!binding) {
185
199
  throw new Error(`Unknown agent ${agentId}`);
186
200
  }
187
- return binding.langchainAgentParams?.tools ?? binding.deepAgentParams?.tools ?? [];
201
+ return getBindingPrimaryTools(binding);
188
202
  }
189
203
  resolveAgentTools(agentId) {
190
204
  const binding = this.getBinding(agentId);
@@ -259,10 +273,11 @@ export class AgentHarnessRuntime {
259
273
  return false;
260
274
  }
261
275
  return true;
262
- });
276
+ }).map((approval) => this.toPublicApprovalRecord(approval));
263
277
  }
264
278
  async getApproval(approvalId) {
265
- return this.persistence.getApproval(approvalId);
279
+ const approval = await this.persistence.getApproval(approvalId);
280
+ return approval ? this.toPublicApprovalRecord(approval) : null;
266
281
  }
267
282
  async createToolMcpServer(options) {
268
283
  const tools = this.resolveAgentTools(options.agentId).map(({ compiledTool, resolvedTool }) => ({
@@ -340,7 +355,8 @@ export class AgentHarnessRuntime {
340
355
  threadId,
341
356
  runId,
342
357
  agentId: binding.agent.id,
343
- executionMode: binding.agent.executionMode,
358
+ executionMode: getBindingAdapterKind(binding),
359
+ adapterKind: getBindingAdapterKind(binding),
344
360
  createdAt,
345
361
  });
346
362
  return { threadId, runId, createdAt };
@@ -500,6 +516,7 @@ export class AgentHarnessRuntime {
500
516
  }
501
517
  async dispatchRunListeners(stream, listeners) {
502
518
  let latestEvent;
519
+ let latestResult;
503
520
  let output = "";
504
521
  for await (const item of stream) {
505
522
  if (item.type === "event") {
@@ -507,6 +524,10 @@ export class AgentHarnessRuntime {
507
524
  await this.notifyListener(listeners.onEvent, item.event);
508
525
  continue;
509
526
  }
527
+ if (item.type === "result") {
528
+ latestResult = item.result;
529
+ continue;
530
+ }
510
531
  if (item.type === "content") {
511
532
  output += item.content;
512
533
  await this.notifyListener(listeners.onChunk, item.content);
@@ -531,6 +552,13 @@ export class AgentHarnessRuntime {
531
552
  if (!latestEvent) {
532
553
  throw new Error("run did not emit any events");
533
554
  }
555
+ if (latestResult) {
556
+ return {
557
+ ...latestResult,
558
+ output: latestResult.output || output,
559
+ finalMessageText: latestResult.finalMessageText ?? latestResult.output ?? output,
560
+ };
561
+ }
534
562
  const thread = await this.getThread(latestEvent.threadId);
535
563
  if (!thread) {
536
564
  throw new Error(`Unknown thread ${latestEvent.threadId}`);
@@ -561,6 +589,7 @@ export class AgentHarnessRuntime {
561
589
  }
562
590
  const releaseRunSlot = await this.acquireRunSlot();
563
591
  try {
592
+ const invocation = this.normalizeInvocationEnvelope(options);
564
593
  const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
565
594
  const binding = this.workspace.bindings.get(selectedAgentId);
566
595
  if (!binding) {
@@ -575,13 +604,13 @@ export class AgentHarnessRuntime {
575
604
  agentId: binding.agent.id,
576
605
  requestedAgentId: options.agentId ?? AUTO_AGENT_ID,
577
606
  selectedAgentId,
578
- executionMode: binding.agent.executionMode,
607
+ executionMode: getBindingAdapterKind(binding),
579
608
  });
580
609
  try {
581
610
  const actual = await this.invokeWithHistory(binding, options.input, threadId, runId, undefined, {
582
- context: options.context,
583
- state: options.state,
584
- files: options.files,
611
+ context: invocation.context,
612
+ state: invocation.state,
613
+ files: invocation.files,
585
614
  });
586
615
  const finalized = await this.finalizeContinuedRun(threadId, runId, options.input, actual, {
587
616
  previousState: null,
@@ -615,6 +644,7 @@ export class AgentHarnessRuntime {
615
644
  async *streamEvents(options) {
616
645
  const releaseRunSlot = await this.acquireRunSlot();
617
646
  try {
647
+ const invocation = this.normalizeInvocationEnvelope(options);
618
648
  const selectedAgentId = await this.resolveSelectedAgentId(options.input, options.agentId, options.threadId);
619
649
  const binding = this.workspace.bindings.get(selectedAgentId);
620
650
  if (!binding) {
@@ -644,9 +674,9 @@ export class AgentHarnessRuntime {
644
674
  let assistantOutput = "";
645
675
  const toolErrors = [];
646
676
  for await (const chunk of this.runtimeAdapter.stream(binding, options.input, threadId, priorHistory, {
647
- context: options.context,
648
- state: options.state,
649
- files: options.files,
677
+ context: invocation.context,
678
+ state: invocation.state,
679
+ files: invocation.files,
650
680
  })) {
651
681
  if (chunk) {
652
682
  const normalizedChunk = typeof chunk === "string"
@@ -669,6 +699,20 @@ export class AgentHarnessRuntime {
669
699
  type: "event",
670
700
  event: approvalRequest.event,
671
701
  };
702
+ yield {
703
+ type: "result",
704
+ result: {
705
+ threadId,
706
+ runId,
707
+ agentId: selectedAgentId,
708
+ state: "waiting_for_approval",
709
+ output: assistantOutput,
710
+ finalMessageText: assistantOutput,
711
+ interruptContent: normalizedChunk.content,
712
+ approvalId: approvalRequest.approval.approvalId,
713
+ pendingActionId: approvalRequest.approval.pendingActionId,
714
+ },
715
+ };
672
716
  return;
673
717
  }
674
718
  if (normalizedChunk.kind === "reasoning") {
@@ -728,6 +772,17 @@ export class AgentHarnessRuntime {
728
772
  }
729
773
  }
730
774
  await this.appendAssistantMessage(threadId, runId, assistantOutput);
775
+ yield {
776
+ type: "result",
777
+ result: {
778
+ threadId,
779
+ runId,
780
+ agentId: selectedAgentId,
781
+ state: "completed",
782
+ output: assistantOutput,
783
+ finalMessageText: assistantOutput,
784
+ },
785
+ };
731
786
  yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, "completed", {
732
787
  previousState: null,
733
788
  }) };
@@ -753,6 +808,17 @@ export class AgentHarnessRuntime {
753
808
  agentId: selectedAgentId,
754
809
  content: renderRuntimeFailure(error),
755
810
  };
811
+ yield {
812
+ type: "result",
813
+ result: {
814
+ threadId,
815
+ runId,
816
+ agentId: selectedAgentId,
817
+ state: "failed",
818
+ output: renderRuntimeFailure(error),
819
+ finalMessageText: renderRuntimeFailure(error),
820
+ },
821
+ };
756
822
  return;
757
823
  }
758
824
  try {
@@ -761,6 +827,15 @@ export class AgentHarnessRuntime {
761
827
  if (actual.output) {
762
828
  yield await this.emitOutputDeltaAndCreateItem(threadId, runId, selectedAgentId, actual.output);
763
829
  }
830
+ yield {
831
+ type: "result",
832
+ result: {
833
+ ...actual,
834
+ threadId,
835
+ runId,
836
+ agentId: selectedAgentId,
837
+ },
838
+ };
764
839
  yield { type: "event", event: await this.setRunStateAndEmit(threadId, runId, 4, actual.state, {
765
840
  previousState: null,
766
841
  }) };
@@ -779,6 +854,17 @@ export class AgentHarnessRuntime {
779
854
  agentId: selectedAgentId,
780
855
  content: renderRuntimeFailure(invokeError),
781
856
  };
857
+ yield {
858
+ type: "result",
859
+ result: {
860
+ threadId,
861
+ runId,
862
+ agentId: selectedAgentId,
863
+ state: "failed",
864
+ output: renderRuntimeFailure(invokeError),
865
+ finalMessageText: renderRuntimeFailure(invokeError),
866
+ },
867
+ };
782
868
  return;
783
869
  }
784
870
  }
@@ -1,4 +1,5 @@
1
1
  import { readSkillMetadata } from "./support/skill-metadata.js";
2
+ import { getBindingPrimaryTools } from "./support/compiled-binding.js";
2
3
  function listHostBindings(workspace) {
3
4
  return Array.from(workspace.bindings.values()).filter((binding) => binding.harnessRuntime.hostFacing);
4
5
  }
@@ -31,7 +32,7 @@ export function listAgentTools(workspace, agentId) {
31
32
  if (!binding) {
32
33
  return [];
33
34
  }
34
- return dedupeTools(binding.langchainAgentParams?.tools ?? binding.deepAgentParams?.tools ?? []);
35
+ return dedupeTools(getBindingPrimaryTools(binding));
35
36
  }
36
37
  export function listAgentSkills(workspace, agentId) {
37
38
  const binding = findAgentBinding(workspace, agentId);
@@ -0,0 +1,15 @@
1
+ import type { CompiledAgentBinding, CompiledModel, CompiledTool, DeepAgentParams, LangChainAgentParams } from "../../contracts/types.js";
2
+ export declare function getBindingAdapterKind(binding: CompiledAgentBinding): string;
3
+ export declare function getBindingAdapterConfig(binding: CompiledAgentBinding): Record<string, unknown>;
4
+ export declare function getBindingLangChainParams(binding: CompiledAgentBinding): LangChainAgentParams | undefined;
5
+ export declare function getBindingDeepAgentParams(binding: CompiledAgentBinding): DeepAgentParams | undefined;
6
+ export declare function isLangChainBinding(binding: CompiledAgentBinding): boolean;
7
+ export declare function isDeepAgentBinding(binding: CompiledAgentBinding): boolean;
8
+ export declare function getBindingPrimaryTools(binding: CompiledAgentBinding): CompiledTool[];
9
+ export declare function getBindingPrimaryModel(binding: CompiledAgentBinding): CompiledModel | undefined;
10
+ export declare function getBindingSystemPrompt(binding: CompiledAgentBinding): string | undefined;
11
+ export declare function getBindingMiddlewareConfigs(binding: CompiledAgentBinding): Array<Record<string, unknown>> | undefined;
12
+ export declare function getBindingInterruptCompatibilityRules(binding: CompiledAgentBinding): Record<string, boolean | object> | undefined;
13
+ export declare function getBindingModelInit(binding: CompiledAgentBinding): Record<string, unknown> | undefined;
14
+ export declare function getBindingStoreConfig(binding: CompiledAgentBinding): Record<string, unknown> | undefined;
15
+ export declare function bindingHasSubagents(binding: CompiledAgentBinding): boolean;
@@ -0,0 +1,56 @@
1
+ function asRecord(value) {
2
+ return typeof value === "object" && value !== null && !Array.isArray(value)
3
+ ? value
4
+ : undefined;
5
+ }
6
+ export function getBindingAdapterKind(binding) {
7
+ return binding.adapter?.kind ?? binding.agent.executionMode;
8
+ }
9
+ export function getBindingAdapterConfig(binding) {
10
+ return binding.adapter?.config ?? {};
11
+ }
12
+ export function getBindingLangChainParams(binding) {
13
+ const adapterParams = asRecord(getBindingAdapterConfig(binding).params);
14
+ if (getBindingAdapterKind(binding) === "langchain-v1" && adapterParams) {
15
+ return adapterParams;
16
+ }
17
+ return binding.langchainAgentParams;
18
+ }
19
+ export function getBindingDeepAgentParams(binding) {
20
+ const adapterParams = asRecord(getBindingAdapterConfig(binding).params);
21
+ if (getBindingAdapterKind(binding) === "deepagent" && adapterParams) {
22
+ return adapterParams;
23
+ }
24
+ return binding.deepAgentParams;
25
+ }
26
+ export function isLangChainBinding(binding) {
27
+ return getBindingAdapterKind(binding) === "langchain-v1" || Boolean(binding.langchainAgentParams);
28
+ }
29
+ export function isDeepAgentBinding(binding) {
30
+ return getBindingAdapterKind(binding) === "deepagent" || Boolean(binding.deepAgentParams);
31
+ }
32
+ export function getBindingPrimaryTools(binding) {
33
+ return binding.langchainAgentParams?.tools ?? binding.deepAgentParams?.tools ?? [];
34
+ }
35
+ export function getBindingPrimaryModel(binding) {
36
+ return binding.langchainAgentParams?.model ?? binding.deepAgentParams?.model;
37
+ }
38
+ export function getBindingSystemPrompt(binding) {
39
+ return binding.langchainAgentParams?.systemPrompt ?? binding.deepAgentParams?.systemPrompt;
40
+ }
41
+ export function getBindingMiddlewareConfigs(binding) {
42
+ return binding.langchainAgentParams?.middleware ?? binding.deepAgentParams?.middleware;
43
+ }
44
+ export function getBindingInterruptCompatibilityRules(binding) {
45
+ return binding.deepAgentParams?.interruptOn ??
46
+ binding.agent.langchainAgentConfig?.interruptOn;
47
+ }
48
+ export function getBindingModelInit(binding) {
49
+ return getBindingPrimaryModel(binding)?.init;
50
+ }
51
+ export function getBindingStoreConfig(binding) {
52
+ return binding.deepAgentParams?.store ?? binding.harnessRuntime.store;
53
+ }
54
+ export function bindingHasSubagents(binding) {
55
+ return (binding.deepAgentParams?.subagents.length ?? 0) > 0;
56
+ }
@@ -1,4 +1,4 @@
1
- import type { ApprovalRecord, HarnessEvent, WorkspaceBundle } from "../../contracts/types.js";
1
+ import type { HarnessEvent, InternalApprovalRecord, WorkspaceBundle } from "../../contracts/types.js";
2
2
  export declare function renderRuntimeFailure(error: unknown): string;
3
3
  export declare function renderToolFailure(toolName: string, output: unknown): string;
4
4
  export declare function parseInterruptContent(content: string): {
@@ -19,7 +19,7 @@ export declare function heuristicRoute(input: string, primaryBinding?: {
19
19
  };
20
20
  }): string;
21
21
  export declare function createHarnessEvent(threadId: string, runId: string, sequence: number, eventType: string, payload: Record<string, unknown>, source?: HarnessEvent["source"]): HarnessEvent;
22
- export declare function createPendingApproval(threadId: string, runId: string, checkpointRef: string, input: string, interruptContent?: string): ApprovalRecord;
22
+ export declare function createPendingApproval(threadId: string, runId: string, checkpointRef: string, input: string, interruptContent?: string): InternalApprovalRecord;
23
23
  export declare function inferRoutingBindings(workspace: WorkspaceBundle): {
24
24
  primaryBinding: import("../../contracts/types.js").CompiledAgentBinding;
25
25
  secondaryBinding: import("../../contracts/types.js").CompiledAgentBinding | undefined;
@@ -1,4 +1,5 @@
1
1
  import { createPersistentId } from "../../utils/id.js";
2
+ import { isDelegationCapableBinding } from "../../workspace/support/agent-capabilities.js";
2
3
  export function renderRuntimeFailure(error) {
3
4
  const message = error instanceof Error ? error.message : String(error);
4
5
  return `runtime_error=${message}`.trim();
@@ -131,20 +132,21 @@ export function inferRoutingBindings(workspace) {
131
132
  const hostBindings = Array.from(workspace.bindings.values()).filter((binding) => binding.harnessRuntime.hostFacing);
132
133
  const researchBinding = hostBindings.find((binding) => binding.agent.id === "research-lite" || binding.agent.id === "research");
133
134
  const directBinding = hostBindings.find((binding) => binding.agent.id === "direct");
134
- const langchainHost = hostBindings.find((binding) => binding.agent.executionMode === "langchain-v1" && binding.agent.id !== researchBinding?.agent.id);
135
- const deepagentHosts = hostBindings.filter((binding) => binding.agent.executionMode === "deepagent");
136
- const defaultDeepagentHost = deepagentHosts.find((binding) => binding.agent.id === "orchestra") ??
137
- deepagentHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
138
- deepagentHosts[0];
139
- const deepagentWithSubagents = deepagentHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
140
- deepagentHosts[0];
141
- const primaryBinding = defaultDeepagentHost ?? directBinding ?? langchainHost ?? hostBindings[0];
142
- const secondaryBinding = langchainHost && langchainHost.agent.id !== primaryBinding?.agent.id
143
- ? langchainHost
135
+ const delegationHosts = hostBindings.filter((binding) => isDelegationCapableBinding(binding));
136
+ const lightweightHosts = hostBindings.filter((binding) => !isDelegationCapableBinding(binding));
137
+ const defaultOrchestratingHost = hostBindings.find((binding) => binding.agent.id === "orchestra") ??
138
+ delegationHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
139
+ delegationHosts[0];
140
+ const delegationPreferredSecondary = delegationHosts.find((binding) => (binding.deepAgentParams?.subagents.length ?? 0) > 0) ??
141
+ delegationHosts[0];
142
+ const genericLightweightHost = lightweightHosts.find((binding) => binding.agent.id !== researchBinding?.agent.id);
143
+ const primaryBinding = defaultOrchestratingHost ?? directBinding ?? genericLightweightHost ?? hostBindings[0];
144
+ const secondaryBinding = genericLightweightHost && genericLightweightHost.agent.id !== primaryBinding?.agent.id
145
+ ? genericLightweightHost
144
146
  : directBinding && directBinding.agent.id !== primaryBinding?.agent.id
145
147
  ? directBinding
146
- : deepagentWithSubagents && deepagentWithSubagents.agent.id !== primaryBinding?.agent.id
147
- ? deepagentWithSubagents
148
+ : delegationPreferredSecondary && delegationPreferredSecondary.agent.id !== primaryBinding?.agent.id
149
+ ? delegationPreferredSecondary
148
150
  : hostBindings.find((binding) => binding.agent.id !== primaryBinding?.agent.id);
149
151
  return { primaryBinding, secondaryBinding, researchBinding, hostBindings };
150
152
  }
@@ -1,6 +1,7 @@
1
1
  import path from "node:path";
2
2
  import { getSkillInheritancePolicy, resolveToolTargets } from "../extensions.js";
3
3
  import { compileModel, compileTool } from "./resource-compilers.js";
4
+ import { inferAgentCapabilities } from "./support/agent-capabilities.js";
4
5
  import { discoverSkillPaths } from "./support/discovery.js";
5
6
  import { compileAgentMemories, getRuntimeDefaults, resolvePromptValue, resolveRefId } from "./support/workspace-ref-utils.js";
6
7
  const WORKSPACE_BOUNDARY_GUIDANCE = "Keep repository and file exploration bounded to the current workspace root unless the user explicitly asks for broader host or filesystem access. " +
@@ -168,10 +169,21 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
168
169
  : path.join(workspaceRoot, "run-data");
169
170
  const base = {
170
171
  agent,
172
+ adapter: {
173
+ kind: agent.executionMode,
174
+ config: agent.executionMode === "deepagent"
175
+ ? {
176
+ deepAgent: true,
177
+ }
178
+ : {
179
+ langchainV1: true,
180
+ },
181
+ },
171
182
  harnessRuntime: {
172
183
  runRoot,
173
184
  workspaceRoot,
174
185
  hostFacing: !internalSubagent,
186
+ capabilities: inferAgentCapabilities(agent),
175
187
  ...(checkpointer ? { checkpointer: checkpointer.config } : {}),
176
188
  ...(store ? { store: store.config } : {}),
177
189
  },
@@ -194,37 +206,50 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
194
206
  };
195
207
  return {
196
208
  ...base,
209
+ adapter: {
210
+ kind: "langchain-v1",
211
+ config: {
212
+ params: langchainAgentParams,
213
+ },
214
+ },
197
215
  langchainAgentParams,
198
216
  directAgentParams: langchainAgentParams,
199
217
  };
200
218
  }
219
+ const deepAgentParams = {
220
+ model: compiledAgentModel,
221
+ tools: requireTools(tools, agent.toolRefs, agent.id),
222
+ systemPrompt: resolveSystemPrompt(agent),
223
+ responseFormat: agent.deepAgentConfig?.responseFormat,
224
+ contextSchema: agent.deepAgentConfig?.contextSchema,
225
+ middleware: compileMiddlewareConfigs(agent.deepAgentConfig?.middleware, models, agent.id),
226
+ description: agent.description,
227
+ subagents: agent.subagentRefs.map((ref) => {
228
+ const subagent = agents.get(resolveRefId(ref));
229
+ if (!subagent) {
230
+ throw new Error(`Missing subagent ${ref} for agent ${agent.id}`);
231
+ }
232
+ return buildSubagent(subagent, workspaceRoot, models, tools, compiledAgentSkills, compiledAgentModel, compiledAgentMemory);
233
+ }),
234
+ interruptOn: resolveInterruptOn(agent),
235
+ ...(backend ? { backend: backend.config } : {}),
236
+ ...(store ? { store: store.config } : {}),
237
+ name: resolveAgentRuntimeName(agent),
238
+ memory: compiledAgentMemory,
239
+ skills: compiledAgentSkills,
240
+ generalPurposeAgent: typeof agent.deepAgentConfig?.generalPurposeAgent === "boolean" ? agent.deepAgentConfig.generalPurposeAgent : undefined,
241
+ taskDescription: typeof agent.deepAgentConfig?.taskDescription === "string" && agent.deepAgentConfig.taskDescription.trim()
242
+ ? agent.deepAgentConfig.taskDescription
243
+ : undefined,
244
+ };
201
245
  return {
202
246
  ...base,
203
- deepAgentParams: {
204
- model: compiledAgentModel,
205
- tools: requireTools(tools, agent.toolRefs, agent.id),
206
- systemPrompt: resolveSystemPrompt(agent),
207
- responseFormat: agent.deepAgentConfig?.responseFormat,
208
- contextSchema: agent.deepAgentConfig?.contextSchema,
209
- middleware: compileMiddlewareConfigs(agent.deepAgentConfig?.middleware, models, agent.id),
210
- description: agent.description,
211
- subagents: agent.subagentRefs.map((ref) => {
212
- const subagent = agents.get(resolveRefId(ref));
213
- if (!subagent) {
214
- throw new Error(`Missing subagent ${ref} for agent ${agent.id}`);
215
- }
216
- return buildSubagent(subagent, workspaceRoot, models, tools, compiledAgentSkills, compiledAgentModel, compiledAgentMemory);
217
- }),
218
- interruptOn: resolveInterruptOn(agent),
219
- ...(backend ? { backend: backend.config } : {}),
220
- ...(store ? { store: store.config } : {}),
221
- name: resolveAgentRuntimeName(agent),
222
- memory: compiledAgentMemory,
223
- skills: compiledAgentSkills,
224
- generalPurposeAgent: typeof agent.deepAgentConfig?.generalPurposeAgent === "boolean" ? agent.deepAgentConfig.generalPurposeAgent : undefined,
225
- taskDescription: typeof agent.deepAgentConfig?.taskDescription === "string" && agent.deepAgentConfig.taskDescription.trim()
226
- ? agent.deepAgentConfig.taskDescription
227
- : undefined,
247
+ adapter: {
248
+ kind: "deepagent",
249
+ config: {
250
+ params: deepAgentParams,
251
+ },
228
252
  },
253
+ deepAgentParams,
229
254
  };
230
255
  }
@@ -88,6 +88,8 @@ function normalizeNamedResourceSpec(document, kind) {
88
88
  }
89
89
  function normalizeKind(kind) {
90
90
  switch (kind) {
91
+ case "Agent":
92
+ return "agent";
91
93
  case "LangChainAgent":
92
94
  return "langchain-agent";
93
95
  case "DeepAgent":
@@ -209,6 +211,37 @@ function readObjectArray(items) {
209
211
  .map((item) => ({ ...item }));
210
212
  return records.length > 0 ? records : undefined;
211
213
  }
214
+ function readCapabilities(value) {
215
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
216
+ return undefined;
217
+ }
218
+ const typed = value;
219
+ const capabilities = {
220
+ ...(typeof typed.delegation === "boolean" ? { delegation: typed.delegation } : {}),
221
+ ...(typeof typed.memory === "boolean" ? { memory: typed.memory } : {}),
222
+ };
223
+ return Object.keys(capabilities).length > 0 ? capabilities : undefined;
224
+ }
225
+ function readExecutionConfig(value) {
226
+ return typeof value === "object" && value !== null && !Array.isArray(value)
227
+ ? { ...value }
228
+ : undefined;
229
+ }
230
+ function resolveExecutionBackend(item, current) {
231
+ const execution = readExecutionConfig(item.execution) ?? readExecutionConfig(current?.execution);
232
+ const backend = typeof execution?.backend === "string"
233
+ ? execution.backend.trim().toLowerCase()
234
+ : typeof execution?.mode === "string"
235
+ ? execution.mode.trim().toLowerCase()
236
+ : undefined;
237
+ if (backend === "langchain-v1" || backend === "langchain" || backend === "langchain-agent") {
238
+ return "langchain-v1";
239
+ }
240
+ if (backend === "deepagent" || backend === "deepagents") {
241
+ return "deepagent";
242
+ }
243
+ return undefined;
244
+ }
212
245
  function readSharedAgentConfig(item) {
213
246
  const middleware = readMiddlewareArray(item.middleware);
214
247
  return {
@@ -244,12 +277,16 @@ export function parseAgentItem(item, sourcePath) {
244
277
  const subagentRefs = readRefArray(item.subagents);
245
278
  const subagentPathRefs = readPathArray(item.subagents);
246
279
  const kind = typeof item.kind === "string" ? item.kind : "agent";
247
- const executionMode = String((kind === "langchain-agent" ? "langchain-v1" : undefined) ??
280
+ const executionMode = String(resolveExecutionBackend(item) ??
281
+ (kind === "langchain-agent" ? "langchain-v1" : undefined) ??
248
282
  (kind === "deepagent" ? "deepagent" : undefined) ??
249
283
  "deepagent");
250
284
  return {
251
285
  id: String(item.id),
252
286
  executionMode: executionMode,
287
+ capabilities: readCapabilities(item.capabilities) ?? (executionMode === "deepagent"
288
+ ? { delegation: true, memory: true }
289
+ : { delegation: false, memory: false }),
253
290
  description: String(item.description ?? ""),
254
291
  modelRef: readSingleRef(item.modelRef) ?? "",
255
292
  runRoot: typeof item.runRoot === "string" ? item.runRoot : undefined,
@@ -469,7 +506,7 @@ async function readNamedModelItems(root) {
469
506
  return records;
470
507
  }
471
508
  function isAgentKind(kind) {
472
- return kind === "deepagent" || kind === "langchain-agent";
509
+ return kind === "deepagent" || kind === "langchain-agent" || kind === "agent";
473
510
  }
474
511
  async function readConfigAgentItems(configRoot) {
475
512
  const records = await readYamlItems(configRoot, "agents", { recursive: true });
@@ -517,6 +554,10 @@ export async function readToolModuleItems(root) {
517
554
  return records;
518
555
  }
519
556
  function inferExecutionMode(item, current) {
557
+ const explicitExecution = resolveExecutionBackend(item, current);
558
+ if (explicitExecution) {
559
+ return explicitExecution;
560
+ }
520
561
  const kind = typeof item.kind === "string" ? item.kind : typeof current?.kind === "string" ? current.kind : undefined;
521
562
  if (kind === "langchain-agent") {
522
563
  return "langchain-v1";
@@ -0,0 +1,7 @@
1
+ import type { CompiledAgentBinding, ParsedAgentObject, RuntimeCapabilities } from "../../contracts/types.js";
2
+ export declare function inferAgentCapabilities(agent: ParsedAgentObject): RuntimeCapabilities;
3
+ export declare function inferBindingCapabilities(binding: CompiledAgentBinding): RuntimeCapabilities;
4
+ export declare function isDelegationCapableAgent(agent: ParsedAgentObject): boolean;
5
+ export declare function isMemoryCapableAgent(agent: ParsedAgentObject): boolean;
6
+ export declare function isDelegationCapableBinding(binding: CompiledAgentBinding): boolean;
7
+ export declare function isMemoryCapableBinding(binding: CompiledAgentBinding): boolean;
@@ -0,0 +1,30 @@
1
+ function normalizeCapabilities(capabilities) {
2
+ return {
3
+ delegation: capabilities?.delegation === true,
4
+ memory: capabilities?.memory === true,
5
+ };
6
+ }
7
+ export function inferAgentCapabilities(agent) {
8
+ if (agent.capabilities) {
9
+ return normalizeCapabilities(agent.capabilities);
10
+ }
11
+ return {
12
+ delegation: agent.executionMode === "deepagent",
13
+ memory: agent.executionMode === "deepagent",
14
+ };
15
+ }
16
+ export function inferBindingCapabilities(binding) {
17
+ return normalizeCapabilities(binding.harnessRuntime.capabilities ?? binding.agent.capabilities ?? inferAgentCapabilities(binding.agent));
18
+ }
19
+ export function isDelegationCapableAgent(agent) {
20
+ return inferAgentCapabilities(agent).delegation === true;
21
+ }
22
+ export function isMemoryCapableAgent(agent) {
23
+ return inferAgentCapabilities(agent).memory === true;
24
+ }
25
+ export function isDelegationCapableBinding(binding) {
26
+ return inferBindingCapabilities(binding).delegation === true;
27
+ }
28
+ export function isMemoryCapableBinding(binding) {
29
+ return inferBindingCapabilities(binding).memory === true;
30
+ }
@@ -1,3 +1,4 @@
1
+ import { isDelegationCapableAgent, isMemoryCapableAgent } from "./support/agent-capabilities.js";
1
2
  const allowedExecutionModes = new Set(["deepagent", "langchain-v1"]);
2
3
  function hasPromptContent(value) {
3
4
  return typeof value === "string" && value.trim().length > 0;
@@ -52,14 +53,14 @@ export function validateAgent(agent) {
52
53
  if (!agent.description.trim()) {
53
54
  throw new Error(`Agent ${agent.id} description must not be empty`);
54
55
  }
55
- if (agent.executionMode === "langchain-v1" && (agent.subagentRefs.length > 0 || agent.subagentPathRefs.length > 0)) {
56
- throw new Error(`Agent ${agent.id} cannot define subagents unless execution.mode is deepagent`);
56
+ if (!isDelegationCapableAgent(agent) && (agent.subagentRefs.length > 0 || agent.subagentPathRefs.length > 0)) {
57
+ throw new Error(`Agent ${agent.id} cannot define subagents unless it uses a delegation-capable backend`);
57
58
  }
58
- if (agent.executionMode === "langchain-v1" && agent.memorySources.length > 0) {
59
- throw new Error(`Agent ${agent.id} cannot define memory unless execution.mode is deepagent`);
59
+ if (!isMemoryCapableAgent(agent) && agent.memorySources.length > 0) {
60
+ throw new Error(`Agent ${agent.id} cannot define memory unless it uses a memory-capable backend`);
60
61
  }
61
- if ((agent.subagentRefs.length > 0 || agent.subagentPathRefs.length > 0) && agent.executionMode !== "deepagent") {
62
- throw new Error(`Agent ${agent.id} must use deepagent execution when subagents are defined`);
62
+ if ((agent.subagentRefs.length > 0 || agent.subagentPathRefs.length > 0) && !isDelegationCapableAgent(agent)) {
63
+ throw new Error(`Agent ${agent.id} must use a delegation-capable backend when subagents are defined`);
63
64
  }
64
65
  validateCheckpointerConfig(agent);
65
66
  validateMiddlewareConfig(agent);
@@ -83,8 +84,8 @@ export function validateTopology(agents) {
83
84
  if (!referencedSubagentIds.has(agent.id)) {
84
85
  continue;
85
86
  }
86
- if (agent.executionMode !== "deepagent") {
87
- throw new Error(`Subagent ${agent.id} must use deepagent execution`);
87
+ if (!isDelegationCapableAgent(agent)) {
88
+ throw new Error(`Subagent ${agent.id} must use a delegation-capable backend`);
88
89
  }
89
90
  if (!hasPromptContent(agent.deepAgentConfig?.systemPrompt)) {
90
91
  throw new Error(`Subagent ${agent.id} requires deepagent.systemPrompt`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.42",
3
+ "version": "0.0.43",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",