bare-agent 0.21.0 → 0.21.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.
package/README.md CHANGED
@@ -94,7 +94,7 @@ Every piece works alone — take what you need, ignore the rest. Two axes: **Act
94
94
 
95
95
  ### Recurse — break a hard task into a tree *(the RLM primitive)*
96
96
 
97
- `recurse(task, ctx, opts)` does **decompose → fan-out → verify → synthesize** in one call — Recursive Language Models as a single import, composed *around* the Loop (never a new engine). The default is **model-driven**: the worker is handed a `spawn_child` tool and decides whether to split, bounded by depth + bareguard (no second guard layer). Forced fan-out (`count` / `mode:'fanout'`) and data-driven width (`mode:'partition'`, measured from a corpus) are opt-in. The headline guarantee: **aggregation is code, never a model-stated number**, and a dead worker or exhausted guard returns an honest `{ incomplete, missingSlices }` — never a faked pass.
97
+ `recurse(task, ctx, opts)` does **decompose → fan-out → verify → synthesize** in one call — Recursive Language Models as a single import, composed *around* the Loop (never a new engine). The default is **model-driven**: the worker is handed a `spawn_child` tool and decides whether to split, bounded by depth + bareguard (no second guard layer). Forced fan-out (`count` / `mode:'fanout'`) and data-driven width (`mode:'partition'`, measured from a corpus) are opt-in. Give workers a stance with `opts.persona` (prepended to every worker, carries down the tree, deliberately kept out of the isolated verifier). The headline guarantee: **aggregation is code, never a model-stated number**, and a dead worker or exhausted guard returns an honest `{ incomplete, missingSlices }` — never a faked pass.
98
98
 
99
99
  Over a corpus, context reaches a worker as a **handle routed by question shape** (`opts.retrieval`):
100
100
 
@@ -1,7 +1,7 @@
1
1
  # bareagent — Integration Guide
2
2
 
3
3
  > For AI assistants and developers wiring bareagent into a project.
4
- > v0.21.0 | Node.js >= 18 | zero required deps (`bareguard ^0.9.0` optional peer for governance) | Apache 2.0
4
+ > v0.21.1 | Node.js >= 18 | zero required deps (`bareguard ^0.9.0` optional peer for governance) | Apache 2.0
5
5
  >
6
6
  > Full human guide with composition examples, design philosophy, and recipes: [Usage Guide](docs/02-features/usage-guide.md)
7
7
 
@@ -658,7 +658,7 @@ console.log(result.count, result.matchedIds); // a code-derived count + the
658
658
 
659
659
  **Synthesis (`opts.synthesize`):** a **function** (deterministic code-reduce over child `results` — use for arithmetic/aggregation; LLM arithmetic over partials carried ~10–15% error), or `'concat'` (lossless no-LLM join), or `'merge'` (isolated Loop-driven subjective merge). Default = the parent model's own closing-turn synthesis. **`opts.contract`** = a definition-of-done the verifier grades against (instead of the loose task); **`opts.evaluate`** overrides the verifier. Exported helpers for the per-query face: `buildScanTool`, `buildSearchTool`, `buildExactTool`, `litectxCorpus`.
660
660
 
661
- **Worker persona (`opts.persona`, v0.21.0):** a string PREPENDED to every Family-A worker's system prompt — give workers a stance (`persona: 'You are a senior security engineer; be specific and cite the exact file:line'`). It **augments**, never replaces, the built-in decomposition policy + depth-scrub (those drive the spawn mechanics), and **carries down the whole tree** (a child of a "senior security engineer" is still one). It is deliberately **not** applied to the isolated verifier (that isolation is what defeats self-grading sycophancy) nor the deterministic scan judge. Absent ⇒ the worker prompt is unchanged from pre-0.21.
661
+ **Worker persona (`opts.persona`, v0.21.0):** a string PREPENDED to every Family-A worker's system prompt — give workers a stance (`persona: 'You are a senior security engineer; be specific and cite the exact file:line'`). It **augments**, never replaces, the built-in decomposition policy + depth-scrub (those drive the spawn mechanics), and **carries down the whole tree** (a child of a "senior security engineer" is still one). It is deliberately **not** applied to the isolated verifier (that isolation is what defeats self-grading sycophancy) nor the deterministic scan judge. Absent ⇒ the worker prompt is unchanged from pre-0.21. **Security:** treat `persona` like a system prompt — it is prepended ahead of the decomposition policy and can override it for every worker, so pass caller-trusted text only, never untrusted/end-user input.
662
662
 
