@botbotgo/agent-harness 0.0.75 → 0.0.76

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.
Files changed (39) hide show
  1. package/dist/api.d.ts +2 -1
  2. package/dist/api.js +3 -0
  3. package/dist/benchmark/checkpoint-resume-cost-benchmark.d.ts +33 -0
  4. package/dist/benchmark/checkpoint-resume-cost-benchmark.js +55 -0
  5. package/dist/benchmark/deepagent-local-model-benchmark.d.ts +27 -0
  6. package/dist/benchmark/deepagent-local-model-benchmark.js +35 -0
  7. package/dist/config/agents/direct.yaml +1 -1
  8. package/dist/config/agents/orchestra.yaml +1 -2
  9. package/dist/config/workspace.yaml +31 -0
  10. package/dist/contracts/types.d.ts +38 -1
  11. package/dist/index.d.ts +1 -1
  12. package/dist/index.js +1 -1
  13. package/dist/package-version.d.ts +1 -1
  14. package/dist/package-version.js +1 -1
  15. package/dist/persistence/file-store.d.ts +3 -40
  16. package/dist/persistence/file-store.js +5 -2
  17. package/dist/persistence/sqlite-store.d.ts +68 -0
  18. package/dist/persistence/sqlite-store.js +569 -0
  19. package/dist/persistence/types.d.ts +83 -0
  20. package/dist/persistence/types.js +1 -0
  21. package/dist/runtime/agent-runtime-adapter.d.ts +3 -0
  22. package/dist/runtime/agent-runtime-adapter.js +58 -2
  23. package/dist/runtime/checkpoint-maintenance.d.ts +11 -2
  24. package/dist/runtime/checkpoint-maintenance.js +41 -5
  25. package/dist/runtime/harness.d.ts +5 -1
  26. package/dist/runtime/harness.js +45 -3
  27. package/dist/runtime/health-monitor.d.ts +81 -0
  28. package/dist/runtime/health-monitor.js +448 -0
  29. package/dist/runtime/runtime-record-maintenance.d.ts +43 -0
  30. package/dist/runtime/runtime-record-maintenance.js +169 -0
  31. package/dist/runtime/store.d.ts +2 -0
  32. package/dist/runtime/store.js +38 -20
  33. package/dist/runtime/support/embedding-models.js +57 -1
  34. package/dist/runtime/thread-memory-sync.d.ts +3 -2
  35. package/dist/runtime/thread-memory-sync.js +7 -1
  36. package/dist/workspace/agent-binding-compiler.js +3 -1
  37. package/dist/workspace/support/workspace-ref-utils.d.ts +9 -0
  38. package/dist/workspace/support/workspace-ref-utils.js +38 -0
  39. package/package.json +2 -2
@@ -54,9 +54,15 @@ export class FileBackedStore {
54
54
  filePath;
55
55
  delegate = new InMemoryStore();
56
56
  loaded = false;
57
+ operationChain = Promise.resolve();
57
58
  constructor(filePath) {
58
59
  this.filePath = filePath;
59
60
  }
61
+ async runSerialized(operation) {
62
+ const pending = this.operationChain.then(operation, operation);
63
+ this.operationChain = pending.then(() => undefined, () => undefined);
64
+ return pending;
65
+ }
60
66
  async ensureLoaded() {
61
67
  if (this.loaded) {
62
68
  return;
@@ -81,36 +87,48 @@ export class FileBackedStore {
81
87
  }, null, 2), "utf8");
82
88
  }
83
89
  async batch(operations) {
84
- await this.ensureLoaded();
85
- const result = await this.delegate.batch(operations);
86
- if (operations.some((operation) => "value" in operation)) {
87
- await this.persist();
88
- }
89
- return result;
90
+ return this.runSerialized(async () => {
91
+ await this.ensureLoaded();
92
+ const result = await this.delegate.batch(operations);
93
+ if (operations.some((operation) => "value" in operation)) {
94
+ await this.persist();
95
+ }
96
+ return result;
97
+ });
90
98
  }
91
99
  async get(namespace, key) {
92
- await this.ensureLoaded();
93
- return this.delegate.get(namespace, key);
100
+ return this.runSerialized(async () => {
101
+ await this.ensureLoaded();
102
+ return this.delegate.get(namespace, key);
103
+ });
94
104
  }
95
105
  async search(namespacePrefix, options) {
96
- await this.ensureLoaded();
97
- return this.delegate.search(namespacePrefix, options);
106
+ return this.runSerialized(async () => {
107
+ await this.ensureLoaded();
108
+ return this.delegate.search(namespacePrefix, options);
109
+ });
98
110
  }
