bare-agent 0.30.0 → 0.32.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 +2 -2
- package/bareagent.context.md +11 -2
- package/package.json +1 -1
- package/src/evaluator.d.ts +3 -0
- package/src/evaluator.js +27 -1
- package/src/provider-clipipe-tools.d.ts +121 -0
- package/src/provider-clipipe-tools.js +271 -0
- package/src/provider-clipipe.d.ts +53 -2
- package/src/provider-clipipe.js +112 -20
- package/src/recurse.d.ts +76 -4
- package/src/recurse.js +392 -36
package/src/provider-clipipe.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
const { spawn } = require('child_process');
|
|
4
4
|
const { ProviderError } = require('./errors');
|
|
5
|
+
const { buildToolSystemPrompt, renderTranscript, resolveToolProtocol, mapClaudeMeta } = require('./provider-clipipe-tools');
|
|
5
6
|
|
|
6
7
|
/** @typedef {import('../types').Message} Message */
|
|
7
8
|
/** @typedef {import('../types').ToolDef} ToolDef */
|
|
@@ -17,6 +18,8 @@ const { ProviderError } = require('./errors');
|
|
|
17
18
|
* @property {string} [systemPromptFlag] - CLI flag for system prompt (e.g. '--system'). When set, system messages are extracted and passed via this flag instead of stdin.
|
|
18
19
|
* @property {(chunk: string) => void} [onChunk] - Called with each stdout chunk as it streams.
|
|
19
20
|
* @property {'claude-json'|((stdout: string) => Partial<GenerateResult>)} [parse] - Opt-in structured-output parser for stdout. Default (unset) returns stdout verbatim as `text` with zero usage (no behavior change). `'claude-json'` is a shipped preset for `claude -p --output-format json`: it maps the CLI's result envelope onto `GenerateResult` (text←`result`, usage←`usage.*`, model←first `modelUsage` key, costUsd←`total_cost_usd`) and throws `ProviderError` on malformed JSON or an error envelope (`is_error`/non-success subtype). A function is the CLI-agnostic escape hatch: it receives trimmed stdout and returns a partial `GenerateResult` (merged over defaults); throw to signal a parse failure.
|
|
21
|
+
* @property {'claude'} [toolProtocol] - Opt into TOOL MODE (v0.32.0). A subscription CLI is a plain turn-provider with no native tool channel; this enables schema-validated tool EMULATION so a caller's `tools` work over the CLI (letting a Claude/etc. subscription drive an agentic Loop without metered-API spend). When set, ANY `generate(msgs, tools)` call with a non-empty `tools` array auto-uses emulation (the caller's own system stance + a tool manifest + a JSON envelope, parsed back into normalized `toolCalls`); an empty `tools` array is unchanged plain-text. When NOT set, `tools` are IGNORED (plain-text mode, the long-standing behavior — a non-tool-calling CLI can legitimately sit in a Loop that has tools mounted) with a one-time `console.warn` for visibility. Claude-only for now (`'claude'`); the claude-specific flags/schema/parse live in `provider-clipipe-tools.js` so a second CLI slots in behind the same seam. NOTE: tool mode requires a capable model — weak models (e.g. haiku) answer in prose instead of calling tools; see `probeCapability`.
|
|
22
|
+
* @property {boolean} [probeCapability=true] - (tool mode only) On the first tool-mode `generate`, run ONE cheap upfront probe that asks the model to obtain unknowable info via a tool. If it answers in prose instead of emitting a tool_call, throw a loud `ProviderError` naming the model — FAIL FAST rather than silently degrade mid-run (the weak-model failure mode). Behaviour-based, never a model name-list (a roster goes stale, BA-10). The verdict is cached per instance (one probe per provider, not per turn). Set `false` to skip when the caller already knows the model is capable.
|
|
20
23
|
*/
|
|
21
24
|
|
|
22
25
|
class CLIPipeProvider {
|
|
@@ -38,12 +41,23 @@ class CLIPipeProvider {
|
|
|
38
41
|
throw new Error("[CLIPipeProvider] options.parse must be 'claude-json' or a function");
|
|
39
42
|
}
|
|
40
43
|
this.parse = options.parse || null;
|
|
44
|
+
// Tool mode (v0.32.0). Resolve the protocol adapter eagerly so an unknown name fails at
|
|
45
|
+
// construction, not mid-run. `_toolCapability` caches the upfront probe verdict per instance
|
|
46
|
+
// (null = not yet probed; a Promise while in flight; true once confirmed capable).
|
|
47
|
+
this.toolProtocol = options.toolProtocol ? resolveToolProtocol(options.toolProtocol) : null;
|
|
48
|
+
this.probeCapability = options.probeCapability !== false;
|
|
49
|
+
/** @type {Promise<void>|null} */
|
|
50
|
+
this._toolCapability = null;
|
|
41
51
|
}
|
|
42
52
|
|
|
43
53
|
/**
|
|
44
|
-
* Generate a response by piping messages to the CLI command.
|
|
54
|
+
* Generate a response by piping messages to the CLI command. With a `toolProtocol` configured and
|
|
55
|
+
* a non-empty `tools` array, routes to schema-validated tool EMULATION (v0.32.0); otherwise the
|
|
56
|
+
* plain-text path below (unchanged). Passing `tools` with no `toolProtocol` warns ONCE and ignores
|
|
57
|
+
* them (a non-tool-calling CLI legitimately coexists in a Loop with tools mounted); the loud
|
|
58
|
+
* failure for a genuinely tool-incapable model lives in the tool-mode capability probe.
|
|
45
59
|
* @param {Message[]} messages - Conversation messages in OpenAI format.
|
|
46
|
-
* @param {ToolDef[]} [tools=[]] -
|
|
60
|
+
* @param {ToolDef[]} [tools=[]] - Caller tools. Honored only in tool mode (`toolProtocol` set).
|
|
47
61
|
* @param {Record<string, any>} [options={}] - Unused.
|
|
48
62
|
* @returns {Promise<GenerateResult>}
|
|
49
63
|
* @throws {Error} `[CLIPipeProvider] failed to spawn "cmd": ...` — when the command cannot be found or executed.
|
|
@@ -52,6 +66,24 @@ class CLIPipeProvider {
|
|
|
52
66
|
* @throws {Error} `[CLIPipeProvider] process produced no output` — when stdout is empty.
|
|
53
67
|
*/
|
|
54
68
|
async generate(messages, tools = [], options = {}) {
|
|
69
|
+
if (Array.isArray(tools) && tools.length > 0) {
|
|
70
|
+
if (this.toolProtocol) return this._generateWithTools(messages, tools);
|
|
71
|
+
// No protocol configured → plain-text mode, tools IGNORED — the long-standing behavior, kept
|
|
72
|
+
// for backward compatibility (a non-tool-calling CLI legitimately coexists in a Loop that has
|
|
73
|
+
// tools mounted, e.g. via MCP; the Loop's contract lets a provider simply not call them).
|
|
74
|
+
// A silent ignore is the trap the caller might not notice, so warn ONCE per instance (the
|
|
75
|
+
// provider-temperature BA-10 pattern) — visible, not fatal. Genuine loud-failure for tool
|
|
76
|
+
// mode lives in the capability probe, where a weak model that SHOULD call tools cannot.
|
|
77
|
+
if (!this._warnedNoProtocol) {
|
|
78
|
+
this._warnedNoProtocol = true;
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.warn(
|
|
81
|
+
'[CLIPipeProvider] received tools but no toolProtocol is configured — tools are IGNORED ' +
|
|
82
|
+
"(plain-text mode). Construct with { toolProtocol: 'claude' } to enable tool emulation.",
|
|
83
|
+
);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
55
87
|
/** @type {string[]} */
|
|
56
88
|
let extraArgs = [];
|
|
57
89
|
let promptMessages = messages;
|
|
@@ -85,6 +117,82 @@ class CLIPipeProvider {
|
|
|
85
117
|
};
|
|
86
118
|
}
|
|
87
119
|
|
|
120
|
+
/**
|
|
121
|
+
* Tool mode (v0.32.0) — one turn of schema-validated tool emulation. Renders the Loop's
|
|
122
|
+
* OpenAI-shaped transcript to text, injects the caller's system stance + a tool manifest + the
|
|
123
|
+
* envelope contract, spawns the CLI under the protocol's flags, and parses the envelope back into
|
|
124
|
+
* normalized `toolCalls` (a `tool_call`) or `text` (a `final_answer`). The Loop drives the cycle.
|
|
125
|
+
* @param {Message[]} messages
|
|
126
|
+
* @param {ToolDef[]} tools
|
|
127
|
+
* @returns {Promise<GenerateResult>}
|
|
128
|
+
*/
|
|
129
|
+
async _generateWithTools(messages, tools) {
|
|
130
|
+
await this._ensureToolCapability();
|
|
131
|
+
const proto = this.toolProtocol;
|
|
132
|
+
if (!proto) throw new ProviderError('[CLIPipeProvider] tool mode not configured', /** @type {any} */ ({ status: 0 })); // unreachable: only called from the tools branch
|
|
133
|
+
const sysMsg = messages.find((m) => m.role === 'system');
|
|
134
|
+
const systemPrompt = buildToolSystemPrompt(sysMsg && typeof sysMsg.content === 'string' ? sysMsg.content : null, tools);
|
|
135
|
+
const stdout = await this._spawn(renderTranscript(messages), proto.turnArgs(systemPrompt));
|
|
136
|
+
const parsed = proto.parseResult(stdout);
|
|
137
|
+
|
|
138
|
+
/** @type {GenerateResult} */
|
|
139
|
+
const result = { text: '', toolCalls: [], usage: parsed.usage, model: parsed.model ?? null };
|
|
140
|
+
if (Number.isFinite(parsed.costUsd)) result.costUsd = parsed.costUsd;
|
|
141
|
+
if (parsed.action === 'tool_call') {
|
|
142
|
+
this._toolCallSeq = (this._toolCallSeq || 0) + 1;
|
|
143
|
+
result.toolCalls = [{ id: `cli_${this._toolCallSeq}`, name: parsed.toolName || '', arguments: parsed.toolArguments || {} }];
|
|
144
|
+
} else {
|
|
145
|
+
result.text = parsed.answer || '';
|
|
146
|
+
}
|
|
147
|
+
return result;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* Run the upfront capability probe ONCE per instance (cached), unless `probeCapability` is off.
|
|
152
|
+
* A model that answers the probe in prose instead of emitting a tool_call throws a loud
|
|
153
|
+
* `ProviderError` — fail fast, never silently degrade to a no-tools run mid-conversation.
|
|
154
|
+
* NOTE: the probe is a single internal CLI turn whose token usage/cost is NOT surfaced to the Loop
|
|
155
|
+
* (it never flows to `onLlmResult`), so a wired budget gate does not see it — negligible for the
|
|
156
|
+
* subscription use case this exists for (flat cost, one probe per instance), by design.
|
|
157
|
+
* @returns {Promise<void>}
|
|
158
|
+
*/
|
|
159
|
+
_ensureToolCapability() {
|
|
160
|
+
if (!this.probeCapability) return Promise.resolve();
|
|
161
|
+
// Cache the in-flight promise so concurrent first-calls share one probe, and a resolved
|
|
162
|
+
// capable verdict is never re-probed.
|
|
163
|
+
if (this._toolCapability) return this._toolCapability;
|
|
164
|
+
const proto = this.toolProtocol;
|
|
165
|
+
if (!proto) return Promise.resolve(); // unreachable: only called from the tools branch
|
|
166
|
+
this._toolCapability = (async () => {
|
|
167
|
+
const stdout = await this._spawn(proto.probe.user, proto.turnArgs(proto.probe.system));
|
|
168
|
+
let parsed;
|
|
169
|
+
try {
|
|
170
|
+
parsed = proto.parseResult(stdout);
|
|
171
|
+
} catch (err) {
|
|
172
|
+
// A malformed probe response is itself an incapability signal — name it as such.
|
|
173
|
+
this._toolCapability = null; // allow a retry on a transient parse failure
|
|
174
|
+
throw new ProviderError(`[CLIPipeProvider] tool-mode capability probe failed to parse: ${/** @type {Error} */ (err).message}`, /** @type {any} */ ({ status: 0 }));
|
|
175
|
+
}
|
|
176
|
+
if (!proto.probe.isCapable(parsed)) {
|
|
177
|
+
const model = this._modelFromArgs();
|
|
178
|
+
throw new ProviderError(
|
|
179
|
+
`[CLIPipeProvider] the CLI model${model ? ` '${model}'` : ''} is not capable of tool use: the ` +
|
|
180
|
+
'capability probe answered in prose instead of emitting a tool_call. Weak models (e.g. haiku) ' +
|
|
181
|
+
'cannot drive tool emulation reliably — use a stronger model for tool mode, or run without ' +
|
|
182
|
+
'tools for plain-text. (Set { probeCapability: false } to skip this check.)',
|
|
183
|
+
/** @type {any} */ ({ status: 0 }),
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
})();
|
|
187
|
+
return this._toolCapability;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/** Best-effort model id from `--model X` in the base args, for a clearer probe-failure message. */
|
|
191
|
+
_modelFromArgs() {
|
|
192
|
+
const i = this.args.indexOf('--model');
|
|
193
|
+
return i >= 0 && i + 1 < this.args.length ? this.args[i + 1] : null;
|
|
194
|
+
}
|
|
195
|
+
|
|
88
196
|
/**
|
|
89
197
|
* Map the `claude -p --output-format json` result envelope onto a normalized GenerateResult.
|
|
90
198
|
* The caller explicitly opted into structured output, so a malformed or error envelope is a LOUD
|
|
@@ -109,21 +217,7 @@ class CLIPipeProvider {
|
|
|
109
217
|
throw new ProviderError(`[CLIPipeProvider] claude CLI reported failure (subtype='${obj.subtype}'): ${detail}`, /** @type {any} */ ({ status: 0 }));
|
|
110
218
|
}
|
|
111
219
|
|
|
112
|
-
const
|
|
113
|
-
/** @type {import('../types').Usage} */
|
|
114
|
-
const usage = {
|
|
115
|
-
inputTokens: Number(u.input_tokens) || 0,
|
|
116
|
-
outputTokens: Number(u.output_tokens) || 0,
|
|
117
|
-
};
|
|
118
|
-
// Absent cache tiers mean the model didn't cache — omit rather than emit a synthetic 0 (per Usage docs).
|
|
119
|
-
if (Number.isFinite(u.cache_read_input_tokens)) usage.cacheReadTokens = u.cache_read_input_tokens;
|
|
120
|
-
if (Number.isFinite(u.cache_creation_input_tokens)) usage.cacheCreationTokens = u.cache_creation_input_tokens;
|
|
121
|
-
|
|
122
|
-
// `modelUsage` is an object keyed by model id (e.g. {"claude-opus-4-8[1m]": {...}}) — take the first key.
|
|
123
|
-
const model = (obj.modelUsage && typeof obj.modelUsage === 'object')
|
|
124
|
-
? (Object.keys(obj.modelUsage)[0] ?? null)
|
|
125
|
-
: null;
|
|
126
|
-
|
|
220
|
+
const { usage, model, costUsd } = mapClaudeMeta(obj);
|
|
127
221
|
/** @type {GenerateResult} */
|
|
128
222
|
const result = {
|
|
129
223
|
text: typeof obj.result === 'string' ? obj.result : '',
|
|
@@ -131,9 +225,7 @@ class CLIPipeProvider {
|
|
|
131
225
|
usage,
|
|
132
226
|
model,
|
|
133
227
|
};
|
|
134
|
-
|
|
135
|
-
// marginal) — feeds bareguard's USD axis with no local rate table. Only a finite number counts.
|
|
136
|
-
if (Number.isFinite(obj.total_cost_usd)) result.costUsd = obj.total_cost_usd;
|
|
228
|
+
if (costUsd !== undefined) result.costUsd = costUsd;
|
|
137
229
|
return result;
|
|
138
230
|
}
|
|
139
231
|
|
package/src/recurse.d.ts
CHANGED
|
@@ -107,6 +107,13 @@ export type RecurseOptions = {
|
|
|
107
107
|
* NEVER a worker side-effect a worker with edit tools could GAME (writing a passing file then returning junk, or
|
|
108
108
|
* editing the failing test itself). A gameable close is the reward-hacking surface every RSI system in the field
|
|
109
109
|
* got bitten by; the loop optimizes against WHATEVER the sensor reads, so keep it outside what the worker can write.
|
|
110
|
+
* **Broken sensor ≠ failing model (BA-15):** a sensor that THROWS (non-Halt) or returns a MALFORMED verdict
|
|
111
|
+
* (anything but `{pass: boolean}` or a valid tri-state `status`) is a faulty ARBITER — the loop stops at the
|
|
112
|
+
* FIRST broken close (never retries against it) and returns a labeled `{incomplete, blocker:'broken-sensor'}`
|
|
113
|
+
* (+ `receipts.blockerDetail`), with `best` preserving the model's last attempt. A `HaltError` thrown by the
|
|
114
|
+
* sensor stays a clean governance halt. The sensor's EXECUTION environment is the caller's: run untrusted /
|
|
115
|
+
* model-generated checks in an isolated child process WITH A TIMEOUT — a sensor that hangs forever hangs the
|
|
116
|
+
* leaf (no bareguard checkpoint fires between sensor start and return; confirmed by `poc/rlmplans-hung-sensor.mjs`).
|
|
110
117
|
* **`rejectedBuffer` (BA-14):** a SkillOpt-shaped rejected-attempt buffer — instead of only the LATEST critique,
|
|
111
118
|
* surface the model's OWN prior failed attempts verbatim ("you wrote these, they failed X — write something
|
|
112
119
|
* STRUCTURALLY DIFFERENT"). This is DIRECTED diversity (attack the specific repeated mistake), where escalation
|
|
@@ -233,10 +240,29 @@ export type RecurseNode = {
|
|
|
233
240
|
incomplete: boolean;
|
|
234
241
|
halted: boolean;
|
|
235
242
|
/**
|
|
236
|
-
* -
|
|
237
|
-
* short-circuited a consecutive-policy-deny
|
|
243
|
+
* - Set when this node stopped for a specific non-model reason (mirrors
|
|
244
|
+
* `RecurseResult.blocker`): `'governance-deny'` (BA-11) — its Loop short-circuited a consecutive-policy-deny
|
|
245
|
+
* spin; `'broken-sensor'` (BA-15) — the caller's `refineLeaf.sensor` threw or returned a malformed verdict;
|
|
246
|
+
* `'broken-verifier'` (BA-15) — the caller's `opts.evaluate` did (the default Evaluator path is never labeled).
|
|
238
247
|
*/
|
|
239
248
|
blocker?: string | undefined;
|
|
249
|
+
/**
|
|
250
|
+
* - (BA-15) with a `broken-*` blocker: what the arbiter did (threw with
|
|
251
|
+
* which message, or which malformed shape it returned) — the actionable half of the label.
|
|
252
|
+
*/
|
|
253
|
+
blockerDetail?: string | undefined;
|
|
254
|
+
/**
|
|
255
|
+
* - (BA-15) a
|
|
256
|
+
* DESCENDANT's blocker, surfaced here so an aggregating node still reports the fault upward. Deliberately
|
|
257
|
+
* SEPARATE from this node's own `blocker` (which means "THIS node's arbiter/Loop broke"): stamping a
|
|
258
|
+
* descendant's label onto every ancestor made the receipts tree accuse nodes whose sensor never ran, and
|
|
259
|
+
* re-labelled a parent `governance-deny` when only one child was denied. `blockerTask` names the culprit.
|
|
260
|
+
*/
|
|
261
|
+
blockerFrom?: {
|
|
262
|
+
blocker: string;
|
|
263
|
+
blockerDetail?: string;
|
|
264
|
+
blockerTask?: string;
|
|
265
|
+
} | undefined;
|
|
240
266
|
/**
|
|
241
267
|
* - The worker Loop's `metrics.tokens`.
|
|
242
268
|
*/
|
|
@@ -318,8 +344,26 @@ export type RecurseResult = {
|
|
|
318
344
|
* - Present when `incomplete` for a specific, actionable reason. `'governance-deny'`
|
|
319
345
|
* (BA-11): the worker's Loop short-circuited after N consecutive policy denials rather than burn to the
|
|
320
346
|
* budget cap — the caller can widen scope / re-gate / escalate instead of reading it as a model failure.
|
|
347
|
+
* `'broken-sensor'` (BA-15): the caller's `refineLeaf.sensor` threw or returned a malformed verdict — the
|
|
348
|
+
* ARBITER is faulty, not the model; fix the sensor and re-run (`receipts.blockerDetail` says what it did).
|
|
349
|
+
* `'broken-verifier'` (BA-15): same fault class at the verify slot — the caller's `opts.evaluate` threw
|
|
350
|
+
* (non-Halt) or returned a malformed verdict; the default Evaluator path is never labeled (its failures are
|
|
351
|
+
* provider-class faults). For both `broken-*` blockers `best` preserves the model's last non-empty output
|
|
352
|
+
* (BA-5) — the arbiter broke, so treat it as best-effort work rather than a graded pass.
|
|
321
353
|
*/
|
|
322
354
|
blocker?: string | undefined;
|
|
355
|
+
/**
|
|
356
|
+
* - (BA-15) with a `broken-*` blocker: what the arbiter did — the
|
|
357
|
+
* ACTIONABLE half of the label, surfaced on the result (not only in `receipts`) so a caller branching on
|
|
358
|
+
* `blocker` can report the cause without walking the receipts tree.
|
|
359
|
+
*/
|
|
360
|
+
blockerDetail?: string | undefined;
|
|
361
|
+
/**
|
|
362
|
+
* - (BA-15) when the blocker was INHERITED from a descendant in a nested run:
|
|
363
|
+
* which sub-task actually broke. Without it a nested failure reports "a sensor broke" with no way to find
|
|
364
|
+
* which one.
|
|
365
|
+
*/
|
|
366
|
+
blockerTask?: string | undefined;
|
|
323
367
|
/**
|
|
324
368
|
* - The audit node for this call (RC-10).
|
|
325
369
|
*/
|
|
@@ -406,6 +450,13 @@ export type Slice = {
|
|
|
406
450
|
* NEVER a worker side-effect a worker with edit tools could GAME (writing a passing file then returning junk, or
|
|
407
451
|
* editing the failing test itself). A gameable close is the reward-hacking surface every RSI system in the field
|
|
408
452
|
* got bitten by; the loop optimizes against WHATEVER the sensor reads, so keep it outside what the worker can write.
|
|
453
|
+
* **Broken sensor ≠ failing model (BA-15):** a sensor that THROWS (non-Halt) or returns a MALFORMED verdict
|
|
454
|
+
* (anything but `{pass: boolean}` or a valid tri-state `status`) is a faulty ARBITER — the loop stops at the
|
|
455
|
+
* FIRST broken close (never retries against it) and returns a labeled `{incomplete, blocker:'broken-sensor'}`
|
|
456
|
+
* (+ `receipts.blockerDetail`), with `best` preserving the model's last attempt. A `HaltError` thrown by the
|
|
457
|
+
* sensor stays a clean governance halt. The sensor's EXECUTION environment is the caller's: run untrusted /
|
|
458
|
+
* model-generated checks in an isolated child process WITH A TIMEOUT — a sensor that hangs forever hangs the
|
|
459
|
+
* leaf (no bareguard checkpoint fires between sensor start and return; confirmed by `poc/rlmplans-hung-sensor.mjs`).
|
|
409
460
|
* **`rejectedBuffer` (BA-14):** a SkillOpt-shaped rejected-attempt buffer — instead of only the LATEST critique,
|
|
410
461
|
* surface the model's OWN prior failed attempts verbatim ("you wrote these, they failed X — write something
|
|
411
462
|
* STRUCTURALLY DIFFERENT"). This is DIRECTED diversity (attack the specific repeated mistake), where escalation
|
|
@@ -473,8 +524,17 @@ export type Slice = {
|
|
|
473
524
|
* @property {Verdict|null} verdict
|
|
474
525
|
* @property {boolean} incomplete
|
|
475
526
|
* @property {boolean} halted
|
|
476
|
-
* @property {string} [blocker] -
|
|
477
|
-
* short-circuited a consecutive-policy-deny
|
|
527
|
+
* @property {string} [blocker] - Set when this node stopped for a specific non-model reason (mirrors
|
|
528
|
+
* `RecurseResult.blocker`): `'governance-deny'` (BA-11) — its Loop short-circuited a consecutive-policy-deny
|
|
529
|
+
* spin; `'broken-sensor'` (BA-15) — the caller's `refineLeaf.sensor` threw or returned a malformed verdict;
|
|
530
|
+
* `'broken-verifier'` (BA-15) — the caller's `opts.evaluate` did (the default Evaluator path is never labeled).
|
|
531
|
+
* @property {string} [blockerDetail] - (BA-15) with a `broken-*` blocker: what the arbiter did (threw with
|
|
532
|
+
* which message, or which malformed shape it returned) — the actionable half of the label.
|
|
533
|
+
* @property {{blocker: string, blockerDetail?: string, blockerTask?: string}} [blockerFrom] - (BA-15) a
|
|
534
|
+
* DESCENDANT's blocker, surfaced here so an aggregating node still reports the fault upward. Deliberately
|
|
535
|
+
* SEPARATE from this node's own `blocker` (which means "THIS node's arbiter/Loop broke"): stamping a
|
|
536
|
+
* descendant's label onto every ancestor made the receipts tree accuse nodes whose sensor never ran, and
|
|
537
|
+
* re-labelled a parent `governance-deny` when only one child was denied. `blockerTask` names the culprit.
|
|
478
538
|
* @property {object|null} tokens - The worker Loop's `metrics.tokens`.
|
|
479
539
|
* @property {{iterations: number, passed: boolean, temperatures: (number|null)[], rejectedBuffer: boolean}} [refineLeaf] - (BA-8) when
|
|
480
540
|
* this leaf ran as a bounded refine loop: how many attempts it took and whether the deterministic sensor finally
|
|
@@ -507,6 +567,18 @@ export type Slice = {
|
|
|
507
567
|
* @property {string} [blocker] - Present when `incomplete` for a specific, actionable reason. `'governance-deny'`
|
|
508
568
|
* (BA-11): the worker's Loop short-circuited after N consecutive policy denials rather than burn to the
|
|
509
569
|
* budget cap — the caller can widen scope / re-gate / escalate instead of reading it as a model failure.
|
|
570
|
+
* `'broken-sensor'` (BA-15): the caller's `refineLeaf.sensor` threw or returned a malformed verdict — the
|
|
571
|
+
* ARBITER is faulty, not the model; fix the sensor and re-run (`receipts.blockerDetail` says what it did).
|
|
572
|
+
* `'broken-verifier'` (BA-15): same fault class at the verify slot — the caller's `opts.evaluate` threw
|
|
573
|
+
* (non-Halt) or returned a malformed verdict; the default Evaluator path is never labeled (its failures are
|
|
574
|
+
* provider-class faults). For both `broken-*` blockers `best` preserves the model's last non-empty output
|
|
575
|
+
* (BA-5) — the arbiter broke, so treat it as best-effort work rather than a graded pass.
|
|
576
|
+
* @property {string} [blockerDetail] - (BA-15) with a `broken-*` blocker: what the arbiter did — the
|
|
577
|
+
* ACTIONABLE half of the label, surfaced on the result (not only in `receipts`) so a caller branching on
|
|
578
|
+
* `blocker` can report the cause without walking the receipts tree.
|
|
579
|
+
* @property {string} [blockerTask] - (BA-15) when the blocker was INHERITED from a descendant in a nested run:
|
|
580
|
+
* which sub-task actually broke. Without it a nested failure reports "a sensor broke" with no way to find
|
|
581
|
+
* which one.
|
|
510
582
|
* @property {RecurseNode} receipts - The audit node for this call (RC-10).
|
|
511
583
|
*/
|
|
512
584
|
/**
|