bare-agent 0.43.0 → 0.44.1

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 +446 -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/src/complexity.js CHANGED
@@ -89,6 +89,11 @@ const MAX_ASSESS_LEN = 4000;
89
89
  * Assess the complexity of a goal/prompt from its text alone (no LLM).
90
90
  * @param {string} prompt - The goal to classify.
91
91
  * @returns {ComplexityResult}
92
+ * @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
93
+ * @fails never throws — pure text scoring; non-string/blank input scores lowest. Keyword lists are frozen and a critical-safety override always wins.
94
+ * @example
95
+ * const { level, needsPlanning } = assessComplexity(goal);
96
+ * if (needsPlanning) await planner.plan(goal);
92
97
  */
93
98
  function assessComplexity(prompt) {
94
99
  if (typeof prompt !== 'string' || !prompt.trim()) {
@@ -169,6 +174,10 @@ function assessComplexity(prompt) {
169
174
  * non-string / blank input is `false`.
170
175
  * @param {string} prompt - The goal to test.
171
176
  * @returns {boolean}
177
+ * @when you want the durable critical-safety floor alone (security/production/compliance/financial) to gate extra scrutiny, without the full scorer
178
+ * @fails never throws — deterministic override; non-string/blank input is false. This floor is non-overridable by design.
179
+ * @example
180
+ * if (isCritical(goal)) verdict = await evaluator.evaluate(goal, result, { contract });
172
181
  */
173
182
  function isCritical(prompt) {
174
183
  if (typeof prompt !== 'string' || !prompt.trim()) return false;
@@ -32,6 +32,10 @@ export function fromUnits(units: Array<Record<string, any>>): Array<Record<strin
32
32
  * error (incl. HaltError) is left to the Loop's own fail-open / HaltError handling — not swallowed here.
33
33
  * @param {(units: Array<Record<string, any>>, ctx: any) => (any | Promise<any>)} assembleUnits
34
34
  * @returns {(msgs: Array<Record<string, any>>, ctx: any) => Promise<Array<Record<string, any>>>}
35
+ * @when you want to adapt a litectx-style neutral-unit assemble(units, ctx) verb into the Loop's assemble(msgs, ctx) context-assembly seam
36
+ * @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.
37
+ * @example
38
+ * const loop = new Loop({ provider, assemble: unitAssembler(litectx.assemble) });
35
39
  */
36
40
  export function unitAssembler(assembleUnits: (units: Array<Record<string, any>>, ctx: any) => (any | Promise<any>)): (msgs: Array<Record<string, any>>, ctx: any) => Promise<Array<Record<string, any>>>;
37
41
  /**
@@ -67,6 +71,10 @@ export function unitAssembler(assembleUnits: (units: Array<Record<string, any>>,
67
71
  * `({ key, content, unit }) => void|Promise` (REQUIRED; the harvest policy point). `policy` — litectx
68
72
  * TrimPolicy: `{ keepLastN }` or `{ maxTokens }` (maxTokens wins). Both verbs are runtime-checked.
69
73
  * @returns {((msgs: Array<Record<string, any>>, ctx?: any) => Promise<Array<Record<string, any>>>) & { flush: (msgs: Array<Record<string, any>>, ctx?: any) => Promise<void> }}
74
+ * @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
75
+ * @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.
76
+ * @example
77
+ * const loop = new Loop({ provider, trim: unitTrimmer({ trim: litectx.trim, onHarvest, policy: { keepLastN: 20 } }) });
70
78
  */
71
79
  export function unitTrimmer(opts?: {
72
80
  trim?: Function;
@@ -93,6 +101,10 @@ export function unitTrimmer(opts?: {
93
101
  * distinct turns). Normal provider ids (`call_…`) round-trip unchanged through the escape.
94
102
  * @param {Record<string, any>} unit - a unit from {@link toUnits} (its `_msgs` backing is read).
95
103
  * @returns {string}
104
+ * @when you need the stable content-address for a transcript unit (the key harvest-before-evict writes under) — a collision-resistant 64-bit id
105
+ * @fails never throws; normal provider ids round-trip unchanged, and two near-independent hash streams avoid 32-bit birthday collisions.
106
+ * @example
107
+ * const key = harvestKey(unit); // stable id for this turn's harvest
96
108
  */
97
109
  export function harvestKey(unit: Record<string, any>): string;
98
110
  /** chars/4 token estimate over a list of messages (matches poc2 / the Loop's own heuristic). */
@@ -207,6 +207,10 @@ function fromUnits(units) {
207
207
  * error (incl. HaltError) is left to the Loop's own fail-open / HaltError handling — not swallowed here.
208
208
  * @param {(units: Array<Record<string, any>>, ctx: any) => (any | Promise<any>)} assembleUnits
209
209
  * @returns {(msgs: Array<Record<string, any>>, ctx: any) => Promise<Array<Record<string, any>>>}
210
+ * @when you want to adapt a litectx-style neutral-unit assemble(units, ctx) verb into the Loop's assemble(msgs, ctx) context-assembly seam
211
+ * @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.
212
+ * @example
213
+ * const loop = new Loop({ provider, assemble: unitAssembler(litectx.assemble) });
210
214
  */
211
215
  function unitAssembler(assembleUnits) {
212
216
  if (typeof assembleUnits !== 'function') {
@@ -240,6 +244,10 @@ function unitAssembler(assembleUnits) {
240
244
  * distinct turns). Normal provider ids (`call_…`) round-trip unchanged through the escape.
241
245
  * @param {Record<string, any>} unit - a unit from {@link toUnits} (its `_msgs` backing is read).
242
246
  * @returns {string}
247
+ * @when you need the stable content-address for a transcript unit (the key harvest-before-evict writes under) — a collision-resistant 64-bit id
248
+ * @fails never throws; normal provider ids round-trip unchanged, and two near-independent hash streams avoid 32-bit birthday collisions.
249
+ * @example
250
+ * const key = harvestKey(unit); // stable id for this turn's harvest
243
251
  */
244
252
  function harvestKey(unit) {
245
253
  const back = (unit && unit._msgs) || [];
@@ -296,6 +304,10 @@ function harvestKey(unit) {
296
304
  * `({ key, content, unit }) => void|Promise` (REQUIRED; the harvest policy point). `policy` — litectx
297
305
  * TrimPolicy: `{ keepLastN }` or `{ maxTokens }` (maxTokens wins). Both verbs are runtime-checked.
298
306
  * @returns {((msgs: Array<Record<string, any>>, ctx?: any) => Promise<Array<Record<string, any>>>) & { flush: (msgs: Array<Record<string, any>>, ctx?: any) => Promise<void> }}
307
+ * @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
308
+ * @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.
309
+ * @example
310
+ * const loop = new Loop({ provider, trim: unitTrimmer({ trim: litectx.trim, onHarvest, policy: { keepLastN: 20 } }) });
299
311
  */
300
312
  function unitTrimmer(opts) {
301
313
  const { trim, onHarvest, policy = {} } = opts || {};
@@ -114,7 +114,15 @@ export type EvaluateOptions = {
114
114
  * Built flagged-and-deletable per D11 — opt-in by import; calibrate the rubric/prompt from execution traces.
115
115
  */
116
116
  export class Evaluator {
117
- /** @param {EvaluatorOptions} [options] */
117
+ /**
118
+ * @param {EvaluatorOptions} [options]
119
+ * @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
120
+ * @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.
121
+ * @example
122
+ * const evaluator = new Evaluator({ provider });
123
+ * const verdict = await evaluator.evaluate(goal, result, { rubric });
124
+ * if (!verdict.pass) revise(verdict.critique);
125
+ */
118
126
  constructor(options?: EvaluatorOptions);
119
127
  provider: import("../types").Provider | null;
120
128
  prompt: string;
package/src/evaluator.js CHANGED
@@ -108,7 +108,15 @@ Output your FINAL answer as ONLY this JSON, no markdown, no prose:
108
108
  * Built flagged-and-deletable per D11 — opt-in by import; calibrate the rubric/prompt from execution traces.
109
109
  */
110
110
  class Evaluator {
111
- /** @param {EvaluatorOptions} [options] */
111
+ /**
112
+ * @param {EvaluatorOptions} [options]
113
+ * @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
114
+ * @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.
115
+ * @example
116
+ * const evaluator = new Evaluator({ provider });
117
+ * const verdict = await evaluator.evaluate(goal, result, { rubric });
118
+ * if (!verdict.pass) revise(verdict.critique);
119
+ */
112
120
  constructor(options = /** @type {EvaluatorOptions} */ ({})) {
113
121
  this.provider = options.provider || null;
114
122
  this.prompt = options.prompt || GRADER_PROMPT;
@@ -129,6 +129,11 @@ export function gradeRun(runs: Array<{
129
129
  * a separate admission gate — a leak in any style blocks admission even at a passing clear-case floor.
130
130
  * @param {(payload:object)=>any} [opts.onLlmResult] - Budget hook forwarded to each judge call.
131
131
  * @returns {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 } }>}
132
+ * @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
133
+ * @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.
134
+ * @example
135
+ * const report = await calibrate({ provider, reps: 5, floor: 7 });
136
+ * if (!report.injectionBattery.allResisted) reject('injection leak');
132
137
  */
133
138
  export function calibrate(opts?: {
134
139
  provider: import("../types").Provider;
@@ -125,6 +125,11 @@ function gradeRun(runs, floor) {
125
125
  * a separate admission gate — a leak in any style blocks admission even at a passing clear-case floor.
126
126
  * @param {(payload:object)=>any} [opts.onLlmResult] - Budget hook forwarded to each judge call.
127
127
  * @returns {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 } }>}
128
+ * @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
129
+ * @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.
130
+ * @example
131
+ * const report = await calibrate({ provider, reps: 5, floor: 7 });
132
+ * if (!report.injectionBattery.allResisted) reject('injection leak');
128
133
  */
129
134
  async function calibrate(opts = /** @type {any} */ ({})) {
130
135
  const provider = opts.provider;
package/src/judge.d.ts CHANGED
@@ -157,6 +157,11 @@ export type JudgeVerdict = {
157
157
  * @throws {ValidationError} on bad inputs (missing request/artifact/provider) — stamped `context.lib='bare-agent'`
158
158
  * at the throw site (contract 4: typed attribution, never sniffed from prose).
159
159
  * @throws {HaltError} propagated clean from the provider (a governance halt is not a judge failure).
160
+ * @when you need a decisive honored/broke verdict on whether one egress artifact honored the verbatim request — a return-time integrity check
161
+ * @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.
162
+ * @example
163
+ * const { verdict, where } = await judge({ request, artifact, provider });
164
+ * if (verdict === 'broke') flag(where);
160
165
  */
161
166
  export function judge(options?: JudgeOptions): Promise<JudgeVerdict>;
162
167
  /**
package/src/judge.js CHANGED
@@ -145,6 +145,11 @@ function normalizeWhere(where) {
145
145
  * @throws {ValidationError} on bad inputs (missing request/artifact/provider) — stamped `context.lib='bare-agent'`
146
146
  * at the throw site (contract 4: typed attribution, never sniffed from prose).
147
147
  * @throws {HaltError} propagated clean from the provider (a governance halt is not a judge failure).
148
+ * @when you need a decisive honored/broke verdict on whether one egress artifact honored the verbatim request — a return-time integrity check
149
+ * @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.
150
+ * @example
151
+ * const { verdict, where } = await judge({ request, artifact, provider });
152
+ * if (verdict === 'broke') flag(where);
148
153
  */
149
154
  async function judge(options = /** @type {JudgeOptions} */ ({})) {
150
155
  const { request, artifact, provider } = options;
package/src/loop.d.ts CHANGED
@@ -105,6 +105,12 @@ export class Loop {
105
105
  * tool outcomes to `gate.record` (via wireGate) and never kill the loop on error.
106
106
  * @param {LoopOptions} options
107
107
  * @throws {Error} `[Loop] requires a provider` — when options.provider is missing.
108
+ * @when you are running a model think/act/observe cycle and need round accounting, tool dispatch, spin guards, and a governance chokepoint
109
+ * @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.
110
+ * @example
111
+ * const loop = new Loop({ provider, policy });
112
+ * const { text, error, metrics } = await loop.run(messages, tools);
113
+ * if (error) handle(error);
108
114
  */
109
115
  constructor(options?: LoopOptions);
110
116
  provider: import("../types").Provider;
package/src/loop.js CHANGED
@@ -273,6 +273,12 @@ class Loop {
273
273
  * tool outcomes to `gate.record` (via wireGate) and never kill the loop on error.
274
274
  * @param {LoopOptions} options
275
275
  * @throws {Error} `[Loop] requires a provider` — when options.provider is missing.
276
+ * @when you are running a model think/act/observe cycle and need round accounting, tool dispatch, spin guards, and a governance chokepoint
277
+ * @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.
278
+ * @example
279
+ * const loop = new Loop({ provider, policy });
280
+ * const { text, error, metrics } = await loop.run(messages, tools);
281
+ * if (error) handle(error);
276
282
  */
277
283
  constructor(options = /** @type {LoopOptions} */ ({})) {
278
284
  if (!options.provider) throw new Error('[Loop] requires a provider');
@@ -92,6 +92,11 @@ export type RpcClient = {
92
92
  * of this hook also opts default discovery into the project-cwd `./.mcp.json`,
93
93
  * since each command is then vetted regardless of source.
94
94
  * @returns {Promise<{tools: ToolDef[], metaTools?: ToolDef[], servers: string[], systemContext: string, denied: DeniedTool[], errors?: Array<{server: string, error: string}>, close: Function}>}
95
+ * @when you want to auto-discover MCP servers and expose them as bareagent tools in one call — with a trust hook gating command execution
96
+ * @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.
97
+ * @example
98
+ * const { tools, close } = await createMCPBridge();
99
+ * const loop = new Loop({ provider, tools });
95
100
  */
96
101
  export function createMCPBridge(opts?: {
97
102
  bridgePath?: string | undefined;
@@ -120,6 +125,10 @@ export function createMCPBridge(opts?: {
120
125
  * @param {{ includeProjectConfig?: boolean }} [opts] - When no explicit `configPaths` are given,
121
126
  * set `includeProjectConfig: true` to also scan `./.mcp.json`. Default false — see PROJECT_CONFIG_PATH.
122
127
  * @returns {Map<string, ServerDef>}
128
+ * @when you want to find configured MCP servers from the trusted $HOME/IDE config paths (or explicit paths) — discovery without invoking them
129
+ * @fails honors explicit configPaths verbatim and scans trusted defaults otherwise; returns a Map and never executes a server command.
130
+ * @example
131
+ * const servers = discoverServers();
123
132
  */
124
133
  export function discoverServers(configPaths?: string[], { includeProjectConfig }?: {
125
134
  includeProjectConfig?: boolean;
@@ -142,5 +151,9 @@ export function discoverServers(configPaths?: string[], { includeProjectConfig }
142
151
  * @param {ToolDef[]} tools - The bulk-loaded, name-prefixed tools array.
143
152
  * @param {string} [discoveredAt] - ISO timestamp from .mcp-bridge.json.
144
153
  * @returns {ToolDef[]} [mcp_discover, mcp_invoke]
154
+ * @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
155
+ * @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).
156
+ * @example
157
+ * const metaTools = buildMetaTools(tools);
145
158
  */
146
159
  export function buildMetaTools(tools: ToolDef[], discoveredAt?: string): ToolDef[];
package/src/mcp-bridge.js CHANGED
@@ -80,6 +80,10 @@ const TRUSTED_CONFIG_PATHS = [
80
80
  * @param {{ includeProjectConfig?: boolean }} [opts] - When no explicit `configPaths` are given,
81
81
  * set `includeProjectConfig: true` to also scan `./.mcp.json`. Default false — see PROJECT_CONFIG_PATH.
82
82
  * @returns {Map<string, ServerDef>}
83
+ * @when you want to find configured MCP servers from the trusted $HOME/IDE config paths (or explicit paths) — discovery without invoking them
84
+ * @fails honors explicit configPaths verbatim and scans trusted defaults otherwise; returns a Map and never executes a server command.
85
+ * @example
86
+ * const servers = discoverServers();
83
87
  */
84
88
  function discoverServers(configPaths, { includeProjectConfig = false } = {}) {
85
89
  let paths;
@@ -523,6 +527,10 @@ function buildSystemContext(servers, tools, denied) {
523
527
  * @param {ToolDef[]} tools - The bulk-loaded, name-prefixed tools array.
524
528
  * @param {string} [discoveredAt] - ISO timestamp from .mcp-bridge.json.
525
529
  * @returns {ToolDef[]} [mcp_discover, mcp_invoke]
530
+ * @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
531
+ * @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).
532
+ * @example
533
+ * const metaTools = buildMetaTools(tools);
526
534
  */
527
535
  function buildMetaTools(tools, discoveredAt) {
528
536
  // Catalog descriptors: same info the LLM would see for bulk-loaded tools,
@@ -637,6 +645,11 @@ function buildMetaTools(tools, discoveredAt) {
637
645
  * of this hook also opts default discovery into the project-cwd `./.mcp.json`,
638
646
  * since each command is then vetted regardless of source.
639
647
  * @returns {Promise<{tools: ToolDef[], metaTools?: ToolDef[], servers: string[], systemContext: string, denied: DeniedTool[], errors?: Array<{server: string, error: string}>, close: Function}>}
648
+ * @when you want to auto-discover MCP servers and expose them as bareagent tools in one call — with a trust hook gating command execution
649
+ * @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.
650
+ * @example
651
+ * const { tools, close } = await createMCPBridge();
652
+ * const loop = new Loop({ provider, tools });
640
653
  */
641
654
  async function createMCPBridge(opts = {}) {
642
655
  if ('policy' in opts) {
package/src/memory.d.ts CHANGED
@@ -19,6 +19,11 @@ export class Memory {
19
19
  /**
20
20
  * @param {{ store?: Store }} [options] - Store backend (must implement store/search/get/delete).
21
21
  * @throws {Error} `[Memory] requires options.store` — when options.store is missing.
22
+ * @when you want thin store-backed memory (store/search/get/delete) an agent can write to and recall from, over any swappable backend
23
+ * @fails throws if constructed without a store; recall/store metering is opt-in via ctx. All persistence delegates to the backend.
24
+ * @example
25
+ * const memory = new Memory({ store: new JsonFile({ path: './mem.json' }) });
26
+ * await memory.store({ text: 'a durable fact' });
22
27
  */
23
28
  constructor(options?: {
24
29
  store?: Store;
package/src/memory.js CHANGED
@@ -21,6 +21,11 @@ class Memory {
21
21
  /**
22
22
  * @param {{ store?: Store }} [options] - Store backend (must implement store/search/get/delete).
23
23
  * @throws {Error} `[Memory] requires options.store` — when options.store is missing.
24
+ * @when you want thin store-backed memory (store/search/get/delete) an agent can write to and recall from, over any swappable backend
25
+ * @fails throws if constructed without a store; recall/store metering is opt-in via ctx. All persistence delegates to the backend.
26
+ * @example
27
+ * const memory = new Memory({ store: new JsonFile({ path: './mem.json' }) });
28
+ * await memory.store({ text: 'a durable fact' });
24
29
  */
25
30
  constructor(options = {}) {
26
31
  if (!options.store) throw new Error('[Memory] requires options.store');
package/src/planner.d.ts CHANGED
@@ -46,6 +46,12 @@ export class Planner {
46
46
  /**
47
47
  * @param {PlannerOptions} options
48
48
  * @throws {Error} `[Planner] requires a provider` — when options.provider is missing.
49
+ * @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
50
+ * @fails throws if constructed without a provider; a plan call's HaltError (governance cap) propagates clean. Plan-call usage forwards via onLlmResult.
51
+ * @example
52
+ * const planner = new Planner({ provider });
53
+ * const steps = await planner.plan('ship the release', { count: 4 });
54
+ * await runPlan(steps, ctx);
49
55
  */
50
56
  constructor(options?: PlannerOptions);
51
57
  provider: import("../types").Provider;
package/src/planner.js CHANGED
@@ -39,6 +39,12 @@ class Planner {
39
39
  /**
40
40
  * @param {PlannerOptions} options
41
41
  * @throws {Error} `[Planner] requires a provider` — when options.provider is missing.
42
+ * @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
43
+ * @fails throws if constructed without a provider; a plan call's HaltError (governance cap) propagates clean. Plan-call usage forwards via onLlmResult.
44
+ * @example
45
+ * const planner = new Planner({ provider });
46
+ * const steps = await planner.plan('ship the release', { count: 4 });
47
+ * await runPlan(steps, ctx);
42
48
  */
43
49
  constructor(options = /** @type {PlannerOptions} */ ({})) {
44
50
  if (!options.provider) throw new Error('[Planner] requires a provider');
@@ -62,6 +62,10 @@ export class AnthropicProvider {
62
62
  /**
63
63
  * @param {AnthropicOptions} [options]
64
64
  * @throws {Error} `[AnthropicProvider] requires apiKey` — when apiKey is missing.
65
+ * @when you want Claude models as the Loop's provider — native Messages API with opt-in prompt caching and thinking-block passthrough
66
+ * @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.
67
+ * @example
68
+ * const provider = new AnthropicProvider({ apiKey, model: 'claude-sonnet-5' });
65
69
  */
66
70
  constructor(options?: AnthropicOptions);
67
71
  apiKey: string;
@@ -41,6 +41,10 @@ class AnthropicProvider {
41
41
  /**
42
42
  * @param {AnthropicOptions} [options]
43
43
  * @throws {Error} `[AnthropicProvider] requires apiKey` — when apiKey is missing.
44
+ * @when you want Claude models as the Loop's provider — native Messages API with opt-in prompt caching and thinking-block passthrough
45
+ * @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.
46
+ * @example
47
+ * const provider = new AnthropicProvider({ apiKey, model: 'claude-sonnet-5' });
44
48
  */
45
49
  constructor(options = {}) {
46
50
  if (!options.apiKey) throw new Error('[AnthropicProvider] requires apiKey');
@@ -123,6 +123,10 @@ export class CLIPipeProvider {
123
123
  * Provider that pipes prompts to a CLI command via stdin and reads stdout.
124
124
  * @param {CLIPipeOptions} [options]
125
125
  * @throws {Error} `[CLIPipeProvider] requires command` — when options.command is missing.
126
+ * @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
127
+ * @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.
128
+ * @example
129
+ * const provider = new CLIPipeProvider({ command: 'claude' });
126
130
  */
127
131
  constructor(options?: CLIPipeOptions);
128
132
  command: string;
@@ -112,6 +112,10 @@ class CLIPipeProvider {
112
112
  * Provider that pipes prompts to a CLI command via stdin and reads stdout.
113
113
  * @param {CLIPipeOptions} [options]
114
114
  * @throws {Error} `[CLIPipeProvider] requires command` — when options.command is missing.
115
+ * @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
116
+ * @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.
117
+ * @example
118
+ * const provider = new CLIPipeProvider({ command: 'claude' });
115
119
  */
116
120
  constructor(options = {}) {
117
121
  if (!options.command) throw new Error('[CLIPipeProvider] requires command');
@@ -27,6 +27,10 @@ export class FallbackProvider {
27
27
  * @param {Provider[]} providers - Ordered list of providers with generate().
28
28
  * @param {FallbackOptions} [options={}]
29
29
  * @throws {Error} `[FallbackProvider] requires at least one provider` — when providers is empty.
30
+ * @when you want to try several providers in order, failing over to the next when one errors — resilience across tiers or vendors
31
+ * @fails throws if given no providers; returns the first provider's success, else propagates the last provider's error.
32
+ * @example
33
+ * const provider = new FallbackProvider([primary, backup]);
30
34
  */
31
35
  constructor(providers: Provider[], options?: FallbackOptions);
32
36
  providers: import("../types").Provider[];
@@ -17,6 +17,10 @@ class FallbackProvider {
17
17
  * @param {Provider[]} providers - Ordered list of providers with generate().
18
18
  * @param {FallbackOptions} [options={}]
19
19
  * @throws {Error} `[FallbackProvider] requires at least one provider` — when providers is empty.
20
+ * @when you want to try several providers in order, failing over to the next when one errors — resilience across tiers or vendors
21
+ * @fails throws if given no providers; returns the first provider's success, else propagates the last provider's error.
22
+ * @example
23
+ * const provider = new FallbackProvider([primary, backup]);
20
24
  */
21
25
  constructor(providers, options = {}) {
22
26
  if (!Array.isArray(providers) || providers.length === 0) {
@@ -45,7 +45,13 @@ export type GeminiOptions = {
45
45
  * cache-read tier populates with no opt-in.
46
46
  */
47
47
  export class GeminiProvider {
48
- /** @param {GeminiOptions} [options] */
48
+ /**
49
+ * @param {GeminiOptions} [options]
50
+ * @when you want Gemini models as the Loop's provider — native generateContent with implicit prompt caching
51
+ * @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.
52
+ * @example
53
+ * const provider = new GeminiProvider({ apiKey, model: 'gemini-2.5-pro' });
54
+ */
49
55
  constructor(options?: GeminiOptions);
50
56
  apiKey: string | undefined;
51
57
  model: string;
@@ -40,7 +40,13 @@ function isLoopbackHost(hostname) {
40
40
  * cache-read tier populates with no opt-in.
41
41
  */
42
42
  class GeminiProvider {
43
- /** @param {GeminiOptions} [options] */
43
+ /**
44
+ * @param {GeminiOptions} [options]
45
+ * @when you want Gemini models as the Loop's provider — native generateContent with implicit prompt caching
46
+ * @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.
47
+ * @example
48
+ * const provider = new GeminiProvider({ apiKey, model: 'gemini-2.5-pro' });
49
+ */
44
50
  constructor(options = {}) {
45
51
  this.apiKey = options.apiKey?.trim();
46
52
  this.model = options.model || 'gemini-2.5-flash';
@@ -28,6 +28,10 @@ export type OllamaOptions = {
28
28
  export class OllamaProvider {
29
29
  /**
30
30
  * @param {OllamaOptions} [options]
31
+ * @when you want local models via Ollama as the Loop's provider — no API key, self-hosted
32
+ * @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.
33
+ * @example
34
+ * const provider = new OllamaProvider({ model: 'llama3' });
31
35
  */
32
36
  constructor(options?: OllamaOptions);
33
37
  model: string;
@@ -27,6 +27,10 @@ const OLLAMA_USAGE_KEYS = ['prompt_eval_count', 'eval_count'];
27
27
  class OllamaProvider {
28
28
  /**
29
29
  * @param {OllamaOptions} [options]
30
+ * @when you want local models via Ollama as the Loop's provider — no API key, self-hosted
31
+ * @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.
32
+ * @example
33
+ * const provider = new OllamaProvider({ model: 'llama3' });
30
34
  */
31
35
  constructor(options = {}) {
32
36
  this.model = options.model || 'llama3.2';
@@ -68,6 +68,10 @@ export type OpenAIOptions = {
68
68
  export class OpenAIProvider {
69
69
  /**
70
70
  * @param {OpenAIOptions} [options]
71
+ * @when you want OpenAI or any OpenAI-compatible endpoint as the Loop's provider — tool-calling, toolChoice, and a custom baseUrl
72
+ * @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.
73
+ * @example
74
+ * const provider = new OpenAIProvider({ apiKey, model: 'gpt-5' });
71
75
  */
72
76
  constructor(options?: OpenAIOptions);
73
77
  apiKey: string | undefined;
@@ -76,6 +76,10 @@ function isLoopbackHost(hostname) {
76
76
  class OpenAIProvider {
77
77
  /**
78
78
  * @param {OpenAIOptions} [options]
79
+ * @when you want OpenAI or any OpenAI-compatible endpoint as the Loop's provider — tool-calling, toolChoice, and a custom baseUrl
80
+ * @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.
81
+ * @example
82
+ * const provider = new OpenAIProvider({ apiKey, model: 'gpt-5' });
79
83
  */
80
84
  constructor(options = {}) {
81
85
  this.apiKey = options.apiKey?.trim();
@@ -99,6 +99,12 @@ export function normalizeCorpus(corpus: unknown): Slice[];
99
99
  * @param {{recall: Function}} litectx
100
100
  * @param {{kinds?: string[], n?: number}} [opts]
101
101
  * @returns {ToolDef}
102
+ * @category integration
103
+ * @when you want to give a recurse worker a needle-search handle over litectx (embeddings recall for the relevant few) — for FINDING, never counting
104
+ * @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.
105
+ * @example
106
+ * const tool = buildSearchTool(litectx, { n: 8 });
107
+ * await recurse(task, ctx, { retrieval: 'search', tools: [tool] });
102
108
  */
103
109
  export function buildSearchTool(litectx: {
104
110
  recall: Function;
@@ -114,6 +120,11 @@ export function buildSearchTool(litectx: {
114
120
  * OFF to stay exact — deferred; the code-side filter is the embeddings-free path shipped now.)
115
121
  * @param {Slice[]} corpus - The validated slice-source.
116
122
  * @returns {ToolDef}
123
+ * @when you want an embeddings-free, exact AND-term filter handle over a slice-source — complete over its slices, for precise lexical matches
124
+ * @fails only as good as a lexical rule; complete over the slices it is given (no recall cap) but matches nothing outside them.
125
+ * @example
126
+ * const tool = buildExactTool(corpus);
127
+ * await recurse(task, ctx, { retrieval: 'exact', tools: [tool] });
117
128
  */
118
129
  export function buildExactTool(corpus: Slice[]): ToolDef;
119
130
  /**
@@ -131,6 +142,11 @@ export function buildExactTool(corpus: Slice[]): ToolDef;
131
142
  * materialized lazily on first call and cached for the tool's lifetime.
132
143
  * @param {{provider: Provider, window?: number, passes?: number, ctx?: object, onLlmResult?: Function, policy?: Function}} opts
133
144
  * @returns {ToolDef}
145
+ * @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"
146
+ * @fails a dead window surfaces as `INCOMPLETE — the count is a floor`, never a clean number over a hole; a governance HaltError propagates clean.
147
+ * @example
148
+ * const tool = buildScanTool(corpus, { provider, window: 8 });
149
+ * await recurse('how many mention X', ctx, { tools: [tool] });
134
150
  */
135
151
  export function buildScanTool(corpus: Slice[] | (() => Promise<Slice[]>), opts: {
136
152
  provider: Provider;
@@ -154,6 +170,12 @@ export function buildScanTool(corpus: Slice[] | (() => Promise<Slice[]>), opts:
154
170
  * @param {{enumerate: Function}} litectx
155
171
  * @param {{kind?: 'fact'|'episode', pageSize?: number}} [opts]
156
172
  * @returns {() => Promise<Slice[]>}
173
+ * @category integration
174
+ * @when you want a resident slice-source that paginates a litectx corpus via enumerate — for a corpus ALREADY in litectx, feeding scan/partition
175
+ * @fails never ingests a fresh corpus (strictly worse than scanning an in-hand array); returns an async source materialized once and cached.
176
+ * @example
177
+ * const corpus = litectxCorpus(litectx, { kind: 'fact' });
178
+ * await recurse(task, ctx, { mode: 'partition', corpus });
157
179
  */
158
180
  export function litectxCorpus(litectx: {
159
181
  enumerate: Function;
@@ -188,6 +188,12 @@ function normalizeCorpus(corpus) {
188
188
  * @param {{recall: Function}} litectx
189
189
  * @param {{kinds?: string[], n?: number}} [opts]
190
190
  * @returns {ToolDef}
191
+ * @category integration
192
+ * @when you want to give a recurse worker a needle-search handle over litectx (embeddings recall for the relevant few) — for FINDING, never counting
193
+ * @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.
194
+ * @example
195
+ * const tool = buildSearchTool(litectx, { n: 8 });
196
+ * await recurse(task, ctx, { retrieval: 'search', tools: [tool] });
191
197
  */
192
198
  function buildSearchTool(litectx, opts = {}) {
193
199
  const kinds = Array.isArray(opts.kinds) && opts.kinds.length ? opts.kinds : ['fact', 'episode'];
@@ -224,6 +230,11 @@ function buildSearchTool(litectx, opts = {}) {
224
230
  * OFF to stay exact — deferred; the code-side filter is the embeddings-free path shipped now.)
225
231
  * @param {Slice[]} corpus - The validated slice-source.
226
232
  * @returns {ToolDef}
233
+ * @when you want an embeddings-free, exact AND-term filter handle over a slice-source — complete over its slices, for precise lexical matches
234
+ * @fails only as good as a lexical rule; complete over the slices it is given (no recall cap) but matches nothing outside them.
235
+ * @example
236
+ * const tool = buildExactTool(corpus);
237
+ * await recurse(task, ctx, { retrieval: 'exact', tools: [tool] });
227
238
  */
228
239
  function buildExactTool(corpus) {
229
240
  const slices = normalizeCorpus(corpus);
@@ -269,6 +280,11 @@ function buildExactTool(corpus) {
269
280
  * materialized lazily on first call and cached for the tool's lifetime.
270
281
  * @param {{provider: Provider, window?: number, passes?: number, ctx?: object, onLlmResult?: Function, policy?: Function}} opts
271
282
  * @returns {ToolDef}
283
+ * @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"
284
+ * @fails a dead window surfaces as `INCOMPLETE — the count is a floor`, never a clean number over a hole; a governance HaltError propagates clean.
285
+ * @example
286
+ * const tool = buildScanTool(corpus, { provider, window: 8 });
287
+ * await recurse('how many mention X', ctx, { tools: [tool] });
272
288
  */
273
289
  function buildScanTool(corpus, opts) {
274
290
  /** @type {Slice[]|null} */
@@ -337,6 +353,12 @@ const ENUM_PAGE = 200;
337
353
  * @param {{enumerate: Function}} litectx
338
354
  * @param {{kind?: 'fact'|'episode', pageSize?: number}} [opts]
339
355
  * @returns {() => Promise<Slice[]>}
356
+ * @category integration
357
+ * @when you want a resident slice-source that paginates a litectx corpus via enumerate — for a corpus ALREADY in litectx, feeding scan/partition
358
+ * @fails never ingests a fresh corpus (strictly worse than scanning an in-hand array); returns an async source materialized once and cached.
359
+ * @example
360
+ * const corpus = litectxCorpus(litectx, { kind: 'fact' });
361
+ * await recurse(task, ctx, { mode: 'partition', corpus });
340
362
  */
341
363
  function litectxCorpus(litectx, opts = {}) {
342
364
  const kind = opts.kind === 'episode' ? 'episode' : 'fact'; // enumerate v1 is the memory axis (fact/episode)
package/src/recurse.d.ts CHANGED
@@ -604,5 +604,11 @@ export type Slice = {
604
604
  * @returns {Promise<RecurseResult>} `{ result, verdict, receipts }` on convergence; `{ incomplete, best,
605
605
  * receipts }` on guard exhaustion. NEVER a fabricated success (RC-9).
606
606
  * @throws {Error} no provider supplied (on neither `ctx.provider` nor `opts.provider`).
607
+ * @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
608
+ * @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.
609
+ * @example
610
+ * const ctx = wireGate(gate);
611
+ * const { result, incomplete } = await recurse('audit 400 logs', ctx, { provider, corpus });
612
+ * if (incomplete) retryOrEscalate(result);
607
613
  */
608
614
  export function recurse(task: string, ctx?: RecurseCtx, opts?: RecurseOptions): Promise<RecurseResult>;
package/src/recurse.js CHANGED
@@ -395,6 +395,12 @@ function auditSafeCtx(ctx, overrides = {}) {
395
395
  * @returns {Promise<RecurseResult>} `{ result, verdict, receipts }` on convergence; `{ incomplete, best,
396
396
  * receipts }` on guard exhaustion. NEVER a fabricated success (RC-9).
397
397
  * @throws {Error} no provider supplied (on neither `ctx.provider` nor `opts.provider`).
398
+ * @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
399
+ * @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.
400
+ * @example
401
+ * const ctx = wireGate(gate);
402
+ * const { result, incomplete } = await recurse('audit 400 logs', ctx, { provider, corpus });
403
+ * if (incomplete) retryOrEscalate(result);
398
404
  */
399
405
  async function recurse(task, ctx = {}, opts = {}) {
400
406
  if (typeof task !== 'string' || task.length === 0) {
package/src/refine.d.ts CHANGED
@@ -98,5 +98,10 @@ export type RefineOutcome = {
98
98
  *
99
99
  * @param {RefineOptions} options
100
100
  * @returns {Promise<RefineOutcome>}
101
+ * @when you have a caller-supplied attempt + evaluate pair and want to iterate generate → grade → regenerate until it passes or hits a bound
102
+ * @fails returns the last outcome on maxIterations or a terminal `failed` verdict (never a faked pass); a HaltError from either callback propagates clean.
103
+ * @example
104
+ * const { result, passed } = await refine({ attempt, evaluate, maxIterations: 3 });
105
+ * if (!passed) escalate(result);
101
106
  */
102
107
  export function refine(options: RefineOptions): Promise<RefineOutcome>;