@botbotgo/agent-harness 0.0.24 → 0.0.26

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
@@ -106,6 +106,13 @@ Or pass a prebuilt `WorkspaceBundle`:
106
106
  const harness = await createAgentHarness(workspaceBundle);
107
107
  ```
108
108
 
109
+ Implementation details:
110
+
111
+ - `createAgentHarness(workspaceRoot)` loads and compiles one `WorkspaceBundle` before the runtime starts
112
+ - the loader reads `config/workspace.yaml`, `config/models.yaml`, `config/agent-context.md`, and every `config/agents/*.yaml` entry file, then validates the final topology
113
+ - local resource refs are hydrated after config load, so `resources/tools/`, `resources/skills/`, and agent-declared MCP servers are resolved before the harness accepts runs
114
+ - after compilation, the harness initializes persistence under the resolved `runRoot`, wires the runtime adapter, starts thread-memory syncing, and starts checkpoint maintenance when configured
115
+
109
116
  ### Run A Request
110
117
 
111
118
  ```ts
@@ -124,6 +131,13 @@ The result includes:
124
131
  - `state`
125
132
  - `output`
126
133
 
134
+ Runtime behavior:
135
+
136
+ - every `run(...)` call creates or continues a persisted thread under `runRoot`
137
+ - the harness stores thread metadata, run lifecycle, streamed events, approvals, delegations, and artifacts on disk so `getThread(...)` can reconstruct the session later
138
+ - when a store is configured, the harness also mirrors thread status and open approvals into `/memories/threads/<threadId>/`
139
+ - `stop(...)` should always be called so background persistence and maintenance loops shut down cleanly
140
+
127
141
  ### Let The Harness Choose The Host Agent
128
142
 
129
143
  Use `agentId: "auto"` when your workspace defines routing:
@@ -135,6 +149,12 @@ const result = await run(harness, {
135
149
  });
136
150
  ```
137
151
 
152
+ Implementation details:
153
+
154
+ - `agentId: "auto"` asks the runtime adapter to classify between the primary and secondary host bindings discovered from the workspace
155
+ - when the run belongs to an existing thread, the router includes recent conversation turns so follow-up requests can stay on the correct lane
156
+ - if no model-driven routing prompt is configured, the harness falls back to heuristic routing for obvious research or implementation-style requests
157
+
138
158
  ### Stream Output And Events
139
159
 
140
160
  ```ts
@@ -166,6 +186,13 @@ spec:
166
186
  - ref: tool/local-toolset
167
187
  ```
168
188
 
189
+ Tool implementation details:
190
+
191
+ - tool modules are discovered from `resources/tools/*.js`, `resources/tools/*.mjs`, and `resources/tools/*.cjs`
192
+ - the preferred format is exporting `tool({...})`; the harness also accepts exported functions plus `nameSchema`, `nameSchemaShape`, or generic `schema` metadata
193
+ - when a module exports exactly one tool function, generic `schema` or `schemaShape` metadata can describe that function
194
+ - keep runtime dependencies for local tools in `resources/package.json`, because the resource package is the execution boundary for those modules
195
+
169
196
  ### Bridge MCP Servers Into Agents
170
197
 
171
198
  Use `mcpServers:` inside agent YAML to bridge MCP servers into the agent's tool list:
@@ -245,6 +272,12 @@ Use this file for workspace-wide behavior such as:
245
272
  - routing via `routing.systemPrompt`
246
273
  - background checkpoint maintenance via `maintenance.checkpoints.*`
247
274
 
275
+ Implementation details:
276
+
277
+ - if `runRoot` is omitted, the harness defaults to `<workspace-root>/run-data`
278
+ - the resolved `runRoot` stores thread indexes, per-thread transcripts, per-run events, approvals, delegations, artifacts, and checkpoint references
279
+ - checkpoint maintenance only starts when `maintenance.checkpoints.enabled: true`; for SQLite checkpoints, cleanup policies can sweep old rows and optionally `VACUUM` the database
280
+
248
281
  ### `config/agent-context.md`
249
282
 
250
283
  Use this file for shared bootstrap context that agents read at construction time.
@@ -276,8 +309,19 @@ Use `resources/` for executable extensions:
276
309
 
277
310
  Each resource package should include its own `package.json`.
278
311
 
312
+ Implementation details:
313
+
314
+ - keep runtime extension source under `resources/`; keep tests outside the published source tree, for example under the repository `test/` folder
315
+ - `resources/package.json` is the module resolution boundary used when local tools import third-party packages or skill-local scripts
316
+ - SKILL packages stay file-based, so prompts, templates, helper scripts, and metadata can ship together without extra registration steps
317
+
279
318
  ### Skills And MCP
280
319
 
281
320
  - Use `resources/skills/` for SKILL packages that carry reusable instructions, templates, scripts, and metadata
282
321
  - Use `mcpServers:` in `config/agents/*.yaml` when you want the harness to bridge external MCP tools into an agent
283
322
  - Use `createToolMcpServer(...)` or `serveToolsOverStdio(...)` when you want the harness itself to act as an MCP server for another client
323
+
324
+ Implementation details:
325
+
326
+ - agent-declared `mcpServers:` entries are hydrated into concrete MCP tool refs during workspace compilation, then validated against the collected MCP server definitions
327
+ - `createToolMcpServer(...)` exposes the selected agent toolset through one MCP server surface; `serveToolsOverStdio(...)` is the stdio transport wrapper around that same server construction
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.23";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.25";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.23";
1
+ export const AGENT_HARNESS_VERSION = "0.0.25";
@@ -1,9 +1,10 @@
1
- import { existsSync } from "node:fs";
1
+ import { existsSync, mkdirSync } from "node:fs";
2
2
  import { createRequire } from "node:module";
3
3
  import path from "node:path";
4
4
  import { stat } from "node:fs/promises";
5
5
  import { readFile } from "node:fs/promises";
6
6
  import { fileURLToPath, pathToFileURL } from "node:url";
7
+ import { CompositeBackend, LocalShellBackend, StateBackend, StoreBackend } from "deepagents";
7
8
  import { Client } from "@modelcontextprotocol/sdk/client/index.js";
8
9
  import { SSEClientTransport } from "@modelcontextprotocol/sdk/client/sse.js";
9
10
  import { StdioClientTransport } from "@modelcontextprotocol/sdk/client/stdio.js";
@@ -62,6 +63,113 @@ function createProviderBackendResolver(provider, workspace) {
62
63
  return (provider?.createResourceBackendResolver?.(workspace) ??
63
64
  provider?.createBuiltinBackendResolver?.(workspace));
64
65
  }
66
+ class CompatibleCompositeBackend {
67
+ id;
68
+ execute;
69
+ composite;
70
+ constructor(defaultBackend, routes) {
71
+ this.composite = new CompositeBackend(defaultBackend, routes);
72
+ const sandboxLike = defaultBackend;
73
+ if (typeof sandboxLike.id === "string" && typeof sandboxLike.execute === "function") {
74
+ this.id = sandboxLike.id;
75
+ this.execute = (command) => this.composite.execute(command);
76
+ }
77
+ }
78
+ lsInfo(filePath) {
79
+ return this.composite.lsInfo(filePath);
80
+ }
81
+ read(filePath, offset, limit) {
82
+ return this.composite.read(filePath, offset, limit);
83
+ }
84
+ readRaw(filePath) {
85
+ return this.composite.readRaw(filePath);
86
+ }
87
+ grepRaw(pattern, filePath, glob) {
88
+ return this.composite.grepRaw(pattern, filePath ?? undefined, glob ?? undefined);
89
+ }
90
+ globInfo(pattern, filePath) {
91
+ return this.composite.globInfo(pattern, filePath);
92
+ }
93
+ write(filePath, content) {
94
+ return this.composite.write(filePath, content);
95
+ }
96
+ edit(filePath, oldString, newString, replaceAll) {
97
+ return this.composite.edit(filePath, oldString, newString, replaceAll);
98
+ }
99
+ uploadFiles(files) {
100
+ return this.composite.uploadFiles(files);
101
+ }
102
+ downloadFiles(paths) {
103
+ return this.composite.downloadFiles(paths);
104
+ }
105
+ }
106
+ function createInlineBackendResolver(workspace) {
107
+ return (binding) => {
108
+ const backendConfig = binding.deepAgentParams?.backend;
109
+ if (!backendConfig || typeof backendConfig !== "object") {
110
+ return undefined;
111
+ }
112
+ const resolveBackendRootDir = (configuredRootDir) => {
113
+ if (typeof configuredRootDir === "string" && configuredRootDir.trim().length > 0) {
114
+ return path.isAbsolute(configuredRootDir)
115
+ ? configuredRootDir
116
+ : path.resolve(workspace.workspaceRoot, configuredRootDir);
117
+ }
118
+ return workspace.workspaceRoot;
119
+ };
120
+ const createBackend = (kind, config, runtimeLike) => {
121
+ switch (kind) {
122
+ case "LocalShellBackend": {
123
+ const rootDir = resolveBackendRootDir(config?.rootDir);
124
+ mkdirSync(rootDir, { recursive: true });
125
+ return new LocalShellBackend({
126
+ rootDir,
127
+ virtualMode: config?.virtualMode === true,
128
+ timeout: typeof config?.timeout === "number" ? config.timeout : undefined,
129
+ maxOutputBytes: typeof config?.maxOutputBytes === "number" ? config.maxOutputBytes : undefined,
130
+ env: typeof config?.env === "object" && config.env
131
+ ? Object.fromEntries(Object.entries(config.env).filter((entry) => typeof entry[1] === "string"))
132
+ : undefined,
133
+ inheritEnv: config?.inheritEnv !== false,
134
+ });
135
+ }
136
+ case "StateBackend":
137
+ return new StateBackend(runtimeLike);
138
+ case "StoreBackend":
139
+ return new StoreBackend(runtimeLike);
140
+ default:
141
+ throw new Error(`Unsupported DeepAgent backend kind "${kind}". Supported inline kinds: LocalShellBackend, StateBackend, StoreBackend, CompositeBackend.`);
142
+ }
143
+ };
144
+ return (runtimeLike) => {
145
+ const kind = typeof backendConfig.kind === "string" ? backendConfig.kind : "CompositeBackend";
146
+ switch (kind) {
147
+ case "LocalShellBackend":
148
+ return createBackend("LocalShellBackend", backendConfig, runtimeLike);
149
+ case "StateBackend":
150
+ return new StateBackend(runtimeLike);
151
+ case "StoreBackend":
152
+ return new StoreBackend(runtimeLike);
153
+ case "CompositeBackend": {
154
+ const stateConfig = typeof backendConfig.state === "object" && backendConfig.state
155
+ ? backendConfig.state
156
+ : { kind: "StateBackend" };
157
+ const defaultBackendKind = typeof stateConfig.kind === "string" ? stateConfig.kind : "StateBackend";
158
+ const routes = typeof backendConfig.routes === "object" && backendConfig.routes
159
+ ? backendConfig.routes
160
+ : { "/memories/": { kind: "StoreBackend" } };
161
+ const mappedRoutes = Object.fromEntries(Object.entries(routes).map(([route, routeConfig]) => {
162
+ const routeKind = typeof routeConfig?.kind === "string" ? routeConfig.kind : "StoreBackend";
163
+ return [route, createBackend(routeKind, routeConfig, runtimeLike)];
164
+ }));
165
+ return new CompatibleCompositeBackend(createBackend(defaultBackendKind, stateConfig, runtimeLike), mappedRoutes);
166
+ }
167
+ default:
168
+ throw new Error(`Unsupported DeepAgent backend kind "${kind}". Supported inline kinds: LocalShellBackend, StateBackend, StoreBackend, CompositeBackend.`);
169
+ }
170
+ };
171
+ };
172
+ }
65
173
  function requireLocalResource(feature) {
66
174
  if (localResource) {
67
175
  return localResource;
@@ -385,7 +493,17 @@ export async function listResourceToolsForSource(source, workspaceRoot = process
385
493
  }
386
494
  export function createResourceBackendResolver(workspace) {
387
495
  const localResolver = createProviderBackendResolver(localResource, workspace);
388
- return (binding) => localResolver?.(binding);
496
+ const remoteResolvers = (workspace.resourceSources ?? workspace.builtinSources ?? [])
497
+ .map((source) => remoteResourceCache.get(source))
498
+ .filter((provider) => Boolean(provider))
499
+ .map((provider) => createProviderBackendResolver(provider, workspace))
500
+ .filter((resolver) => Boolean(resolver));
501
+ const inlineResolver = createInlineBackendResolver(workspace);
502
+ return (binding) => {
503
+ const providerResolved = localResolver?.(binding) ??
504
+ remoteResolvers.map((resolver) => resolver(binding)).find((resolved) => resolved !== undefined);
505
+ return providerResolved ?? inlineResolver(binding);
506
+ };
389
507
  }
390
508
  export function createResourceToolResolver(workspace, options = {}) {
391
509
  const functionResolver = createFunctionToolResolver(workspace);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.24",
3
+ "version": "0.0.26",
4
4
  "description": "Agent Harness framework package",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",