bare-agent 0.19.0 → 0.20.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.
@@ -0,0 +1,361 @@
1
+ /**
2
+ * The per-run runtime blob — the wiring, threaded down the whole recursion tree (and forwarded to the worker
3
+ * Loop's `policy`/governance via `options.ctx`). Distinct from `opts` (the policy knobs).
4
+ */
5
+ export type RecurseCtx = {
6
+ /**
7
+ * - The model the workers call. Required (here or on `opts.provider`).
8
+ */
9
+ provider?: import("../types").Provider | undefined;
10
+ /**
11
+ * - bareguard `policy(tool, args, ctx)` — the gate. Sees `ctx.depth` so it can
12
+ * enforce `limits.maxDepth`/budget/calls. recurse adds NO second guard layer (§6).
13
+ */
14
+ policy?: Function | undefined;
15
+ /**
16
+ * - Budget hook forwarded to every worker Loop AND the verifier — judge
17
+ * and worker tokens are all real spend (BA1: never invisible).
18
+ */
19
+ onLlmResult?: Function | undefined;
20
+ /**
21
+ * - The current recursion depth (0 at the top). Incremented on each self-call;
22
+ * threaded into `policy`. Callers normally omit it (defaults to 0).
23
+ */
24
+ depth?: number | undefined;
25
+ /**
26
+ * - Optional event stream forwarded to each worker Loop (receipts substrate).
27
+ */
28
+ stream?: object;
29
+ /**
30
+ * - Optional litectx handle (RC-5, §10 step 7). Backs the `search`
31
+ * retrieval mode (`recall`). NOT used by `scan` — litectx has no exhaustive enumerate verb today; scan reads
32
+ * the generic array slice-source `opts.corpus` instead (the litectx-resident scan case waits on the litectx
33
+ * `enumerate` verb and drops in behind the same socket).
34
+ */
35
+ litectx?: {
36
+ recall: Function;
37
+ } | undefined;
38
+ };
39
+ export type RecurseOptions = {
40
+ /**
41
+ * - Fallback provider if `ctx.provider` is absent.
42
+ */
43
+ provider?: import("../types").Provider | undefined;
44
+ /**
45
+ * - Open topology ceiling (§1): the depth past which the `spawn_child` tool is
46
+ * no longer offered (`maxDepth=1` ⇒ flat fan-out, no nesting). NOT the safety halt — that is bareguard's,
47
+ * and actual depth is always ≤ `limits.maxDepth`.
48
+ */
49
+ maxDepth?: number | undefined;
50
+ /**
51
+ * - Handle tools offered to EVERY worker (RC-5 pull-default: litectx
52
+ * `recall`/`get`, wired at build step 7). Workers query on demand; never the whole corpus.
53
+ */
54
+ tools?: import("../types").ToolDef[] | undefined;
55
+ /**
56
+ * - Definition of done (A3). When present, the verifier grades against THIS,
57
+ * not the loose task, and verification always runs.
58
+ */
59
+ contract?: string | undefined;
60
+ /**
61
+ * Override the verifier (fills `recurse()`'s verify slot, §7.1). Default = an `Evaluator` rubric pass.
62
+ */
63
+ evaluate?: ((result: any, ctx: {
64
+ contract: string | null;
65
+ task: string;
66
+ }) => (Verdict | Promise<Verdict>)) | undefined;
67
+ /**
68
+ * Override synthesis/reduce (NB-3). A FUNCTION is a deterministic code-reduce over the child `results` — the
69
+ * §9.1 aggregation path (LLM arithmetic over partials carried ~10–15% error). A STRATEGY string runs the
70
+ * built-in reducer: `'concat'` (lossless no-LLM join) or `'merge'` (an isolated Loop-driven subjective
71
+ * merge); a string is ignored when no child ran. Default (unset) = the worker's own final text (Family A:
72
+ * the parent model already combined the children's results in its closing turn).
73
+ */
74
+ synthesize?: "concat" | "merge" | ((args: {
75
+ task: string;
76
+ text: string | null;
77
+ results: any[];
78
+ children: object[];
79
+ ctx: RecurseCtx;
80
+ }) => any) | undefined;
81
+ /**
82
+ * - (Opt-in, NB-2 / Family B) FORCED fan-out: decompose into exactly this many
83
+ * independent parallel workers via `Planner`→`runPlan`, then reduce. A positive integer here is the count;
84
+ * it OVERRIDES the tier→count map. Setting it (or `mode:'fanout'`) takes the deterministic-parallelism path
85
+ * instead of the model-driven Family-A default. For known-parallel tasks where the caller wants guaranteed
86
+ * fan-out, not the model's adaptive choice.
87
+ */
88
+ count?: number | undefined;
89
+ /**
90
+ * - (Opt-in, NB-2 / Family B) `'fanout'` = forced semantic fan-out
91
+ * WITHOUT a fixed count — derived from `assessComplexity`'s tier via the calibrated map (medium/complex/
92
+ * critical → 2/4/6; simple → 1); `opts.count` takes precedence. `'partition'` = the DATA-DRIVEN WIDTH path
93
+ * (§11): measure `opts.corpus` and partition it into `max(opts.count floor, ⌈size/workerBudget⌉)` parallel
94
+ * scan-workers (capped by the guards), CODE-reducing the per-chunk counts. Distinct from `'fanout'`: a data
95
+ * partition, not a `Planner` semantic split.
96
+ */
97
+ mode?: "fanout" | "partition" | undefined;
98
+ /**
99
+ * - (`mode:'partition'`) items per worker; width = `⌈corpus.length /
100
+ * workerBudget⌉` (default 100). A calibratable knob (the §9.1 algorithm), not a discovered constant.
101
+ */
102
+ workerBudget?: number | undefined;
103
+ /**
104
+ * - (Family B) max workers run at once per wave (default 4). The wave
105
+ * structure is `runPlan`'s; bareguard still bounds the family rate independently.
106
+ */
107
+ concurrency?: number | undefined;
108
+ /**
109
+ * - (§10 step 7) the retrieval shape for a task OVER A
110
+ * CORPUS, routed by question shape (§9.2.1). `'scan'` (the default WHEN `opts.corpus` is present) = process
111
+ * every slice + LLM-judge + CODE-count — the only COMPLETE path (for "how many / all"). `'search'` = litectx
112
+ * `recall` handle tool offered to the worker (needle; CANNOT count; requires `ctx.litectx`). `'exact'` = a
113
+ * deterministic code-side AND-term filter tool over `opts.corpus`. `'tools'` = the PER-QUERY Family-A face:
114
+ * offer the worker `scan_count` (over `opts.corpus`) + `search_memory` (when `ctx.litectx`) + `exact_match`
115
+ * (array corpus) ALL AT ONCE, and let it pick the shape PER SUB-QUERY — the routing lives in the tool
116
+ * descriptions (scan says "use for how many / all / count"; search says "never count"), so a mixed task gets
117
+ * needle-search AND complete-count without per-sub-query adopter declaration. The completeness guard upgrades a
118
+ * `'search'` on a "how many / all" ask to `'scan'` (UPGRADE-only, never a silent downgrade); it does NOT fire
119
+ * for `'tools'` (the complete `scan_count` is always offered there, so a mixed task keeps its search tool).
120
+ * Absent `corpus` AND `retrieval`, behaviour is unchanged (Family A / single-shot) — fully backward-compatible.
121
+ */
122
+ retrieval?: "tools" | "search" | "scan" | "exact" | undefined;
123
+ /**
124
+ * - (§10 step 7) the generic slice-source scan/partition
125
+ * reads: an in-hand `{id, text}[]` array, OR an async `() => Promise<Slice[]>` (e.g. `litectxCorpus(litectx,
126
+ * {kind})` materializing a litectx-resident corpus via `enumerate`). recurse depends on this SHAPE, never on
127
+ * litectx. Malformed entries are dropped, never miscounted.
128
+ */
129
+ corpus?: Slice[] | (() => Promise<Slice[]>) | undefined;
130
+ /**
131
+ * - (scan) items per judge window. Default 8 (§9.2.1 recall knee — the one
132
+ * calibrated number; per-model).
133
+ */
134
+ window?: number | undefined;
135
+ /**
136
+ * - (scan) shuffled-boundary passes unioned for recall. Default 2 (~0.91 recall).
137
+ */
138
+ passes?: number | undefined;
139
+ };
140
+ /**
141
+ * One audit/receipts node (RC-10) — the recursion tree reconstructs from these alone: parent→child lineage
142
+ * (`spawned`), each subgoal (`task`), each gap report (`verdict`), cost per node (`tokens`).
143
+ */
144
+ export type RecurseNode = {
145
+ task: string;
146
+ depth: number;
147
+ complexity: {
148
+ level: string;
149
+ score: number;
150
+ };
151
+ critical: boolean;
152
+ /**
153
+ * - Child nodes (lineage).
154
+ */
155
+ spawned: RecurseNode[];
156
+ verdict: Verdict | null;
157
+ incomplete: boolean;
158
+ halted: boolean;
159
+ /**
160
+ * - The worker Loop's `metrics.tokens`.
161
+ */
162
+ tokens: object | null;
163
+ model: string | null;
164
+ /**
165
+ * - (§10 step 7) the retrieval mode this node ran (`scan`/`search`/`exact`),
166
+ * or null/absent for a plain reasoning node.
167
+ */
168
+ retrieval?: string | null | undefined;
169
+ /**
170
+ * - set when the completeness guard upgraded the mode (e.g.
171
+ * `'search→scan (completeness)'`) — the audit trail for RC-9-applied-to-retrieval.
172
+ */
173
+ retrievalUpgraded?: string | undefined;
174
+ /**
175
+ * - (scan) the scan
176
+ * shape: window/passes used, slices scanned, ids matched (CODE-counted).
177
+ */
178
+ scan?: {
179
+ window: number;
180
+ passes: number;
181
+ scanned: number;
182
+ matched: number;
183
+ } | undefined;
184
+ /**
185
+ * - (`mode:'partition'`) the data-driven width audit: corpus size, the budget knob, the count floor, the
186
+ * data-derived width `⌈size/budget⌉`, the chosen `width = max(floor, dataWidth)`, and matched count.
187
+ */
188
+ partition?: {
189
+ size: number;
190
+ workerBudget: number;
191
+ floor: number;
192
+ dataWidth: number;
193
+ width: number;
194
+ matched?: number;
195
+ } | undefined;
196
+ };
197
+ export type RecurseResult = {
198
+ /**
199
+ * - The synthesized answer (on convergence).
200
+ */
201
+ result?: any;
202
+ /**
203
+ * - The verifier's gap report (null when verification did not run).
204
+ */
205
+ verdict?: import("./evaluator").Verdict | null | undefined;
206
+ /**
207
+ * - true on guard exhaustion / a dead worker / an incomplete child (RC-9) —
208
+ * never a faked pass.
209
+ */
210
+ incomplete?: boolean | undefined;
211
+ /**
212
+ * - The best partial answer when `incomplete` (RC-9).
213
+ */
214
+ best?: any;
215
+ /**
216
+ * - When `incomplete` because a child failed: the sub-task(s) that came
217
+ * back incomplete (§9 scenario 1) — the anti-survivor-sum signal, not a quiet undercount.
218
+ */
219
+ missingSlices?: string[] | undefined;
220
+ /**
221
+ * - The audit node for this call (RC-10).
222
+ */
223
+ receipts: RecurseNode;
224
+ };
225
+ export type Provider = import("../types").Provider;
226
+ export type ToolDef = import("../types").ToolDef;
227
+ export type Verdict = import("./evaluator").Verdict;
228
+ export type Slice = {
229
+ id: string;
230
+ text: string;
231
+ };
232
+ /**
233
+ * @typedef {object} RecurseCtx
234
+ * The per-run runtime blob — the wiring, threaded down the whole recursion tree (and forwarded to the worker
235
+ * Loop's `policy`/governance via `options.ctx`). Distinct from `opts` (the policy knobs).
236
+ * @property {Provider} [provider] - The model the workers call. Required (here or on `opts.provider`).
237
+ * @property {Function} [policy] - bareguard `policy(tool, args, ctx)` — the gate. Sees `ctx.depth` so it can
238
+ * enforce `limits.maxDepth`/budget/calls. recurse adds NO second guard layer (§6).
239
+ * @property {Function} [onLlmResult] - Budget hook forwarded to every worker Loop AND the verifier — judge
240
+ * and worker tokens are all real spend (BA1: never invisible).
241
+ * @property {number} [depth] - The current recursion depth (0 at the top). Incremented on each self-call;
242
+ * threaded into `policy`. Callers normally omit it (defaults to 0).
243
+ * @property {object} [stream] - Optional event stream forwarded to each worker Loop (receipts substrate).
244
+ * @property {{recall: Function}} [litectx] - Optional litectx handle (RC-5, §10 step 7). Backs the `search`
245
+ * retrieval mode (`recall`). NOT used by `scan` — litectx has no exhaustive enumerate verb today; scan reads
246
+ * the generic array slice-source `opts.corpus` instead (the litectx-resident scan case waits on the litectx
247
+ * `enumerate` verb and drops in behind the same socket).
248
+ */
249
+ /**
250
+ * @typedef {object} RecurseOptions
251
+ * @property {Provider} [provider] - Fallback provider if `ctx.provider` is absent.
252
+ * @property {number} [maxDepth=3] - Open topology ceiling (§1): the depth past which the `spawn_child` tool is
253
+ * no longer offered (`maxDepth=1` ⇒ flat fan-out, no nesting). NOT the safety halt — that is bareguard's,
254
+ * and actual depth is always ≤ `limits.maxDepth`.
255
+ * @property {ToolDef[]} [tools] - Handle tools offered to EVERY worker (RC-5 pull-default: litectx
256
+ * `recall`/`get`, wired at build step 7). Workers query on demand; never the whole corpus.
257
+ * @property {string} [contract] - Definition of done (A3). When present, the verifier grades against THIS,
258
+ * not the loose task, and verification always runs.
259
+ * @property {(result: any, ctx: {contract: string|null, task: string}) => (Verdict|Promise<Verdict>)} [evaluate]
260
+ * Override the verifier (fills `recurse()`'s verify slot, §7.1). Default = an `Evaluator` rubric pass.
261
+ * @property {((args: {task: string, text: string|null, results: any[], children: object[], ctx: RecurseCtx}) => any) | 'concat' | 'merge'} [synthesize]
262
+ * Override synthesis/reduce (NB-3). A FUNCTION is a deterministic code-reduce over the child `results` — the
263
+ * §9.1 aggregation path (LLM arithmetic over partials carried ~10–15% error). A STRATEGY string runs the
264
+ * built-in reducer: `'concat'` (lossless no-LLM join) or `'merge'` (an isolated Loop-driven subjective
265
+ * merge); a string is ignored when no child ran. Default (unset) = the worker's own final text (Family A:
266
+ * the parent model already combined the children's results in its closing turn).
267
+ * @property {number} [count] - (Opt-in, NB-2 / Family B) FORCED fan-out: decompose into exactly this many
268
+ * independent parallel workers via `Planner`→`runPlan`, then reduce. A positive integer here is the count;
269
+ * it OVERRIDES the tier→count map. Setting it (or `mode:'fanout'`) takes the deterministic-parallelism path
270
+ * instead of the model-driven Family-A default. For known-parallel tasks where the caller wants guaranteed
271
+ * fan-out, not the model's adaptive choice.
272
+ * @property {'fanout'|'partition'} [mode] - (Opt-in, NB-2 / Family B) `'fanout'` = forced semantic fan-out
273
+ * WITHOUT a fixed count — derived from `assessComplexity`'s tier via the calibrated map (medium/complex/
274
+ * critical → 2/4/6; simple → 1); `opts.count` takes precedence. `'partition'` = the DATA-DRIVEN WIDTH path
275
+ * (§11): measure `opts.corpus` and partition it into `max(opts.count floor, ⌈size/workerBudget⌉)` parallel
276
+ * scan-workers (capped by the guards), CODE-reducing the per-chunk counts. Distinct from `'fanout'`: a data
277
+ * partition, not a `Planner` semantic split.
278
+ * @property {number} [workerBudget] - (`mode:'partition'`) items per worker; width = `⌈corpus.length /
279
+ * workerBudget⌉` (default 100). A calibratable knob (the §9.1 algorithm), not a discovered constant.
280
+ * @property {number} [concurrency] - (Family B) max workers run at once per wave (default 4). The wave
281
+ * structure is `runPlan`'s; bareguard still bounds the family rate independently.
282
+ * @property {'scan'|'search'|'exact'|'tools'} [retrieval] - (§10 step 7) the retrieval shape for a task OVER A
283
+ * CORPUS, routed by question shape (§9.2.1). `'scan'` (the default WHEN `opts.corpus` is present) = process
284
+ * every slice + LLM-judge + CODE-count — the only COMPLETE path (for "how many / all"). `'search'` = litectx
285
+ * `recall` handle tool offered to the worker (needle; CANNOT count; requires `ctx.litectx`). `'exact'` = a
286
+ * deterministic code-side AND-term filter tool over `opts.corpus`. `'tools'` = the PER-QUERY Family-A face:
287
+ * offer the worker `scan_count` (over `opts.corpus`) + `search_memory` (when `ctx.litectx`) + `exact_match`
288
+ * (array corpus) ALL AT ONCE, and let it pick the shape PER SUB-QUERY — the routing lives in the tool
289
+ * descriptions (scan says "use for how many / all / count"; search says "never count"), so a mixed task gets
290
+ * needle-search AND complete-count without per-sub-query adopter declaration. The completeness guard upgrades a
291
+ * `'search'` on a "how many / all" ask to `'scan'` (UPGRADE-only, never a silent downgrade); it does NOT fire
292
+ * for `'tools'` (the complete `scan_count` is always offered there, so a mixed task keeps its search tool).
293
+ * Absent `corpus` AND `retrieval`, behaviour is unchanged (Family A / single-shot) — fully backward-compatible.
294
+ * @property {Slice[] | (() => Promise<Slice[]>)} [corpus] - (§10 step 7) the generic slice-source scan/partition
295
+ * reads: an in-hand `{id, text}[]` array, OR an async `() => Promise<Slice[]>` (e.g. `litectxCorpus(litectx,
296
+ * {kind})` materializing a litectx-resident corpus via `enumerate`). recurse depends on this SHAPE, never on
297
+ * litectx. Malformed entries are dropped, never miscounted.
298
+ * @property {number} [window] - (scan) items per judge window. Default 8 (§9.2.1 recall knee — the one
299
+ * calibrated number; per-model).
300
+ * @property {number} [passes] - (scan) shuffled-boundary passes unioned for recall. Default 2 (~0.91 recall).
301
+ */
302
+ /**
303
+ * @typedef {object} RecurseNode
304
+ * One audit/receipts node (RC-10) — the recursion tree reconstructs from these alone: parent→child lineage
305
+ * (`spawned`), each subgoal (`task`), each gap report (`verdict`), cost per node (`tokens`).
306
+ * @property {string} task
307
+ * @property {number} depth
308
+ * @property {{level: string, score: number}} complexity
309
+ * @property {boolean} critical
310
+ * @property {RecurseNode[]} spawned - Child nodes (lineage).
311
+ * @property {Verdict|null} verdict
312
+ * @property {boolean} incomplete
313
+ * @property {boolean} halted
314
+ * @property {object|null} tokens - The worker Loop's `metrics.tokens`.
315
+ * @property {string|null} model
316
+ * @property {string|null} [retrieval] - (§10 step 7) the retrieval mode this node ran (`scan`/`search`/`exact`),
317
+ * or null/absent for a plain reasoning node.
318
+ * @property {string} [retrievalUpgraded] - set when the completeness guard upgraded the mode (e.g.
319
+ * `'search→scan (completeness)'`) — the audit trail for RC-9-applied-to-retrieval.
320
+ * @property {{window: number, passes: number, scanned: number, matched: number}} [scan] - (scan) the scan
321
+ * shape: window/passes used, slices scanned, ids matched (CODE-counted).
322
+ * @property {{size: number, workerBudget: number, floor: number, dataWidth: number, width: number, matched?: number}} [partition]
323
+ * - (`mode:'partition'`) the data-driven width audit: corpus size, the budget knob, the count floor, the
324
+ * data-derived width `⌈size/budget⌉`, the chosen `width = max(floor, dataWidth)`, and matched count.
325
+ */
326
+ /**
327
+ * @typedef {object} RecurseResult
328
+ * @property {any} [result] - The synthesized answer (on convergence).
329
+ * @property {Verdict|null} [verdict] - The verifier's gap report (null when verification did not run).
330
+ * @property {boolean} [incomplete] - true on guard exhaustion / a dead worker / an incomplete child (RC-9) —
331
+ * never a faked pass.
332
+ * @property {any} [best] - The best partial answer when `incomplete` (RC-9).
333
+ * @property {string[]} [missingSlices] - When `incomplete` because a child failed: the sub-task(s) that came
334
+ * back incomplete (§9 scenario 1) — the anti-survivor-sum signal, not a quiet undercount.
335
+ * @property {RecurseNode} receipts - The audit node for this call (RC-10).
336
+ */
337
+ /**
338
+ * Decompose a task into fresh-context workers, verify against a setpoint, and synthesize one result —
339
+ * assembled from existing primitives, not reimplemented (G1/G6).
340
+ *
341
+ * ⚠️ RESOURCE BOUNDS ARE bareguard's, not recurse()'s — OPEN BY DESIGN (§6, "no second guard layer"), and the
342
+ * one thing to know before running it. `recurse()` adds NO intrinsic total-work cap. The **Family-A default**
343
+ * (model-driven `spawn_child`) lets a node spawn UP TO each Loop's `HARD_ROUND_LIMIT` (100) children PER LEVEL,
344
+ * each recursing to `opts.maxDepth` (default 3) — so node count, and therefore TOKEN + $ SPEND, compounds
345
+ * multiplicatively and is **not capped by recurse itself**. (The forced paths — `mode:'fanout'`/`'partition'` —
346
+ * ARE bounded: a deterministic `count` + a `concurrency` cap. The uncapped path is the model-driven default.)
347
+ * This is real, not theoretical: a live POC (`poc/rlm-defer2-history-overflow.mjs`) showed a weak model
348
+ * over-decomposing into 40–117 calls on a single run. **So: running WITHOUT bareguard — or without ANY
349
+ * token/cost cap — CAN BURN TOKENS / $ unboundedly (up to ~100×depth nodes).** **WIRE bareguard**
350
+ * (`ctx.policy` via `wireGate`) for any non-trivial or untrusted run — it enforces depth/budget/call caps and
351
+ * the pre-wave fan-out checkpoint, turning a runaway into a clean `{incomplete}`. With no gate available, the
352
+ * only local brakes are `opts.maxDepth: 1` (flat — no nesting) and the provider/key's own usage limits.
353
+ *
354
+ * @param {string} task - The goal.
355
+ * @param {RecurseCtx} [ctx] - The runtime wiring (provider, policy, depth, …). Threaded down the tree.
356
+ * @param {RecurseOptions} [opts] - The policy knobs.
357
+ * @returns {Promise<RecurseResult>} `{ result, verdict, receipts }` on convergence; `{ incomplete, best,
358
+ * receipts }` on guard exhaustion. NEVER a fabricated success (RC-9).
359
+ * @throws {Error} no provider supplied (on neither `ctx.provider` nor `opts.provider`).
360
+ */
361
+ export function recurse(task: string, ctx?: RecurseCtx, opts?: RecurseOptions): Promise<RecurseResult>;