@botbotgo/agent-harness 0.0.48 → 0.0.49

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
@@ -4,20 +4,26 @@
4
4
 
5
5
  `@botbotgo/agent-harness` is a workspace-shaped application runtime for real agent products.
6
6
 
7
- It is not a new agent framework. It is the layer that sits around LangChain v1 and DeepAgents so an application can be assembled, configured, operated, and recovered as one runtime.
7
+ It is not a new agent framework. It is the runtime layer around LangChain v1 and DeepAgents that turns one workspace into one operable application runtime.
8
8
 
9
- The package is built for the gap between:
9
+ The product boundary is:
10
10
 
11
- - agent execution semantics owned by LangChain v1 and DeepAgents
12
- - application runtime concerns such as persisted threads, approvals, recovery, routing, maintenance, and local resource loading
11
+ - LangChain v1 and DeepAgents own agent execution semantics
12
+ - `agent-harness` owns application runtime semantics
13
13
 
14
- What it provides:
14
+ That means:
15
15
 
16
- - a small runtime API centered on `createAgentHarness(...)`, `run(...)`, `subscribe(...)`, inspection methods, and `stop(...)`
17
- - YAML-defined runtime assembly for hosts, models, routing, recovery, concurrency, MCP, and maintenance policy
18
- - backend-adapted execution with a generic runtime contract and current LangChain v1 / DeepAgents adapters
19
- - local `resources/tools/` and `resources/skills/` loading
20
- - persisted runs, threads, approvals, events, queued tasks, and resumable checkpoints
16
+ - public API stays small
17
+ - complex assembly and policy live in YAML
18
+ - runtime lifecycle stays stable even if the backend implementation changes
19
+
20
+ What the runtime provides:
21
+
22
+ - `createAgentHarness(...)`, `run(...)`, `subscribe(...)`, inspection methods, and `stop(...)`
23
+ - YAML-defined workspace assembly for routing, models, tools, stores, MCP, recovery, and maintenance
24
+ - backend-adapted execution with current LangChain v1 and DeepAgents adapters
25
+ - local `resources/tools/` and `resources/skills/` discovery
26
+ - persisted threads, runs, approvals, events, queue state, and recovery metadata
21
27
 
22
28
  ## Quick Start
23
29
 
@@ -32,8 +38,8 @@ Workspace layout:
32
38
  ```text
33
39
  your-workspace/
34
40
  config/
35
- agent-context.md
36
41
  workspace.yaml
42
+ agent-context.md
37
43
  models.yaml
38
44
  embedding-models.yaml
39
45
  vector-stores.yaml
@@ -71,15 +77,14 @@ try {
71
77
  ## Feature List
72
78
 
73
79
  - Workspace runtime for multi-agent applications
74
- - Generic runtime contract with backend-adapted execution
75
- - YAML-defined host routing
80
+ - Small public runtime contract
81
+ - YAML-defined host routing and runtime policy
82
+ - LangChain v1 and DeepAgents backend adaptation
76
83
  - Auto-discovered local tools and SKILL packages
77
84
  - MCP bridge support for agent-declared MCP servers
78
85
  - MCP server support for exposing harness tools outward
79
- - Persisted threads, runs, approvals, and lifecycle events
80
- - Recovery policy and resumable checkpoints
81
- - Background checkpoint maintenance
82
- - Runtime-level concurrency control and queued-run persistence
86
+ - Persisted threads, runs, approvals, lifecycle events, and queued runs
87
+ - Runtime-managed recovery and checkpoint maintenance
83
88
 
84
89
  ## How To Use
85
90
 
@@ -91,8 +96,6 @@ import { AgentHarnessRuntime, createAgentHarness } from "@botbotgo/agent-harness
91
96
  const runtime: AgentHarnessRuntime = await createAgentHarness("/absolute/path/to/workspace");
92
97
  ```
93
98
 
94
- You can also create a runtime from a precompiled `WorkspaceBundle`.
95
-
96
99
  `createAgentHarness(...)` loads one workspace, resolves `resources/`, initializes persistence under `runRoot`, and starts runtime maintenance.
97
100
 
98
101
  ### Run A Request
