bare-agent 0.43.0 → 0.44.0

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 (78) hide show
  1. package/package.json +8 -3
  2. package/primitives.json +447 -0
  3. package/src/bareguard-adapter.d.ts +6 -0
  4. package/src/bareguard-adapter.js +6 -0
  5. package/src/checkpoint.d.ts +5 -0
  6. package/src/checkpoint.js +5 -0
  7. package/src/circuit-breaker.d.ts +5 -0
  8. package/src/circuit-breaker.js +5 -0
  9. package/src/complexity.d.ts +9 -0
  10. package/src/complexity.js +9 -0
  11. package/src/context-units.d.ts +12 -0
  12. package/src/context-units.js +12 -0
  13. package/src/evaluator.d.ts +9 -1
  14. package/src/evaluator.js +9 -1
  15. package/src/judge-calibration.d.ts +5 -0
  16. package/src/judge-calibration.js +5 -0
  17. package/src/judge.d.ts +5 -0
  18. package/src/judge.js +5 -0
  19. package/src/loop.d.ts +6 -0
  20. package/src/loop.js +6 -0
  21. package/src/mcp-bridge.d.ts +13 -0
  22. package/src/mcp-bridge.js +13 -0
  23. package/src/memory.d.ts +5 -0
  24. package/src/memory.js +5 -0
  25. package/src/planner.d.ts +6 -0
  26. package/src/planner.js +6 -0
  27. package/src/provider-anthropic.d.ts +4 -0
  28. package/src/provider-anthropic.js +4 -0
  29. package/src/provider-clipipe.d.ts +4 -0
  30. package/src/provider-clipipe.js +4 -0
  31. package/src/provider-fallback.d.ts +4 -0
  32. package/src/provider-fallback.js +4 -0
  33. package/src/provider-gemini.d.ts +7 -1
  34. package/src/provider-gemini.js +7 -1
  35. package/src/provider-ollama.d.ts +4 -0
  36. package/src/provider-ollama.js +4 -0
  37. package/src/provider-openai.d.ts +4 -0
  38. package/src/provider-openai.js +4 -0
  39. package/src/recurse-retrieval.d.ts +22 -0
  40. package/src/recurse-retrieval.js +22 -0
  41. package/src/recurse.d.ts +6 -0
  42. package/src/recurse.js +6 -0
  43. package/src/refine.d.ts +5 -0
  44. package/src/refine.js +5 -0
  45. package/src/remember.d.ts +5 -0
  46. package/src/remember.js +5 -0
  47. package/src/retry.d.ts +8 -1
  48. package/src/retry.js +8 -1
  49. package/src/run-plan.d.ts +5 -0
  50. package/src/run-plan.js +5 -0
  51. package/src/scheduler.d.ts +8 -1
  52. package/src/scheduler.js +8 -1
  53. package/src/skills.d.ts +6 -0
  54. package/src/skills.js +6 -0
  55. package/src/stash.d.ts +5 -0
  56. package/src/stash.js +5 -0
  57. package/src/state.d.ts +8 -1
  58. package/src/state.js +8 -1
  59. package/src/store-jsonfile.d.ts +5 -0
  60. package/src/store-jsonfile.js +5 -0
  61. package/src/store-sqlite.d.ts +5 -0
  62. package/src/store-sqlite.js +5 -0
  63. package/src/stream.d.ts +5 -0
  64. package/src/stream.js +5 -0
  65. package/src/transport-jsonl.d.ts +4 -0
  66. package/src/transport-jsonl.js +4 -0
  67. package/tools/browse.d.ts +5 -0
  68. package/tools/browse.js +5 -0
  69. package/tools/defer.d.ts +13 -1
  70. package/tools/defer.js +13 -1
  71. package/tools/litectx-mcp.d.ts +6 -0
  72. package/tools/litectx-mcp.js +6 -0
  73. package/tools/mobile.d.ts +5 -0
  74. package/tools/mobile.js +5 -0
  75. package/tools/shell.d.ts +5 -0
  76. package/tools/shell.js +5 -0
  77. package/tools/spawn.d.ts +10 -0
  78. package/tools/spawn.js +10 -0
package/package.json CHANGED
@@ -1,9 +1,10 @@
1
1
  {
2
2
  "name": "bare-agent",
3
- "version": "0.43.0",
3
+ "version": "0.44.0",
4
4
  "files": [
5
5
  "index.js",
6
6
  "index.d.ts",
7
+ "primitives.json",
7
8
  "bareagent.context.md",
8
9
  "src/",
9
10
  "bin/",
@@ -22,6 +23,7 @@
22
23
  },
23
24
  "main": "index.js",
24
25
  "types": "./index.d.ts",
26
+ "primitives": "./primitives.json",
25
27
  "bin": {
26
28
  "bare-agent": "bin/cli.js"
27
29
  },
@@ -58,7 +60,8 @@
58
60
  "types": "./src/bareguard-adapter.d.ts",
59
61
  "default": "./src/bareguard-adapter.js"
60
62
  },
61
- "./package.json": "./package.json"
63
+ "./package.json": "./package.json",
64
+ "./primitives.json": "./primitives.json"
62
65
  },
63
66
  "engines": {
64
67
  "node": ">=18"
@@ -97,7 +100,9 @@
97
100
  "typecheck": "tsc --noEmit",
98
101
  "prebuild:types": "node scripts/clean-types.js",
99
102
  "build:types": "tsc",
100
- "prepublishOnly": "npm run build:types"
103
+ "build:primitives": "node scripts/gen-primitives.mjs",
104
+ "check:primitives": "node scripts/gen-primitives.mjs --check",
105
+ "prepublishOnly": "npm run build:primitives && npm run build:types"
101
106
  },
