@botbotgo/agent-harness 0.0.50 → 0.0.52

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
@@ -6,21 +6,22 @@
6
6
 
7
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 product boundary is:
9
+ The boundary is strict:
10
10
 
11
11
  - LangChain v1 and DeepAgents own agent execution semantics
12
- - `agent-harness` owns application runtime semantics
12
+ - `agent-harness` owns application-level orchestration and lifecycle management
13
13
 
14
14
  That means:
15
15
 
16
16
  - public API stays small
17
- - complex assembly and policy live in YAML
18
- - runtime lifecycle stays stable even if the backend implementation changes
17
+ - complex setup and operating policy live in YAML
18
+ - application-level orchestration and lifecycle management stays in the harness
19
+ - runtime lifecycle stays stable even if backend implementations change
19
20
 
20
21
  What the runtime provides:
21
22
 
22
23
  - `createAgentHarness(...)`, `run(...)`, `subscribe(...)`, inspection methods, and `stop(...)`
23
- - YAML-defined workspace assembly for routing, models, tools, stores, MCP, recovery, and maintenance
24
+ - YAML-defined workspace assembly for routing, models, tools, stores, backends, MCP, recovery, and maintenance
24
25
  - backend-adapted execution with current LangChain v1 and DeepAgents adapters
25
26
  - local `resources/tools/` and `resources/skills/` discovery
26
27
  - persisted threads, runs, approvals, events, queue state, and recovery metadata
@@ -44,6 +45,7 @@ your-workspace/
44
45
  embedding-models.yaml
45
46
  vector-stores.yaml
46
47
  stores.yaml
48
+ backends.yaml
47
49
  tools.yaml
48
50
  mcp.yaml
49
51
  agents/
@@ -201,11 +203,11 @@ metadata:
201
203
  spec:
202
204
  execution:
203
205
  backend: deepagent
204
- modelRef: model/default
205
- mcpServers:
206
- - name: browser
207
- command: node
208
- args: ["./mcp-browser-server.mjs"]
206
+ modelRef: model/default
207
+ mcpServers:
208
+ - name: browser
209
+ command: node
210
+ args: ["./mcp-browser-server.mjs"]
209
211
  ```
210
212
 
211
213
  The runtime discovers MCP tools, filters them through agent configuration, and exposes them like other tools.
@@ -242,6 +244,7 @@ Core workspace files:
242
244
  - `config/embedding-models.yaml`
243
245
  - `config/vector-stores.yaml`
244
246
  - `config/stores.yaml`
247
+ - `config/backends.yaml`
245
248
  - `config/tools.yaml`
246
249
  - `config/mcp.yaml`
247
250
  - `config/agents/direct.yaml`
@@ -323,6 +326,38 @@ spec:
323
326
  checkpointerKind: MemorySaver
324
327
  ```
325
328
 
329
+ Built-in store kinds today:
330
+
331
+ - `FileStore`
332
+ - `InMemoryStore`
333
+
334
+ Built-in checkpointer kinds today:
335
+
336
+ - `MemorySaver`
337
+ - `FileCheckpointer`
338
+ - `SqliteSaver`
339
+
340
+ If you need other store or checkpointer implementations, inject them through runtime resolvers instead of treating them as built-in harness features.
341
+
342
+ ### `config/backends.yaml`
343
+
344
+ Use reusable DeepAgent backend presets so filesystem and long-term memory topology stays in YAML instead of application code:
345
+
346
+ ```yaml
347
+ apiVersion: agent-harness/v1alpha1
348
+ kind: Backends
349
+ spec:
350
+ - kind: Backend
351
+ name: default
352
+ backendKind: CompositeBackend
353
+ state:
354
+ kind: VfsSandbox
355
+ timeout: 600
356
+ routes:
357
+ /memories/:
358
+ kind: StoreBackend
359
+ ```
360
+
326
361
  ### `config/tools.yaml`
327
362
 
328
363
  Use this file for reusable tool objects.
@@ -347,6 +382,13 @@ Use this file for named MCP server presets.
347
382
 
348
383
  Agents are always declared with `kind: Agent` and `spec.execution.backend`.
349
384
 
385
+ Use two nested sections inside each agent:
386
+
387
+ - `spec.runtime` for harness-owned runtime placement such as `spec.runtime.runRoot`
388
+ - `spec.execution` for upstream execution semantics and adapter-facing config
389
+
390
+ This keeps the public product model small while letting LangChain v1 and DeepAgents concepts pass through with minimal translation.
391
+
350
392
  Example lightweight host:
351
393
 
352
394
  ```yaml
@@ -357,10 +399,11 @@ metadata:
357
399
  spec:
358
400
  execution:
359
401
  backend: langchain-v1
360
- modelRef: model/default
361
- checkpointer:
362
- ref: checkpointer/default
363
- systemPrompt: Answer simple requests directly.
402
+ modelRef: model/default
403
+ config:
404
+ checkpointer:
405
+ ref: checkpointer/default
406
+ systemPrompt: Answer simple requests directly.
364
407
  ```
365
408
 
366
409
  Example main execution host:
@@ -373,21 +416,16 @@ metadata:
373
416
  spec:
374
417
  execution:
375
418
  backend: deepagent