@@ -103,25 +106,28 @@ import { run } from "@botbotgo/agent-harness";
103
106
  const result = await run(runtime, {
104
107
  agentId: "orchestra",
105
108
  input: "Summarize the runtime design.",
106
- context: {
107
- requestId: "req-123",
108
- },
109
- state: {
110
- visitCount: 1,
109
+ invocation: {
110
+ context: {
111
+ requestId: "req-123",
112
+ },
113
+ inputs: {
114
+ visitCount: 1,
115
+ },
116
+ attachments: {
117
+ "/tmp/spec.md": { content: "draft" },
118
+ },
111
119
  },
112
120
  });
113
121
  ```
114
122
 
115
123
  Each run creates or continues a persisted thread and returns `threadId`, `runId`, `state`, and the final user-facing output text.
116
124
 
117
- The preferred runtime-facing way to pass execution metadata is `invocation`:
125
+ Use `invocation` as the runtime-facing request envelope:
118
126
 
119
- - `invocation.context` for request-scoped context
127
+ - `invocation.context` for request-scoped execution context
120
128
  - `invocation.inputs` for additional structured runtime inputs
121
129
  - `invocation.attachments` for attachment-like payloads that the active backend can interpret
122
130
 
123
- For compatibility, `context`, `state`, and `files` are still accepted as existing aliases and are normalized into the same runtime invocation envelope.
124
-
125
131
  ### Let The Runtime Route
126
132
 
127
133
  ```ts
@@ -152,7 +158,7 @@ const result = await run(runtime, {
152
158
 
153
159
  `subscribe(...)` is a read-only observer surface over stored lifecycle events.
154
160
 
155
- The event stream includes:
161
+ The runtime event stream includes:
156
162
 
157
163
  - `run.created`
158
164
  - `run.queued`
@@ -183,14 +189,18 @@ These methods return runtime-facing records, not raw persistence or backend chec
183
189
  ### Bridge MCP Servers Into Agents
184
190
 
185
191
  ```yaml
192
+ apiVersion: agent-harness/v1alpha1
193
+ kind: Agent
194
+ metadata:
195
+ name: orchestra
186
196
  spec:
197
+ execution:
198
+ backend: deepagent
199
+ modelRef: model/default
187
200
  mcpServers:
188
201
  - name: browser
189
202
  command: node
190
203
  args: ["./mcp-browser-server.mjs"]
191
- - name: docs
192
- transport: http
193
- url: https://example.com/mcp
194
204
  ```
195
205
 
196
206
  The runtime discovers MCP tools, filters them through agent configuration, and exposes them like other tools.
@@ -214,6 +224,11 @@ await stop(runtime);
214
224
 
215
225
  ## How To Configure
216
226
 
227
+ Use Kubernetes-style YAML:
228
+
229
+ - collection files use `apiVersion`, plural `kind`, and `spec: []`
230
+ - single-object files use `apiVersion`, singular `kind`, `metadata`, and `spec`
231
+
217
232
  Core workspace files:
218
233
 
219
234
  - `config/workspace.yaml`
@@ -226,24 +241,14 @@ Core workspace files:
226
241
  - `config/mcp.yaml`
227
242
  - `config/agents/direct.yaml`
228
243
  - `config/agents/orchestra.yaml`
229
- - `resources/package.json`
230
244
  - `resources/tools/`
231
245
  - `resources/skills/`
232
246
 
233
- Use Kubernetes-style YAML:
234
-
235
- - collection files use `apiVersion`, plural `kind`, and `spec: []`
236
- - single-object files use `apiVersion`, singular `kind`, `metadata`, and `spec`
237
-
238
- Use distinct names for named objects such as models, stores, checkpointers, tools, and MCP servers.
239
-
240
247
  ### Client-Configurable YAML Reference
241
248
 
242
- This section is the client-facing explanation of what can be configured in YAML today and what each field changes at runtime.
249
+ There are three configuration layers:
243
250
 
244
- There are three layers of client configuration:
245
-
246
- - runtime-level policy in `config/workspace.yaml`
251
+ - runtime policy in `config/workspace.yaml`
247
252
  - reusable object catalogs in `config/*.yaml`
248
253
  - agent assembly in `config/agents/*.yaml`
249
254
 
@@ -253,92 +258,30 @@ Use this file for runtime-level policy shared by the whole workspace.
253
258
 
254
259
  Primary fields:
255
260
 
256
- - `runRoot`: root directory where the runtime stores thread indexes, runs, approvals, artifacts, queued requests, and default local persistence
257
- - `routing.defaultAgentId`: default host selected when no explicit routing rule matches
258
- - `routing.rules`: ordered YAML routing rules evaluated before backend routing
259
- - `routing.systemPrompt`: optional model-classifier prompt used only when model routing is enabled
260
- - `routing.modelRouting`: opt in to model-driven host classification fallback
261
- - `concurrency.maxConcurrentRuns`: maximum number of active runs; extra runs enter the persistent queue
262
- - `recovery.enabled`: enables runtime-managed startup recovery
263
- - `recovery.resumeOnStartup`: compatibility alias for resuming interrupted approval-driven runs on startup
264
- - `recovery.resumeResumingRunsOnStartup`: explicit control for resuming interrupted approval-driven runs on startup
265
- - `recovery.maxRecoveryAttempts`: upper bound for startup recovery retries
266
- - `maintenance.checkpoints.enabled`: turns on background checkpoint cleanup
267
- - `maintenance.checkpoints.schedule.intervalSeconds`: maintenance loop interval
268
- - `maintenance.checkpoints.schedule.runOnStartup`: run checkpoint cleanup during startup
269
- - `maintenance.checkpoints.policies.maxAgeSeconds`: age-based checkpoint cleanup
270
- - `maintenance.checkpoints.policies.maxBytes`: size-based checkpoint cleanup
271
- - `maintenance.checkpoints.sqlite.sweepBatchSize`: batch size for SQLite cleanup scans
272
- - `maintenance.checkpoints.sqlite.vacuum`: vacuum SQLite after deletions
261
+ - `runRoot`
262
+ - `concurrency.maxConcurrentRuns`
263
+ - `routing.defaultAgentId`
264
+ - `routing.rules`
265
+ - `routing.systemPrompt`
266
+ - `routing.modelRouting`
267
+ - `maintenance.checkpoints`
268
+ - `recovery.enabled`
269
+ - `recovery.resumeResumingRunsOnStartup`
270
+ - `recovery.maxRecoveryAttempts`
273
271
 
274
272
  If `runRoot` is omitted, the runtime defaults to `<workspace-root>/run-data`.
275
273
 
276
- Example:
277
-
278
- ```yaml
279
- apiVersion: agent-harness/v1alpha1
280
- kind: Runtime
281
- metadata:
282
- name: default
283
- spec:
284
- runRoot: ./.agent
285
- concurrency:
286
- maxConcurrentRuns: 3
287
- routing:
288
- defaultAgentId: orchestra
289
- modelRouting: false
290
- rules:
291
- - agentId: orchestra
292
- contains: ["latest", "recent", "today", "news"]
293
- - agentId: orchestra
294
- regex:
295
- - "\\b(create|build|implement|fix|debug|review|inspect)\\b"
296
- maintenance:
297
- checkpoints:
298
- enabled: true
299
- schedule:
300
- intervalSeconds: 3600
301
- runOnStartup: true
302
- policies:
303
- maxAgeSeconds: 604800
304
- sqlite:
305
- sweepBatchSize: 200
306
- vacuum: false
307
- recovery:
308
- enabled: true
309
- resumeResumingRunsOnStartup: true
310
- maxRecoveryAttempts: 3
311
- ```
312
-
313
- Notes:
314
-
315
- - `routing.rules` only choose the starting host agent; they do not replace backend planning semantics
316
- - queued runs are persisted under `runRoot` and continue after process restart
317
- - `running` runs are only replayed on startup when the bound tools are retryable
274
+ Queued runs are persisted under `runRoot` and continue after process restart. `running` runs are only replayed on startup when the bound tools are retryable.
318
275
 
319
276
  ### `config/agent-context.md`
320
277
 
321
- Use this file for shared startup context loaded into agents at construction time.
278
+ Use this file for shared bootstrap context loaded into agents at construction time.
322
279
 
323
280
  Put stable project context here. Do not use it as mutable long-term memory.
324
281
 
325
- Good uses:
326
-
327
- - product positioning
328
- - codebase conventions
329
- - stable domain vocabulary
330
- - organization-specific rules
331
-
332
- Bad uses:
333
-
334
- - transient scratch notes
335
- - per-run execution state
336
- - approval packets
337
- - long-term memory that should live in the store
338
-
339
282
  ### `config/models.yaml`
340
283
 
341
- Use one file for multiple named models:
284
+ Use named chat-model presets:
342
285
 
343
286
  ```yaml
344
287
  apiVersion: agent-harness/v1alpha1
@@ -348,80 +291,21 @@ spec:
348
291
  provider: openai
349
292
  model: gpt-4.1
350
293
  temperature: 0.2
351
- - name: planner
352
- provider: openai
353
- model: gpt-4.1-mini
354
294
  ```
355
295
 
356
- These load as `model/default` and `model/planner`.
357
-
358
- Client-configurable model fields:
359
-
360
- - `name`: catalog name referenced by `model/<name>`
361
- - `provider`: provider family such as `openai`, `openai-compatible`, `ollama`, `anthropic`, or `google`
362
- - `model`: provider model id
363
- - top-level provider init fields such as `temperature`, `baseUrl`, API-specific settings, and client options
364
- - `clientRef`: optional external client reference
365
- - `fallbacks`: optional fallback model refs
366
- - `metadata`: optional model metadata
296
+ These load as `model/<name>`.
367
297
 
368
298
  ### `config/embedding-models.yaml`
369
299
 
370
- Use this file for named embedding model presets used by retrieval-oriented tools.
371
-
372
- ```yaml
373
- apiVersion: agent-harness/v1alpha1
374
- kind: EmbeddingModels
375
- spec:
376
- - name: default
377
- provider: ollama
378
- model: nomic-embed-text
379
- baseUrl: http://localhost:11434
380
- ```
381
-
382
- Client-configurable embedding fields:
383
-
384
- - `name`
385
- - `provider`
386
- - `model`
387
- - top-level provider init fields such as `baseUrl`
388
- - `clientRef`
389
- - `metadata`
390
-
391
- These load as `embedding-model/default`.
300
+ Use named embedding-model presets for retrieval-oriented tools.
392
301
 
393
302
  ### `config/vector-stores.yaml`
394
303
 
395
- Use this file for named vector store presets referenced by retrieval tools.
396
-
397
- ```yaml
398
- apiVersion: agent-harness/v1alpha1
399
- kind: VectorStores
400
- spec:
401
- - name: default
402
- storeKind: LibSQLVectorStore
403
- url: file:.agent/vector-store.db
404
- table: rag_chunks
405
- column: embedding
406
- embeddingModelRef: embedding-model/default
407
- ```
408
-
409
- Client-configurable vector store fields:
410
-
411
- - `name`
412
- - `storeKind`
413
- - `url`
414
- - `authToken`
415
- - `table`
416
- - `column`
417
- - `embeddingModelRef`
418
- - `metadata`
419
-
420
- These load as `vector-store/default`.
304
+ Use named vector-store presets referenced by retrieval tools.
421
305
 
422
306
  ### `config/stores.yaml`
423
307
 
424
- Use one file for named persistence presets:
308
+ Use reusable store and checkpointer presets:
425
309
 
426
310
  ```yaml
427
311
  apiVersion: agent-harness/v1alpha1
@@ -436,119 +320,31 @@ spec:
436
320
  checkpointerKind: MemorySaver
437
321
  ```
438
322
 
439
- These load as `store/default` and `checkpointer/default`.
440
-
441
- Client-configurable store fields:
442
-
443
- - `kind: Store` for backend stores
444
- - `kind: Checkpointer` for resumable execution state
445
- - `name` for refs
446
- - `storeKind` such as `FileStore`, `InMemoryStore`, `RedisStore`, `PostgresStore`
447
- - `checkpointerKind` such as `MemorySaver`, `FileCheckpointer`, `SqliteSaver`
448
- - storage-specific fields such as `path`, connection strings, auth, and provider options
449
-
450
323
  ### `config/tools.yaml`
451
324
 
452
- Use this file for reusable tool presets and tool bundles.
453
-
454
- Minimal collection form:
325
+ Use this file for reusable tool objects.
455
326
 
456
- ```yaml
457
- apiVersion: agent-harness/v1alpha1
458
- kind: Tools
459
- spec:
460
- - kind: Tool
461
- name: fetch_docs
462
- type: function
463
- description: Fetch a documentation page.
464
- ```
327
+ Supported tool families in the built-in runtime include:
465
328
 
466
- Client-configurable tool fields:
329
+ - function tools
330
+ - backend tools
331
+ - MCP tools
332
+ - provider-native tools
333
+ - bundles
467
334
 
468
- - `name`
469
- - `type`: `function`, `backend`, `mcp`, or `bundle`
470
- - `description`
471
- - `implementationName` for local JS tool modules
472
- - `inputSchema.ref`
473
- - `backend.operation`
474
- - `mcp.ref` or `mcp.tool`
475
- - `refs` for bundle composition
476
- - `hitl.enabled` and `hitl.allow` for approval-gated tools
477
- - `retryable: true` for tools that are safe to replay during startup recovery
478
- - `config` for tool-specific options
335
+ Provider-native tools are declared in YAML and resolved directly to upstream provider tool factories such as OpenAI and Anthropic tool objects.
479
336
 
480
337
  Use `retryable` carefully. Mark a tool retryable only when repeated execution is safe or intentionally idempotent.
481
338
 
482
339
  ### `config/mcp.yaml`
483
340
 
484
- Use this file for reusable MCP server definitions and MCP-backed tool presets.
485
-
486
- ```yaml
487
- apiVersion: agent-harness/v1alpha1
488
- kind: McpServers
489
- spec:
490
- - name: docs
491
- transport: http
492
- url: https://example.com/mcp
493
- - name: local-browser
494
- transport: stdio
495
- command: node
496
- args: ["./mcp-browser-server.mjs"]
497
- ```
498
-
499
- Client-configurable MCP fields:
500
-
501
- - `name`
502
- - `transport`: `stdio`, `http`, `sse`, or `websocket`
503
- - `command`, `args`, `env`, `cwd` for stdio servers
504
- - `url`, `token`, `headers` for network servers
505
-
506
- These load as `mcp/<name>`.
341
+ Use this file for named MCP server presets.
507
342
 
508
343
  ### `config/agents/*.yaml`
509
344
 
510
- Prefer the generic agent form and declare the current execution backend explicitly:
511
-
512
- ```yaml
513
- apiVersion: agent-harness/v1alpha1
514
- kind: Agent
515
- metadata:
516
- name: orchestra
517
- spec:
518
- modelRef: model/default
519
- execution:
520
- backend: deepagent
521
- systemPrompt: Coordinate the request.
522
- ```
523
-
524
- Only `kind: Agent` is supported for agent objects. Select the concrete backend with `spec.execution.backend`.
525
-
526
- Common client-configurable agent fields:
527
-
528
- - `metadata.name`
529
- - `metadata.description`
530
- - `spec.execution.backend`
531
- - `spec.modelRef`
532
- - `spec.systemPrompt`
533
- - `spec.tools`
534
- - `spec.skills`
535
- - `spec.memory`
536
- - `spec.checkpointer`
537
- - `spec.store`
538
- - `spec.backend`
539
- - `spec.middleware`
540
- - `spec.subagents`
541
- - `spec.mcpServers`
542
- - `spec.responseFormat`
543
- - `spec.contextSchema`
544
-
545
- Typical patterns:
345
+ Agents are always declared with `kind: Agent` and `spec.execution.backend`.
546
346
 
547
- - use `direct` as a lightweight host for simple one-turn requests
548
- - use `orchestra` as the main execution host for tools, multi-step work, and delegation
549
- - keep routing policy in `config/workspace.yaml`, not buried in prompts
550
-
551
- Example direct agent:
347
+ Example lightweight host:
552
348
 
553
349
  ```yaml
554
350
  apiVersion: agent-harness/v1alpha1
@@ -561,12 +357,10 @@ spec:
561
357
  modelRef: model/default
562
358
  checkpointer:
563
359
  ref: checkpointer/default
564
- systemPrompt: |-
565
- You are the direct agent.
566
- Answer simple requests directly.
360
+ systemPrompt: Answer simple requests directly.
567
361
  ```
568
362
 
569
- Example orchestra agent:
363
+ Example main execution host:
570
364
 
571
365
  ```yaml
572
366
  apiVersion: agent-harness/v1alpha1
@@ -593,6 +387,25 @@ spec:
593
387
  kind: StoreBackend
594
388
  ```
595
389
 
390
+ Client-configurable agent fields include:
391
+
392
+ - `metadata.name`
393
+ - `metadata.description`
394
+ - `spec.execution.backend`
395
+ - `spec.modelRef`
396
+ - `spec.systemPrompt`
397
+ - `spec.tools`
398
+ - `spec.skills`
399
+ - `spec.memory`
400
+ - `spec.checkpointer`
401
+ - `spec.store`
402
+ - `spec.backend`
403
+ - `spec.middleware`
404
+ - `spec.subagents`
405
+ - `spec.mcpServers`
406
+ - `spec.responseFormat`
407
+ - `spec.contextSchema`
408
+
596
409
  ### `resources/`
597
410
 
598
411
  Use `resources/` for executable local extensions:
@@ -604,43 +417,32 @@ Tool modules are discovered from `resources/tools/*.js`, `resources/tools/*.mjs`
604
417
 
605
418
  The preferred tool module format is exporting `tool({...})`.
606
419
 
607
- Example:
608
-
609
- ```js
610
- import { z } from "zod";
611
- import { tool } from "@botbotgo/agent-harness/tools";
612
-
613
- export const local_lookup = tool({
614
- description: "Lookup a ticker from a local tool module.",
615
- retryable: true,
616
- schema: {
617
- ticker: z.string().min(1),
618
- },
619
- async invoke(input) {
620
- return input.ticker.toUpperCase();
621
- },
622
- });
623
- ```
624
-
625
- Keep runtime extension source under `resources/`. Keep tests outside the published source tree, for example under repository `test/`.
420
+ SKILL packages are discovered from `resources/skills/` and attached to agents through YAML.
626
421
 
627
422
  ## Design Notes
628
423
 
629
- - `agent-harness` should keep the public runtime contract generic while mapping cleanly onto current backend capabilities
630
- - agent-level execution behavior stays upstream
424
+ - public runtime contract stays generic and small
631
425
  - application-level orchestration and lifecycle management stays in the harness
632
- - checkpoint resume is treated as a system-managed runtime behavior, not a primary public abstraction
633
- - public runtime contract generic does not mean backend-agnostic implementation internals; it means client-facing semantics stay stable even when adapters change
426
+ - upstream LangChain v1 and DeepAgents concepts should be expressed as directly as possible in YAML
427
+ - recovery, approvals, threads, runs, and events are runtime concepts, not backend-specific escape hatches
428
+ - backend implementation details should stay internal unless product requirements force exposure
429
+
430
+ In short: `agent-harness` is a public runtime contract generic enough to survive backend changes, while the deep execution semantics stay upstream.
634
431
 
635
432
  ## API Summary
636
433
 
637
- - `createAgentHarness(...)`
638
- - `run(runtime, {...})`
639
- - `subscribe(...)`
640
- - `listThreads(...)`
641
- - `getThread(...)`
642
- - `listApprovals(...)`
643
- - `getApproval(...)`
644
- - `createToolMcpServer(...)`
645
- - `serveToolsOverStdio(...)`
646
- - `stop(...)`
434
+ Primary exports:
435
+
436
+ - `createAgentHarness`
437
+ - `run`
438
+ - `subscribe`
439
+ - `listThreads`
440
+ - `getThread`
441
+ - `deleteThread`
442
+ - `listApprovals`
443
+ - `getApproval`
444
+ - `createToolMcpServer`
445
+ - `serveToolsOverStdio`
446
+ - `stop`
447
+
448
+ `AgentHarnessRuntime` is the concrete runtime class behind the public facade.
@@ -96,6 +96,11 @@ export type LangChainAgentParams = {
96
96
  responseFormat?: unknown;
97
97
  contextSchema?: unknown;
98
98
  middleware?: Array<Record<string, unknown>>;
99
+ subagents?: CompiledSubAgent[];
100
+ memory?: string[];
101
+ skills?: string[];
102
+ generalPurposeAgent?: boolean;
103
+ taskDescription?: string;
99
104
  includeAgentName?: "inline";
100
105
  version?: "v1" | "v2";
101
106
  name?: string;
@@ -290,9 +295,6 @@ export type RunStartOptions = {
290
295
  input: MessageContent;
291
296
  threadId?: string;
292
297
  invocation?: InvocationEnvelope;
293
- context?: Record<string, unknown>;
294
- state?: Record<string, unknown>;
295
- files?: Record<string, unknown>;
296
298
  listeners?: RunListeners;
297
299
  };
298
300
  export type RunDecisionOptions = {
@@ -198,6 +198,44 @@ registerToolKind({
198
198
  ];
199
199
  },
200
200
  });
201
+ registerToolKind({
202
+ type: "provider",
203
+ validate(tool) {
204
+ if (!tool.name || !tool.description) {
205
+ throw new Error(`Tool ${tool.id} provider tool requires name and description`);
206
+ }
207
+ const providerTool = typeof tool.config?.providerTool === "object" && tool.config.providerTool
208
+ ? tool.config.providerTool
209
+ : undefined;
210
+ if (typeof providerTool?.provider !== "string" || !providerTool.provider.trim()) {
211
+ throw new Error(`Tool ${tool.id} provider tool must define providerTool.provider`);
212
+ }
213
+ if (typeof providerTool?.tool !== "string" || !providerTool.tool.trim()) {
214
+ throw new Error(`Tool ${tool.id} provider tool must define providerTool.tool`);
215
+ }
216
+ },
217
+ compile(tool) {
218
+ return [
219
+ {
220
+ id: tool.id,
221
+ type: "provider",
222
+ name: tool.name,
223
+ description: tool.description,
224
+ config: tool.config,
225
+ inputSchemaRef: tool.inputSchemaRef,
226
+ bundleRefs: [],
227
+ hitl: tool.hitl
228
+ ? {
229
+ enabled: tool.hitl.enabled,
230
+ allow: tool.hitl.allow ?? ["approve", "edit", "reject"],
231
+ }
232
+ : undefined,
233
+ retryable: tool.retryable,
234
+ runtimeValue: { name: tool.name, description: tool.description, type: "provider" },
235
+ },
236
+ ];
237
+ },
238
+ });
201
239
  registerToolKind({
202
240
  type: "bundle",
203
241
  validate(tool, tools) {
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.47";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.48";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.47";
1
+ export const AGENT_HARNESS_VERSION = "0.0.48";
@@ -20,12 +20,6 @@ function installedResourceProviderEntry() {
20
20
  try {
21
21
  return require.resolve("@botbotgo/agent-harness-resource");
22
22
  }
23
- catch {
24
- // Fall through to the legacy package name for backward compatibility only.
25
- }
26
- try {
27
- return require.resolve("@botbotgo/agent-harness-builtin");
28
- }
29
23
  catch {
30
24
  return null;
31
25
  }
@@ -33,6 +33,8 @@ export declare class AgentRuntimeAdapter {
33
33
  private normalizeInterruptPolicy;
34
34
  private compileInterruptOn;
35
35
  private resolveInterruptOn;
36
+ private resolveFilesystemBackend;
37
+ private resolveLangChainAutomaticMiddleware;
36
38
  private resolveMiddleware;
37
39
  private resolveCheckpointer;
38
40
  private buildRouteSystemPrompt;
@@ -1,10 +1,12 @@
1
1
  import { Command, MemorySaver } from "@langchain/langgraph";
2
2
  import { tool as createLangChainTool } from "@langchain/core/tools";
3
- import { createDeepAgent } from "deepagents";
3
+ import { createDeepAgent, createMemoryMiddleware, createSkillsMiddleware, createSubAgentMiddleware, FilesystemBackend, } from "deepagents";
4
4
  import { ChatAnthropic } from "@langchain/anthropic";
5
+ import { tools as anthropicProviderTools } from "@langchain/anthropic";
5
6
  import { ChatGoogle } from "@langchain/google";
6
7
  import { ChatOllama } from "@langchain/ollama";
7
8
  import { ChatOpenAI } from "@langchain/openai";
9
+ import { tools as openAIProviderTools } from "@langchain/openai";
8
10
  import { createAgent, humanInTheLoopMiddleware, initChatModel } from "langchain";
9
11
  import { z } from "zod";
10
12
  import { extractEmptyAssistantMessageFailure, extractReasoningText, extractToolFallbackContext, extractVisibleOutput, isLikelyToolArgsObject, isToolCallParseFailure, STRICT_TOOL_JSON_INSTRUCTION, sanitizeVisibleText, tryParseJson, wrapResolvedModel, } from "./parsing/output-parsing.js";
@@ -112,6 +114,35 @@ function buildToolNameMapping(tools) {
112
114
  }
113
115
  return { originalToModelFacing, modelFacingToOriginal };
114
116
  }
117
+ function hasConfiguredSubagentSupport(binding) {
118
+ const params = getBindingLangChainParams(binding);
119
+ if (!params) {
120
+ return false;
121
+ }
122
+ return (params.subagents?.length ?? 0) > 0 || params.generalPurposeAgent === true || Boolean(params.taskDescription?.trim());
123
+ }
124
+ function instantiateProviderTool(compiledTool) {
125
+ const providerTool = asRecord(compiledTool.config?.providerTool);
126
+ const provider = typeof providerTool?.provider === "string" ? providerTool.provider.trim().toLowerCase() : "";
127
+ const toolName = typeof providerTool?.tool === "string" ? providerTool.tool.trim() : "";
128
+ const args = asRecord(providerTool?.args) ?? {};
129
+ if (!provider || !toolName) {
130
+ throw new Error(`Provider tool ${compiledTool.id} must define providerTool.provider and providerTool.tool`);
131
+ }
132
+ const registry = provider === "openai"
133
+ ? openAIProviderTools
134
+ : provider === "anthropic"
135
+ ? anthropicProviderTools
136
+ : undefined;
137
+ if (!registry) {
138
+ throw new Error(`Provider tool ${compiledTool.id} uses unsupported provider ${provider}`);
139
+ }
140
+ const factory = registry[toolName];
141
+ if (typeof factory !== "function") {
142
+ throw new Error(`Provider tool ${compiledTool.id} references unknown ${provider} tool ${toolName}`);
143
+ }
144
+ return factory(args);
145
+ }
115
146
  function wrapResolvedToolWithModelFacingName(resolvedTool, modelFacingName) {
116
147
  if (typeof resolvedTool !== "object" || resolvedTool === null) {
117
148
  return resolvedTool;
@@ -393,10 +424,10 @@ export class AgentRuntimeAdapter {
393
424
  resolveTools(tools, binding) {
394
425
  const resolved = this.options.toolResolver ? this.options.toolResolver(tools.map((tool) => tool.id), binding) : [];
395
426
  const toolNameMapping = this.buildToolNameMapping(tools);
396
- return resolved.map((resolvedTool, index) => {
397
- const compiledTool = tools[index];
398
- if (!compiledTool) {
399
- return resolvedTool;
427
+ return tools.flatMap((compiledTool, index) => {
428
+ const resolvedTool = resolved[index] ?? (compiledTool.type === "provider" ? instantiateProviderTool(compiledTool) : undefined);
429
+ if (resolvedTool === undefined) {
430
+ return [];
400
431
  }
401
432
  const wrappedTool = wrapToolForExecution(resolvedTool, compiledTool, binding);
402
433
  const modelFacingName = toolNameMapping.originalToModelFacing.get(compiledTool.name) ?? compiledTool.name;
@@ -443,14 +474,55 @@ export class AgentRuntimeAdapter {
443
474
  resolveInterruptOn(binding) {
444
475
  return this.compileInterruptOn(getBindingPrimaryTools(binding), getBindingInterruptCompatibilityRules(binding));
445
476
  }
477
+ resolveFilesystemBackend(binding) {
478
+ return new FilesystemBackend({
479
+ rootDir: "/",
480
+ virtualMode: false,
481
+ maxFileSizeMb: 10,
482
+ });
483
+ }
484
+ async resolveLangChainAutomaticMiddleware(binding) {
485
+ const params = getBindingLangChainParams(binding);
486
+ if (!params) {
487
+ return [];
488
+ }
489
+ const automaticMiddleware = [];
490
+ if ((params.skills?.length ?? 0) > 0) {
491
+ automaticMiddleware.push(createSkillsMiddleware({
492
+ backend: this.resolveFilesystemBackend(binding),
493
+ sources: params.skills,
494
+ }));
495
+ }
496
+ if ((params.memory?.length ?? 0) > 0) {
497
+ automaticMiddleware.push(createMemoryMiddleware({
498
+ backend: this.resolveFilesystemBackend(binding),
499
+ sources: params.memory,
500
+ }));
501
+ }
502
+ if (hasConfiguredSubagentSupport(binding)) {
503
+ automaticMiddleware.push(createSubAgentMiddleware({
504
+ defaultModel: (await this.resolveModel(params.model)),
505
+ defaultTools: this.resolveTools(params.tools, binding),
506
+ defaultInterruptOn: getBindingInterruptCompatibilityRules(binding),
507
+ subagents: (await this.resolveSubagents(params.subagents ?? [], binding)),
508
+ generalPurposeAgent: params.generalPurposeAgent,
509
+ taskDescription: params.taskDescription ?? null,
510
+ }));
511
+ }
512
+ return automaticMiddleware;
513
+ }
446
514
  async resolveMiddleware(binding, interruptOn) {
447
515
  const declarativeMiddleware = await resolveDeclaredMiddleware(getBindingMiddlewareConfigs(binding), {
448
516
  resolveModel: (model) => this.resolveModel(model),
449
517
  resolveCustom: this.options.declaredMiddlewareResolver,
450
518
  binding,
451
519
  });
520
+ const automaticMiddleware = isLangChainBinding(binding)
521
+ ? await this.resolveLangChainAutomaticMiddleware(binding)
522
+ : [];
452
523
  const middleware = [
453
524
  ...declarativeMiddleware,
525
+ ...automaticMiddleware,
454
526
  ...(this.options.middlewareResolver ? this.options.middlewareResolver(binding) : []),
455
527
  ];
456
528
  if (interruptOn && Object.keys(interruptOn).length > 0) {
@@ -1,4 +1,4 @@
1
- import { anthropicPromptCachingMiddleware, contextEditingMiddleware, llmToolSelectorMiddleware, modelCallLimitMiddleware, modelFallbackMiddleware, modelRetryMiddleware, openAIModerationMiddleware, summarizationMiddleware, todoListMiddleware, toolCallLimitMiddleware, toolEmulatorMiddleware, toolRetryMiddleware, } from "langchain";
1
+ import { anthropicPromptCachingMiddleware, contextEditingMiddleware, llmToolSelectorMiddleware, modelCallLimitMiddleware, modelFallbackMiddleware, modelRetryMiddleware, openAIModerationMiddleware, piiMiddleware, piiRedactionMiddleware, summarizationMiddleware, todoListMiddleware, toolCallLimitMiddleware, toolEmulatorMiddleware, toolRetryMiddleware, } from "langchain";
2
2
  function asMiddlewareConfig(value) {
3
3
  return typeof value === "object" && value !== null && !Array.isArray(value) ? { ...value } : null;
4
4
  }
@@ -80,6 +80,18 @@ export async function resolveDeclaredMiddleware(middlewareConfigs, options) {
80
80
  case "openAIModeration":
81
81
  resolved.push(openAIModerationMiddleware(runtimeConfig));
82
82
  break;
83
+ case "pii": {
84
+ const piiType = typeof runtimeConfig.piiType === "string" ? runtimeConfig.piiType : undefined;
85
+ if (!piiType) {
86
+ throw new Error("pii middleware requires piiType");
87
+ }
88
+ const { piiType: _piiType, ...piiOptions } = runtimeConfig;
89
+ resolved.push(piiMiddleware(piiType, piiOptions));
90
+ break;
91
+ }
92
+ case "piiRedaction":
93
+ resolved.push(piiRedactionMiddleware(runtimeConfig));
94
+ break;
83
95
  case "anthropicPromptCaching":
84
96
  resolved.push(anthropicPromptCachingMiddleware(runtimeConfig));
85
97
  break;
@@ -47,9 +47,9 @@ export class AgentHarnessRuntime {
47
47
  normalizeInvocationEnvelope(options) {
48
48
  const invocation = options.invocation;
49
49
  return {
50
- context: invocation?.context ?? options.context,
51
- state: invocation?.inputs ?? options.state,
52
- files: invocation?.attachments ?? options.files,
50
+ context: invocation?.context,
51
+ state: invocation?.inputs,
52
+ files: invocation?.attachments,
53
53
  invocation,
54
54
  };
55
55
  }
@@ -243,6 +243,19 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
243
243
  responseFormat: agent.langchainAgentConfig?.responseFormat,
244
244
  contextSchema: agent.langchainAgentConfig?.contextSchema,
245
245
  middleware: compileMiddlewareConfigs(agent.langchainAgentConfig?.middleware, models, agent.id),
246
+ subagents: agent.subagentRefs.map((ref) => {
247
+ const subagent = agents.get(resolveRefId(ref));
248
+ if (!subagent) {
249
+ throw new Error(`Missing subagent ${ref} for agent ${agent.id}`);
250
+ }
251
+ return buildSubagent(subagent, workspaceRoot, models, tools, compiledAgentSkills, compiledAgentModel, compiledAgentMemory);
252
+ }),
253
+ memory: compiledAgentMemory,
254
+ skills: compiledAgentSkills,
255
+ generalPurposeAgent: typeof agent.langchainAgentConfig?.generalPurposeAgent === "boolean" ? agent.langchainAgentConfig.generalPurposeAgent : undefined,
256
+ taskDescription: typeof agent.langchainAgentConfig?.taskDescription === "string" && agent.langchainAgentConfig.taskDescription.trim()
257
+ ? agent.langchainAgentConfig.taskDescription
258
+ : undefined,
246
259
  includeAgentName: agent.langchainAgentConfig?.includeAgentName === "inline" ? "inline" : undefined,
247
260
  version: agent.langchainAgentConfig?.version === "v1" || agent.langchainAgentConfig?.version === "v2"
248
261
  ? agent.langchainAgentConfig.version
@@ -222,10 +222,10 @@ function resolveExecutionBackend(item, current) {
222
222
  : typeof execution?.mode === "string"
223
223
  ? execution.mode.trim().toLowerCase()
224
224
  : undefined;
225
- if (backend === "langchain-v1" || backend === "langchain" || backend === "langchain-agent") {
225
+ if (backend === "langchain-v1") {
226
226
  return "langchain-v1";
227
227
  }
228
- if (backend === "deepagent" || backend === "deepagents") {
228
+ if (backend === "deepagent") {
229
229
  return "deepagent";
230
230
  }
231
231
  return undefined;
@@ -250,6 +250,8 @@ function readLangchainAgentConfig(item) {
250
250
  return {
251
251
  ...readSharedAgentConfig(item),
252
252
  ...(typeof item.store === "object" && item.store ? { store: item.store } : {}),
253
+ ...(typeof item.taskDescription === "string" && item.taskDescription.trim() ? { taskDescription: item.taskDescription } : {}),
254
+ ...(typeof item.generalPurposeAgent === "boolean" ? { generalPurposeAgent: item.generalPurposeAgent } : {}),
253
255
  };
254
256
  }
255
257
  function readDeepAgentConfig(item) {
@@ -264,17 +266,13 @@ function readDeepAgentConfig(item) {
264
266
  export function parseAgentItem(item, sourcePath) {
265
267
  const subagentRefs = readRefArray(item.subagents);
266
268
  const subagentPathRefs = readPathArray(item.subagents);
267
- const kind = typeof item.kind === "string" ? item.kind : "agent";
268
- const executionMode = String(resolveExecutionBackend(item) ??
269
- (kind === "langchain-agent" ? "langchain-v1" : undefined) ??
270
- (kind === "deepagent" ? "deepagent" : undefined) ??
271
- "deepagent");
269
+ const executionMode = String(resolveExecutionBackend(item) ?? "deepagent");
272
270
  return {
273
271
  id: String(item.id),
274
272
  executionMode: executionMode,
275
273
  capabilities: readCapabilities(item.capabilities) ?? (executionMode === "deepagent"
276
274
  ? { delegation: true, memory: true }
277
- : { delegation: false, memory: false }),
275
+ : { delegation: true, memory: true }),
278
276
  description: String(item.description ?? ""),
279
277
  modelRef: readSingleRef(item.modelRef) ?? "",
280
278
  runRoot: typeof item.runRoot === "string" ? item.runRoot : undefined,
@@ -487,7 +485,7 @@ async function readNamedModelItems(root) {
487
485
  return records;
488
486
  }
489
487
  function isAgentKind(kind) {
490
- return kind === "deepagent" || kind === "langchain-agent" || kind === "agent";
488
+ return kind === "agent";
491
489
  }
492
490
  async function readConfigAgentItems(configRoot) {
493
491
  const records = await readYamlItems(configRoot, "agents", { recursive: true });
@@ -536,18 +534,7 @@ export async function readToolModuleItems(root) {
536
534
  return records;
537
535
  }
538
536
  function inferExecutionMode(item, current) {
539
- const explicitExecution = resolveExecutionBackend(item, current);
540
- if (explicitExecution) {
541
- return explicitExecution;
542
- }
543
- const kind = typeof item.kind === "string" ? item.kind : typeof current?.kind === "string" ? current.kind : undefined;
544
- if (kind === "langchain-agent") {
545
- return "langchain-v1";
546
- }
547
- if (kind === "deepagent") {
548
- return "deepagent";
549
- }
550
- return undefined;
537
+ return resolveExecutionBackend(item, current);
551
538
  }
552
539
  export async function loadWorkspaceObjects(workspaceRoot, options = {}) {
553
540
  const refs = new Map();
@@ -207,6 +207,7 @@ export function parseToolObject(object) {
207
207
  const value = object.value;
208
208
  const backend = asObject(value.backend);
209
209
  const mcp = asObject(value.mcp);
210
+ const providerTool = asObject(value.providerTool) ?? asObject(value.provider);
210
211
  const mcpReferenceConfig = mcp
211
212
  ? {
212
213
  ...(typeof mcp.serverRef === "string"
@@ -226,16 +227,22 @@ export function parseToolObject(object) {
226
227
  ? "bundle"
227
228
  : mcp
228
229
  ? "mcp"
229
- : backend
230
- ? "backend"
231
- : "function";
230
+ : providerTool
231
+ ? "provider"
232
+ : backend
233
+ ? "backend"
234
+ : "function";
232
235
  return {
233
236
  id: object.id,
234
237
  type: String(inferredType),
235
238
  name: String(value.name ?? "").trim(),
236
239
  description: String(value.description ?? "").trim(),
237
240
  implementationName: typeof value.implementationName === "string" ? value.implementationName : undefined,
238
- config: mergeObjects(asObject(value.config), (mcpReferenceConfig && Object.keys(mcpReferenceConfig).length > 0) || (mcpServerConfig && Object.keys(mcpServerConfig).length > 0)
241
+ config: mergeObjects(mergeObjects(asObject(value.config), providerTool
242
+ ? {
243
+ providerTool,
244
+ }
245
+ : undefined), (mcpReferenceConfig && Object.keys(mcpReferenceConfig).length > 0) || (mcpServerConfig && Object.keys(mcpServerConfig).length > 0)
239
246
  ? {
240
247
  mcp: mcpReferenceConfig,
241
248
  ...(mcpServerConfig && Object.keys(mcpServerConfig).length > 0 ? { mcpServer: mcpServerConfig } : {}),
@@ -9,8 +9,8 @@ export function inferAgentCapabilities(agent) {
9
9
  return normalizeCapabilities(agent.capabilities);
10
10
  }
11
11
  return {
12
- delegation: agent.executionMode === "deepagent",
13
- memory: agent.executionMode === "deepagent",
12
+ delegation: agent.executionMode === "deepagent" || agent.executionMode === "langchain-v1",
13
+ memory: agent.executionMode === "deepagent" || agent.executionMode === "langchain-v1",
14
14
  };
15
15
  }
16
16
  export function inferBindingCapabilities(binding) {
@@ -12,10 +12,6 @@ export type RoutingRule = {
12
12
  hasThreadId?: boolean;
13
13
  caseSensitive?: boolean;
14
14
  };
15
- export type RuntimeRecoveryConfig = {
16
- enabled: boolean;
17
- resumeOnStartup: boolean;
18
- };
19
15
  export type RecoveryConfig = {
20
16
  enabled: boolean;
21
17
  resumeResumingRunsOnStartup: boolean;
@@ -26,7 +22,6 @@ export type ConcurrencyConfig = {
26
22
  };
27
23
  export declare function getWorkspaceObject(refs: Map<string, WorkspaceObject | ParsedAgentObject>, ref: string | undefined): WorkspaceObject | undefined;
28
24
  export declare function getRuntimeDefaults(refs: Map<string, WorkspaceObject | ParsedAgentObject>): Record<string, unknown> | undefined;
29
- export declare function getRuntimeRecoveryConfig(refs: Map<string, WorkspaceObject | ParsedAgentObject>): RuntimeRecoveryConfig;
30
25
  export declare function getRecoveryConfig(refs: Map<string, WorkspaceObject | ParsedAgentObject>): RecoveryConfig;
31
26
  export declare function getConcurrencyConfig(refs: Map<string, WorkspaceObject | ParsedAgentObject>): ConcurrencyConfig;
32
27
  export declare function getRoutingSystemPrompt(refs: Map<string, WorkspaceObject | ParsedAgentObject>): string | undefined;
@@ -25,16 +25,6 @@ export function getRuntimeDefaults(refs) {
25
25
  }
26
26
  return runtimes[0].value;
27
27
  }
28
- export function getRuntimeRecoveryConfig(refs) {
29
- const runtimeDefaults = getRuntimeDefaults(refs);
30
- const recovery = typeof runtimeDefaults?.recovery === "object" && runtimeDefaults.recovery
31
- ? runtimeDefaults.recovery
32
- : undefined;
33
- return {
34
- enabled: recovery?.enabled !== false,
35
- resumeOnStartup: recovery?.resumeOnStartup !== false,
36
- };
37
- }
38
28
  export function getRecoveryConfig(refs) {
39
29
  const runtimeDefaults = getRuntimeDefaults(refs);
40
30
  const recovery = typeof runtimeDefaults?.recovery === "object" && runtimeDefaults.recovery
@@ -49,7 +39,7 @@ export function getRecoveryConfig(refs) {
49
39
  enabled: recovery.enabled !== false,
50
40
  resumeResumingRunsOnStartup: typeof recovery.resumeResumingRunsOnStartup === "boolean"
51
41
  ? recovery.resumeResumingRunsOnStartup
52
- : recovery.resumeOnStartup !== false,
42
+ : true,
53
43
  maxRecoveryAttempts,
54
44
  };
55
45
  }
@@ -38,6 +38,9 @@ function validateMiddlewareConfig(agent) {
38
38
  if (kind === "modelFallback" && !Array.isArray(typed.fallbackModels) && !Array.isArray(typed.models)) {
39
39
  throw new Error(`Agent ${agent.id} modelFallback middleware requires fallbackModels or models`);
40
40
  }
41
+ if (kind === "pii" && typeof typed.piiType !== "string") {
42
+ throw new Error(`Agent ${agent.id} pii middleware requires piiType`);
43
+ }
41
44
  }
42
45
  }
43
46
  export function validateAgent(agent) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.48",
3
+ "version": "0.0.49",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",