663
663
  ```javascript
664
664
  const out = await recurse('Audit auth.js, billing.js, gateway.js for authz bugs', ctx, {
@@ -668,6 +668,41 @@ const out = await recurse('Audit auth.js, billing.js, gateway.js for authz bugs'
668
668
 
669
669
  **What a delegated child inherits (important — the setpoint is the TOP node's job):** when a worker delegates with `spawn_child`, the child runs a **fresh `recurse`** that inherits `tools`, `synthesize`, `maxDepth`, and `persona` — but the parent's **`contract`/`evaluate` are stripped** (and the forced `count`/`mode` + the corpus `retrieval` knobs). A slice is not graded against the *whole*-task definition-of-done (that verdict is the top node's, and a slice satisfying the whole DoD is the wrong question); only the top `recurse` verifies the synthesized result. The non-overridable `critical → force-verify` safety floor still fires per node (it keys on the task text, not the contract). So: set `contract`/`evaluate` once at the top; they do not — and should not — re-run per intermediate node.
670
670
 
671
+ ## Wiring with Evaluator + refine (output-side verification)
672
+
673
+ `Evaluator` is the output-side judge (the mirror of `Planner`): it grades a result against a goal and returns a tri-state `Verdict`. `refine` is the bounded generate → evaluate → regenerate loop. Both compose *around* a Loop — neither lives inside `loop.js`.
674
+
675
+ ```javascript
676
+ const { Evaluator, refine } = require('bare-agent');
677
+
678
+ const evaluator = new Evaluator({ provider }); // provider REQUIRED for rubric/agentic; predicate needs none
679
+
680
+ // Three criteria types — pass EXACTLY ONE:
681
+ const v1 = await evaluator.evaluate(goal, result, { predicate: (r) => r.includes('DONE') }); // deterministic, 0 tokens
682
+ const v2 = await evaluator.evaluate(goal, result, { rubric: 'Cites a source for every claim.' }); // isolated adversarial LLM grader
683
+ const v3 = await evaluator.evaluate(goal, url, { agentic: 'Open the page, click Submit, check the console for errors.' }); // tool-running critic that EXERCISES the artifact
684
+
685
+ // Verdict: { status: 'satisfied' | 'needs_revision' | 'failed', pass, score, critique, suggestions }
686
+ // pass = (status === 'satisfied'); needs_revision is retryable; failed is terminal (stop spending).
687
+ if (!v2.pass) console.log(v2.critique, v2.suggestions);
688
+ ```
689
+
690
+ Key invariants:
691
+ - The **rubric path runs an isolated adversarial grader** — a separate context window with a harsh, independent prompt, never the generator's transcript. That isolation (not a feedback knob) is what defeats the self-evaluation trap; the grader treats the RESULT as untrusted DATA (judge prompt-injection defence).
692
+ - **`agentic`** (the third type) spins up a fresh Loop with scoped tools (set on the Evaluator, or per-call `opts.tools`) that **exercises** the live artifact — clicks, reads console/network — rather than reading text. Each critic round forwards to `onLlmResult`; a governance `HaltError` re-throws clean.
693
+ - **`contract`** (a definition of done) is graded against instead of the loose goal: `evaluate(goal, result, { rubric, contract })`. Judge tokens forward to the gate via `onLlmResult` (`kind:'evaluate'`) so verification spend is visible to the budget.
694
+
695
+ **`refine`** drives a caller-supplied `attempt`/`evaluate` until a satisfied verdict, a terminal `failed`, or `maxIterations` (the real bound is bareguard maxTurns/budget). It threads the latest `critique` into the next attempt (fresh-feedback, not anchoring on a failed answer) and a shared `contract` to both sides.
696
+
697
+ ```javascript
698
+ const { result, verdict, iterations, history } = await refine({
699
+ attempt: ({ critique, contract }) => generate(prompt, { critique, contract }), // critique = null on the first pass
700
+ evaluate: (result, { contract }) => evaluator.evaluate(goal, result, { rubric, contract }),
701
+ contract: 'No TODOs; every public fn has a JSDoc; tests pass.',
702
+ maxIterations: 3, // hard cap; the REAL bound is the gate
703
+ });
704
+ ```
705
+
671
706
  ## Provider options
672
707
 
673
708
  ```javascript