99
111
  async put(namespace, key, value, index) {
100
- await this.ensureLoaded();
101
- const result = await this.delegate.put(namespace, key, value, index);
102
- await this.persist();
103
- return result;
112
+ return this.runSerialized(async () => {
113
+ await this.ensureLoaded();
114
+ const result = await this.delegate.put(namespace, key, value, index);
115
+ await this.persist();
116
+ return result;
117
+ });
104
118
  }
105
119
  async delete(namespace, key) {
106
- await this.ensureLoaded();
107
- const result = await this.delegate.delete(namespace, key);
108
- await this.persist();
109
- return result;
120
+ return this.runSerialized(async () => {
121
+ await this.ensureLoaded();
122
+ const result = await this.delegate.delete(namespace, key);
123
+ await this.persist();
124
+ return result;
125
+ });
110
126
  }
111
127
  async listNamespaces(options) {
112
- await this.ensureLoaded();
113
- return this.delegate.listNamespaces(options);
128
+ return this.runSerialized(async () => {
129
+ await this.ensureLoaded();
130
+ return this.delegate.listNamespaces(options);
131
+ });
114
132
  }
115
133
  }
116
134
  export function createInMemoryStore() {
@@ -1,7 +1,51 @@
1
1
  import { OllamaEmbeddings } from "@langchain/ollama";
2
+ import { OpenAIEmbeddings } from "@langchain/openai";
2
3
  import { compileEmbeddingModel } from "../../workspace/resource-compilers.js";
3
4
  import { resolveRefId } from "../../workspace/support/workspace-ref-utils.js";
4
5
  import { createLlamaIndexEmbeddingModel } from "./llamaindex.js";
6
+ function asObject(value) {
7
+ return typeof value === "object" && value ? value : undefined;
8
+ }
9
+ function isPlaceholderApiKey(value) {
10
+ return typeof value === "string" && value.trim().toLowerCase() === "dummy";
11
+ }
12
+ function buildAuthOmittingFetch(baseFetch = fetch) {
13
+ return async (input, init) => {
14
+ const sanitizedHeaders = new Headers(input instanceof Request ? input.headers : undefined);
15
+ const initHeaders = new Headers(init?.headers);
16
+ initHeaders.forEach((value, key) => {
17
+ sanitizedHeaders.set(key, value);
18
+ });
19
+ sanitizedHeaders.delete("authorization");
20
+ if (input instanceof Request) {
21
+ return baseFetch(new Request(input, {
22
+ ...init,
23
+ headers: sanitizedHeaders,
24
+ }));
25
+ }
26
+ return baseFetch(input, {
27
+ ...init,
28
+ headers: sanitizedHeaders,
29
+ });
30
+ };
31
+ }
32
+ function normalizeOpenAICompatibleInit(init) {
33
+ const normalized = { ...init };
34
+ const configuration = asObject(init.configuration) ?? {};
35
+ const baseUrl = typeof init.baseUrl === "string" && init.baseUrl.trim() ? init.baseUrl.trim() : undefined;
36
+ const omitAuthHeader = init.omitAuthHeader === true || isPlaceholderApiKey(init.apiKey);
37
+ const nextConfiguration = { ...configuration };
38
+ if (baseUrl && typeof nextConfiguration.baseURL !== "string") {
39
+ nextConfiguration.baseURL = baseUrl;
40
+ }
41
+ if (omitAuthHeader) {
42
+ nextConfiguration.fetch = buildAuthOmittingFetch(typeof configuration.fetch === "function" ? configuration.fetch : fetch);
43
+ }
44
+ normalized.configuration = nextConfiguration;
45
+ delete normalized.baseUrl;
46
+ delete normalized.omitAuthHeader;
47
+ return normalized;
48
+ }
5
49
  export function resolveCompiledEmbeddingModelRef(workspace, embeddingModelRef) {
6
50
  const resolvedId = embeddingModelRef ? resolveRefId(embeddingModelRef) : "default";
7
51
  const embeddingModel = workspace.embeddings.get(resolvedId);
@@ -26,8 +70,20 @@ export async function resolveCompiledEmbeddingModel(embeddingModel, resolver) {
26
70
  ...(embeddingModel.init ?? {}),
27
71
  });
28
72
  }
