bare-agent 0.29.0 → 0.30.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.
- package/README.md +1 -1
- package/bareagent.context.md +7 -3
- package/package.json +1 -1
- package/src/loop.js +5 -0
- package/src/recurse.d.ts +49 -7
- package/src/recurse.js +65 -11
- package/src/refine.d.ts +23 -7
- package/src/refine.js +11 -5
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. Give workers a stance with `opts.persona` (prepended to every worker, carries down the tree, deliberately kept out of the isolated verifier), and tell them *where they are* with `opts.context` (a read-only paths/cwd blob threaded to every worker so a sliced child can locate its artifact — facts, not a stance). For a leaf that should self-correct, pass `opts.refineLeaf` (opt-in): a definite leaf becomes a bounded generate→sense→regenerate loop driven by *your* deterministic sensor (test/compile/lint), feeding the gap back (with escalating temperature on models that accept it; on a temperature-fixed model like `claude-sonnet-5` the gap critique carries recovery, and the receipt records the effective temps). 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), and tell them *where they are* with `opts.context` (a read-only paths/cwd blob threaded to every worker so a sliced child can locate its artifact — facts, not a stance). For a leaf that should self-correct, pass `opts.refineLeaf` (opt-in): a definite leaf becomes a bounded generate→sense→regenerate loop driven by *your* deterministic sensor (test/compile/lint — one that judges the *returned* result, never a worker side-effect it could game), feeding the gap back (with escalating temperature on models that accept it; on a temperature-fixed model like `claude-sonnet-5` the gap critique carries recovery, and the receipt records the effective temps). On a temperature-fixed model, `refineLeaf.rejectedBuffer` adds a second lever — it feeds the model's own prior *failed attempts* back verbatim ("write something structurally different"), the directed-diversity complement to temperature's random diversity (adaptive by default; the two are antagonistic, so it holds temperature flat when it engages). 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.
|
|
4
|
+
> v0.30.0 | Node.js >= 18 | zero required deps (`bareguard >=0.9.0 <0.13.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
|
|
|
@@ -38,7 +38,7 @@ Eight entry points:
|
|
|
38
38
|
| Count / answer "how many / all" over a corpus, honestly | recurse(task, ctx, `{ corpus, retrieval: 'scan' }`) — scans every slice, CODE-counts |
|
|
39
39
|
| Give recurse workers a persona/role (senior-dev stance) | recurse(task, ctx, `{ persona }`) — prepended to every worker, carries down the tree; not applied to the verifier |
|
|
40
40
|
| Tell recurse workers WHERE they are (paths/cwd) so a slice can find its file | recurse(task, ctx, `{ context }`) — read-only blob on every worker's task message + the Planner + verifier; carries down (facts, not a stance) |
|
|
41
|
-
| Let a recurse LEAF retry its own failure with a deterministic check | recurse(task, ctx, `{ refineLeaf: { sensor } }`) — leaf becomes a bounded generate→sense→regenerate loop; your sensor (test/compile/lint), gap fed back, escalating temperature |
|
|
41
|
+
| Let a recurse LEAF retry its own failure with a deterministic check | recurse(task, ctx, `{ refineLeaf: { sensor } }`) — leaf becomes a bounded generate→sense→regenerate loop; your sensor (test/compile/lint) judges the RETURNED result, gap fed back, escalating temperature; `rejectedBuffer` feeds prior failed attempts back on a temp-fixed model |
|
|
42
42
|
| Track task state (pending/running/done/failed) | StateMachine |
|
|
43
43
|
| Run agent turns on a schedule (cron, timers) | Scheduler |
|
|
44
44
|
| Require human approval before dangerous actions | Checkpoint |
|
|
@@ -708,7 +708,11 @@ const out = await recurse('Audit auth.js, billing.js, gateway.js for authz bugs'
|
|
|
708
708
|
|
|
709
709
|
**Worker context (`opts.context`, v0.23.0):** a read-only working-context string (paths/cwd) PREPENDED to every worker's TASK message as a `Working context:` block — so a sliced child can **locate its artifact** (the Planner paraphrases the goal into subtasks and drops absolute paths; without this, workers guess `.`/`~`/`/tmp` and get denied). Forwarded to the Planner as `info` (path-aware slices) and shown to the verifier too (neutral FACTS, not a stance — distinct from `persona`, which is a privileged SYSTEM-prompt stance). Carries down the tree. **Security:** it still becomes part of the prompt, so pass caller-trusted run-state only, never untrusted/end-user text (lower-privilege than `persona` — user message, not system — but still an injection surface). Absent ⇒ the task message is unchanged.
|
|
710
710
|
|
|
711
|
-
**Leaf self-correction (`opts.refineLeaf`, v0.23.0, opt-in):** turn a **definite leaf** (a node offered no `spawn_child` — `simple` tier or at `maxDepth`) into a bounded generate→sense→regenerate loop instead of a single pass: `{ sensor, maxIterations?, temperatures? }`. `sensor(result, { task, context, contract }) → Verdict` is YOUR **deterministic** close (test/compile/lint — not a model judge); on a non-pass its `critique` (the gap, not the transcript) is fed FRESH into the next attempt and — **on models that accept `temperature`** — the **retry temperature ESCALATES** (default `[0.2, 0.7, 1.0]` — load-bearing there: a weak model at a flat temperature regenerates identical wrong code and ignores even crisp feedback). On a **temperature-fixed model** (e.g. `claude-sonnet-5`, which 400s any non-default temperature) the provider silently drops the param (see below), the escalation lever is inert, and the fed-back gap critique carries recovery alone; `receipts.refineLeaf.temperatures` then records the EFFECTIVE temps — a `null` marks an attempt that ran at the model's default (never the ignored requested value). Each attempt is gate-checked + metered; a HaltError mid-loop → clean `{ incomplete }`; honest non-recovery → `receipts.refineLeaf.passed === false` (never a faked pass); `receipts.tokens` sums all attempts. The error-keyed `recall` stays YOUR tool (`opts.tools`), keyed off the fed-back critique — bareagent stays litectx-agnostic. Carries down (engages at the leaves). Absent ⇒ a leaf is a single pass.
|
|
711
|
+
**Leaf self-correction (`opts.refineLeaf`, v0.23.0, opt-in):** turn a **definite leaf** (a node offered no `spawn_child` — `simple` tier or at `maxDepth`) into a bounded generate→sense→regenerate loop instead of a single pass: `{ sensor, maxIterations?, temperatures?, rejectedBuffer? }`. `sensor(result, { task, context, contract }) → Verdict` is YOUR **deterministic** close (test/compile/lint — not a model judge); on a non-pass its `critique` (the gap, not the transcript) is fed FRESH into the next attempt and — **on models that accept `temperature`** — the **retry temperature ESCALATES** (default `[0.2, 0.7, 1.0]` — load-bearing there: a weak model at a flat temperature regenerates identical wrong code and ignores even crisp feedback). On a **temperature-fixed model** (e.g. `claude-sonnet-5`, which 400s any non-default temperature) the provider silently drops the param (see below), the escalation lever is inert, and the fed-back gap critique carries recovery alone; `receipts.refineLeaf.temperatures` then records the EFFECTIVE temps — a `null` marks an attempt that ran at the model's default (never the ignored requested value). Each attempt is gate-checked + metered; a HaltError mid-loop → clean `{ incomplete }`; honest non-recovery → `receipts.refineLeaf.passed === false` (never a faked pass); `receipts.tokens` sums all attempts. The error-keyed `recall` stays YOUR tool (`opts.tools`), keyed off the fed-back critique — bareagent stays litectx-agnostic. Carries down (engages at the leaves). Absent ⇒ a leaf is a single pass.
|
|
712
|
+
|
|
713
|
+
> **Sensor integrity (`refineLeaf.sensor`):** the sensor must judge the **returned result** (tamper-proof — build/run the returned string in isolation), never a worker **side-effect** a worker with edit tools could game (writing a passing file then returning junk, or editing the failing test itself). The loop optimizes against whatever the sensor reads — keep the close outside what the worker can write. (RSI field lesson: reward-hacking appeared in every optimization loop with a gameable close.)
|
|
714
|
+
|
|
715
|
+
> **`rejectedBuffer` (BA-14, v0.30.0):** a second lever for a **temperature-fixed** model, where escalation is inert. Instead of only the latest critique, it surfaces the model's OWN prior failed attempts VERBATIM — *"you wrote these, they failed X — write something STRUCTURALLY DIFFERENT."* This is **directed** diversity (attack the specific repeated mistake); escalation is **random** diversity, and the two are **antagonistic** — temperature monotonically degrades the buffer (`poc/ba14b`: flat-0.2 100% → 0.7 70% → 1.0 50%), so when the buffer engages the retry temperature is **held flat** at `temperatures[0]`, never escalated. Trigger: `true` = force on (also on temperature-accepting models); `false` = force off (pure BA-8 escalation); **unset = adaptive** — engage only once a prior attempt's temperature was dropped (i.e. a temp-fixed model where escalation is inert and the buffer is the sole lever). On a temperature-accepting model the default leaves behavior byte-identical. `receipts.refineLeaf.rejectedBuffer` reports whether it engaged. Efficacy is a **weak-model / fixation** phenomenon (live on `claude-sonnet-5` it engaged 6/6 but recovered no better than critique-only — cost-neutral, hence adaptive-not-always-on).
|
|
712
716
|
|
|
713
717
|
```javascript
|
|
714
718
|
const out = await recurse('Fix the failing function in calc.js', ctx, {
|
package/package.json
CHANGED
package/src/loop.js
CHANGED
|
@@ -701,6 +701,11 @@ class Loop {
|
|
|
701
701
|
const generate = () => this.provider.generate(toSend, activeTools, options);
|
|
702
702
|
result = this.retry ? await this.retry.call(generate) : await generate();
|
|
703
703
|
} catch (err) {
|
|
704
|
+
// A HaltError is a governance exit, not a provider failure — re-throw it (like every other seam) so it
|
|
705
|
+
// reaches the outer handler, which seals dangling tool_calls and returns error:`halt:<rule>`. Without
|
|
706
|
+
// this, throwOnError:false laundered a provider-surfaced HaltError into a generic `error:<message>`,
|
|
707
|
+
// indistinguishable from a real fault (the retry never retries it — DEFAULT_RETRY_ON is false for it).
|
|
708
|
+
if (err instanceof HaltError) throw err;
|
|
704
709
|
this._reportError('provider', err, { round });
|
|
705
710
|
if (this.throwOnError) throw err;
|
|
706
711
|
// BA-5: a mid-run provider failure must not erase the work of the rounds that succeeded.
|
package/src/recurse.d.ts
CHANGED
|
@@ -94,13 +94,29 @@ export type RecurseOptions = {
|
|
|
94
94
|
* (`temperatures`, default `[0.2,0.7,1.0]`) — the live-validated lever that lets a weak model escape a
|
|
95
95
|
* repeat-the-same-mistake rut (`poc/ba8-leaf-refine.mjs`: 0/5 → 2-3/5; flat temp recovers 0/5). On a
|
|
96
96
|
* temperature-fixed model (BA-10) the provider drops the param, `receipts.refineLeaf.temperatures` records
|
|
97
|
-
* `null`, and the fed-back gap critique carries recovery alone
|
|
97
|
+
* `null`, and the fed-back gap critique carries recovery alone — UNLESS the rejected-attempt buffer engages
|
|
98
|
+
* (below). `maxIterations` defaults to
|
|
98
99
|
* `temperatures.length`; the REAL bound is bareguard (each attempt is gate-checked + metered). CARRIES DOWN the
|
|
99
100
|
* tree (preserved by `forChild`), so it engages at the leaves of a Family-A decomposition. Recovery is PARTIAL
|
|
100
101
|
* (a stubborn blind spot may persist) — `receipts.refineLeaf.passed` reports honestly. Does NOT apply to a node
|
|
101
102
|
* that delegates (its children + the tree verify own quality), nor to the scan/fanout/partition dispatch paths.
|
|
102
103
|
* Absent ⇒ a leaf is a single pass (byte-identical to pre-BA-8). An error-keyed `recall` is the CALLER's tool
|
|
103
104
|
* (`opts.tools`) keyed off the fed-back critique — bareagent stays litectx-agnostic.
|
|
105
|
+
* **Sensor integrity (RSI-learnings #1/#5, "audit the close"):** the `sensor` MUST judge the RETURNED result
|
|
106
|
+
* (tamper-proof — e.g. build/run the returned string in an isolated context, as `poc/ba8-leaf-refine.mjs` does),
|
|
107
|
+
* NEVER a worker side-effect a worker with edit tools could GAME (writing a passing file then returning junk, or
|
|
108
|
+
* editing the failing test itself). A gameable close is the reward-hacking surface every RSI system in the field
|
|
109
|
+
* got bitten by; the loop optimizes against WHATEVER the sensor reads, so keep it outside what the worker can write.
|
|
110
|
+
* **`rejectedBuffer` (BA-14):** a SkillOpt-shaped rejected-attempt buffer — instead of only the LATEST critique,
|
|
111
|
+
* surface the model's OWN prior failed attempts verbatim ("you wrote these, they failed X — write something
|
|
112
|
+
* STRUCTURALLY DIFFERENT"). This is DIRECTED diversity (attack the specific repeated mistake), where escalation
|
|
113
|
+
* is RANDOM diversity; the two are ANTAGONISTIC (`poc/ba14b-temp-with-buffer.mjs`: temperature monotonically
|
|
114
|
+
* degrades the buffer, 100%→70%→50% across 0.2/0.7/1.0), so when the buffer engages the retry temperature is
|
|
115
|
+
* HELD at `temperatures[0]` (flat-low), never escalated. `true` = force on (also on temperature-accepting
|
|
116
|
+
* models); `false` = force off (pure BA-8 escalation); UNSET = ADAPTIVE — engage only once a prior attempt's
|
|
117
|
+
* temperature was dropped (a temperature-fixed model, BA-10, where escalation is inert and the buffer is the
|
|
118
|
+
* sole lever — `poc/ba14-rejected-buffer.mjs`: flat-temp 50%→100%). `receipts.refineLeaf.rejectedBuffer`
|
|
119
|
+
* reports whether any iteration injected it. Bounded by `maxIterations` (the buffer never outgrows it).
|
|
104
120
|
*/
|
|
105
121
|
refineLeaf?: {
|
|
106
122
|
sensor: (result: any, ctx: {
|
|
@@ -110,6 +126,7 @@ export type RecurseOptions = {
|
|
|
110
126
|
}) => (Verdict | Promise<Verdict>);
|
|
111
127
|
maxIterations?: number;
|
|
112
128
|
temperatures?: number[];
|
|
129
|
+
rejectedBuffer?: boolean;
|
|
113
130
|
} | undefined;
|
|
114
131
|
/**
|
|
115
132
|
* - Definition of done (A3). When present, the verifier grades against THIS,
|
|
@@ -225,15 +242,20 @@ export type RecurseNode = {
|
|
|
225
242
|
*/
|
|
226
243
|
tokens: object | null;
|
|
227
244
|
/**
|
|
228
|
-
* - (BA-8) when
|
|
229
|
-
* leaf ran as a bounded refine loop: how many attempts it took and whether the deterministic sensor finally
|
|
245
|
+
* - (BA-8) when
|
|
246
|
+
* this leaf ran as a bounded refine loop: how many attempts it took and whether the deterministic sensor finally
|
|
230
247
|
* passed (false = honest non-recovery, not a faked success). `temperatures` are the EFFECTIVE per-attempt temps
|
|
231
248
|
* (BA-10): a `null` marks an attempt the model ran at its DEFAULT because it rejected the requested temperature.
|
|
249
|
+
* `rejectedBuffer` (BA-14): whether any iteration injected the rejected-attempt buffer (prior failed attempts
|
|
250
|
+
* surfaced verbatim); when it engages the REQUESTED retry temperature is held flat at `temperatures[0]`, never
|
|
251
|
+
* escalated (ba14b antagonism) — but a temperature-fixed model still drops it, so the EFFECTIVE temp recorded
|
|
252
|
+
* above is `null`, not `temperatures[0]`.
|
|
232
253
|
*/
|
|
233
254
|
refineLeaf?: {
|
|
234
255
|
iterations: number;
|
|
235
256
|
passed: boolean;
|
|
236
257
|
temperatures: (number | null)[];
|
|
258
|
+
rejectedBuffer: boolean;
|
|
237
259
|
} | undefined;
|
|
238
260
|
model: string | null;
|
|
239
261
|
/**
|
|
@@ -362,7 +384,7 @@ export type Slice = {
|
|
|
362
384
|
* TRUSTED run-state (paths/cwd); do NOT pass untrusted / end-user-controlled text here.
|
|
363
385
|
* @property {ToolDef[]} [tools] - Handle tools offered to EVERY worker (RC-5 pull-default: litectx
|
|
364
386
|
* `recall`/`get`, wired at build step 7). Workers query on demand; never the whole corpus.
|
|
365
|
-
* @property {{sensor: (result: any, ctx: {task: string, context: string|undefined, contract: string|null}) => (Verdict|Promise<Verdict>), maxIterations?: number, temperatures?: number[]}} [refineLeaf]
|
|
387
|
+
* @property {{sensor: (result: any, ctx: {task: string, context: string|undefined, contract: string|null}) => (Verdict|Promise<Verdict>), maxIterations?: number, temperatures?: number[], rejectedBuffer?: boolean}} [refineLeaf]
|
|
366
388
|
* (Opt-in, BA-8 / relayfact F17) Turn a DEFINITE LEAF (a node that is offered no `spawn_child` — `simple`
|
|
367
389
|
* tier or at `maxDepth`) into a bounded generate→sense→regenerate loop instead of a single pass, so a failed
|
|
368
390
|
* slice can self-correct. `sensor` is a DETERMINISTIC close (test/compile/lint — NOT a model judge, R-S8) that
|
|
@@ -371,13 +393,29 @@ export type Slice = {
|
|
|
371
393
|
* (`temperatures`, default `[0.2,0.7,1.0]`) — the live-validated lever that lets a weak model escape a
|
|
372
394
|
* repeat-the-same-mistake rut (`poc/ba8-leaf-refine.mjs`: 0/5 → 2-3/5; flat temp recovers 0/5). On a
|
|
373
395
|
* temperature-fixed model (BA-10) the provider drops the param, `receipts.refineLeaf.temperatures` records
|
|
374
|
-
* `null`, and the fed-back gap critique carries recovery alone
|
|
396
|
+
* `null`, and the fed-back gap critique carries recovery alone — UNLESS the rejected-attempt buffer engages
|
|
397
|
+
* (below). `maxIterations` defaults to
|
|
375
398
|
* `temperatures.length`; the REAL bound is bareguard (each attempt is gate-checked + metered). CARRIES DOWN the
|
|
376
399
|
* tree (preserved by `forChild`), so it engages at the leaves of a Family-A decomposition. Recovery is PARTIAL
|
|
377
400
|
* (a stubborn blind spot may persist) — `receipts.refineLeaf.passed` reports honestly. Does NOT apply to a node
|
|
378
401
|
* that delegates (its children + the tree verify own quality), nor to the scan/fanout/partition dispatch paths.
|
|
379
402
|
* Absent ⇒ a leaf is a single pass (byte-identical to pre-BA-8). An error-keyed `recall` is the CALLER's tool
|
|
380
403
|
* (`opts.tools`) keyed off the fed-back critique — bareagent stays litectx-agnostic.
|
|
404
|
+
* **Sensor integrity (RSI-learnings #1/#5, "audit the close"):** the `sensor` MUST judge the RETURNED result
|
|
405
|
+
* (tamper-proof — e.g. build/run the returned string in an isolated context, as `poc/ba8-leaf-refine.mjs` does),
|
|
406
|
+
* NEVER a worker side-effect a worker with edit tools could GAME (writing a passing file then returning junk, or
|
|
407
|
+
* editing the failing test itself). A gameable close is the reward-hacking surface every RSI system in the field
|
|
408
|
+
* got bitten by; the loop optimizes against WHATEVER the sensor reads, so keep it outside what the worker can write.
|
|
409
|
+
* **`rejectedBuffer` (BA-14):** a SkillOpt-shaped rejected-attempt buffer — instead of only the LATEST critique,
|
|
410
|
+
* surface the model's OWN prior failed attempts verbatim ("you wrote these, they failed X — write something
|
|
411
|
+
* STRUCTURALLY DIFFERENT"). This is DIRECTED diversity (attack the specific repeated mistake), where escalation
|
|
412
|
+
* is RANDOM diversity; the two are ANTAGONISTIC (`poc/ba14b-temp-with-buffer.mjs`: temperature monotonically
|
|
413
|
+
* degrades the buffer, 100%→70%→50% across 0.2/0.7/1.0), so when the buffer engages the retry temperature is
|
|
414
|
+
* HELD at `temperatures[0]` (flat-low), never escalated. `true` = force on (also on temperature-accepting
|
|
415
|
+
* models); `false` = force off (pure BA-8 escalation); UNSET = ADAPTIVE — engage only once a prior attempt's
|
|
416
|
+
* temperature was dropped (a temperature-fixed model, BA-10, where escalation is inert and the buffer is the
|
|
417
|
+
* sole lever — `poc/ba14-rejected-buffer.mjs`: flat-temp 50%→100%). `receipts.refineLeaf.rejectedBuffer`
|
|
418
|
+
* reports whether any iteration injected it. Bounded by `maxIterations` (the buffer never outgrows it).
|
|
381
419
|
* @property {string} [contract] - Definition of done (A3). When present, the verifier grades against THIS,
|
|
382
420
|
* not the loose task, and verification always runs.
|
|
383
421
|
* @property {(result: any, ctx: {contract: string|null, task: string}) => (Verdict|Promise<Verdict>)} [evaluate]
|
|
@@ -438,10 +476,14 @@ export type Slice = {
|
|
|
438
476
|
* @property {string} [blocker] - (BA-11) set to `'governance-deny'` when this node stopped because its Loop
|
|
439
477
|
* short-circuited a consecutive-policy-deny spin (not a model failure). Mirrors `RecurseResult.blocker`.
|
|
440
478
|
* @property {object|null} tokens - The worker Loop's `metrics.tokens`.
|
|
441
|
-
* @property {{iterations: number, passed: boolean, temperatures: (number|null)[]}} [refineLeaf] - (BA-8) when
|
|
442
|
-
* leaf ran as a bounded refine loop: how many attempts it took and whether the deterministic sensor finally
|
|
479
|
+
* @property {{iterations: number, passed: boolean, temperatures: (number|null)[], rejectedBuffer: boolean}} [refineLeaf] - (BA-8) when
|
|
480
|
+
* this leaf ran as a bounded refine loop: how many attempts it took and whether the deterministic sensor finally
|
|
443
481
|
* passed (false = honest non-recovery, not a faked success). `temperatures` are the EFFECTIVE per-attempt temps
|
|
444
482
|
* (BA-10): a `null` marks an attempt the model ran at its DEFAULT because it rejected the requested temperature.
|
|
483
|
+
* `rejectedBuffer` (BA-14): whether any iteration injected the rejected-attempt buffer (prior failed attempts
|
|
484
|
+
* surfaced verbatim); when it engages the REQUESTED retry temperature is held flat at `temperatures[0]`, never
|
|
485
|
+
* escalated (ba14b antagonism) — but a temperature-fixed model still drops it, so the EFFECTIVE temp recorded
|
|
486
|
+
* above is `null`, not `temperatures[0]`.
|
|
445
487
|
* @property {string|null} model
|
|
446
488
|
* @property {string|null} [retrieval] - (§10 step 7) the retrieval mode this node ran (`scan`/`search`/`exact`),
|
|
447
489
|
* or null/absent for a plain reasoning node.
|
package/src/recurse.js
CHANGED
|
@@ -216,7 +216,7 @@ function auditSafeCtx(ctx, overrides = {}) {
|
|
|
216
216
|
* TRUSTED run-state (paths/cwd); do NOT pass untrusted / end-user-controlled text here.
|
|
217
217
|
* @property {ToolDef[]} [tools] - Handle tools offered to EVERY worker (RC-5 pull-default: litectx
|
|
218
218
|
* `recall`/`get`, wired at build step 7). Workers query on demand; never the whole corpus.
|
|
219
|
-
* @property {{sensor: (result: any, ctx: {task: string, context: string|undefined, contract: string|null}) => (Verdict|Promise<Verdict>), maxIterations?: number, temperatures?: number[]}} [refineLeaf]
|
|
219
|
+
* @property {{sensor: (result: any, ctx: {task: string, context: string|undefined, contract: string|null}) => (Verdict|Promise<Verdict>), maxIterations?: number, temperatures?: number[], rejectedBuffer?: boolean}} [refineLeaf]
|
|
220
220
|
* (Opt-in, BA-8 / relayfact F17) Turn a DEFINITE LEAF (a node that is offered no `spawn_child` — `simple`
|
|
221
221
|
* tier or at `maxDepth`) into a bounded generate→sense→regenerate loop instead of a single pass, so a failed
|
|
222
222
|
* slice can self-correct. `sensor` is a DETERMINISTIC close (test/compile/lint — NOT a model judge, R-S8) that
|
|
@@ -225,13 +225,29 @@ function auditSafeCtx(ctx, overrides = {}) {
|
|
|
225
225
|
* (`temperatures`, default `[0.2,0.7,1.0]`) — the live-validated lever that lets a weak model escape a
|
|
226
226
|
* repeat-the-same-mistake rut (`poc/ba8-leaf-refine.mjs`: 0/5 → 2-3/5; flat temp recovers 0/5). On a
|
|
227
227
|
* temperature-fixed model (BA-10) the provider drops the param, `receipts.refineLeaf.temperatures` records
|
|
228
|
-
* `null`, and the fed-back gap critique carries recovery alone
|
|
228
|
+
* `null`, and the fed-back gap critique carries recovery alone — UNLESS the rejected-attempt buffer engages
|
|
229
|
+
* (below). `maxIterations` defaults to
|
|
229
230
|
* `temperatures.length`; the REAL bound is bareguard (each attempt is gate-checked + metered). CARRIES DOWN the
|
|
230
231
|
* tree (preserved by `forChild`), so it engages at the leaves of a Family-A decomposition. Recovery is PARTIAL
|
|
231
232
|
* (a stubborn blind spot may persist) — `receipts.refineLeaf.passed` reports honestly. Does NOT apply to a node
|
|
232
233
|
* that delegates (its children + the tree verify own quality), nor to the scan/fanout/partition dispatch paths.
|
|
233
234
|
* Absent ⇒ a leaf is a single pass (byte-identical to pre-BA-8). An error-keyed `recall` is the CALLER's tool
|
|
234
235
|
* (`opts.tools`) keyed off the fed-back critique — bareagent stays litectx-agnostic.
|
|
236
|
+
* **Sensor integrity (RSI-learnings #1/#5, "audit the close"):** the `sensor` MUST judge the RETURNED result
|
|
237
|
+
* (tamper-proof — e.g. build/run the returned string in an isolated context, as `poc/ba8-leaf-refine.mjs` does),
|
|
238
|
+
* NEVER a worker side-effect a worker with edit tools could GAME (writing a passing file then returning junk, or
|
|
239
|
+
* editing the failing test itself). A gameable close is the reward-hacking surface every RSI system in the field
|
|
240
|
+
* got bitten by; the loop optimizes against WHATEVER the sensor reads, so keep it outside what the worker can write.
|
|
241
|
+
* **`rejectedBuffer` (BA-14):** a SkillOpt-shaped rejected-attempt buffer — instead of only the LATEST critique,
|
|
242
|
+
* surface the model's OWN prior failed attempts verbatim ("you wrote these, they failed X — write something
|
|
243
|
+
* STRUCTURALLY DIFFERENT"). This is DIRECTED diversity (attack the specific repeated mistake), where escalation
|
|
244
|
+
* is RANDOM diversity; the two are ANTAGONISTIC (`poc/ba14b-temp-with-buffer.mjs`: temperature monotonically
|
|
245
|
+
* degrades the buffer, 100%→70%→50% across 0.2/0.7/1.0), so when the buffer engages the retry temperature is
|
|
246
|
+
* HELD at `temperatures[0]` (flat-low), never escalated. `true` = force on (also on temperature-accepting
|
|
247
|
+
* models); `false` = force off (pure BA-8 escalation); UNSET = ADAPTIVE — engage only once a prior attempt's
|
|
248
|
+
* temperature was dropped (a temperature-fixed model, BA-10, where escalation is inert and the buffer is the
|
|
249
|
+
* sole lever — `poc/ba14-rejected-buffer.mjs`: flat-temp 50%→100%). `receipts.refineLeaf.rejectedBuffer`
|
|
250
|
+
* reports whether any iteration injected it. Bounded by `maxIterations` (the buffer never outgrows it).
|
|
235
251
|
* @property {string} [contract] - Definition of done (A3). When present, the verifier grades against THIS,
|
|
236
252
|
* not the loose task, and verification always runs.
|
|
237
253
|
* @property {(result: any, ctx: {contract: string|null, task: string}) => (Verdict|Promise<Verdict>)} [evaluate]
|
|
@@ -293,10 +309,14 @@ function auditSafeCtx(ctx, overrides = {}) {
|
|
|
293
309
|
* @property {string} [blocker] - (BA-11) set to `'governance-deny'` when this node stopped because its Loop
|
|
294
310
|
* short-circuited a consecutive-policy-deny spin (not a model failure). Mirrors `RecurseResult.blocker`.
|
|
295
311
|
* @property {object|null} tokens - The worker Loop's `metrics.tokens`.
|
|
296
|
-
* @property {{iterations: number, passed: boolean, temperatures: (number|null)[]}} [refineLeaf] - (BA-8) when
|
|
297
|
-
* leaf ran as a bounded refine loop: how many attempts it took and whether the deterministic sensor finally
|
|
312
|
+
* @property {{iterations: number, passed: boolean, temperatures: (number|null)[], rejectedBuffer: boolean}} [refineLeaf] - (BA-8) when
|
|
313
|
+
* this leaf ran as a bounded refine loop: how many attempts it took and whether the deterministic sensor finally
|
|
298
314
|
* passed (false = honest non-recovery, not a faked success). `temperatures` are the EFFECTIVE per-attempt temps
|
|
299
315
|
* (BA-10): a `null` marks an attempt the model ran at its DEFAULT because it rejected the requested temperature.
|
|
316
|
+
* `rejectedBuffer` (BA-14): whether any iteration injected the rejected-attempt buffer (prior failed attempts
|
|
317
|
+
* surfaced verbatim); when it engages the REQUESTED retry temperature is held flat at `temperatures[0]`, never
|
|
318
|
+
* escalated (ba14b antagonism) — but a temperature-fixed model still drops it, so the EFFECTIVE temp recorded
|
|
319
|
+
* above is `null`, not `temperatures[0]`.
|
|
300
320
|
* @property {string|null} model
|
|
301
321
|
* @property {string|null} [retrieval] - (§10 step 7) the retrieval mode this node ran (`scan`/`search`/`exact`),
|
|
302
322
|
* or null/absent for a plain reasoning node.
|
|
@@ -608,10 +628,25 @@ async function recurse(task, ctx = {}, opts = {}) {
|
|
|
608
628
|
async function recurseRefineLeaf(task, ctx, opts, state) {
|
|
609
629
|
const { provider, system, handleTools, depth, critical, node, sensor } = state;
|
|
610
630
|
node.model = provider.model || null;
|
|
611
|
-
const cfg = /** @type {{maxIterations?: number, temperatures?: number[]}} */ (opts.refineLeaf || {});
|
|
631
|
+
const cfg = /** @type {{maxIterations?: number, temperatures?: number[], rejectedBuffer?: boolean}} */ (opts.refineLeaf || {});
|
|
612
632
|
const temps = Array.isArray(cfg.temperatures) && cfg.temperatures.length ? cfg.temperatures : DEFAULT_REFINE_TEMPS;
|
|
613
633
|
const maxIterations = Number.isInteger(cfg.maxIterations) && /** @type {number} */ (cfg.maxIterations) > 0
|
|
614
634
|
? /** @type {number} */ (cfg.maxIterations) : temps.length;
|
|
635
|
+
// BA-14 rejected-attempt buffer: surface the model's OWN prior failed attempts verbatim ("you wrote these,
|
|
636
|
+
// they failed X — write something DIFFERENT") — a SkillOpt-shaped directed-diversity lever. `rejectedBuffer`:
|
|
637
|
+
// `true` = force on (also on temperature-accepting models); `false` = force off; unset = ADAPTIVE (engage only
|
|
638
|
+
// once a prior attempt's temperature was DROPPED, i.e. a temperature-fixed model where BA-8 escalation is inert
|
|
639
|
+
// and the buffer is the only lever — ba14 D>C). Escalation and the buffer are ANTAGONISTIC (ba14b: temp
|
|
640
|
+
// monotonically degrades the buffer 100→70→50% across 0.2/0.7/1.0), so when the buffer engages we HOLD temps[0].
|
|
641
|
+
const bufferForced = cfg.rejectedBuffer === true;
|
|
642
|
+
const bufferDisabled = cfg.rejectedBuffer === false;
|
|
643
|
+
let bufferUsed = false; // receipt: did any iteration actually inject the ledger?
|
|
644
|
+
const LEDGER_ENTRY_CAP = 600, LEDGER_WHY_CAP = 400;
|
|
645
|
+
const formatLedger = (/** @type {Array<{result: any, verdict: any}>} */ history) => history.map((h, i) => {
|
|
646
|
+
const code = String(h.result == null ? '' : h.result).replace(/```[a-zA-Z]*\n?/g, '').trim().slice(0, LEDGER_ENTRY_CAP);
|
|
647
|
+
const why = h.verdict && typeof h.verdict.critique === 'string' ? h.verdict.critique.slice(0, LEDGER_WHY_CAP) : '';
|
|
648
|
+
return `--- Rejected attempt ${i + 1} (already failed — do NOT reproduce) ---\n${code}${why ? `\nFailed: ${why}` : ''}`;
|
|
649
|
+
}).join('\n\n');
|
|
615
650
|
|
|
616
651
|
// A refine leaf runs N Loops, so its receipts.tokens SUMS every attempt's spend (not just the last) — the
|
|
617
652
|
// honest cost of the node. The 4-tier tokens object (`{input,output,cacheCreation,cacheRead}`, loop.js) is flat
|
|
@@ -632,8 +667,16 @@ async function recurseRefineLeaf(task, ctx, opts, state) {
|
|
|
632
667
|
const effectiveTemps = [];
|
|
633
668
|
// One attempt = a fresh leaf Loop (no spawn tool: a retry is a direct correction, not a re-decomposition) at the
|
|
634
669
|
// iteration's temperature, with the GAP fed forward as fresh feedback. A governance halt → throw so refine stops.
|
|
635
|
-
const attempt = async ({ iteration, critique }) => {
|
|
636
|
-
const
|
|
670
|
+
const attempt = async ({ iteration, critique, history }) => {
|
|
671
|
+
const hist = Array.isArray(history) ? history : [];
|
|
672
|
+
// ADAPTIVE trigger: a prior attempt whose temperature the model rejected (BA-10) records `null` in
|
|
673
|
+
// effectiveTemps → escalation is inert on this (temperature-fixed) model, so engage the buffer. Forced-on
|
|
674
|
+
// engages regardless (incl. temperature-accepting models). Needs ≥1 prior attempt to have something to buffer.
|
|
675
|
+
const tempDropped = effectiveTemps.some((t) => t === null);
|
|
676
|
+
const useBuffer = hist.length > 0 && !bufferDisabled && (bufferForced || tempDropped);
|
|
677
|
+
// ba14b: temperature is antagonistic to the buffer's directed diversity — HOLD temps[0] when it engages;
|
|
678
|
+
// otherwise escalate (BA-8, the no-memory lever). On a temperature-fixed model both collapse to the default.
|
|
679
|
+
const temperature = useBuffer ? temps[0] : temps[Math.min(iteration, temps.length - 1)];
|
|
637
680
|
const loop = new Loop({
|
|
638
681
|
provider, system,
|
|
639
682
|
policy: ctx.policy || undefined,
|
|
@@ -642,9 +685,15 @@ async function recurseRefineLeaf(task, ctx, opts, state) {
|
|
|
642
685
|
throwOnError: false,
|
|
643
686
|
});
|
|
644
687
|
const base = withContext(task, opts.context);
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
688
|
+
let userText;
|
|
689
|
+
if (useBuffer) {
|
|
690
|
+
bufferUsed = true;
|
|
691
|
+
userText = `${base}\n\nYou have already tried the following and each FAILED. Do NOT reproduce them — write a STRUCTURALLY DIFFERENT result that passes ALL checks:\n\n${formatLedger(hist)}`;
|
|
692
|
+
} else if (critique) {
|
|
693
|
+
userText = `${base}\n\nYour previous attempt FAILED these checks:\n${critique}\n\nReturn a corrected result that passes ALL of them.`;
|
|
694
|
+
} else {
|
|
695
|
+
userText = base;
|
|
696
|
+
}
|
|
648
697
|
const out = await loop.run([{ role: 'user', content: userText }], handleTools, { ctx: auditSafeCtx(ctx, { depth }), temperature });
|
|
649
698
|
// `temperatureDropped` is set on the Loop result only when the model rejected the requested temperature
|
|
650
699
|
// (BA-10); it's absent on the error/halt return shapes, so read it through a narrow cast.
|
|
@@ -667,7 +716,7 @@ async function recurseRefineLeaf(task, ctx, opts, state) {
|
|
|
667
716
|
// `temperatures` = the EFFECTIVE temps (BA-10): a `null` marks an attempt whose requested temperature the
|
|
668
717
|
// model rejected and ran at its default — so the receipt never claims a value the model ignored. On a
|
|
669
718
|
// temperature-accepting model this equals the requested `temps.slice(0, iterations)` (byte-identical receipt).
|
|
670
|
-
node.refineLeaf = { iterations: outcome.iterations, passed: !!(outcome.verdict && outcome.verdict.pass), temperatures: effectiveTemps.slice(0, outcome.iterations) };
|
|
719
|
+
node.refineLeaf = { iterations: outcome.iterations, passed: !!(outcome.verdict && outcome.verdict.pass), temperatures: effectiveTemps.slice(0, outcome.iterations), rejectedBuffer: bufferUsed };
|
|
671
720
|
const result = outcome.result;
|
|
672
721
|
|
|
673
722
|
// Optional rubric layer on top of the deterministic sensor (RC-7): forced for critical, or a contract/override.
|
|
@@ -682,6 +731,11 @@ async function recurseRefineLeaf(task, ctx, opts, state) {
|
|
|
682
731
|
return { result, verdict: outcome.verdict || null, receipts: node };
|
|
683
732
|
} catch (err) {
|
|
684
733
|
node.tokens = tokensSum; // record whatever attempts DID spend, on both the halt and fault paths
|
|
734
|
+
// The refineLeaf receipt must ride EVERY terminating path, not just the clean one (same invariant as BA-10's
|
|
735
|
+
// `temperatureDropped`): a leaf that ran attempts then halted/faulted still spent tokens and may have engaged
|
|
736
|
+
// the buffer. `effectiveTemps[iteration]` is set BEFORE each attempt's throw, so it reflects every attempt
|
|
737
|
+
// made; `passed:false` because the catch is only reached on a throw (a pass returns from the try above).
|
|
738
|
+
node.refineLeaf = { iterations: effectiveTemps.length, passed: false, temperatures: effectiveTemps.slice(), rejectedBuffer: bufferUsed };
|
|
685
739
|
if (err instanceof HaltError) {
|
|
686
740
|
node.halted = true;
|
|
687
741
|
node.incomplete = true;
|
package/src/refine.d.ts
CHANGED
|
@@ -1,15 +1,25 @@
|
|
|
1
1
|
export type Verdict = import("./evaluator").Verdict;
|
|
2
2
|
export type RefineOptions = {
|
|
3
3
|
/**
|
|
4
|
-
* Build one generation. On iteration 0, `lastResult`/`critique` are null. Fresh-feedback
|
|
5
|
-
* consumer's to realize here: seed a NEW Loop with `{goal + critique}` rather than continuing the
|
|
6
|
-
* transcript — anchoring on a wrong answer defeats the independent verifier.
|
|
4
|
+
* Build one generation. On iteration 0, `lastResult`/`critique` are null and `history` is empty. Fresh-feedback
|
|
5
|
+
* (D6/A1) is the consumer's to realize here: seed a NEW Loop with `{goal + critique}` rather than continuing the
|
|
6
|
+
* failed transcript — anchoring on a wrong answer defeats the independent verifier. `history` is a fresh
|
|
7
|
+
* shallow COPY of every prior `{result, verdict}` in order (a SkillOpt rejected-attempt buffer, BA-14): a
|
|
8
|
+
* consumer MAY surface the failed attempts verbatim ("you already wrote these, they failed X — write something
|
|
9
|
+
* different"), the lever that recovers a temperature-fixed model's fixation rut where escalation is inert
|
|
10
|
+
* (`poc/ba14-rejected-buffer.mjs`: flat-temp 50%→100%). *Structurally* mutating the copy (push/splice/reorder)
|
|
11
|
+
* is safe; the `{result, verdict}` entries are SHARED references into refine's internal history (and the returned
|
|
12
|
+
* `outcome.history`), so treat them as READ-ONLY — deep-mutating an entry corrupts the outcome.
|
|
7
13
|
*/
|
|
8
14
|
attempt: (args: {
|
|
9
15
|
iteration: number;
|
|
10
16
|
lastResult: any;
|
|
11
17
|
critique: string | null;
|
|
12
18
|
contract: string | null;
|
|
19
|
+
history: Array<{
|
|
20
|
+
result: any;
|
|
21
|
+
verdict: Verdict;
|
|
22
|
+
}>;
|
|
13
23
|
}) => any;
|
|
14
24
|
/**
|
|
15
25
|
* Judge a result — typically `evaluator.evaluate(goal, result, { rubric, contract })`.
|
|
@@ -56,10 +66,16 @@ export type RefineOutcome = {
|
|
|
56
66
|
/** @typedef {import('./evaluator').Verdict} Verdict */
|
|
57
67
|
/**
|
|
58
68
|
* @typedef {object} RefineOptions
|
|
59
|
-
* @property {(args: {iteration: number, lastResult: any, critique: string|null, contract: string|null}) => any} attempt
|
|
60
|
-
* Build one generation. On iteration 0, `lastResult`/`critique` are null. Fresh-feedback
|
|
61
|
-
* consumer's to realize here: seed a NEW Loop with `{goal + critique}` rather than continuing the
|
|
62
|
-
* transcript — anchoring on a wrong answer defeats the independent verifier.
|
|
69
|
+
* @property {(args: {iteration: number, lastResult: any, critique: string|null, contract: string|null, history: Array<{result: any, verdict: Verdict}>}) => any} attempt
|
|
70
|
+
* Build one generation. On iteration 0, `lastResult`/`critique` are null and `history` is empty. Fresh-feedback
|
|
71
|
+
* (D6/A1) is the consumer's to realize here: seed a NEW Loop with `{goal + critique}` rather than continuing the
|
|
72
|
+
* failed transcript — anchoring on a wrong answer defeats the independent verifier. `history` is a fresh
|
|
73
|
+
* shallow COPY of every prior `{result, verdict}` in order (a SkillOpt rejected-attempt buffer, BA-14): a
|
|
74
|
+
* consumer MAY surface the failed attempts verbatim ("you already wrote these, they failed X — write something
|
|
75
|
+
* different"), the lever that recovers a temperature-fixed model's fixation rut where escalation is inert
|
|
76
|
+
* (`poc/ba14-rejected-buffer.mjs`: flat-temp 50%→100%). *Structurally* mutating the copy (push/splice/reorder)
|
|
77
|
+
* is safe; the `{result, verdict}` entries are SHARED references into refine's internal history (and the returned
|
|
78
|
+
* `outcome.history`), so treat them as READ-ONLY — deep-mutating an entry corrupts the outcome.
|
|
63
79
|
* @property {(result: any, ctx: {iteration: number, contract: string|null}) => (Verdict | Promise<Verdict>)} evaluate
|
|
64
80
|
* Judge a result — typically `evaluator.evaluate(goal, result, { rubric, contract })`.
|
|
65
81
|
* @property {string} [contract] - The shared definition of done (A3/D10), forwarded to BOTH `attempt` and
|
package/src/refine.js
CHANGED
|
@@ -4,10 +4,16 @@
|
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* @typedef {object} RefineOptions
|
|
7
|
-
* @property {(args: {iteration: number, lastResult: any, critique: string|null, contract: string|null}) => any} attempt
|
|
8
|
-
* Build one generation. On iteration 0, `lastResult`/`critique` are null. Fresh-feedback
|
|
9
|
-
* consumer's to realize here: seed a NEW Loop with `{goal + critique}` rather than continuing the
|
|
10
|
-
* transcript — anchoring on a wrong answer defeats the independent verifier.
|
|
7
|
+
* @property {(args: {iteration: number, lastResult: any, critique: string|null, contract: string|null, history: Array<{result: any, verdict: Verdict}>}) => any} attempt
|
|
8
|
+
* Build one generation. On iteration 0, `lastResult`/`critique` are null and `history` is empty. Fresh-feedback
|
|
9
|
+
* (D6/A1) is the consumer's to realize here: seed a NEW Loop with `{goal + critique}` rather than continuing the
|
|
10
|
+
* failed transcript — anchoring on a wrong answer defeats the independent verifier. `history` is a fresh
|
|
11
|
+
* shallow COPY of every prior `{result, verdict}` in order (a SkillOpt rejected-attempt buffer, BA-14): a
|
|
12
|
+
* consumer MAY surface the failed attempts verbatim ("you already wrote these, they failed X — write something
|
|
13
|
+
* different"), the lever that recovers a temperature-fixed model's fixation rut where escalation is inert
|
|
14
|
+
* (`poc/ba14-rejected-buffer.mjs`: flat-temp 50%→100%). *Structurally* mutating the copy (push/splice/reorder)
|
|
15
|
+
* is safe; the `{result, verdict}` entries are SHARED references into refine's internal history (and the returned
|
|
16
|
+
* `outcome.history`), so treat them as READ-ONLY — deep-mutating an entry corrupts the outcome.
|
|
11
17
|
* @property {(result: any, ctx: {iteration: number, contract: string|null}) => (Verdict | Promise<Verdict>)} evaluate
|
|
12
18
|
* Judge a result — typically `evaluator.evaluate(goal, result, { rubric, contract })`.
|
|
13
19
|
* @property {string} [contract] - The shared definition of done (A3/D10), forwarded to BOTH `attempt` and
|
|
@@ -49,7 +55,7 @@ async function refine(options) {
|
|
|
49
55
|
let lastVerdict = null;
|
|
50
56
|
|
|
51
57
|
for (let iteration = 0; iteration < maxIterations; iteration++) {
|
|
52
|
-
const result = await attempt({ iteration, lastResult, critique: lastVerdict ? lastVerdict.critique : null, contract });
|
|
58
|
+
const result = await attempt({ iteration, lastResult, critique: lastVerdict ? lastVerdict.critique : null, contract, history: history.slice() });
|
|
53
59
|
const verdict = await evaluate(result, { iteration, contract });
|
|
54
60
|
history.push({ result, verdict });
|
|
55
61
|
lastResult = result;
|