@@ -677,6 +712,9 @@ new OpenAI({ apiKey, model: 'gpt-4o-mini', baseUrl: 'https://api.openai.com/v1'
677
712
  // Anthropic
678
713
  new Anthropic({ apiKey, model: 'claude-haiku-4-5-20251001' })
679
714
 
715
+ // Gemini (native generateContent — needed for prompt-cache token tiers; the OpenAI-compat endpoint drops them)
716
+ new Gemini({ apiKey, model: 'gemini-2.5-flash', baseUrl: 'https://generativelanguage.googleapis.com/v1beta' })
717
+
680
718
  // Ollama (local, no key needed)
681
719
  new Ollama({ model: 'llama3.2', url: 'http://localhost:11434' })
682
720
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "bare-agent",
3
- "version": "0.21.0",
3
+ "version": "0.21.1",
4
4
  "files": [
5
5
  "index.js",
6
6
  "index.d.ts",
package/src/recurse.d.ts CHANGED
@@ -54,6 +54,9 @@ export type RecurseOptions = {
54
54
  * (preserved by `forChild` — a durable worker stance, unlike the top-only `contract`/`evaluate`). Deliberately
55
55
  * NOT applied to the isolated verifier (would defeat the anti-sycophancy isolation, A1) nor the deterministic
56
56
  * scan judge. Absent/blank ⇒ the worker prompt is byte-identical to pre-0.21 (backward-compatible).
57
+ * **SECURITY:** this is a PRIVILEGED system-prompt seam — treat `persona` like a system prompt. Do NOT pass
58
+ * untrusted / end-user-controlled text here; a hostile persona is prepended ahead of the decomposition policy
59
+ * and can override it (and any safety framing) for every worker in the tree. Caller-trusted input only.
57
60
  */
58
61
  persona?: string | undefined;
59
62
  /**
@@ -267,6 +270,9 @@ export type Slice = {
267
270
  * (preserved by `forChild` — a durable worker stance, unlike the top-only `contract`/`evaluate`). Deliberately
268
271
  * NOT applied to the isolated verifier (would defeat the anti-sycophancy isolation, A1) nor the deterministic
269
272
  * scan judge. Absent/blank ⇒ the worker prompt is byte-identical to pre-0.21 (backward-compatible).
273
+ * **SECURITY:** this is a PRIVILEGED system-prompt seam — treat `persona` like a system prompt. Do NOT pass
274
+ * untrusted / end-user-controlled text here; a hostile persona is prepended ahead of the decomposition policy
275
+ * and can override it (and any safety framing) for every worker in the tree. Caller-trusted input only.
270
276
  * @property {ToolDef[]} [tools] - Handle tools offered to EVERY worker (RC-5 pull-default: litectx
271
277
  * `recall`/`get`, wired at build step 7). Workers query on demand; never the whole corpus.
272
278
  * @property {string} [contract] - Definition of done (A3). When present, the verifier grades against THIS,
package/src/recurse.js CHANGED
@@ -78,7 +78,8 @@ function partitionInto(arr, n) {
78
78
  * @returns {string}
79
79
  */
80
80
  function workerPersonaPrefix(persona) {
81
- return typeof persona === 'string' && persona.trim() ? persona.trim() + '\n\n' : '';
81
+ const p = typeof persona === 'string' ? persona.trim() : '';
82
+ return p ? p + '\n\n' : '';
82
83
  }
83
84
 
84
85
  /**
@@ -140,6 +141,9 @@ function forChild(opts) {
140
141
  * (preserved by `forChild` — a durable worker stance, unlike the top-only `contract`/`evaluate`). Deliberately
141
142
  * NOT applied to the isolated verifier (would defeat the anti-sycophancy isolation, A1) nor the deterministic
142
143
  * scan judge. Absent/blank ⇒ the worker prompt is byte-identical to pre-0.21 (backward-compatible).
144
+ * **SECURITY:** this is a PRIVILEGED system-prompt seam — treat `persona` like a system prompt. Do NOT pass
145
+ * untrusted / end-user-controlled text here; a hostile persona is prepended ahead of the decomposition policy
146
+ * and can override it (and any safety framing) for every worker in the tree. Caller-trusted input only.
143
147
  * @property {ToolDef[]} [tools] - Handle tools offered to EVERY worker (RC-5 pull-default: litectx
144
148
  * `recall`/`get`, wired at build step 7). Workers query on demand; never the whole corpus.
145
149
  * @property {string} [contract] - Definition of done (A3). When present, the verifier grades against THIS,