376
- modelRef: model/default
377
- memory:
378
- - path: config/agent-context.md
379
- store:
380
- ref: store/default
381
- checkpointer:
382
- ref: checkpointer/default
383
- backend:
384
- kind: CompositeBackend
385
- state:
386
- kind: VfsSandbox
387
- timeout: 600
388
- routes:
389
- /memories/:
390
- kind: StoreBackend
419
+ modelRef: model/default
420
+ memory:
421
+ - path: config/agent-context.md
422
+ config:
423
+ store:
424
+ ref: store/default
425
+ checkpointer:
426
+ ref: checkpointer/default
427
+ backend:
428
+ ref: backend/default
391
429
  ```
392
430
 
393
431
  Client-configurable agent fields include:
@@ -395,21 +433,29 @@ Client-configurable agent fields include:
395
433
  - `metadata.name`
396
434
  - `metadata.description`
397
435
  - `spec.execution.backend`
398
- - `spec.modelRef`
399
- - `spec.systemPrompt`
400
- - `spec.tools`
401
- - `spec.skills`
402
- - `spec.memory`
403
- - `spec.checkpointer`
404
- - `spec.store`
405
- - `spec.backend`
406
- - `spec.middleware`
407
- - `spec.subagents`
408
- - `spec.mcpServers`
409
- - `spec.responseFormat`
410
- - `spec.contextSchema`
411
-
412
- For backend-specific agent options, prefer passing the upstream concept directly in YAML. The loader keeps a small stable product shape, but it also preserves agent-level passthrough fields so new LangChain v1 or DeepAgents parameters can flow into adapters without expanding the public API surface.
436
+ - `spec.runtime.runRoot`
437
+ - `spec.execution.modelRef`
438
+ - `spec.execution.tools`
439
+ - `spec.execution.skills`
440
+ - `spec.execution.memory`
441
+ - `spec.execution.subagents`
442
+ - `spec.execution.mcpServers`
443
+ - `spec.execution.config.systemPrompt`
444
+ - `spec.execution.config.checkpointer`
445
+ - `spec.execution.config.store`
446
+ - `spec.execution.config.backend`
447
+ - `spec.execution.config.middleware`
448
+ - `spec.execution.config.responseFormat`
449
+ - `spec.execution.config.contextSchema`
450
+ - `spec.execution.config.stateSchema`
451
+ - `spec.execution.config.interruptOn`
452
+ - `spec.execution.config.filesystem`
453
+ - `spec.execution.config.taskDescription`
454
+ - `spec.execution.config.generalPurposeAgent`
455
+ - `spec.execution.config.includeAgentName`
456
+ - `spec.execution.config.version`
457
+
458
+ For backend-specific agent options, prefer passing the upstream concept directly inside `spec.execution.config`. The loader keeps a small stable product shape, but it also preserves adapter-facing passthrough fields so new LangChain v1 or DeepAgents parameters can flow into adapters without expanding the public API surface.
413
459
 
414
460
  ### `resources/`
415
461
 
@@ -431,7 +477,6 @@ SKILL packages are discovered from `resources/skills/` and attached to agents th
431
477
  - upstream LangChain v1 and DeepAgents concepts should be expressed as directly as possible in YAML
432
478
  - recovery, approvals, threads, runs, and events are runtime concepts, not backend-specific escape hatches
433
479
  - backend implementation details should stay internal unless product requirements force exposure
434
- - application-level orchestration and lifecycle management stays in the harness
435
480
 
436
481
  In short: `agent-harness` is a public runtime contract generic enough to survive backend changes, while the deep execution semantics stay upstream.
437
482
 
@@ -9,41 +9,43 @@ metadata:
9
9
  # agent-harness feature: human-readable summary for inventory and UI.
10
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.
11
11
  spec:
12
- execution:
13
- # Current backend adapter for this host profile.
14
- backend: langchain-v1
12
+ runtime: {}
15
13
  # =====================
16
14
  # Runtime Agent Features
17
15
  # =====================
18
- # Upstream execution feature: model ref for the underlying LLM used by the direct-response agent.
19
- # This should point at a cheap, fast, general-purpose chat model, because `direct` is intended
20
- # to be the low-latency path for simple requests.
21
- modelRef: model/default
22
- # Runtime execution feature: checkpointer config passed into the selected backend adapter.
23
- # Even the lightweight direct path can benefit from resumable state during interactive use.
24
- # Available `kind` options in this harness: `FileCheckpointer`, `MemorySaver`, `SqliteSaver`.
25
- # `path` is only used by `FileCheckpointer` and `SqliteSaver`; omit it for `MemorySaver`.
26
- checkpointer:
27
- ref: checkpointer/default
28
- # Upstream execution feature: system prompt for the lightweight direct-response host.
29
- # This prompt should keep the agent focused on:
30
- # - answering simple requests in one turn
31
- # - staying lightweight instead of planning or orchestrating
32
- # - avoiding specialist-style decomposition unless the caller explicitly switches agents
33
- #
34
- # The direct host is intentionally narrower than the orchestra host:
35
- # - `direct` is optimized for latency and straightforward completion
36
- # - `orchestra` is optimized for multi-step work, tools, delegation, and specialist use
37
- #
38
- # Keep this prompt biased toward concise, self-contained answers. If richer routing policy is
39
- # needed for choosing between host agents, configure that separately via `Runtime.spec.routing`
40
- # rather than overloading the direct host prompt with classifier behavior.
41
- systemPrompt: |-
42
- You are the direct agent.
16
+ execution:
17
+ # Current backend adapter for this host profile.
18
+ backend: langchain-v1
19
+ # Upstream execution feature: model ref for the underlying LLM used by the direct-response agent.
20
+ # This should point at a cheap, fast, general-purpose chat model, because `direct` is intended
21
+ # to be the low-latency path for simple requests.
22
+ modelRef: model/default
23
+ config:
24
+ # Runtime execution feature: checkpointer config passed into the selected backend adapter.
25
+ # Even the lightweight direct path can benefit from resumable state during interactive use.
26
+ # Available `kind` options in this harness: `FileCheckpointer`, `MemorySaver`, `SqliteSaver`.
27
+ # `path` is only used by `FileCheckpointer` and `SqliteSaver`; omit it for `MemorySaver`.
28
+ checkpointer:
29
+ ref: checkpointer/default
30
+ # Upstream execution feature: system prompt for the lightweight direct-response host.
31
+ # This prompt should keep the agent focused on:
32
+ # - answering simple requests in one turn
33
+ # - staying lightweight instead of planning or orchestrating
34
+ # - avoiding specialist-style decomposition unless the caller explicitly switches agents
35
+ #
36
+ # The direct host is intentionally narrower than the orchestra host:
37
+ # - `direct` is optimized for latency and straightforward completion
38
+ # - `orchestra` is optimized for multi-step work, tools, delegation, and specialist use
39
+ #
40
+ # Keep this prompt biased toward concise, self-contained answers. If richer routing policy is
41
+ # needed for choosing between host agents, configure that separately via `Runtime.spec.routing`
42
+ # rather than overloading the direct host prompt with classifier behavior.
43
+ systemPrompt: |-
44
+ You are the direct agent.
43
45
 
44
- This is a manual low-latency host.
45
- Answer simple requests directly.
46
- Keep the path lightweight.
47
- Do not delegate.
48
- Do not perform broad multi-step execution.
49
- Do not behave like the default execution host.
46
+ This is a manual low-latency host.
47
+ Answer simple requests directly.
48
+ Keep the path lightweight.
49
+ Do not delegate.
50
+ Do not perform broad multi-step execution.
51
+ Do not behave like the default execution host.
@@ -9,86 +9,80 @@ metadata:
9
9
  # agent-harness feature: human-readable summary for inventory and UI.
10
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.
11
11
  spec:
12
- execution:
13
- # Current backend adapter for this host profile.
14
- backend: deepagent
12
+ runtime: {}
15
13
  # =====================
16
14
  # Runtime Agent Features
17
15
  # =====================
18
- # Upstream execution feature: model ref for the underlying LLM used by this execution host.
19
- modelRef: model/default
20
- # Runtime execution feature: checkpointer config passed into the selected backend adapter.
21
- # This persists resumable graph state for this agent.
22
- # Available `kind` options in this harness: `FileCheckpointer`, `MemorySaver`, `SqliteSaver`.
23
- # `path` is only used by `FileCheckpointer` and `SqliteSaver`; omit it for `MemorySaver`.
24
- checkpointer:
25
- # ref: checkpointer/sqlite
26
- ref: checkpointer/default
27
- memory:
28
- # Upstream execution feature: bootstrap memory sources supplied to the selected backend at construction time.
29
- # These paths resolve relative to the workspace root unless they are already absolute.
30
- # Treat this as agent-owned startup context, not as a dynamic long-term memory sink:
31
- # - keep `systemPrompt` for stable role, boundaries, and hard behavioral rules
32
- # - use `memory:` for stable project knowledge, operating conventions, and shared or agent-specific context files
33
- # - use `/memories/*` via the backend/store below for durable knowledge learned from prior runs
34
- # - use the harness checkpointer for resumable graph state for an in-flight run
35
- # Updating these files changes future agent constructions, but they are still bootstrap inputs rather than
36
- # self-updating runtime memory.
37
- - path: config/agent-context.md
38
- # Upstream execution feature: store config passed into the selected backend adapter.
39
- # In the default deepagent adapter this is the LangGraph store used by `StoreBackend` routes.
40
- # Available `kind` options in this harness: `FileStore`, `InMemoryStore`, `RedisStore`, `PostgresStore`.
41
- store:
42
- ref: store/default
43
- # Upstream execution feature: backend config passed into the selected backend adapter.
44
- # This directly defines the backend topology for this agent:
45
- # - workspace execution uses a lightweight VFS sandbox
46
- # - long-term memory under `/memories/*` uses `StoreBackend`
47
- # - `CompositeBackend` composes those backend instances together
48
- # The harness also injects a persistent file-backed store and a file-backed checkpointer so that
49
- # `/memories/*` and resumable run state survive restarts in the default setup.
50
- # Available top-level `kind` options in this harness: `CompositeBackend`, `StateBackend`, `StoreBackend`.
51
- backend:
52
- kind: CompositeBackend
53
- state:
54
- # Available state backend `kind` options today: `StateBackend`, `LocalShellBackend`, `VfsSandbox`.
55
- kind: VfsSandbox
56
- timeout: 600
57
- routes:
58
- /memories/:
59
- # Available route backend `kind` options today: `StoreBackend`.
60
- kind: StoreBackend
61
- # Upstream execution feature: system prompt for the orchestration host.
62
- # This becomes the top-level instruction block for the selected execution backend and should hold the
63
- # agent's durable role, priorities, and behavioral guardrails rather than bulky project facts.
64
- systemPrompt: |-
65
- You are the orchestra agent.
16
+ execution:
17
+ # Current backend adapter for this host profile.
18
+ backend: deepagent
19
+ # Upstream execution feature: model ref for the underlying LLM used by this execution host.
20
+ modelRef: model/default
21
+ memory:
22
+ # Upstream execution feature: bootstrap memory sources supplied to the selected backend at construction time.
23
+ # These paths resolve relative to the workspace root unless they are already absolute.
24
+ # Treat this as agent-owned startup context, not as a dynamic long-term memory sink:
25
+ # - keep `systemPrompt` for stable role, boundaries, and hard behavioral rules
26
+ # - use `memory:` for stable project knowledge, operating conventions, and shared or agent-specific context files
27
+ # - use `/memories/*` via the backend/store below for durable knowledge learned from prior runs
28
+ # - use the harness checkpointer for resumable graph state for an in-flight run
29
+ # Updating these files changes future agent constructions, but they are still bootstrap inputs rather than
30
+ # self-updating runtime memory.
31
+ - path: config/agent-context.md
32
+ config:
33
+ # Runtime execution feature: checkpointer config passed into the selected backend adapter.
34
+ # This persists resumable graph state for this agent.
35
+ # Available `kind` options in this harness: `FileCheckpointer`, `MemorySaver`, `SqliteSaver`.
36
+ # `path` is only used by `FileCheckpointer` and `SqliteSaver`; omit it for `MemorySaver`.
37
+ checkpointer:
38
+ # ref: checkpointer/sqlite
39
+ ref: checkpointer/default
40
+ # Upstream execution feature: store config passed into the selected backend adapter.
41
+ # In the default deepagent adapter this is the LangGraph store used by `StoreBackend` routes.
42
+ # Built-in kinds in this harness today: `FileStore`, `InMemoryStore`.
43
+ # Other store kinds should flow through a custom runtime resolver instead of being claimed as built in.
44
+ store:
45
+ ref: store/default
46
+ # Upstream execution feature: backend config passed into the selected backend adapter.
47
+ # Prefer a reusable backend preset via `ref` so backend topology stays declarative and reusable in YAML.
48
+ # The default preset keeps DeepAgent execution semantics upstream-owned:
49
+ # - workspace execution uses a lightweight VFS sandbox
50
+ # - long-term memory under `/memories/*` uses `StoreBackend`
51
+ # - `CompositeBackend` composes those backend instances together
52
+ # The harness injects the resolved store/checkpointer instances, but the backend topology itself stays upstream-shaped.
53
+ backend:
54
+ ref: backend/default
55
+ # Upstream execution feature: system prompt for the orchestration host.
56
+ # This becomes the top-level instruction block for the selected execution backend and should hold the
57
+ # agent's durable role, priorities, and behavioral guardrails rather than bulky project facts.
58
+ systemPrompt: |-
59
+ You are the orchestra agent.
66
60
 
67
- You are the default execution host.
68
- Try to finish the request yourself before delegating.
69
- Use your own tools first when they are sufficient.
70
- Use your own skills first when they are sufficient.
71
- Delegate only when a specialist is a clearly better fit or when your own tools and skills are not enough.
72
- If neither you nor any suitable specialist can do the work, say so plainly.
61
+ You are the default execution host.
62
+ Try to finish the request yourself before delegating.
63
+ Use your own tools first when they are sufficient.
64
+ Use your own skills first when they are sufficient.
65
+ Delegate only when a specialist is a clearly better fit or when your own tools and skills are not enough.
66
+ If neither you nor any suitable specialist can do the work, say so plainly.
73
67
 
74
- Do not delegate by reflex.
75
- Do not delegate just because a task has multiple steps.
76
- Do not delegate when a direct answer or a short local tool pass is enough.
77
- Keep the critical path local when immediate progress depends on it; otherwise delegate bounded sidecar work to
78
- the most appropriate specialist.
68
+ Do not delegate by reflex.
69
+ Do not delegate just because a task has multiple steps.
70
+ Do not delegate when a direct answer or a short local tool pass is enough.
71
+ Keep the critical path local when immediate progress depends on it; otherwise delegate bounded sidecar work to
72
+ the most appropriate specialist.
79
73
 
80
- Use your own tools for lightweight discovery, inventory, and context gathering.
81
- Prefer the structured checkout, indexing, retrieval, and inventory tools that are already attached to you over
82
- ad hoc shell work when those tools are sufficient.
83
- Use the attached specialist descriptions as the source of truth for what each specialist is for.
84
- Do not delegate to a specialist whose description does not clearly match the task.
85
- Integrate specialist results into one coherent answer and do not claim checks or evidence you did not obtain.
74
+ Use your own tools for lightweight discovery, inventory, and context gathering.
75
+ Prefer the structured checkout, indexing, retrieval, and inventory tools that are already attached to you over
76
+ ad hoc shell work when those tools are sufficient.
77
+ Use the attached specialist descriptions as the source of truth for what each specialist is for.
78
+ Do not delegate to a specialist whose description does not clearly match the task.
79
+ Integrate specialist results into one coherent answer and do not claim checks or evidence you did not obtain.
86
80
 
87
- When the user asks about available tools, skills, or agents, use the attached inventory tools instead of
88
- inferring from memory.
81
+ When the user asks about available tools, skills, or agents, use the attached inventory tools instead of
82
+ inferring from memory.
89
83
 
90
- Write to `/memories/*` only when the information is durable, reusable across future runs or threads, and likely
91
- to matter again: user preferences, project conventions, confirmed decisions, reusable summaries, and stable
92
- ownership facts are good candidates.
93
- Do not store transient reasoning, temporary plans, scratch work, one-off search results, or intermediate
94
- outputs that can be cheaply recomputed.
84
+ Write to `/memories/*` only when the information is durable, reusable across future runs or threads, and likely
85
+ to matter again: user preferences, project conventions, confirmed decisions, reusable summaries, and stable
86
+ ownership facts are good candidates.
87
+ Do not store transient reasoning, temporary plans, scratch work, one-off search results, or intermediate
88
+ outputs that can be cheaply recomputed.
@@ -0,0 +1,16 @@
1
+ # agent-harness feature: schema version for reusable backend presets.
2
+ apiVersion: agent-harness/v1alpha1
3
+ # agent-harness feature: object type for named DeepAgent backend presets.
4
+ kind: Backends
5
+ spec:
6
+ # upstream deepagents feature: default hybrid backend for workspace execution plus durable /memories storage.
7
+ - kind: Backend
8
+ name: default
9
+ description: Default DeepAgent backend preset with a virtual workspace sandbox and durable /memories storage.
10
+ backendKind: CompositeBackend
11
+ state:
12
+ kind: VfsSandbox
13
+ timeout: 600
14
+ routes:
15
+ /memories/:
16
+ kind: StoreBackend
@@ -95,6 +95,7 @@ export type LangChainAgentParams = {
95
95
  stateSchema?: unknown;
96
96
  responseFormat?: unknown;
97
97
  contextSchema?: unknown;
98
+ filesystem?: Record<string, unknown>;
98
99
  middleware?: Array<Record<string, unknown>>;
99
100
  passthrough?: Record<string, unknown>;
100
101
  subagents?: CompiledSubAgent[];
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.49";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.51";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.49";
1
+ export const AGENT_HARNESS_VERSION = "0.0.51";
@@ -1,3 +1,4 @@
1
+ import path from "node:path";
1
2
  import { Command, MemorySaver } from "@langchain/langgraph";
2
3
  import { tool as createLangChainTool } from "@langchain/core/tools";
3
4
  import { createDeepAgent, createMemoryMiddleware, createSkillsMiddleware, createSubAgentMiddleware, FilesystemBackend, } from "deepagents";
@@ -475,10 +476,20 @@ export class AgentRuntimeAdapter {
475
476
  return this.compileInterruptOn(getBindingPrimaryTools(binding), getBindingInterruptCompatibilityRules(binding));
476
477
  }
477
478
  resolveFilesystemBackend(binding) {
479
+ const filesystemConfig = getBindingLangChainParams(binding)?.filesystem;
480
+ const configuredRootDir = typeof filesystemConfig?.rootDir === "string" && filesystemConfig.rootDir.trim().length > 0
481
+ ? filesystemConfig.rootDir
482
+ : undefined;
483
+ const workspaceRoot = binding.harnessRuntime.workspaceRoot;
484
+ const rootDir = configuredRootDir
485
+ ? (path.isAbsolute(configuredRootDir) ? configuredRootDir : path.resolve(workspaceRoot ?? process.cwd(), configuredRootDir))
486
+ : workspaceRoot ?? process.cwd();
478
487
  return new FilesystemBackend({
479
- rootDir: "/",
480
- virtualMode: false,
481
- maxFileSizeMb: 10,
488
+ rootDir,
489
+ virtualMode: filesystemConfig?.virtualMode === true,
490
+ maxFileSizeMb: typeof filesystemConfig?.maxFileSizeMb === "number" && Number.isFinite(filesystemConfig.maxFileSizeMb)
491
+ ? filesystemConfig.maxFileSizeMb
492
+ : 10,
482
493
  });
483
494
  }
484
495
  async resolveLangChainAutomaticMiddleware(binding) {
@@ -127,14 +127,22 @@ function resolveInterruptOn(agent) {
127
127
  return (agent.deepAgentConfig?.interruptOn ??
128
128
  agent.langchainAgentConfig?.interruptOn);
129
129
  }
130
- function resolveBackendConfig(agent) {
130
+ function resolveBackendConfig(agent, refs) {
131
131
  if (agent.executionMode !== "deepagent") {
132
132
  return undefined;
133
133
  }
134
134
  const backendConfig = typeof agent.deepAgentConfig?.backend === "object" && agent.deepAgentConfig.backend
135
135
  ? agent.deepAgentConfig.backend
136
136
  : undefined;
137
- return backendConfig ? { config: backendConfig } : undefined;
137
+ if (!backendConfig) {
138
+ return undefined;
139
+ }
140
+ if (isRefConfig(backendConfig)) {
141
+ return {
142
+ config: materializeWorkspaceObjectConfig(refs, backendConfig.ref, ["backend"], `Agent ${agent.id} backend`),
143
+ };
144
+ }
145
+ return { config: backendConfig };
138
146
  }
139
147
  function isRefConfig(value) {
140
148
  if (typeof value?.ref !== "string" || value.ref.trim().length === 0) {
@@ -161,6 +169,11 @@ function materializeWorkspaceObjectConfig(refs, ref, allowedKinds, ownerLabel) {
161
169
  const { checkpointerKind: _checkpointerKind, ...rest } = config;
162
170
  return checkpointerKind ? { kind: checkpointerKind, ...rest } : config;
163
171
  }
172
+ if (object.kind === "backend") {
173
+ const backendKind = typeof config.backendKind === "string" ? config.backendKind : undefined;
174
+ const { backendKind: _backendKind, ...rest } = config;
175
+ return backendKind ? { kind: backendKind, ...rest } : config;
176
+ }
164
177
  return config;
165
178
  }
166
179
  function resolveStoreConfig(agent, refs) {
@@ -208,7 +221,7 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
208
221
  const compiledAgentSkills = compileAgentSkills(workspaceRoot, agent);
209
222
  const compiledAgentMemory = compileAgentMemories(workspaceRoot, agent.memorySources);
210
223
  const compiledAgentModel = requireModel(models, agent.modelRef || (internalSubagent ? "model/default" : ""), agent.id);
211
- const backend = resolveBackendConfig(agent);
224
+ const backend = resolveBackendConfig(agent, refs);
212
225
  const store = resolveStoreConfig(agent, refs);
213
226
  const checkpointer = resolveCheckpointerConfig(agent, refs);
214
227
  const runRoot = typeof agent.runRoot === "string" && agent.runRoot.trim().length > 0
@@ -245,6 +258,9 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
245
258
  stateSchema: agent.langchainAgentConfig?.stateSchema,
246
259
  responseFormat: agent.langchainAgentConfig?.responseFormat,
247
260
  contextSchema: agent.langchainAgentConfig?.contextSchema,
261
+ filesystem: typeof agent.langchainAgentConfig?.filesystem === "object" && agent.langchainAgentConfig.filesystem
262
+ ? { ...agent.langchainAgentConfig.filesystem }
263
+ : undefined,
248
264
  middleware: compileMiddlewareConfigs(agent.langchainAgentConfig?.middleware, models, agent.id),
249
265
  passthrough: typeof agent.langchainAgentConfig?.passthrough === "object" && agent.langchainAgentConfig.passthrough
250
266
  ? { ...agent.langchainAgentConfig.passthrough }
@@ -61,6 +61,11 @@ function toArray(value) {
61
61
  function asObject(value) {
62
62
  return typeof value === "object" && value ? value : undefined;
63
63
  }
64
+ function asMutableObject(value) {
65
+ return typeof value === "object" && value !== null && !Array.isArray(value)
66
+ ? { ...value }
67
+ : undefined;
68
+ }
64
69
  function normalizeCatalogSpec(document, options = {}) {
65
70
  const typed = asObject(document);
66
71
  const spec = typed?.spec;
@@ -211,9 +216,14 @@ function readCapabilities(value) {
211
216
  return Object.keys(capabilities).length > 0 ? capabilities : undefined;
212
217
  }
213
218
  function readExecutionConfig(value) {
214
- return typeof value === "object" && value !== null && !Array.isArray(value)
215
- ? { ...value }
216
- : undefined;
219
+ return asMutableObject(value);
220
+ }
221
+ function readExecutionAgentConfig(item) {
222
+ const execution = readExecutionConfig(item.execution);
223
+ return asMutableObject(execution?.config) ?? {};
224
+ }
225
+ function readRuntimeConfig(item) {
226
+ return asMutableObject(item.runtime);
217
227
  }
218
228
  function cloneConfigValue(value) {
219
229
  if (Array.isArray(value)) {
@@ -245,21 +255,42 @@ function resolveExecutionBackend(item, current) {
245
255
  }
246
256
  return undefined;
247
257
  }
248
- function readSharedAgentConfig(item) {
249
- const middleware = readMiddlewareArray(item.middleware);
250
- const passthrough = readPassthroughConfig(item, [
251
- "id",
252
- "kind",
253
- "description",
254
- "modelRef",
255
- "runRoot",
256
- "tools",
257
- "mcpServers",
258
- "skills",
259
- "memory",
260
- "subagents",
261
- "execution",
262
- "capabilities",
258
+ function readExecutionSingleRef(item, key) {
259
+ const execution = readExecutionConfig(item.execution);
260
+ return readSingleRef(execution?.[key]);
261
+ }
262
+ function readExecutionRefArray(item, key) {
263
+ const execution = readExecutionConfig(item.execution);
264
+ return readRefArray(execution?.[key]);
265
+ }
266
+ function readExecutionPathArray(item, key) {
267
+ const execution = readExecutionConfig(item.execution);
268
+ return readPathArray(execution?.[key]);
269
+ }
270
+ function readExecutionObjectArray(item, key) {
271
+ const execution = readExecutionConfig(item.execution);
272
+ return readObjectArray(execution?.[key]);
273
+ }
274
+ function readSharedAgentConfig(config) {
275
+ const middleware = readMiddlewareArray(config.middleware);
276
+ return {
277
+ ...(typeof config.systemPrompt === "string" ? { systemPrompt: config.systemPrompt } : {}),
278
+ ...((typeof config.checkpointer === "object" && config.checkpointer) || typeof config.checkpointer === "boolean"
279
+ ? { checkpointer: config.checkpointer }
280
+ : {}),
281
+ ...(typeof config.interruptOn === "object" && config.interruptOn ? { interruptOn: config.interruptOn } : {}),
282
+ ...(config.stateSchema !== undefined ? { stateSchema: config.stateSchema } : {}),
283
+ ...(config.responseFormat !== undefined ? { responseFormat: config.responseFormat } : {}),
284
+ ...(config.contextSchema !== undefined ? { contextSchema: config.contextSchema } : {}),
285
+ ...(config.includeAgentName === "inline" ? { includeAgentName: "inline" } : {}),
286
+ ...(config.version === "v1" || config.version === "v2" ? { version: config.version } : {}),
287
+ ...(typeof config.filesystem === "object" && config.filesystem ? { filesystem: config.filesystem } : {}),
288
+ ...(middleware ? { middleware } : {}),
289
+ };
290
+ }
291
+ function readLangchainAgentConfig(item) {
292
+ const config = readExecutionAgentConfig(item);
293
+ const passthrough = readPassthroughConfig(config, [
263
294
  "systemPrompt",
264
295
  "checkpointer",
265
296
  "interruptOn",
@@ -273,43 +304,48 @@ function readSharedAgentConfig(item) {
273
304
  "store",
274
305
  "taskDescription",
275
306
  "generalPurposeAgent",
307
+ "filesystem",
276
308
  ]);
277
309
  return {
278
- ...(typeof item.systemPrompt === "string" ? { systemPrompt: item.systemPrompt } : {}),
279
- ...((typeof item.checkpointer === "object" && item.checkpointer) || typeof item.checkpointer === "boolean"
280
- ? { checkpointer: item.checkpointer }
281
- : {}),
282
- ...(typeof item.interruptOn === "object" && item.interruptOn ? { interruptOn: item.interruptOn } : {}),
283
- ...(item.stateSchema !== undefined ? { stateSchema: item.stateSchema } : {}),
284
- ...(item.responseFormat !== undefined ? { responseFormat: item.responseFormat } : {}),
285
- ...(item.contextSchema !== undefined ? { contextSchema: item.contextSchema } : {}),
286
- ...(item.includeAgentName === "inline" ? { includeAgentName: "inline" } : {}),
287
- ...(item.version === "v1" || item.version === "v2" ? { version: item.version } : {}),
288
- ...(middleware ? { middleware } : {}),
310
+ ...readSharedAgentConfig(config),
311
+ ...(typeof config.store === "object" && config.store ? { store: config.store } : {}),
312
+ ...(typeof config.taskDescription === "string" && config.taskDescription.trim() ? { taskDescription: config.taskDescription } : {}),
313
+ ...(typeof config.generalPurposeAgent === "boolean" ? { generalPurposeAgent: config.generalPurposeAgent } : {}),
289
314
  ...(passthrough ? { passthrough } : {}),
290
315
  };
291
316
  }
292
- function readLangchainAgentConfig(item) {
293
- return {
294
- ...readSharedAgentConfig(item),
295
- ...(typeof item.store === "object" && item.store ? { store: item.store } : {}),
296
- ...(typeof item.taskDescription === "string" && item.taskDescription.trim() ? { taskDescription: item.taskDescription } : {}),
297
- ...(typeof item.generalPurposeAgent === "boolean" ? { generalPurposeAgent: item.generalPurposeAgent } : {}),
298
- };
299
- }
300
317
  function readDeepAgentConfig(item) {
318
+ const config = readExecutionAgentConfig(item);
319
+ const passthrough = readPassthroughConfig(config, [
320
+ "systemPrompt",
321
+ "checkpointer",
322
+ "interruptOn",
323
+ "stateSchema",
324
+ "responseFormat",
325
+ "contextSchema",
326
+ "includeAgentName",
327
+ "version",
328
+ "middleware",
329
+ "backend",
330
+ "store",
331
+ "taskDescription",
332
+ "generalPurposeAgent",
333
+ "filesystem",
334
+ ]);
301
335
  return {
302
- ...readSharedAgentConfig(item),
303
- ...(typeof item.backend === "object" && item.backend ? { backend: item.backend } : {}),
304
- ...(typeof item.store === "object" && item.store ? { store: item.store } : {}),
305
- ...(typeof item.taskDescription === "string" && item.taskDescription.trim() ? { taskDescription: item.taskDescription } : {}),
306
- ...(typeof item.generalPurposeAgent === "boolean" ? { generalPurposeAgent: item.generalPurposeAgent } : {}),
336
+ ...readSharedAgentConfig(config),
337
+ ...(typeof config.backend === "object" && config.backend ? { backend: config.backend } : {}),
338
+ ...(typeof config.store === "object" && config.store ? { store: config.store } : {}),
339
+ ...(typeof config.taskDescription === "string" && config.taskDescription.trim() ? { taskDescription: config.taskDescription } : {}),
340
+ ...(typeof config.generalPurposeAgent === "boolean" ? { generalPurposeAgent: config.generalPurposeAgent } : {}),
341
+ ...(passthrough ? { passthrough } : {}),
307
342
  };
308
343
  }
309
344
  export function parseAgentItem(item, sourcePath) {
310
- const subagentRefs = readRefArray(item.subagents);
311
- const subagentPathRefs = readPathArray(item.subagents);
345
+ const subagentRefs = readExecutionRefArray(item, "subagents");
346
+ const subagentPathRefs = readExecutionPathArray(item, "subagents");
312
347
  const executionMode = String(resolveExecutionBackend(item) ?? "deepagent");
348
+ const runtime = readRuntimeConfig(item);
313
349
  return {
314
350
  id: String(item.id),
315
351
  executionMode: executionMode,
@@ -317,12 +353,12 @@ export function parseAgentItem(item, sourcePath) {
317
353
  ? { delegation: true, memory: true }
318
354
  : { delegation: true, memory: true }),
319
355
  description: String(item.description ?? ""),
320
- modelRef: readSingleRef(item.modelRef) ?? "",
321
- runRoot: typeof item.runRoot === "string" ? item.runRoot : undefined,
322
- toolRefs: readRefArray(item.tools),
323
- mcpServers: readObjectArray(item.mcpServers),
324
- skillPathRefs: readPathArray(item.skills),
325
- memorySources: readPathArray(item.memory),
356
+ modelRef: readExecutionSingleRef(item, "modelRef") ?? "",
357
+ runRoot: typeof runtime?.runRoot === "string" ? runtime.runRoot : undefined,
358
+ toolRefs: readExecutionRefArray(item, "tools"),
359
+ mcpServers: readExecutionObjectArray(item, "mcpServers"),
360
+ skillPathRefs: readExecutionPathArray(item, "skills"),
361
+ memorySources: readExecutionPathArray(item, "memory"),
326
362
  subagentRefs,
327
363
  subagentPathRefs,
328
364
  langchainAgentConfig: readLangchainAgentConfig(item),
@@ -341,11 +377,13 @@ async function objectItemsFromDocument(document, sourcePath) {
341
377
  ? normalizeCatalogSpec(document, { defaultKind: "Model" })
342
378
  : catalogKind === "Stores"
343
379
  ? normalizeCatalogSpec(document)
344
- : catalogKind === "Tools"
345
- ? normalizeCatalogSpec(document, { defaultKind: "Tool" })
346
- : catalogKind === "McpServers"
347
- ? normalizeCatalogSpec(document)
348
- : [];
380
+ : catalogKind === "Backends"
381
+ ? normalizeCatalogSpec(document, { defaultKind: "Backend" })
382
+ : catalogKind === "Tools"
383
+ ? normalizeCatalogSpec(document, { defaultKind: "Tool" })
384
+ : catalogKind === "McpServers"
385
+ ? normalizeCatalogSpec(document)
386
+ : [];
349
387
  if (catalogItems.length > 0) {
350
388
  return catalogItems;
351
389
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.50",
3
+ "version": "0.0.52",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",
@@ -50,7 +50,7 @@
50
50
  "scripts": {
51
51
  "build": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && cp -R config dist/",
52
52
  "check": "tsc -p tsconfig.json --noEmit",
53
- "test": "vitest run test/public-api.test.ts test/resource-optional-provider.test.ts test/resource-isolation.test.ts test/stock-research-app-load-harness.test.ts test/stock-research-app-run.test.ts test/release-workflow.test.ts test/release-version.test.ts test/gitignore.test.ts test/package-lock.test.ts test/readme.test.ts test/runtime-adapter-regressions.test.ts test/runtime-capabilities.test.ts test/runtime-recovery.test.ts test/tool-extension-gaps.test.ts test/checkpoint-maintenance.test.ts test/llamaindex-dependency-compat.test.ts test/skill-standard.test.ts test/routing-config.test.ts test/workspace-compat-regressions.test.ts test/upstream-compat-regressions.test.ts test/yaml-format.test.ts",
53
+ "test": "vitest run test/public-api.test.ts test/resource-optional-provider.test.ts test/resource-isolation.test.ts test/stock-research-app-load-harness.test.ts test/stock-research-app-run.test.ts test/stock-research-app-config.test.ts test/release-workflow.test.ts test/release-version.test.ts test/gitignore.test.ts test/package-lock.test.ts test/readme.test.ts test/runtime-adapter-regressions.test.ts test/runtime-capabilities.test.ts test/runtime-recovery.test.ts test/tool-extension-gaps.test.ts test/checkpoint-maintenance.test.ts test/llamaindex-dependency-compat.test.ts test/skill-standard.test.ts test/routing-config.test.ts test/workspace-compat-regressions.test.ts test/upstream-compat-regressions.test.ts test/yaml-format.test.ts",
54
54
  "test:real-providers": "vitest run test/real-provider-harness.test.ts",
55
55
  "release:prepare": "npm version patch --no-git-tag-version && node ./scripts/sync-example-version.mjs",
56
56
  "release:pack": "npm pack --dry-run",