@botbotgo/agent-harness 0.0.62 → 0.0.64

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
@@ -68,6 +68,7 @@ Boundary documents live in:
68
68
  - `docs/upstream-feature-matrix.md`
69
69
  - `docs/product-boundary.md`
70
70
  - `docs/feature-checklist.md`
71
+ - `docs/long-term-memory.md`
71
72
 
72
73
  ## Why This Exists
73
74
 
@@ -109,6 +110,7 @@ your-workspace/
109
110
  embedding-models.yaml
110
111
  vector-stores.yaml
111
112
  stores.yaml
113
+ runtime-memory.yaml
112
114
  backends.yaml
113
115
  tools.yaml
114
116
  mcp.yaml
@@ -330,6 +332,7 @@ Core workspace files:
330
332
  - `config/embedding-models.yaml`
331
333
  - `config/vector-stores.yaml`
332
334
  - `config/stores.yaml`
335
+ - `config/runtime-memory.yaml`
333
336
  - `config/backends.yaml`
334
337
  - `config/tools.yaml`
335
338
  - `config/mcp.yaml`
@@ -437,6 +440,12 @@ spec:
437
440
  path: checkpoints.sqlite
438
441
  ```
439
442
 
443
+ ### `config/runtime-memory.yaml`
444
+
445
+ Use this singleton file for runtime-owned durable long-term memory defaults.
446
+
447
+ Keep bootstrap context in `config/agent-context.md`. Keep resumable execution state in the checkpointer. Use `RuntimeMemory` for durable memory policy and retrieval defaults.
448
+
440
449
  ### `config/backends.yaml`
441
450
 
442
451
  Use reusable DeepAgents backend presets so filesystem and `/memories/*` topology stays in YAML:
package/dist/cli.d.ts ADDED
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env node
2
+ type CliIo = {
3
+ cwd?: string;
4
+ stdout?: (message: string) => void;
5
+ stderr?: (message: string) => void;
6
+ };
7
+ export declare function runCli(argv: string[], io?: CliIo): Promise<number>;
8
+ export {};
package/dist/cli.js ADDED
@@ -0,0 +1,91 @@
1
+ #!/usr/bin/env node
2
+ import path from "node:path";
3
+ import { pathToFileURL } from "node:url";
4
+ import { initProject } from "./init-project.js";
5
+ function renderUsage() {
6
+ return `Usage:
7
+ agent-harness init <project-name> [--template deep-research|single-agent] [--provider <provider>] [--model <model>] [--with-web-search|--no-web-search]
8
+ `;
9
+ }
10
+ function isTemplate(value) {
11
+ return value === "deep-research" || value === "single-agent";
12
+ }
13
+ function parseInitOptions(args) {
14
+ const options = {};
15
+ for (let index = 0; index < args.length; index += 1) {
16
+ const arg = args[index];
17
+ if (arg === "--with-web-search") {
18
+ options.withWebSearch = true;
19
+ continue;
20
+ }
21
+ if (arg === "--no-web-search") {
22
+ options.withWebSearch = false;
23
+ continue;
24
+ }
25
+ if (arg === "--template" || arg === "--provider" || arg === "--model") {
26
+ const value = args[index + 1];
27
+ if (!value) {
28
+ return { error: `Missing value for ${arg}` };
29
+ }
30
+ if (arg === "--template") {
31
+ if (!isTemplate(value)) {
32
+ return { error: `Unsupported template: ${value}` };
33
+ }
34
+ options.template = value;
35
+ }
36
+ else if (arg === "--provider") {
37
+ options.provider = value;
38
+ }
39
+ else {
40
+ options.model = value;
41
+ }
42
+ index += 1;
43
+ continue;
44
+ }
45
+ return { error: `Unknown option: ${arg}` };
46
+ }
47
+ return { options };
48
+ }
49
+ export async function runCli(argv, io = {}) {
50
+ const cwd = io.cwd ?? process.cwd();
51
+ const stdout = io.stdout ?? ((message) => process.stdout.write(message));
52
+ const stderr = io.stderr ?? ((message) => process.stderr.write(message));
53
+ const [command, projectName, ...rest] = argv;
54
+ if (command === "init") {
55
+ if (!projectName?.trim()) {
56
+ stderr(renderUsage());
57
+ return 1;
58
+ }
59
+ const parsed = parseInitOptions(rest);
60
+ if (parsed.error) {
61
+ stderr(`${parsed.error}\n`);
62
+ stderr(renderUsage());
63
+ return 1;
64
+ }
65
+ try {
66
+ const projectRoot = path.resolve(cwd, projectName);
67
+ const result = await initProject(projectRoot, projectName, parsed.options);
68
+ stdout(`Created ${result.projectSlug} at ${result.projectRoot}\n`);
69
+ stdout("Next steps:\n");
70
+ stdout(` cd ${projectName}\n`);
71
+ stdout(" npm install\n");
72
+ if ((parsed.options?.provider ?? "openai") === "openai") {
73
+ stdout(" export OPENAI_API_KEY=your_key_here\n");
74
+ }
75
+ stdout(' npm run start -- "Research a topic"\n');
76
+ return 0;
77
+ }
78
+ catch (error) {
79
+ const message = error instanceof Error ? error.message : String(error);
80
+ stderr(`${message}\n`);
81
+ return 1;
82
+ }
83
+ }
84
+ stderr(renderUsage());
85
+ return 1;
86
+ }
87
+ const invokedPath = process.argv[1] ? pathToFileURL(path.resolve(process.argv[1])).href : "";
88
+ if (import.meta.url === invokedPath) {
89
+ const exitCode = await runCli(process.argv.slice(2));
90
+ process.exitCode = exitCode;
91
+ }
@@ -44,6 +44,9 @@ spec:
44
44
  # The default direct host keeps this enabled so middleware can use the same durable store surface as other hosts.
45
45
  store:
46
46
  ref: store/default
47
+ # agent-harness feature: workspace-level durable long-term memory defaults for this host profile.
48
+ runtimeMemory:
49
+ ref: runtime-memory/default
47
50
  # Upstream execution feature: no declarative HITL tool routing by default.
48
51
  interruptOn: {}
49
52
  # Upstream execution feature: filesystem middleware settings for LangChain v1 agents.
@@ -54,6 +54,9 @@ spec:
54
54
  # Other store kinds should flow through a custom runtime resolver instead of being claimed as built in.
55
55
  store:
56
56
  ref: store/default
57
+ # agent-harness feature: workspace-level durable long-term memory defaults for this host profile.
58
+ runtimeMemory:
59
+ ref: runtime-memory/default
57
60
  # Upstream execution feature: backend config passed into the selected backend adapter.
58
61
  # Prefer a reusable backend preset via `ref` so backend topology stays declarative and reusable in YAML.
59
62
  # The default preset keeps DeepAgent execution semantics upstream-owned:
@@ -0,0 +1,65 @@
1
+ # agent-harness feature: schema version for this declarative config object.
2
+ apiVersion: agent-harness/v1alpha1
3
+ # agent-harness feature: workspace-global durable long-term memory defaults.
4
+ # This object is harness-owned and defines the stable memory policy/config contract for the workspace.
5
+ # It is loaded automatically as a singleton and can also be referenced from agent execution config.
6
+ kind: RuntimeMemory
7
+ metadata:
8
+ # agent-harness feature: stable singleton name for the default runtime memory object.
9
+ name: default
10
+ spec:
11
+ # ======================
12
+ # agent-harness Features
13
+ # ======================
14
+ # agent-harness feature: enable or disable runtime-owned durable long-term memory for the workspace.
15
+ enabled: true
16
+
17
+ # agent-harness feature: primary durable store used for structured long-term memory records.
18
+ store:
19
+ ref: store/default
20
+
21
+ # agent-harness feature: optional semantic retrieval substrate for long-term memory recall.
22
+ vectorStore:
23
+ ref: vector-store/default
24
+
25
+ # agent-harness feature: default embedding model used with the configured vector store.
26
+ embeddingModel:
27
+ ref: embedding-model/default
28
+
29
+ # agent-harness feature: stable namespace roots used by the runtime memory policy layer.
30
+ namespaces:
31
+ users: memories/users
32
+ projects: memories/projects
33
+ threads: memories/threads
34
+
35
+ # agent-harness feature: retrieval defaults for selecting a bounded number of relevant memories per turn.
36
+ retrieval:
37
+ defaultTopK: 5
38
+ maxPromptMemories: 8
39
+
40
+ # agent-harness feature: selective write policy for durable long-term memory.
41
+ writePolicy:
42
+ mode: selective
43
+ allow:
44
+ - user_preferences
45
+ - project_facts
46
+ - confirmed_decisions
47
+ - reusable_summaries
48
+ deny:
49
+ - scratchpad
50
+ - transient_reasoning
51
+ - intermediate_results
52
+
53
+ # agent-harness feature: background consolidation and retention defaults for stored memories.
54
+ consolidation:
55
+ dedupe: true
56
+ mergeStrategy: replace_or_merge
57
+ decay:
58
+ enabled: true
59
+ maxAgeDays: 180
60
+
61
+ # agent-harness feature: lifecycle events that can trigger runtime memory digestion.
62
+ ingestion:
63
+ backgroundConsolidation: true
64
+ writeOnApprovalResolution: true
65
+ writeOnRunCompletion: true
@@ -204,6 +204,7 @@ export type CompiledAgentBinding = {
204
204
  capabilities?: RuntimeCapabilities;
205
205
  checkpointer?: Record<string, unknown> | boolean;
206
206
  store?: Record<string, unknown>;
207
+ runtimeMemory?: Record<string, unknown>;
207
208
  };
208
209
  };
209
210
  export type WorkspaceBundle = {
@@ -0,0 +1,12 @@
1
+ export type InitTemplate = "deep-research" | "single-agent";
2
+ export type InitProjectOptions = {
3
+ template?: InitTemplate;
4
+ provider?: string;
5
+ model?: string;
6
+ withWebSearch?: boolean;
7
+ };
8
+ export type InitProjectResult = {
9
+ projectRoot: string;
10
+ projectSlug: string;
11
+ };
12
+ export declare function initProject(projectRoot: string, projectName: string, options?: InitProjectOptions): Promise<InitProjectResult>;
@@ -0,0 +1,324 @@
1
+ import path from "node:path";
2
+ import { writeFile } from "node:fs/promises";
3
+ import { AGENT_HARNESS_VERSION } from "./package-version.js";
4
+ import { ensureDir, fileExists } from "./utils/fs.js";
5
+ function toProjectSlug(projectName) {
6
+ const normalized = projectName
7
+ .trim()
8
+ .toLowerCase()
9
+ .replace(/[^a-z0-9]+/g, "-")
10
+ .replace(/^-+|-+$/g, "");
11
+ return normalized || "agent-harness-app";
12
+ }
13
+ function toDisplayName(projectName) {
14
+ return projectName
15
+ .trim()
16
+ .split(/[-_\s]+/)
17
+ .filter(Boolean)
18
+ .map((part) => part.charAt(0).toUpperCase() + part.slice(1))
19
+ .join(" ");
20
+ }
21
+ function resolveOptions(options = {}) {
22
+ return {
23
+ template: options.template ?? "deep-research",
24
+ provider: options.provider?.trim() || "openai",
25
+ model: options.model?.trim() || "gpt-4o-mini",
26
+ withWebSearch: options.withWebSearch ?? true,
27
+ };
28
+ }
29
+ function renderPackageJson(projectSlug, template) {
30
+ return `${JSON.stringify({
31
+ name: projectSlug,
32
+ version: "0.0.1",
33
+ private: true,
34
+ type: "module",
35
+ description: `Starter ${template} application generated by agent-harness init.`,
36
+ scripts: {
37
+ start: "node ./src/run.mjs",
38
+ },
39
+ dependencies: {
40
+ "@botbotgo/agent-harness": `^${AGENT_HARNESS_VERSION}`,
41
+ },
42
+ }, null, 2)}\n`;
43
+ }
44
+ function renderReadme(displayName, projectSlug, options) {
45
+ const webSearchLine = options.withWebSearch
46
+ ? `- ${options.provider} native web search enabled through \`config/tools.yaml\``
47
+ : "- no provider-native web search by default";
48
+ const providerEnvLine = options.provider === "openai" ? "export OPENAI_API_KEY=your_key_here\n" : "";
49
+ const templateLine = options.template === "deep-research"
50
+ ? "- a host research agent plus a specialist analyst subagent"
51
+ : "- a single host research agent for direct iteration";
52
+ return `# ${displayName}
53
+
54
+ This project was scaffolded with \`agent-harness init ${projectSlug}\`.
55
+
56
+ It is a minimal ${options.template} workspace with:
57
+
58
+ ${templateLine}
59
+ ${webSearchLine}
60
+
61
+ ## Run
62
+
63
+ \`\`\`bash
64
+ npm install
65
+ ${providerEnvLine}npm run start -- "Research the latest model serving stack for agent products and summarize the tradeoffs."
66
+ \`\`\`
67
+
68
+ ## Structure
69
+
70
+ - \`src/run.mjs\`: minimal launcher
71
+ - \`config/\`: workspace runtime and agent topology
72
+ - \`resources/\`: place local tools and skills here as your product grows
73
+
74
+ ## Customize
75
+
76
+ - change \`config/models.yaml\` to switch models or providers
77
+ - add local tools under \`resources/tools/\`
78
+ - add product-specific skills under \`resources/skills/\`
79
+ - edit the prompts in \`config/agents/\` to turn this into your own product
80
+ `;
81
+ }
82
+ function renderGitignore() {
83
+ return `node_modules
84
+ .agent
85
+ `;
86
+ }
87
+ function renderWorkspaceYaml() {
88
+ return `apiVersion: agent-harness/v1alpha1
89
+ kind: Runtime
90
+ metadata:
91
+ name: default
92
+ spec:
93
+ runRoot: ./.agent
94
+ `;
95
+ }
96
+ function renderAgentContext(options) {
97
+ const webSearchGuidance = options.withWebSearch
98
+ ? "- Favor recent, attributable sources when the request depends on current information."
99
+ : "- Favor clear internal reasoning and add search or local tools as your product needs evolve.";
100
+ return `# Deep Research Workspace Context
101
+
102
+ - This workspace is a starter for product-oriented research applications.
103
+ ${webSearchGuidance}
104
+ - Synthesize findings clearly, call out uncertainty, and separate facts from inference.
105
+ `;
106
+ }
107
+ function renderModelsYaml(options) {
108
+ return `apiVersion: agent-harness/v1alpha1
109
+ kind: Models
110
+ spec:
111
+ - name: default
112
+ kind: Model
113
+ provider: ${options.provider}
114
+ model: ${options.model}
115
+ temperature: 0.2
116
+ `;
117
+ }
118
+ function renderBackendsYaml() {
119
+ return `apiVersion: agent-harness/v1alpha1
120
+ kind: Backends
121
+ spec:
122
+ - kind: Backend
123
+ name: default
124
+ description: Default DeepAgent backend preset for generated research apps.
125
+ backendKind: CompositeBackend
126
+ state:
127
+ kind: VfsSandbox
128
+ timeout: 600
129
+ routes:
130
+ /memories/:
131
+ kind: StoreBackend
132
+ `;
133
+ }
134
+ function renderRuntimeMemoryYaml() {
135
+ return `apiVersion: agent-harness/v1alpha1
136
+ kind: RuntimeMemory
137
+ metadata:
138
+ name: default
139
+ spec:
140
+ enabled: true
141
+ namespaces:
142
+ users: memories/users
143
+ projects: memories/projects
144
+ threads: memories/threads
145
+ retrieval:
146
+ defaultTopK: 5
147
+ maxPromptMemories: 8
148
+ writePolicy:
149
+ mode: selective
150
+ allow:
151
+ - user_preferences
152
+ - project_facts
153
+ - confirmed_decisions
154
+ - reusable_summaries
155
+ deny:
156
+ - scratchpad
157
+ - transient_reasoning
158
+ - intermediate_results
159
+ `;
160
+ }
161
+ function renderToolsYaml(options) {
162
+ if (!options.withWebSearch) {
163
+ return `apiVersion: agent-harness/v1alpha1
164
+ kind: Tools
165
+ spec: []
166
+ `;
167
+ }
168
+ return `apiVersion: agent-harness/v1alpha1
169
+ kind: Tools
170
+ spec:
171
+ - kind: Tool
172
+ name: web-search
173
+ type: provider
174
+ description: ${options.provider} web search tool for current research.
175
+ providerTool:
176
+ provider: ${options.provider}
177
+ tool: webSearch
178
+ args:
179
+ searchContextSize: medium
180
+ `;
181
+ }
182
+ function renderResearchAgentYaml(options) {
183
+ const toolsBlock = options.withWebSearch ? " tools:\n - ref: tool/web-search\n" : " tools: []\n";
184
+ const subagentsBlock = options.template === "deep-research" ? " subagents:\n - ref: agent/research-analyst\n" : " subagents: []\n";
185
+ const prompt = options.withWebSearch
186
+ ? "Break complex research requests into a clear plan, use web search when current information matters, and return a concise synthesis with sources and explicit uncertainty."
187
+ : "Break complex research requests into a clear plan and return a concise synthesis with explicit assumptions and uncertainty.";
188
+ const delegationLine = options.template === "deep-research"
189
+ ? " Delegate detailed investigation to the analyst when useful."
190
+ : "";
191
+ return `apiVersion: agent-harness/v1alpha1
192
+ kind: Agent
193
+ metadata:
194
+ name: research
195
+ description: Host-facing research agent for investigating and synthesizing answers.
196
+ spec:
197
+ execution:
198
+ backend: deepagent
199
+ modelRef: model/default
200
+ ${toolsBlock} skills:
201
+ - path: ./
202
+ ${subagentsBlock} config:
203
+ backend:
204
+ ref: backend/default
205
+ runtimeMemory:
206
+ ref: runtime-memory/default
207
+ systemPrompt: ${prompt}${delegationLine}
208
+ `;
209
+ }
210
+ function renderResearchAnalystYaml(options) {
211
+ const toolsBlock = options.withWebSearch ? " tools:\n - ref: tool/web-search\n" : " tools: []\n";
212
+ const prompt = options.withWebSearch
213
+ ? "Gather current sources, compare claims carefully, extract the most decision-relevant facts, and return clean notes the host agent can synthesize."
214
+ : "Break down the problem, compare alternatives carefully, extract the most decision-relevant facts, and return clean notes the host agent can synthesize.";
215
+ return `apiVersion: agent-harness/v1alpha1
216
+ kind: Agent
217
+ metadata:
218
+ name: research-analyst
219
+ description: Specialist subagent for source gathering, comparison, and evidence extraction.
220
+ spec:
221
+ execution:
222
+ backend: deepagent
223
+ modelRef: model/default
224
+ ${toolsBlock} skills:
225
+ - path: ./
226
+ config:
227
+ backend:
228
+ ref: backend/default
229
+ runtimeMemory:
230
+ ref: runtime-memory/default
231
+ systemPrompt: ${prompt}
232
+ `;
233
+ }
234
+ function renderResourcePackageJson(projectSlug) {
235
+ return `${JSON.stringify({
236
+ name: `${projectSlug}-resources`,
237
+ version: "0.0.1",
238
+ private: true,
239
+ type: "module",
240
+ description: "Resource package for a generated agent-harness project.",
241
+ }, null, 2)}\n`;
242
+ }
243
+ function renderSkill(options) {
244
+ const stepTwo = options.withWebSearch
245
+ ? "2. Use `web-search` to collect recent primary or otherwise authoritative sources."
246
+ : "2. Collect the key facts and assumptions needed to answer the request cleanly.";
247
+ return `---
248
+ name: deep-research
249
+ description: Default research workflow for product investigations that need careful synthesis.
250
+ ---
251
+
252
+ # Deep Research
253
+
254
+ Use this skill when the user wants comparisons, source-backed analysis, or a concise brief.
255
+
256
+ ## Workflow
257
+
258
+ 1. Clarify the target question and what "done" looks like.
259
+ ${stepTwo}
260
+ 3. Compare evidence instead of trusting a single source.
261
+ 4. Separate verified facts from inference.
262
+ 5. End with a concise synthesis, explicit caveats, and source links when available.
263
+ `;
264
+ }
265
+ function renderRunScript(options) {
266
+ const defaultInput = options.withWebSearch
267
+ ? "Research the latest model and tooling trends for AI product teams. Return a concise brief with sources and caveats."
268
+ : "Analyze the current architecture direction for this product and return a concise brief with assumptions and caveats.";
269
+ return `import { fileURLToPath } from "node:url";
270
+ import { createAgentHarness, run, stop } from "@botbotgo/agent-harness";
271
+
272
+ const appRoot = fileURLToPath(new URL("..", import.meta.url));
273
+ const input = process.argv.slice(2).join(" ").trim() ||
274
+ ${JSON.stringify(defaultInput)};
275
+
276
+ const runtime = await createAgentHarness(appRoot);
277
+
278
+ try {
279
+ const result = await run(runtime, {
280
+ agentId: "research",
281
+ input,
282
+ });
283
+ process.stdout.write(\`\${result.output}\\n\`);
284
+ } finally {
285
+ await stop(runtime);
286
+ }
287
+ `;
288
+ }
289
+ export async function initProject(projectRoot, projectName, options = {}) {
290
+ if (await fileExists(projectRoot)) {
291
+ throw new Error(`Target path already exists: ${projectRoot}`);
292
+ }
293
+ const resolved = resolveOptions(options);
294
+ const projectSlug = toProjectSlug(projectName);
295
+ const displayName = toDisplayName(projectName) || "Agent Harness App";
296
+ const files = new Map([
297
+ ["package.json", renderPackageJson(projectSlug, resolved.template)],
298
+ [".gitignore", renderGitignore()],
299
+ ["README.md", renderReadme(displayName, projectSlug, resolved)],
300
+ ["src/run.mjs", renderRunScript(resolved)],
301
+ ["config/workspace.yaml", renderWorkspaceYaml()],
302
+ ["config/agent-context.md", renderAgentContext(resolved)],
303
+ ["config/models.yaml", renderModelsYaml(resolved)],
304
+ ["config/runtime-memory.yaml", renderRuntimeMemoryYaml()],
305
+ ["config/backends.yaml", renderBackendsYaml()],
306
+ ["config/tools.yaml", renderToolsYaml(resolved)],
307
+ ["config/agents/research.yaml", renderResearchAgentYaml(resolved)],
308
+ ["resources/package.json", renderResourcePackageJson(projectSlug)],
309
+ ["resources/skills/deep-research/SKILL.md", renderSkill(resolved)],
310
+ ]);
311
+ if (resolved.template === "deep-research") {
312
+ files.set("config/agents/research-analyst.yaml", renderResearchAnalystYaml(resolved));
313
+ }
314
+ await ensureDir(projectRoot);
315
+ for (const [relativePath, content] of files) {
316
+ const filePath = path.join(projectRoot, relativePath);
317
+ await ensureDir(path.dirname(filePath));
318
+ await writeFile(filePath, content, "utf8");
319
+ }
320
+ return {
321
+ projectRoot,
322
+ projectSlug,
323
+ };
324
+ }
@@ -1 +1 @@
1
- export declare const AGENT_HARNESS_VERSION = "0.0.61";
1
+ export declare const AGENT_HARNESS_VERSION = "0.0.63";
@@ -1 +1 @@
1
- export const AGENT_HARNESS_VERSION = "0.0.61";
1
+ export const AGENT_HARNESS_VERSION = "0.0.63";
@@ -14,6 +14,7 @@ export declare class AgentHarnessRuntime {
14
14
  private readonly embeddingModels;
15
15
  private readonly vectorStores;
16
16
  private readonly defaultStore;
17
+ private readonly runtimeMemoryStore;
17
18
  private readonly routingSystemPrompt?;
18
19
  private readonly routingRules;
19
20
  private readonly routingDefaultAgentId?;
@@ -35,6 +36,7 @@ export declare class AgentHarnessRuntime {
35
36
  private buildRoutingInput;
36
37
  private resolveSelectedAgentId;
37
38
  private resolveStore;
39
+ private resolveStoreFromConfig;
38
40
  private resolveEmbeddingModel;
39
41
  private resolveVectorStore;
40
42
  constructor(workspace: WorkspaceBundle, runtimeAdapterOptions?: RuntimeAdapterOptions);
@@ -29,6 +29,7 @@ export class AgentHarnessRuntime {
29
29
  embeddingModels = new Map();
30
30
  vectorStores = new Map();
31
31
  defaultStore;
32
+ runtimeMemoryStore;
32
33
  routingSystemPrompt;
33
34
  routingRules;
34
35
  routingDefaultAgentId;
@@ -98,18 +99,18 @@ export class AgentHarnessRuntime {
98
99
  }
99
100
  resolveStore(binding) {
100
101
  const storeConfig = binding ? getBindingStoreConfig(binding) : undefined;
102
+ return this.resolveStoreFromConfig(storeConfig, binding?.harnessRuntime.runRoot ?? this.defaultRunRoot()) ?? this.defaultStore;
103
+ }
104
+ resolveStoreFromConfig(storeConfig, runRoot) {
101
105
  const cacheKey = storeConfig ? JSON.stringify(storeConfig) : undefined;
102
- if (!storeConfig) {
103
- return this.defaultStore;
104
- }
105
- if (!cacheKey) {
106
- return this.defaultStore;
106
+ if (!storeConfig || !cacheKey) {
107
+ return undefined;
107
108
  }
108
109
  const existing = this.stores.get(cacheKey);
109
110
  if (existing) {
110
111
  return existing;
111
112
  }
112
- const created = createStoreForConfig(storeConfig, binding?.harnessRuntime.runRoot ?? this.defaultRunRoot());
113
+ const created = createStoreForConfig(storeConfig, runRoot);
113
114
  this.stores.set(cacheKey, created);
114
115
  return created;
115
116
  }
@@ -142,7 +143,12 @@ export class AgentHarnessRuntime {
142
143
  const runRoot = this.defaultRunRoot();
143
144
  this.persistence = new FilePersistence(runRoot);
144
145
  const defaultStoreConfig = this.listHostBindings()[0]?.harnessRuntime.store;
145
- this.defaultStore = defaultStoreConfig ? createStoreForConfig(defaultStoreConfig, runRoot) : new FileBackedStore(`${runRoot}/store.json`);
146
+ this.defaultStore = this.resolveStoreFromConfig(defaultStoreConfig, runRoot) ?? new FileBackedStore(`${runRoot}/store.json`);
147
+ const runtimeMemoryStoreConfig = typeof this.listHostBindings()[0]?.harnessRuntime.runtimeMemory?.store === "object" &&
148
+ this.listHostBindings()[0]?.harnessRuntime.runtimeMemory?.store
149
+ ? this.listHostBindings()[0]?.harnessRuntime.runtimeMemory?.store
150
+ : undefined;
151
+ this.runtimeMemoryStore = this.resolveStoreFromConfig(runtimeMemoryStoreConfig, runRoot) ?? this.defaultStore;
146
152
  this.resolvedRuntimeAdapterOptions = {
147
153
  ...runtimeAdapterOptions,
148
154
  toolResolver: runtimeAdapterOptions.toolResolver ??
@@ -177,7 +183,7 @@ export class AgentHarnessRuntime {
177
183
  this.routingRules = getRoutingRules(workspace.refs);
178
184
  this.routingDefaultAgentId = getRoutingDefaultAgentId(workspace.refs);
179
185
  this.modelRoutingEnabled = isModelRoutingEnabled(workspace.refs);
180
- this.threadMemorySync = new ThreadMemorySync(this.persistence, this.defaultStore);
186
+ this.threadMemorySync = new ThreadMemorySync(this.persistence, this.runtimeMemoryStore);
181
187
  this.unregisterThreadMemorySync = this.eventBus.registerProjection(this.threadMemorySync);
182
188
  const checkpointMaintenanceConfig = readCheckpointMaintenanceConfig(workspace);
183
189
  this.checkpointMaintenance = checkpointMaintenanceConfig
@@ -3,7 +3,7 @@ import { getSkillInheritancePolicy, resolveToolTargets } from "../extensions.js"
3
3
  import { compileModel, compileTool } from "./resource-compilers.js";
4
4
  import { inferAgentCapabilities } from "./support/agent-capabilities.js";
5
5
  import { discoverSkillPaths } from "./support/discovery.js";
6
- import { compileAgentMemories, getRuntimeDefaults, getWorkspaceObject, resolvePromptValue, resolveRefId } from "./support/workspace-ref-utils.js";
6
+ import { compileAgentMemories, getRuntimeDefaults, getRuntimeMemoryDefaults, getWorkspaceObject, resolvePromptValue, resolveRefId } from "./support/workspace-ref-utils.js";
7
7
  const WORKSPACE_BOUNDARY_GUIDANCE = "Keep repository and file exploration bounded to the current workspace root unless the user explicitly asks for broader host or filesystem access. " +
8
8
  "Do not inspect absolute paths outside the workspace, system directories, or unrelated repos by default. " +
9
9
  "Prefer workspace-local tools, relative paths, and the current repository checkout when analyzing code.";
@@ -215,6 +215,23 @@ function resolveCheckpointerConfig(agent, refs) {
215
215
  }
216
216
  return { config: inlineAgentCheckpointer };
217
217
  }
218
+ function resolveRuntimeMemoryConfig(agent, refs) {
219
+ const inlineRuntimeMemory = typeof agent.deepAgentConfig?.runtimeMemory === "object" && agent.deepAgentConfig.runtimeMemory
220
+ ? agent.deepAgentConfig.runtimeMemory
221
+ : typeof agent.langchainAgentConfig?.runtimeMemory === "object" && agent.langchainAgentConfig.runtimeMemory
222
+ ? agent.langchainAgentConfig.runtimeMemory
223
+ : undefined;
224
+ if (inlineRuntimeMemory) {
225
+ if (isRefConfig(inlineRuntimeMemory)) {
226
+ return {
227
+ config: materializeWorkspaceObjectConfig(refs, inlineRuntimeMemory.ref, ["runtime-memory"], `Agent ${agent.id} runtimeMemory`),
228
+ };
229
+ }
230
+ return { config: inlineRuntimeMemory };
231
+ }
232
+ const runtimeMemoryDefaults = getRuntimeMemoryDefaults(refs);
233
+ return runtimeMemoryDefaults ? { config: runtimeMemoryDefaults } : undefined;
234
+ }
218
235
  export function compileBinding(workspaceRoot, agent, agents, referencedSubagentIds, refs, models, tools) {
219
236
  const internalSubagent = referencedSubagentIds.has(agent.id);
220
237
  const runtimeDefaults = getRuntimeDefaults(refs);
@@ -224,6 +241,7 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
224
241
  const backend = resolveBackendConfig(agent, refs);
225
242
  const store = resolveStoreConfig(agent, refs);
226
243
  const checkpointer = resolveCheckpointerConfig(agent, refs);
244
+ const runtimeMemory = resolveRuntimeMemoryConfig(agent, refs);
227
245
  const runRoot = typeof agent.runRoot === "string" && agent.runRoot.trim().length > 0
228
246
  ? path.resolve(workspaceRoot, agent.runRoot)
229
247
  : typeof runtimeDefaults?.runRoot === "string" && runtimeDefaults.runRoot.trim().length > 0
@@ -248,6 +266,7 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
248
266
  capabilities: inferAgentCapabilities(agent),
249
267
  ...(checkpointer ? { checkpointer: checkpointer.config } : {}),
250
268
  ...(store ? { store: store.config } : {}),
269
+ ...(runtimeMemory ? { runtimeMemory: runtimeMemory.config } : {}),
251
270
  },
252
271
  };
253
272
  if (agent.executionMode !== "deepagent") {
@@ -130,6 +130,8 @@ function normalizeKind(kind) {
130
130
  return "skill";
131
131
  case "Runtime":
132
132
  return "runtime";
133
+ case "RuntimeMemory":
134
+ return "runtime-memory";
133
135
  case "Prompt":
134
136
  return "prompt";
135
137
  case "McpServer":
@@ -303,6 +305,7 @@ function readLangchainAgentConfig(item) {
303
305
  "middleware",
304
306
  "backend",
305
307
  "store",
308
+ "runtimeMemory",
306
309
  "taskDescription",
307
310
  "generalPurposeAgent",
308
311
  "filesystem",
@@ -310,6 +313,7 @@ function readLangchainAgentConfig(item) {
310
313
  return {
311
314
  ...readSharedAgentConfig(config),
312
315
  ...(typeof config.store === "object" && config.store ? { store: config.store } : {}),
316
+ ...(typeof config.runtimeMemory === "object" && config.runtimeMemory ? { runtimeMemory: config.runtimeMemory } : {}),
313
317
  ...(typeof config.taskDescription === "string" && config.taskDescription.trim() ? { taskDescription: config.taskDescription } : {}),
314
318
  ...(typeof config.generalPurposeAgent === "boolean" ? { generalPurposeAgent: config.generalPurposeAgent } : {}),
315
319
  ...(passthrough ? { passthrough } : {}),
@@ -329,6 +333,7 @@ function readDeepAgentConfig(item) {
329
333
  "middleware",
330
334
  "backend",
331
335
  "store",
336
+ "runtimeMemory",
332
337
  "taskDescription",
333
338
  "generalPurposeAgent",
334
339
  "filesystem",
@@ -337,6 +342,7 @@ function readDeepAgentConfig(item) {
337
342
  ...readSharedAgentConfig(config),
338
343
  ...(typeof config.backend === "object" && config.backend ? { backend: config.backend } : {}),
339
344
  ...(typeof config.store === "object" && config.store ? { store: config.store } : {}),
345
+ ...(typeof config.runtimeMemory === "object" && config.runtimeMemory ? { runtimeMemory: config.runtimeMemory } : {}),
340
346
  ...(typeof config.taskDescription === "string" && config.taskDescription.trim() ? { taskDescription: config.taskDescription } : {}),
341
347
  ...(typeof config.generalPurposeAgent === "boolean" ? { generalPurposeAgent: config.generalPurposeAgent } : {}),
342
348
  ...(passthrough ? { passthrough } : {}),
@@ -1,2 +1,2 @@
1
1
  export { collectAgentDiscoverySourceRefs, collectToolSourceRefs } from "./source-collectors.js";
2
- export { compileAgentMemories, getRuntimeDefaults, getWorkspaceObject, resolvePathList, resolvePromptValue, resolveRefId, } from "./workspace-ref-utils.js";
2
+ export { compileAgentMemories, getRuntimeDefaults, getRuntimeMemoryDefaults, getWorkspaceObject, resolvePathList, resolvePromptValue, resolveRefId, } from "./workspace-ref-utils.js";
@@ -1,2 +1,2 @@
1
1
  export { collectAgentDiscoverySourceRefs, collectToolSourceRefs } from "./source-collectors.js";
2
- export { compileAgentMemories, getRuntimeDefaults, getWorkspaceObject, resolvePathList, resolvePromptValue, resolveRefId, } from "./workspace-ref-utils.js";
2
+ export { compileAgentMemories, getRuntimeDefaults, getRuntimeMemoryDefaults, getWorkspaceObject, resolvePathList, resolvePromptValue, resolveRefId, } from "./workspace-ref-utils.js";
@@ -22,6 +22,7 @@ export type ConcurrencyConfig = {
22
22
  };
23
23
  export declare function getWorkspaceObject(refs: Map<string, WorkspaceObject | ParsedAgentObject>, ref: string | undefined): WorkspaceObject | undefined;
24
24
  export declare function getRuntimeDefaults(refs: Map<string, WorkspaceObject | ParsedAgentObject>): Record<string, unknown> | undefined;
25
+ export declare function getRuntimeMemoryDefaults(refs: Map<string, WorkspaceObject | ParsedAgentObject>): Record<string, unknown> | undefined;
25
26
  export declare function getRecoveryConfig(refs: Map<string, WorkspaceObject | ParsedAgentObject>): RecoveryConfig;
26
27
  export declare function getConcurrencyConfig(refs: Map<string, WorkspaceObject | ParsedAgentObject>): ConcurrencyConfig;
27
28
  export declare function getRoutingSystemPrompt(refs: Map<string, WorkspaceObject | ParsedAgentObject>): string | undefined;
@@ -25,6 +25,16 @@ export function getRuntimeDefaults(refs) {
25
25
  }
26
26
  return runtimes[0].value;
27
27
  }
28
+ export function getRuntimeMemoryDefaults(refs) {
29
+ const runtimeMemories = Array.from(refs.values()).filter((object) => !("executionMode" in object) && object.kind === "runtime-memory");
30
+ if (runtimeMemories.length === 0) {
31
+ return undefined;
32
+ }
33
+ if (runtimeMemories.length > 1) {
34
+ throw new Error(`Expected at most one RuntimeMemory object, found ${runtimeMemories.length}`);
35
+ }
36
+ return runtimeMemories[0].value;
37
+ }
28
38
  export function getRecoveryConfig(refs) {
29
39
  const runtimeDefaults = getRuntimeDefaults(refs);
30
40
  const recovery = typeof runtimeDefaults?.recovery === "object" && runtimeDefaults.recovery
@@ -169,7 +179,7 @@ export function resolveRefId(ref) {
169
179
  if (rest.length === 0) {
170
180
  return ref;
171
181
  }
172
- if (["agent", "embedding-model", "mcp", "model", "object", "prompt", "runtime", "tool", "vector-store"].includes(namespace)) {
182
+ if (["agent", "embedding-model", "mcp", "model", "object", "prompt", "runtime", "runtime-memory", "tool", "vector-store"].includes(namespace)) {
173
183
  return rest.join("/");
174
184
  }
175
185
  return ref;
package/package.json CHANGED
@@ -1,11 +1,14 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.62",
3
+ "version": "0.0.64",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",
7
7
  "main": "./dist/index.js",
8
8
  "types": "./dist/index.d.ts",
9
+ "bin": {
10
+ "agent-harness": "./dist/cli.js"
11
+ },
9
12
  "files": [
10
13
  "dist"
11
14
  ],
@@ -50,7 +53,7 @@
50
53
  "scripts": {
51
54
  "build": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && cp -R config dist/",
52
55
  "check": "tsc -p tsconfig.json --noEmit",
53
- "test": "vitest run test/hello-file.test.ts 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/product-boundary-docs.test.ts test/docs-site.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",
56
+ "test": "vitest run test/hello-file.test.ts 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/product-boundary-docs.test.ts test/long-term-memory-docs.test.ts test/docs-site.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 test/config-secrets.test.ts test/init-command.test.ts",
54
57
  "test:real-providers": "vitest run test/real-provider-harness.test.ts",
55
58
  "release:prepare": "npm version patch --no-git-tag-version && node ./scripts/sync-example-version.mjs",
56
59
  "release:pack": "npm pack --dry-run",