102
107
  "devDependencies": {
103
108
  "@types/node": "^22.19.19",
@@ -0,0 +1,447 @@
1
+ {
2
+ "package": "bare-agent",
3
+ "version": "0.44.0",
4
+ "primitives": [
5
+ {
6
+ "name": "AnthropicProvider",
7
+ "category": "providers",
8
+ "when": "you want Claude models as the Loop's provider — native Messages API with opt-in prompt caching and thinking-block passthrough",
9
+ "import": "import { AnthropicProvider } from 'bare-agent/providers'",
10
+ "signature": "new AnthropicProvider(options?: AnthropicOptions)",
11
+ "fails": "throws on a missing apiKey; normalizes stopReason and usage (usage:null when the API omits it); a socket idle/deadline/transport cut rejects with a retryable Timeout/ProviderError.",
12
+ "example": "const provider = new AnthropicProvider({ apiKey, model: 'claude-sonnet-5' });"
13
+ },
14
+ {
15
+ "name": "assessComplexity",
16
+ "category": "routing",
17
+ "when": "you want a fast, no-LLM read of how hard a goal is (simple/medium/complex/critical) to decide whether to invoke the Planner",
18
+ "import": "import { assessComplexity } from 'bare-agent'",
19
+ "signature": "assessComplexity(prompt: string) => ComplexityResult",
20
+ "fails": "never throws — pure text scoring; non-string/blank input scores lowest. Keyword lists are frozen and a critical-safety override always wins.",
21
+ "example": "const { level, needsPlanning } = assessComplexity(goal);\nif (needsPlanning) await planner.plan(goal);"
22
+ },
23
+ {
24
+ "name": "buildExactTool",
25
+ "category": "retrieval",
26
+ "when": "you want an embeddings-free, exact AND-term filter handle over a slice-source — complete over its slices, for precise lexical matches",
27
+ "import": "import { buildExactTool } from 'bare-agent'",
28
+ "signature": "buildExactTool(corpus: Slice[]) => ToolDef",
29
+ "fails": "only as good as a lexical rule; complete over the slices it is given (no recall cap) but matches nothing outside them.",
30
+ "example": "const tool = buildExactTool(corpus);\nawait recurse(task, ctx, { retrieval: 'exact', tools: [tool] });"
31
+ },
32
+ {
33
+ "name": "buildMetaTools",
34
+ "category": "mcp",
35
+ "when": "you want the two bulk MCP meta-tools (mcp_discover, mcp_invoke) instead of exposing every discovered tool individually — one gate-check per invocation",
36
+ "import": "import { buildMetaTools } from 'bare-agent/mcp'",
37
+ "signature": "buildMetaTools(tools: ToolDef[], discoveredAt?: string) => ToolDef[]",
38
+ "fails": "returns [mcp_discover, mcp_invoke]; a tool name does not travel as action.type (a deliberate v0.9 trade for one gate-check per call).",
39
+ "example": "const metaTools = buildMetaTools(tools);"
40
+ },
41
+ {
42
+ "name": "buildScanTool",
43
+ "category": "retrieval",
44
+ "when": "you need the complete count/\"all\" path — scan every slice + LLM-judge + code-count — the only retrieval mode that can honestly answer \"how many\"",
45
+ "import": "import { buildScanTool } from 'bare-agent'",
46
+ "signature": "buildScanTool(corpus: Slice[] | (() => Promise<Slice[]>), opts: {provider: Provider, window?: number, passes?: number, ctx?: object, onLlmResult?: Function, policy?: Function}) => ToolDef",
47
+ "fails": "a dead window surfaces as `INCOMPLETE — the count is a floor`, never a clean number over a hole; a governance HaltError propagates clean.",
48
+ "example": "const tool = buildScanTool(corpus, { provider, window: 8 });\nawait recurse('how many mention X', ctx, { tools: [tool] });"
49
+ },
50
+ {
51
+ "name": "buildSearchTool",
52
+ "category": "integration",
53
+ "when": "you want to give a recurse worker a needle-search handle over litectx (embeddings recall for the relevant few) — for FINDING, never counting",
54
+ "import": "import { buildSearchTool } from 'bare-agent'",
55
+ "signature": "buildSearchTool(litectx: {recall: Function}, opts?: {kinds?: string[], n?: number}) => ToolDef",
56
+ "fails": "returns matched bodies only and CANNOT count (the completeness guard blocks a \"how many\" ask before it is offered); a dead window yields nothing, never a fabricated hit.",
57
+ "example": "const tool = buildSearchTool(litectx, { n: 8 });\nawait recurse(task, ctx, { retrieval: 'search', tools: [tool] });"
58
+ },
59
+ {
60
+ "name": "calibrate",
61
+ "category": "evaluation",
62
+ "when": "you are admitting an LLM tier to the judge role and need to grade it against the frozen clear-case battery and resist every injection style before trusting it",
63
+ "import": "import { calibrate } from 'bare-agent'",
64
+ "signature": "calibrate(opts: object) => Promise<ReturnType<typeof gradeRun> & { reps:number, floor:number, totalCostUsd:number|null, unpricedCalls:number, injectionBattery: { styles: Array<{label:string, usable:number, broke:number, resisted:boolean}>, allResisted:boolean, leaks:number } }>",
65
+ "fails": "never fabricates a pass — a leak in any injection style blocks admission even above the clear-case floor, and the negative control must fail the set. HaltError propagates clean.",
66
+ "example": "const report = await calibrate({ provider, reps: 5, floor: 7 });\nif (!report.injectionBattery.allResisted) reject('injection leak');"
67
+ },
68
+ {
69
+ "name": "Checkpoint",
70
+ "category": "hitl",
71
+ "when": "you want an always-prompt human-in-the-loop approval gate on chosen tools — complementary to bareguard's policy-driven pauses",
72
+ "import": "import { Checkpoint } from 'bare-agent'",
73
+ "signature": "new Checkpoint(options?: CheckpointOptions)",
74
+ "fails": "auto-denies on timeout (0 disables) and denies safe when no reply channel is wired; a TimeoutError is surfaced, never swallowed.",
75
+ "example": "const checkpoint = new Checkpoint({ tools: ['deploy'], waitForReply });\nconst ok = await checkpoint.confirm('deploy', args);"
76
+ },
77
+ {
78
+ "name": "CircuitBreaker",
79
+ "category": "resilience",
80
+ "when": "you want per-key failure isolation — trip open after N failures, probe half-open after a cooldown — so one failing dependency doesn't cascade",
81
+ "import": "import { CircuitBreaker } from 'bare-agent'",
82
+ "signature": "new CircuitBreaker(options?: object)",
83
+ "fails": "throws CircuitOpenError while open (fail-fast, no downstream call); transitions emit onStateChange and reset on a successful half-open probe.",
84
+ "example": "const cb = new CircuitBreaker({ threshold: 5 });\nawait cb.call('api', () => fetch(url));"
85
+ },
86
+ {
87
+ "name": "CLIPipeProvider",
88
+ "category": "providers",
89
+ "when": "you want to drive a CLI (e.g. the claude CLI) as the Loop's provider — native MCP tools or a JSON emulation envelope",
90
+ "import": "import { CLIPipeProvider } from 'bare-agent/providers'",
91
+ "signature": "new CLIPipeProvider(options?: CLIPipeOptions)",
92
+ "fails": "throws on a missing command; in native mode it owns its own cycle (ownsCycle), so per-round Loop seams are refused at construction rather than left silently dead.",
93
+ "example": "const provider = new CLIPipeProvider({ command: 'claude' });"
94
+ },
95
+ {
96
+ "name": "createBrowsingTools",
97
+ "category": "tools",
98
+ "when": "you want to give an agent browser tools (navigate, click, read) via barebrowse for inline snapshots",
99
+ "import": "import { createBrowsingTools } from 'bare-agent/tools'",
100
+ "signature": "createBrowsingTools(opts?: object) => Promise<{tools: Array, close: Function}|null>",
101
+ "fails": "returns null if barebrowse (optional dep) is not installed; otherwise returns {tools, close} — call close() to release the browser.",
102
+ "example": "const b = await createBrowsingTools();\nconst loop = new Loop({ provider, tools: b.tools });"
103
+ },
104
+ {
105
+ "name": "createDeferTool",
106
+ "category": "tools",
107
+ "when": "you want a tool that queues an action to a JSONL file for an external waker (cron) to fire later — two-phase governance (emit-time + fire-time)",
108
+ "import": "import { createDeferTool } from 'bare-agent/tools'",
109
+ "signature": "createDeferTool(options?: object) => {tool: import('../types').ToolDef, readQueue: () => Promise<Record<string, any>[]>, queuePath: string}",
110
+ "fails": "returns {tool, readQueue, queuePath}; the inner action is re-gated at fire time when the waker runs. bareguard caps via defer.ratePerMinute.",
111
+ "example": "const { tool } = createDeferTool();\nconst loop = new Loop({ provider, tools: [tool] });"
112
+ },
113
+ {
114
+ "name": "createMCPBridge",
115
+ "category": "mcp",
116
+ "when": "you want to auto-discover MCP servers and expose them as bareagent tools in one call — with a trust hook gating command execution",
117
+ "import": "import { createMCPBridge } from 'bare-agent/mcp'",
118
+ "signature": "createMCPBridge(opts?: object) => Promise<{tools: ToolDef[], metaTools?: ToolDef[], servers: string[], systemContext: string, denied: DeniedTool[], errors?: Array<{server: string, error: string}>, close: Function}>",
119
+ "fails": "a trust hook returning false skips a server (its command never runs) and a throw is fail-closed (deny); returns tools plus a close() to shut servers down.",
120
+ "example": "const { tools, close } = await createMCPBridge();\nconst loop = new Loop({ provider, tools });"
121
+ },
122
+ {
123
+ "name": "createMobileTools",
124
+ "category": "tools",
125
+ "when": "you want to give an agent Android/iOS device control (tap, type, snapshot) via baremobile",
126
+ "import": "import { createMobileTools } from 'bare-agent/tools'",
127
+ "signature": "createMobileTools(opts?: object) => Promise<{tools: ToolDef[], close: Function}|null>",
128
+ "fails": "returns null if baremobile (optional dep) is not installed; otherwise returns {tools, close} — call close() to disconnect.",
129
+ "example": "const m = await createMobileTools({ platform: 'android' });\nconst loop = new Loop({ provider, tools: m.tools });"
130
+ },
131
+ {
132
+ "name": "createShellTools",
133
+ "category": "tools",
134
+ "when": "you want to give an agent shell/file tools (read, grep, write, edit, run, exec) — cross-platform, pure Node, zero deps",
135
+ "import": "import { createShellTools } from 'bare-agent/tools'",
136
+ "signature": "createShellTools() => {tools: ToolDef[]}",
137
+ "fails": "never throws at creation; gating is the caller's via Loop({ policy }) and fs.writeScope, and shell_edit refuses a non-unique anchor as a tool result (file untouched).",
138
+ "example": "const { tools } = createShellTools();\nconst loop = new Loop({ provider, tools, policy });"
139
+ },
140
+ {
141
+ "name": "createSpawnTool",
142
+ "category": "tools",
143
+ "when": "you want an LLM-callable spawn tool so a model can fork child bareagents itself — governed per-family by bareguard",
144
+ "import": "import { createSpawnTool } from 'bare-agent/tools'",
145
+ "signature": "createSpawnTool(options?: object) => {tool: import('../types').ToolDef, spawnChild: typeof spawnChild}",
146
+ "fails": "returns {tool, spawnChild}; the tool blocks per child and child stderr is re-emitted as child:stderr on the wired Stream.",
147
+ "example": "const { tool } = createSpawnTool();\nconst loop = new Loop({ provider, tools: [tool] });"
148
+ },
149
+ {
150
+ "name": "createStashSkill",
151
+ "category": "skills",
152
+ "when": "you need compaction-first context hygiene — a registrable skill whose checkpoint/compact/restore tools fold the live transcript at round boundaries",
153
+ "import": "import { createStashSkill } from 'bare-agent'",
154
+ "signature": "createStashSkill(options?: Object) => { skill: { name: string, description: string, instructions: string, tools: ToolDef[] }, trim: (msgs: any[], ctx: any) => Promise<any[]>, restoreHandles: () => string[] }",
155
+ "fails": "never throws for a fold; degrades LOUDLY to a lossless park when summarize is unwired. Preserves tool-pairing and role alternation by construction.",
156
+ "example": "const { skill, trim } = createStashSkill({ compaction: { ceilingTokens: 100000 } });\nconst loop = new Loop({ provider, trim });"
157
+ },
158
+ {
159
+ "name": "discoverServers",
160
+ "category": "mcp",
161
+ "when": "you want to find configured MCP servers from the trusted $HOME/IDE config paths (or explicit paths) — discovery without invoking them",
162
+ "import": "import { discoverServers } from 'bare-agent/mcp'",
163
+ "signature": "discoverServers(configPaths?: string[], opts?: { includeProjectConfig?: boolean }) => Map<string, ServerDef>",
164
+ "fails": "honors explicit configPaths verbatim and scans trusted defaults otherwise; returns a Map and never executes a server command.",
165
+ "example": "const servers = discoverServers();"
166
+ },
167
+ {
168
+ "name": "Evaluator",
169
+ "category": "evaluation",
170
+ "when": "you need to judge an output against a goal or contract — deterministically (predicate), by LLM rubric, or with a tool-running critic that exercises the live artifact",
171
+ "import": "import { Evaluator } from 'bare-agent'",
172
+ "signature": "new Evaluator(options?: EvaluatorOptions)",
173
+ "fails": "never throws for a bad grade — returns a Verdict {status: satisfied|needs_revision|failed}; a provider HaltError propagates clean. Judge tokens forward via onLlmResult.",
174
+ "example": "const evaluator = new Evaluator({ provider });\nconst verdict = await evaluator.evaluate(goal, result, { rubric });\nif (!verdict.pass) revise(verdict.critique);"
175
+ },
176
+ {
177
+ "name": "FallbackProvider",
178
+ "category": "providers",
179
+ "when": "you want to try several providers in order, failing over to the next when one errors — resilience across tiers or vendors",
180
+ "import": "import { FallbackProvider } from 'bare-agent/providers'",
181
+ "signature": "new FallbackProvider(providers: Provider[], options?: FallbackOptions)",
182
+ "fails": "throws if given no providers; returns the first provider's success, else propagates the last provider's error.",
183
+ "example": "const provider = new FallbackProvider([primary, backup]);"
184
+ },
185
+ {
186
+ "name": "GeminiProvider",
187
+ "category": "providers",
188
+ "when": "you want Gemini models as the Loop's provider — native generateContent with implicit prompt caching",
189
+ "import": "import { GeminiProvider } from 'bare-agent/providers'",
190
+ "signature": "new GeminiProvider(options?: GeminiOptions)",
191
+ "fails": "throws on a missing apiKey; normalizes stopReason (promoting a complete tool call) and usage; a socket idle/deadline/transport cut rejects with a retryable error.",
192
+ "example": "const provider = new GeminiProvider({ apiKey, model: 'gemini-2.5-pro' });"
193
+ },
194
+ {
195
+ "name": "harvestKey",
196
+ "category": "context",
197
+ "when": "you need the stable content-address for a transcript unit (the key harvest-before-evict writes under) — a collision-resistant 64-bit id",
198
+ "import": "import { harvestKey } from 'bare-agent'",
199
+ "signature": "harvestKey(unit: Record<string, any>) => string",
200
+ "fails": "never throws; normal provider ids round-trip unchanged, and two near-independent hash streams avoid 32-bit birthday collisions.",
201
+ "example": "const key = harvestKey(unit); // stable id for this turn's harvest"
202
+ },
203
+ {
204
+ "name": "isCritical",
205
+ "category": "routing",
206
+ "when": "you want the durable critical-safety floor alone (security/production/compliance/financial) to gate extra scrutiny, without the full scorer",
207
+ "import": "import { isCritical } from 'bare-agent'",
208
+ "signature": "isCritical(prompt: string) => boolean",
209
+ "fails": "never throws — deterministic override; non-string/blank input is false. This floor is non-overridable by design.",
210
+ "example": "if (isCritical(goal)) verdict = await evaluator.evaluate(goal, result, { contract });"
211
+ },
212
+ {
213
+ "name": "JsonFile",
214
+ "category": "stores",
215
+ "when": "you want zero-dependency JSON-file storage for Memory (store/search/get/delete) — the simplest durable backend, no native deps",
216
+ "import": "import { JsonFile } from 'bare-agent/stores'",
217
+ "signature": "new JsonFile(options?: { path?: string })",
218
+ "fails": "throws on a missing path; implements the four-verb Store socket over a plain JSON file.",
219
+ "example": "const store = new JsonFile({ path: './agent.json' });"
220
+ },
221
+ {
222
+ "name": "JsonlTransport",
223
+ "category": "transports",
224
+ "when": "you want to pipe structured Stream events as JSONL to a writable stream — pipe-friendly observability",
225
+ "import": "import { JsonlTransport } from 'bare-agent/transports'",
226
+ "signature": "new JsonlTransport(options?: JsonlTransportOptions)",
227
+ "fails": "never throws on write; defaults to process.stdout when no output stream is given.",
228
+ "example": "const stream = new Stream({ transport: new JsonlTransport() });"
229
+ },
230
+ {
231
+ "name": "judge",
232
+ "category": "evaluation",
233
+ "when": "you need a decisive honored/broke verdict on whether one egress artifact honored the verbatim request — a return-time integrity check",
234
+ "import": "import { judge } from 'bare-agent'",
235
+ "signature": "judge(options: JudgeOptions) => Promise<JudgeVerdict>",
236
+ "fails": "throws ValidationError on bad inputs; HaltError propagates clean. Cannot-confirm-honored floors to `broke`; truncation/parse-error are distinct flagged outcomes, never laundered to honored.",
237
+ "example": "const { verdict, where } = await judge({ request, artifact, provider });\nif (verdict === 'broke') flag(where);"
238
+ },
239
+ {
240
+ "name": "judgeToAnnotation",
241
+ "category": "governance",
242
+ "when": "you want to surface a judge() verdict on a gate's audit/humanChannel via gate.annotate, keeping the render pure (it never calls the gate)",
243
+ "import": "import { judgeToAnnotation } from 'bare-agent'",
244
+ "signature": "judgeToAnnotation(verdict: import('./judge').JudgeVerdict | { verdict?: string, where?: any }, opts?: JudgeToAnnotationOptions) => Annotation",
245
+ "fails": "never throws and never calls the gate; surfaces fail-open (a non-honored verdict surfaces) and bounds fields against the sink's silent caps with a visible clip marker.",
246
+ "example": "gate.annotate(judgeToAnnotation(await judge({ request, artifact, provider })));"
247
+ },
248
+ {
249
+ "name": "litectxCorpus",
250
+ "category": "integration",
251
+ "when": "you want a resident slice-source that paginates a litectx corpus via enumerate — for a corpus ALREADY in litectx, feeding scan/partition",
252
+ "import": "import { litectxCorpus } from 'bare-agent'",
253
+ "signature": "litectxCorpus(litectx: {enumerate: Function}, opts?: {kind?: 'fact'|'episode', pageSize?: number}) => () => Promise<Slice[]>",
254
+ "fails": "never ingests a fresh corpus (strictly worse than scanning an in-hand array); returns an async source materialized once and cached.",
255
+ "example": "const corpus = litectxCorpus(litectx, { kind: 'fact' });\nawait recurse(task, ctx, { mode: 'partition', corpus });"
256
+ },
257
+ {
258
+ "name": "liteCtxMcpBridgeConfig",
259
+ "category": "integration",
260
+ "when": "you want to connect a litectx instance to a bareagent runner over the MCP bridge — a curated, child-db-local litectx server",
261
+ "import": "import { liteCtxMcpBridgeConfig } from 'bare-agent/tools'",
262
+ "signature": "liteCtxMcpBridgeConfig(opts: object) => import('../src/mcp-bridge').BridgeConfig",
263
+ "fails": "read-only by default (opt-in `writable` allows remember/forget); pre-seeding `now` makes createMCPBridge skip IDE discovery and connect straight to this server.",
264
+ "example": "const cfg = liteCtxMcpBridgeConfig({ root: './child.db' });\nconst { tools } = await createMCPBridge({ config: cfg });"
265
+ },
266
+ {
267
+ "name": "Loop",
268
+ "category": "loop",
269
+ "when": "you are running a model think/act/observe cycle and need round accounting, tool dispatch, spin guards, and a governance chokepoint",
270
+ "import": "import { Loop } from 'bare-agent'",
271
+ "signature": "new Loop(options: LoopOptions)",
272
+ "fails": "returns the last assistant text with an `error` tag on any bound (halt/deny-streak/truncation/provider error); throws only if constructed without a provider. A policy HaltError exits clean.",
273
+ "example": "const loop = new Loop({ provider, policy });\nconst { text, error, metrics } = await loop.run(messages, tools);\nif (error) handle(error);"
274
+ },
275
+ {
276
+ "name": "Memory",
277
+ "category": "memory",
278
+ "when": "you want thin store-backed memory (store/search/get/delete) an agent can write to and recall from, over any swappable backend",
279
+ "import": "import { Memory } from 'bare-agent'",
280
+ "signature": "new Memory(options?: { store?: Store })",
281
+ "fails": "throws if constructed without a store; recall/store metering is opt-in via ctx. All persistence delegates to the backend.",
282
+ "example": "const memory = new Memory({ store: new JsonFile({ path: './mem.json' }) });\nawait memory.store({ text: 'a durable fact' });"
283
+ },
284
+ {
285
+ "name": "OllamaProvider",
286
+ "category": "providers",
287
+ "when": "you want local models via Ollama as the Loop's provider — no API key, self-hosted",
288
+ "import": "import { OllamaProvider } from 'bare-agent/providers'",
289
+ "signature": "new OllamaProvider(options?: OllamaOptions)",
290
+ "fails": "normalizes stopReason (promoting a complete tool call) and usage; a malformed tool-call JSON returns no usable calls with usage metered; a socket idle/deadline cut rejects with a retryable error.",
291
+ "example": "const provider = new OllamaProvider({ model: 'llama3' });"
292
+ },
293
+ {
294
+ "name": "OpenAIProvider",
295
+ "category": "providers",
296
+ "when": "you want OpenAI or any OpenAI-compatible endpoint as the Loop's provider — tool-calling, toolChoice, and a custom baseUrl",
297
+ "import": "import { OpenAIProvider } from 'bare-agent/providers'",
298
+ "signature": "new OpenAIProvider(options?: OpenAIOptions)",
299
+ "fails": "throws on a missing apiKey; a malformed tool-call JSON returns no usable calls with usage still metered (BA-27); a socket idle/deadline/transport cut rejects with a retryable error.",
300
+ "example": "const provider = new OpenAIProvider({ apiKey, model: 'gpt-5' });"
301
+ },
302
+ {
303
+ "name": "Planner",
304
+ "category": "orchestration",
305
+ "when": "you need to turn a goal into an ordered step DAG for an LLM to execute — optionally forcing exactly N independent steps for fan-out",
306
+ "import": "import { Planner } from 'bare-agent'",
307
+ "signature": "new Planner(options: PlannerOptions)",
308
+ "fails": "throws if constructed without a provider; a plan call's HaltError (governance cap) propagates clean. Plan-call usage forwards via onLlmResult.",
309
+ "example": "const planner = new Planner({ provider });\nconst steps = await planner.plan('ship the release', { count: 4 });\nawait runPlan(steps, ctx);"
310
+ },
311
+ {
312
+ "name": "readDeferQueue",
313
+ "category": "tools",
314
+ "when": "you want to read the deferred-action queue and reconstruct each id's live status (an external waker or a status check)",
315
+ "import": "import { readDeferQueue } from 'bare-agent/tools'",
316
+ "signature": "readDeferQueue(queuePath?: string)",
317
+ "fails": "never throws on a missing/empty queue; folds append-only status lines (latest wins) and returns the reconstructed records.",
318
+ "example": "const queue = await readDeferQueue();"
319
+ },
320
+ {
321
+ "name": "recurse",
322
+ "category": "orchestration",
323
+ "when": "a task is too big for one model pass and you want it split, fanned out, verified, and merged — with total cost capped by a gate",
324
+ "import": "import { recurse } from 'bare-agent'",
325
+ "signature": "recurse(task: string, ctx?: RecurseCtx, opts?: RecurseOptions) => Promise<RecurseResult>",
326
+ "fails": "returns `{incomplete, best}` on guard exhaustion or a dead worker (never a faked pass); a gate HaltError exits clean. Cost is open by design — run under a budget gate.",
327
+ "example": "const ctx = wireGate(gate);\nconst { result, incomplete } = await recurse('audit 400 logs', ctx, { provider, corpus });\nif (incomplete) retryOrEscalate(result);"
328
+ },
329
+ {
330
+ "name": "refine",
331
+ "category": "evaluation",
332
+ "when": "you have a caller-supplied attempt + evaluate pair and want to iterate generate → grade → regenerate until it passes or hits a bound",
333
+ "import": "import { refine } from 'bare-agent'",
334
+ "signature": "refine(options: RefineOptions) => Promise<RefineOutcome>",
335
+ "fails": "returns the last outcome on maxIterations or a terminal `failed` verdict (never a faked pass); a HaltError from either callback propagates clean.",
336
+ "example": "const { result, passed } = await refine({ attempt, evaluate, maxIterations: 3 });\nif (!passed) escalate(result);"
337
+ },
338
+ {
339
+ "name": "remember",
340
+ "category": "evaluation",
341
+ "when": "you want to distill durable facts from harvested transcript spans and persist them through a Store socket (the consolidation pass)",
342
+ "import": "import { remember } from 'bare-agent'",
343
+ "signature": "remember(spans: Array<string | { content?: string, text?: string }>, options: RememberOptions) => Promise<RememberOutcome>",
344
+ "fails": "skips empty spans and never fabricates; a provider HaltError propagates clean. Each pass forwards usage via onLlmResult; a fact counts once via ctx.recordMemoryOp.",
345
+ "example": "const { facts } = await remember(spans, { provider, store });\nconsole.log(`consolidated ${facts.length} durable facts`);"
346
+ },
347
+ {
348
+ "name": "Retry",
349
+ "category": "resilience",
350
+ "when": "you want backoff-with-jitter around a flaky async call (a provider request, a plan step) — the retry seam Loop and runPlan wrap providers with",
351
+ "import": "import { Retry } from 'bare-agent'",
352
+ "signature": "new Retry(options?: RetryOptions)",
353
+ "fails": "rethrows the last error once attempts are exhausted; by default only transient errors (429/5xx/ECONNRESET/ETIMEDOUT) are retried.",
354
+ "example": "const retry = new Retry({ maxAttempts: 3, jitter: true });\nconst res = await retry.call(() => provider.generate(msgs));"
355
+ },
356
+ {
357
+ "name": "runPlan",
358
+ "category": "orchestration",
359
+ "when": "you have a step DAG from the Planner and want to execute it with wave-based parallelism (independent steps run concurrently)",
360
+ "import": "import { runPlan } from 'bare-agent'",
361
+ "signature": "runPlan(steps: Step[], executeFn: (step: Step) => any, options?: RunPlanOptions) => Promise<StepResult[]>",
362
+ "fails": "throws on a malformed DAG (empty steps, non-function executeFn, duplicate ids, unknown dependency); a step's own error surfaces per StepResult, never crashing the wave.",
363
+ "example": "const steps = await planner.plan(goal);\nconst results = await runPlan(steps, step => execute(step));"
364
+ },
365
+ {
366
+ "name": "Scheduler",
367
+ "category": "scheduling",
368
+ "when": "you need to fire agent turns on a schedule — cron expressions or relative intervals — driven by a periodic tick",
369
+ "import": "import { Scheduler } from 'bare-agent'",
370
+ "signature": "new Scheduler(options?: SchedulerOptions)",
371
+ "fails": "an errored job routes to the onError handler and never crashes the tick loop; a malformed cron/interval is rejected when the job is added.",
372
+ "example": "const sched = new Scheduler({ interval: 60000 });\nsched.add({ id: 'poll', cron: '0 * * * *', run });"
373
+ },
374
+ {
375
+ "name": "SkillRegistry",
376
+ "category": "skills",
377
+ "when": "you want to expose operator-registered skill bundles to a model by progressive disclosure — one meta-tool whose catalog unlocks a skill's tools on demand",
378
+ "import": "import { SkillRegistry } from 'bare-agent'",
379
+ "signature": "new SkillRegistry(options?: Object)",
380
+ "fails": "register() rejects an unsafe or colliding name fail-fast and commits nothing on failure; governance is unchanged — discovery never authorizes.",
381
+ "example": "const skills = new SkillRegistry();\nskills.register({ name: 'deploy', description: 'ship a release', instructions: '...', tools: [] });\nconst loop = new Loop({ provider, tools: skills.activeTools });"
382
+ },
383
+ {
384
+ "name": "spawnChild",
385
+ "category": "tools",
386
+ "when": "you want to fork a child bareagent process programmatically and get a handle — heavyweight delegation, not the in-process recurse default",
387
+ "import": "import { spawnChild } from 'bare-agent/tools'",
388
+ "signature": "spawnChild(opts?: SpawnChildOptions)",
389
+ "fails": "bounded by timeoutMs (wall-clock) and opt-in idleTimeoutMs (heartbeat); the result carries idleKilled if the watchdog fired. Threads BAREGUARD env vars to the child.",
390
+ "example": "const handle = spawnChild({ config, input });\nconst result = await handle.wait();"
391
+ },
392
+ {
393
+ "name": "SQLite",
394
+ "category": "stores",
395
+ "when": "you want durable, queryable SQLite-backed storage for Memory (store/search/get/delete) persisted on disk",
396
+ "import": "import { SQLite } from 'bare-agent/stores'",
397
+ "signature": "new SQLite(options?: { path?: string })",
398
+ "fails": "throws on a missing path or an absent better-sqlite3 peer dep; implements the four-verb Store socket.",
399
+ "example": "const store = new SQLite({ path: './agent.db' });"
400
+ },
401
+ {
402
+ "name": "StateMachine",
403
+ "category": "state",
404
+ "when": "you need to track task lifecycle (pending/running/done/failed/waiting/cancelled) with enforced transitions and optional file persistence",
405
+ "import": "import { StateMachine } from 'bare-agent'",
406
+ "signature": "new StateMachine(options?: { file?: string|null })",
407
+ "fails": "rejects an illegal state transition and never throws on a valid one; state changes are emitted as events.",
408
+ "example": "const sm = new StateMachine();\nsm.create('t1'); sm.transition('t1', 'running');"
409
+ },
410
+ {
411
+ "name": "Stream",
412
+ "category": "observability",
413
+ "when": "you want a structured event emitter for loop/tool/governance events, optionally piped to a transport sink",
414
+ "import": "import { Stream } from 'bare-agent'",
415
+ "signature": "new Stream(options?: StreamOptions)",
416
+ "fails": "never throws on emit; a transport write error is isolated and does not interrupt the run.",
417
+ "example": "const stream = new Stream({ transport });\nstream.emit('loop:round', { n: 1 });"
418
+ },
419
+ {
420
+ "name": "unitAssembler",
421
+ "category": "context",
422
+ "when": "you want to adapt a litectx-style neutral-unit assemble(units, ctx) verb into the Loop's assemble(msgs, ctx) context-assembly seam",
423
+ "import": "import { unitAssembler } from 'bare-agent'",
424
+ "signature": "unitAssembler(assembleUnits: (units: Array<Record<string, any>>, ctx: any) => (any | Promise<any>)) => (msgs: Array<Record<string, any>>, ctx: any) => Promise<Array<Record<string, any>>>",
425
+ "fails": "fail-open — any unexpected return shape sends the original msgs unchanged; a thrown error (incl. HaltError) is left to the Loop's own handling.",
426
+ "example": "const loop = new Loop({ provider, assemble: unitAssembler(litectx.assemble) });"
427
+ },
428
+ {
429
+ "name": "unitTrimmer",
430
+ "category": "context",
431
+ "when": "you want to adapt litectx's trim(units, policy) verb into the Loop's destructive trim(msgs, ctx) seam — harvest-before-evict with an F2 residual .flush",
432
+ "import": "import { unitTrimmer } from 'bare-agent'",
433
+ "signature": "unitTrimmer(opts?: { trim?: Function, onHarvest?: Function, policy?: any }) => ((msgs: Array<Record<string, any>>, ctx?: any) => Promise<Array<Record<string, any>>>) & { flush: (msgs: Array<Record<string, any>>, ctx?: any) => Promise<void> }",
434
+ "fails": "throws if the trim/onHarvest verbs are missing (runtime-checked); the fold is fail-open and a HaltError propagates. `.flush` drains the residual harvest.",
435
+ "example": "const loop = new Loop({ provider, trim: unitTrimmer({ trim: litectx.trim, onHarvest, policy: { keepLastN: 20 } }) });"
436
+ },
437
+ {
438
+ "name": "wireGate",
439
+ "category": "governance",
440
+ "when": "you have a bareguard Gate and want to wire it into a Loop as the policy + metering chokepoint in one line",
441
+ "import": "import { wireGate } from 'bare-agent'",
442
+ "signature": "wireGate(gate: Gate, options?: object) => {policy: Function, onLlmResult: Function, onToolResult: Function, filterTools: Function, wrapTool: Function, wrapTools: Function}",
443
+ "fails": "never throws; a gate halt surfaces as a HaltError the Loop catches and exits cleanly, and a deny is an advisory string fed back to the model.",
444
+ "example": "const { Gate } = require('bareguard');\nconst { Loop } = require('bare-agent');\nconst { wireGate } = require('bare-agent/bareguard');\n\nconst gate = new Gate({\n budget: { maxCostUsd: 0.50 },\n limits: { maxTurns: 20 },\n audit: { path: './audit.jsonl' },\n});\nawait gate.init();\n\nconst { policy, onLlmResult, onToolResult, filterTools } = wireGate(gate);\nconst loop = new Loop({ provider, policy, onLlmResult, onToolResult });\nconst tools = await filterTools(myTools);\nawait loop.run(messages, tools);"
445
+ }
446
+ ]
447
+ }
@@ -119,6 +119,8 @@ export type Annotation = {
119
119
  * does NOT activate `bash`/`fs`/`net` primitives — those need their own
120
120
  * `action.type` value. Adopters using those primitives must translate.
121
121
  * @returns {{policy: Function, onLlmResult: Function, onToolResult: Function, filterTools: Function, wrapTool: Function, wrapTools: Function}}
122
+ * @when you have a bareguard Gate and want to wire it into a Loop as the policy + metering chokepoint in one line
123
+ * @fails never throws; a gate halt surfaces as a HaltError the Loop catches and exits cleanly, and a deny is an advisory string fed back to the model.
122
124
  *
123
125
  * @example
124
126
  * const { Gate } = require('bareguard');
@@ -181,6 +183,10 @@ export function defaultActionTranslator(toolName: string, args: any, ctx: Ctx):
181
183
  * @param {import('./judge').JudgeVerdict | { verdict?: string, where?: any }} verdict - a `judge()` return value.
182
184
  * @param {JudgeToAnnotationOptions} [opts]
183
185
  * @returns {Annotation}
186
+ * @when you want to surface a judge() verdict on a gate's audit/humanChannel via gate.annotate, keeping the render pure (it never calls the gate)
187
+ * @fails never throws and never calls the gate; surfaces fail-open (a non-honored verdict surfaces) and bounds fields against the sink's silent caps with a visible clip marker.
188
+ * @example
189
+ * gate.annotate(judgeToAnnotation(await judge({ request, artifact, provider })));
184
190
  */
185
191
  export function judgeToAnnotation(verdict: import("./judge").JudgeVerdict | {
186
192
  verdict?: string;
@@ -86,6 +86,8 @@ let warnedWrap = false;
86
86
  * does NOT activate `bash`/`fs`/`net` primitives — those need their own
87
87
  * `action.type` value. Adopters using those primitives must translate.
88
88
  * @returns {{policy: Function, onLlmResult: Function, onToolResult: Function, filterTools: Function, wrapTool: Function, wrapTools: Function}}
89
+ * @when you have a bareguard Gate and want to wire it into a Loop as the policy + metering chokepoint in one line
90
+ * @fails never throws; a gate halt surfaces as a HaltError the Loop catches and exits cleanly, and a deny is an advisory string fed back to the model.
89
91
  *
90
92
  * @example
91
93
  * const { Gate } = require('bareguard');
@@ -385,6 +387,10 @@ function renderWhereString(where) {
385
387
  * @param {import('./judge').JudgeVerdict | { verdict?: string, where?: any }} verdict - a `judge()` return value.
386
388
  * @param {JudgeToAnnotationOptions} [opts]
387
389
  * @returns {Annotation}
390
+ * @when you want to surface a judge() verdict on a gate's audit/humanChannel via gate.annotate, keeping the render pure (it never calls the gate)
391
+ * @fails never throws and never calls the gate; surfaces fail-open (a non-honored verdict surfaces) and bounds fields against the sink's silent caps with a visible clip marker.
392
+ * @example
393
+ * gate.annotate(judgeToAnnotation(await judge({ request, artifact, provider })));
388
394
  */
389
395
  function judgeToAnnotation(verdict, opts = {}) {
390
396
  const v = verdict && typeof verdict === 'object' ? verdict : {};
@@ -31,6 +31,11 @@ export type CheckpointOptions = {
31
31
  export class Checkpoint {
32
32
  /**
33
33
  * @param {CheckpointOptions} [options={}]
34
+ * @when you want an always-prompt human-in-the-loop approval gate on chosen tools — complementary to bareguard's policy-driven pauses
35
+ * @fails auto-denies on timeout (0 disables) and denies safe when no reply channel is wired; a TimeoutError is surfaced, never swallowed.
36
+ * @example
37
+ * const checkpoint = new Checkpoint({ tools: ['deploy'], waitForReply });
38
+ * const ok = await checkpoint.confirm('deploy', args);
34
39
  */
35
40
  constructor(options?: CheckpointOptions);
36
41
  tools: Set<string>;
package/src/checkpoint.js CHANGED
@@ -16,6 +16,11 @@ const DEFAULT_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
16
16
  class Checkpoint {
17
17
  /**
18
18
  * @param {CheckpointOptions} [options={}]
19
+ * @when you want an always-prompt human-in-the-loop approval gate on chosen tools — complementary to bareguard's policy-driven pauses
20
+ * @fails auto-denies on timeout (0 disables) and denies safe when no reply channel is wired; a TimeoutError is surfaced, never swallowed.
21
+ * @example
22
+ * const checkpoint = new Checkpoint({ tools: ['deploy'], waitForReply });
23
+ * const ok = await checkpoint.confirm('deploy', args);
19
24
  */
20
25
  constructor(options = /** @type {CheckpointOptions} */ ({})) {
21
26
  this.tools = new Set(options.tools || []);
@@ -15,6 +15,11 @@ export class CircuitBreaker {
15
15
  * @param {number} [options.threshold=5] - Failures before opening.
16
16
  * @param {number} [options.resetAfter=60000] - Ms before half-open probe.
17
17
  * @param {((key: string, from: CircuitState, to: CircuitState) => void)} [options.onStateChange] - Callback(key, from, to).
18
+ * @when you want per-key failure isolation — trip open after N failures, probe half-open after a cooldown — so one failing dependency doesn't cascade
19
+ * @fails throws CircuitOpenError while open (fail-fast, no downstream call); transitions emit onStateChange and reset on a successful half-open probe.
20
+ * @example
21
+ * const cb = new CircuitBreaker({ threshold: 5 });
22
+ * await cb.call('api', () => fetch(url));
18
23
  */
19
24
  constructor(options?: {
20
25
  threshold?: number | undefined;
@@ -13,6 +13,11 @@ class CircuitBreaker {
13
13
  * @param {number} [options.threshold=5] - Failures before opening.
14
14
  * @param {number} [options.resetAfter=60000] - Ms before half-open probe.
15
15
  * @param {((key: string, from: CircuitState, to: CircuitState) => void)} [options.onStateChange] - Callback(key, from, to).
16
+ * @when you want per-key failure isolation — trip open after N failures, probe half-open after a cooldown — so one failing dependency doesn't cascade
17
+ * @fails throws CircuitOpenError while open (fail-fast, no downstream call); transitions emit onStateChange and reset on a successful half-open probe.
18
+ * @example
19
+ * const cb = new CircuitBreaker({ threshold: 5 });
20
+ * await cb.call('api', () => fetch(url));
16
21
  */
17
22
  constructor(options = {}) {
18
23
  this.threshold = options.threshold || 5;
@@ -27,6 +27,11 @@ export type ComplexityResult = {
27
27
  * Assess the complexity of a goal/prompt from its text alone (no LLM).
28
28
  * @param {string} prompt - The goal to classify.
29
29
  * @returns {ComplexityResult}
30
+ * @when you want a fast, no-LLM read of how hard a goal is (simple/medium/complex/critical) to decide whether to invoke the Planner
31
+ * @fails never throws — pure text scoring; non-string/blank input scores lowest. Keyword lists are frozen and a critical-safety override always wins.
32
+ * @example
33
+ * const { level, needsPlanning } = assessComplexity(goal);
34
+ * if (needsPlanning) await planner.plan(goal);
30
35
  */
31
36
  export function assessComplexity(prompt: string): ComplexityResult;
32
37
  /**
@@ -39,5 +44,9 @@ export function assessComplexity(prompt: string): ComplexityResult;
39
44
  * non-string / blank input is `false`.
40
45
  * @param {string} prompt - The goal to test.
41
46
  * @returns {boolean}
47
+ * @when you want the durable critical-safety floor alone (security/production/compliance/financial) to gate extra scrutiny, without the full scorer
48
+ * @fails never throws — deterministic override; non-string/blank input is false. This floor is non-overridable by design.
49
+ * @example
50
+ * if (isCritical(goal)) verdict = await evaluator.evaluate(goal, result, { contract });
42
51
  */
43
52
  export function isCritical(prompt: string): boolean;