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 +1 -1
- package/bareagent.context.md +40 -2
- package/package.json +1 -1
- package/src/recurse.d.ts +6 -0
- package/src/recurse.js +5 -1
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
|
|
package/bareagent.context.md
CHANGED
|
@@ -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.
|
|
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
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
|
-
|
|
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,
|