73
+ if (embeddingModel.provider === "openai-compatible") {
74
+ return new OpenAIEmbeddings({
75
+ model: embeddingModel.model,
76
+ ...normalizeOpenAICompatibleInit(embeddingModel.init ?? {}),
77
+ });
78
+ }
79
+ if (embeddingModel.provider === "openai") {
80
+ return new OpenAIEmbeddings({
81
+ model: embeddingModel.model,
82
+ ...(embeddingModel.init ?? {}),
83
+ });
84
+ }
29
85
  if (embeddingModel.provider === "llamaindex-ollama") {
30
86
  return createLlamaIndexEmbeddingModel(embeddingModel);
31
87
  }
32
- throw new Error(`Embedding model provider ${embeddingModel.provider} is not supported by the built-in runtime. Configure embeddingModelResolver or use ollama/llamaindex-ollama.`);
88
+ throw new Error(`Embedding model provider ${embeddingModel.provider} is not supported by the built-in runtime. Configure embeddingModelResolver or use openai-compatible/openai/ollama/llamaindex-ollama.`);
33
89
  }
@@ -1,11 +1,12 @@
1
1
  import type { HarnessEvent, HarnessEventProjection } from "../contracts/types.js";
2
- import type { FilePersistence } from "../persistence/file-store.js";
2
+ import type { RuntimePersistence } from "../persistence/types.js";
3
3
  export declare class ThreadMemorySync implements HarnessEventProjection {
4
4
  private readonly persistence;
5
5
  private readonly store?;
6
6
  private readonly pending;
7
+ private syncChain;
7
8
  readonly name = "thread-memory-sync";
8
- constructor(persistence: FilePersistence, store?: {
9
+ constructor(persistence: RuntimePersistence, store?: {
9
10
  put: (namespace: string[], key: string, value: Record<string, any>) => Promise<void>;
10
11
  } | undefined);
11
12
  shouldHandle(event: HarnessEvent): boolean;
@@ -53,6 +53,7 @@ export class ThreadMemorySync {
53
53
  persistence;
54
54
  store;
55
55
  pending = new Set();
56
+ syncChain = Promise.resolve();
56
57
  name = "thread-memory-sync";
57
58
  constructor(persistence, store) {
58
59
  this.persistence = persistence;
@@ -65,7 +66,12 @@ export class ThreadMemorySync {
65
66
  if (!this.shouldHandle(event)) {
66
67
  return;
67
68
  }
68
- const task = this.syncThread(event.threadId)
69
+ const task = this.syncChain
70
+ .then(() => this.syncThread(event.threadId))
71
+ .catch(() => {
72
+ // Fail open: background memory digestion must not break the hot path.
73
+ });
74
+ this.syncChain = task
69
75
  .catch(() => {
70
76
  // Fail open: background memory digestion must not break the hot path.
71
77
  })
@@ -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, getRuntimeMemoryDefaults, getWorkspaceObject, resolvePromptValue, resolveRefId } from "./support/workspace-ref-utils.js";
6
+ import { compileAgentMemories, getResilienceConfig, 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.";
@@ -235,6 +235,7 @@ function resolveRuntimeMemoryConfig(agent, refs) {
235
235
  export function compileBinding(workspaceRoot, agent, agents, referencedSubagentIds, refs, models, tools) {
236
236
  const internalSubagent = referencedSubagentIds.has(agent.id);
237
237
  const runtimeDefaults = getRuntimeDefaults(refs);
238
+ const resilience = getResilienceConfig(refs);
238
239
  const compiledAgentSkills = compileAgentSkills(workspaceRoot, agent);
239
240
  const compiledAgentMemory = compileAgentMemories(workspaceRoot, agent.memorySources);
240
241
  const compiledAgentModel = requireModel(models, agent.modelRef || (internalSubagent ? "model/default" : ""), agent.id);
@@ -264,6 +265,7 @@ export function compileBinding(workspaceRoot, agent, agents, referencedSubagentI
264
265
  workspaceRoot,
265
266
  hostFacing: !internalSubagent,
266
267
  capabilities: inferAgentCapabilities(agent),
268
+ resilience,
267
269
  ...(checkpointer ? { checkpointer: checkpointer.config } : {}),
268
270
  ...(store ? { store: store.config } : {}),
269
271
  ...(runtimeMemory ? { runtimeMemory: runtimeMemory.config } : {}),
@@ -20,11 +20,20 @@ export type RecoveryConfig = {
20
20
  export type ConcurrencyConfig = {
21
21
  maxConcurrentRuns: number;
22
22
  };
23
+ export type ProviderRetryConfig = {
24
+ maxAttempts: number;
25
+ backoffMs: number;
26
+ retryableMessages: string[];
27
+ };
28
+ export type ResilienceConfig = {
29
+ providerRetries: ProviderRetryConfig;
30
+ };
23
31
  export declare function getWorkspaceObject(refs: Map<string, WorkspaceObject | ParsedAgentObject>, ref: string | undefined): WorkspaceObject | undefined;
24
32
  export declare function getRuntimeDefaults(refs: Map<string, WorkspaceObject | ParsedAgentObject>): Record<string, unknown> | undefined;
25
33
  export declare function getRuntimeMemoryDefaults(refs: Map<string, WorkspaceObject | ParsedAgentObject>): Record<string, unknown> | undefined;
26
34
  export declare function getRecoveryConfig(refs: Map<string, WorkspaceObject | ParsedAgentObject>): RecoveryConfig;
27
35
  export declare function getConcurrencyConfig(refs: Map<string, WorkspaceObject | ParsedAgentObject>): ConcurrencyConfig;
36
+ export declare function getResilienceConfig(refs: Map<string, WorkspaceObject | ParsedAgentObject>): ResilienceConfig;
28
37
  export declare function getRoutingSystemPrompt(refs: Map<string, WorkspaceObject | ParsedAgentObject>): string | undefined;
29
38
  export declare function getRoutingDefaultAgentId(refs: Map<string, WorkspaceObject | ParsedAgentObject>): string | undefined;
30
39
  export declare function isModelRoutingEnabled(refs: Map<string, WorkspaceObject | ParsedAgentObject>): boolean;
@@ -65,6 +65,44 @@ export function getConcurrencyConfig(refs) {
65
65
  : 3;
66
66
  return { maxConcurrentRuns };
67
67
  }
68
+ export function getResilienceConfig(refs) {
69
+ const runtimeDefaults = getRuntimeDefaults(refs);
70
+ const resilience = typeof runtimeDefaults?.resilience === "object" && runtimeDefaults.resilience
71
+ ? runtimeDefaults.resilience
72
+ : {};
73
+ const providerRetries = typeof resilience.providerRetries === "object" && resilience.providerRetries
74
+ ? resilience.providerRetries
75
+ : {};
76
+ const maxAttempts = typeof providerRetries.maxAttempts === "number" &&
77
+ Number.isFinite(providerRetries.maxAttempts) &&
78
+ providerRetries.maxAttempts > 0
79
+ ? Math.floor(providerRetries.maxAttempts)
80
+ : 2;
81
+ const backoffMs = typeof providerRetries.backoffMs === "number" &&
82
+ Number.isFinite(providerRetries.backoffMs) &&
83
+ providerRetries.backoffMs >= 0
84
+ ? Math.floor(providerRetries.backoffMs)
85
+ : 1_000;
86
+ const retryableMessages = Array.isArray(providerRetries.retryableMessages)
87
+ ? providerRetries.retryableMessages
88
+ .filter((value) => typeof value === "string" && value.trim().length > 0)
89
+ .map((value) => value.trim())
90
+ : [
91
+ "connection error",
92
+ "network error",
93
+ "fetch failed",
94
+ "socket hang up",
95
+ "econnreset",
96
+ "timed out",
97
+ ];
98
+ return {
99
+ providerRetries: {
100
+ maxAttempts,
101
+ backoffMs,
102
+ retryableMessages,
103
+ },
104
+ };
105
+ }
68
106
  export function getRoutingSystemPrompt(refs) {
69
107
  const routing = getRoutingObject(refs);
70
108
  return typeof routing?.systemPrompt === "string" && routing.systemPrompt.trim() ? routing.systemPrompt : undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@botbotgo/agent-harness",
3
- "version": "0.0.75",
3
+ "version": "0.0.76",
4
4
  "description": "Workspace runtime for multi-agent applications",
5
5
  "type": "module",
6
6
  "packageManager": "npm@10.9.2",
@@ -53,7 +53,7 @@
53
53
  "scripts": {
54
54
  "build": "rm -rf dist tsconfig.tsbuildinfo && tsc -p tsconfig.json && cp -R config dist/",
55
55
  "check": "tsc -p tsconfig.json --noEmit",
56
- "test": "vitest run test/hello-file.test.ts test/public-api.test.ts test/memory-runtime.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 test/coding-agent-guide.test.ts",
56
+ "test": "vitest run test/hello-file.test.ts test/public-api.test.ts test/runtime-health.test.ts test/memory-runtime.test.ts test/sqlite-persistence.test.ts test/runtime-record-maintenance.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/local-docs-persistence-inventory.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 test/coding-agent-guide.test.ts",
57
57
  "test:real-providers": "vitest run test/real-provider-harness.test.ts",
58
58
  "release:prepare": "npm version patch --no-git-tag-version && node ./scripts/sync-example-version.mjs",
59
59
  "release:pack": "npm pack --dry-run",