amicus 4.8.0 → 4.9.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/.claude-plugin/plugin.json +1 -1
- package/CHANGELOG.md +355 -0
- package/README.md +10 -5
- package/docs/CITATIONS.md +13 -5
- package/docs/ROADMAP.md +101 -10
- package/docs/configuration.md +55 -5
- package/docs/council.md +102 -14
- package/docs/troubleshooting.md +9 -2
- package/docs/usage.md +128 -12
- package/electron/ipc-setup.js +39 -2
- package/electron/main.js +46 -3
- package/electron/offer-session.js +51 -0
- package/electron/setup-ui-model.js +99 -9
- package/electron/setup-ui-styles.js +22 -0
- package/electron/setup-ui.js +244 -32
- package/electron/workspace-ui/live-dead-seats.js +163 -91
- package/electron/workspace-ui/live-seats.js +4 -4
- package/electron/workspace-ui/workspace-banners.js +30 -7
- package/electron/workspace-ui/workspace-matrix.js +23 -3
- package/electron/workspace-ui/workspace-seats.js +95 -79
- package/package.json +2 -1
- package/schemas/council-run.schema.json +2 -2
- package/schemas/council-tally.schema.json +17 -1
- package/schemas/council-verdict.schema.json +12 -4
- package/schemas/run.schema.json +6 -1
- package/skills/second-opinion/COUNCIL-DESIGN.md +1 -1
- package/skills/second-opinion/MANUAL-ORCHESTRATION.md +1 -1
- package/skills/second-opinion/MODEL-NOTES.md +88 -9
- package/skills/second-opinion/SEAT-BRIEFS.md +36 -4
- package/skills/second-opinion/SKILL.md +151 -36
- package/src/cli-council-run-bench.js +98 -6
- package/src/cli-handlers-council-run.js +18 -6
- package/src/cli-handlers-council.js +57 -7
- package/src/cli-handlers-doctor.js +12 -15
- package/src/cli.js +3 -1
- package/src/council/anonymize.js +2 -1
- package/src/council/briefings-chair-task.js +161 -0
- package/src/council/briefings-chair.js +33 -8
- package/src/council/briefings-debate.js +79 -13
- package/src/council/briefings-stage2-task.js +236 -0
- package/src/council/briefings-stage2.js +103 -26
- package/src/council/briefings-task.js +167 -0
- package/src/council/briefings.js +41 -4
- package/src/council/chair-fallback.js +95 -0
- package/src/council/debate.js +38 -21
- package/src/council/findings.js +3 -2
- package/src/council/ledger.js +2 -2
- package/src/council/parse-stage2.js +64 -16
- package/src/council/report-cost.js +61 -0
- package/src/council/report-html.js +26 -4
- package/src/council/report-md.js +30 -2
- package/src/council/report.js +40 -37
- package/src/council/run-assemble.js +21 -6
- package/src/council/run-chair.js +44 -95
- package/src/council/run-debate-revote.js +81 -49
- package/src/council/run-debate.js +51 -34
- package/src/council/run-finish.js +5 -3
- package/src/council/run-retry-keys.js +4 -4
- package/src/council/run-retry-launch.js +4 -4
- package/src/council/run-retry-notes.js +72 -15
- package/src/council/run-stage1-launch.js +4 -4
- package/src/council/run-stage1-rows.js +9 -6
- package/src/council/run-stage2.js +81 -47
- package/src/council/run-stages.js +9 -21
- package/src/council/run-stats-entry.js +46 -1
- package/src/council/run.js +28 -13
- package/src/council/seats.js +2 -2
- package/src/council/stage1-bind.js +3 -2
- package/src/council/verdict-seat-loss.js +124 -0
- package/src/council/verdict.js +108 -99
- package/src/headless.js +256 -49
- package/src/mcp-council-bench.js +64 -3
- package/src/mcp-council-run.js +10 -3
- package/src/mcp-server.js +52 -12
- package/src/mcp-tools.js +41 -5
- package/src/observe/council-legs.js +2 -2
- package/src/opencode-client.js +19 -1
- package/src/pack/pack-forward.js +15 -12
- package/src/pack/pack-resolve.js +1 -1
- package/src/prompt-builder.js +17 -1
- package/src/sidecar/fanout-leg.js +26 -0
- package/src/sidecar/fanout.js +1 -1
- package/src/sidecar/list-council.js +178 -0
- package/src/sidecar/list-limit.js +3 -1
- package/src/sidecar/list-search.js +2 -1
- package/src/sidecar/models.js +8 -1
- package/src/sidecar/read.js +34 -10
- package/src/sidecar/setup.js +124 -0
- package/src/template/render.js +16 -7
- package/src/utils/alias-audit.js +81 -3
- package/src/utils/alias-shadow-writer.js +220 -0
- package/src/utils/alias-shadow.js +294 -0
- package/src/utils/config.js +1 -1
- package/src/utils/curated-models.js +16 -8
- package/src/utils/degrade.js +12 -5
- package/src/utils/doctor-alias-check.js +149 -0
- package/src/utils/engine-log-parse.js +289 -0
- package/src/utils/engine-log-tail.js +114 -0
- package/src/utils/engine-log.js +250 -0
- package/src/utils/engine-skew-records.js +146 -0
- package/src/utils/engine-skew.js +300 -0
- package/src/utils/gateway-router.js +10 -2
- package/src/utils/model-canonicalization.js +64 -0
- package/src/utils/model-catalog.js +1 -1
- package/src/utils/model-shortlist.js +100 -0
- package/src/utils/provider-default-picker.js +93 -45
- package/src/utils/provider-default-prompt.js +1 -1
- package/src/utils/quick-picks.js +2 -2
- package/src/utils/remediation-hints.js +24 -0
- package/src/utils/result-schema.js +10 -0
- package/src/utils/text-sanitize.js +81 -0
- package/src/utils/ttft.js +57 -0
- package/src/utils/untrusted-fence.js +111 -1
- package/src/workspace/fold-format.js +28 -7
- package/src/workspace/live-normalize.js +2 -1
- package/src/workspace/matrix-model.js +6 -2
- package/src/workspace/run-detail.js +35 -9
- package/src/workspace/seat-space.js +10 -6
|
@@ -8,7 +8,9 @@ const { sumWaveUsage, formatCost } = require('./utils/pricing');
|
|
|
8
8
|
const { failJson, ERROR_CODES } = require('./utils/error-doc');
|
|
9
9
|
const { buildReport } = require('./council/report');
|
|
10
10
|
const { validateFindings, buildValidateDoc } = require('./council/findings');
|
|
11
|
-
const { buildVerdict, readOverallVerdict, readPriorVerdictSurfaces,
|
|
11
|
+
const { buildVerdict, readOverallVerdict, readPriorVerdictSurfaces, readPriorVerdictIntent,
|
|
12
|
+
writeVerdictAtomic } = require('./council/verdict');
|
|
13
|
+
const { readRun } = require('./council/run-state');
|
|
12
14
|
const {
|
|
13
15
|
runSave: runCouncilSave,
|
|
14
16
|
runList: runCouncilList,
|
|
@@ -32,10 +34,19 @@ function runTally(inputPath, useJson, opts = {}) {
|
|
|
32
34
|
return failJson(useJson, { code: ERROR_CODES.BAD_ARGS, message: `malformed tally input: ${e.message}`,
|
|
33
35
|
hint: 'input needs meta.models, findings[], adjudications[], rankings[]' });
|
|
34
36
|
}
|
|
37
|
+
// ⚠️ PR #200 round-5 B1 — DOOR PARITY with the MCP enum (mcp-tools.js ::
|
|
38
|
+
// amicus_council_tally, round-4 C1): raw JSON.parse, no schema, exact-match
|
|
39
|
+
// consumers, so a near-miss ('Task') slid past the gate below into the ledger.
|
|
40
|
+
// Refused whatever `opts.append` says — meta rides VERBATIM into tally.json.
|
|
41
|
+
const intent = record.meta && record.meta.intent;
|
|
42
|
+
if (intent !== undefined && intent !== 'task' && intent !== 'review') {
|
|
43
|
+
return failJson(useJson, { code: ERROR_CODES.BAD_ARGS, message: `malformed tally input: meta.intent must be 'task' or 'review', got ${JSON.stringify(intent)}`,
|
|
44
|
+
hint: "omit meta.intent for a review run; 'task' is the only spelling that marks a task run" });
|
|
45
|
+
}
|
|
35
46
|
// Auto-append the run to the reliability ledger (consumed by `amicus council
|
|
36
|
-
// stats`)
|
|
37
|
-
//
|
|
38
|
-
if (opts.append !== false) {
|
|
47
|
+
// stats`) — tally is the council's finalize step. Best-effort: a ledger write
|
|
48
|
+
// failure must not fail the tally. v4.9 W5.4 gate 2: task runs never feed it.
|
|
49
|
+
if (opts.append !== false && intent !== 'task') {
|
|
39
50
|
try { appendRun(record); }
|
|
40
51
|
catch (e) { process.stderr.write(`Notice: council ledger append failed: ${e.message}\n`); }
|
|
41
52
|
}
|
|
@@ -59,7 +70,19 @@ function renderRecord(r) {
|
|
|
59
70
|
` Cost: ${formatCost(cost)}\n`;
|
|
60
71
|
}
|
|
61
72
|
function renderStats(agg) {
|
|
62
|
-
|
|
73
|
+
// v4.9 W8 (ruling V5 / R10): an empty table is ambiguous — a fresh install and a
|
|
74
|
+
// task-only install look the same, because task runs never append a row (the
|
|
75
|
+
// intent gate in runTally above). Say so here rather than let silence imply that
|
|
76
|
+
// no council ever ran. Human render only; --json's doc shape is untouched.
|
|
77
|
+
// ⚠️ v4.9 fix round 2 (council C5): the line must be true on ANY empty ledger,
|
|
78
|
+
// and the first wording was not. "…a task-only install has no history here"
|
|
79
|
+
// ASSERTS which of the two states the reader is in, and printed that assertion
|
|
80
|
+
// on every fresh install, where it is false. An EXPLAINER of where rows come
|
|
81
|
+
// from disambiguates the same pair without claiming anything about the reader.
|
|
82
|
+
if (!agg.length) {
|
|
83
|
+
return 'No council runs recorded yet.\n'
|
|
84
|
+
+ '(reliability history comes from review runs; task runs never write rows here)\n';
|
|
85
|
+
}
|
|
63
86
|
// v4.7 GOA-7 D10: group keys may be executable ids (>16 chars) — size the
|
|
64
87
|
// model column to the longest key; legacy (alias-keyed) groups get a notes
|
|
65
88
|
// marker beside low-N.
|
|
@@ -191,11 +214,38 @@ function runVerdict(args, useJson) {
|
|
|
191
214
|
// chair-output.md); tally.json carries no copy. Recover it from the RUN
|
|
192
215
|
// folder — the tally's own directory, not `-o` — before rebuilding.
|
|
193
216
|
const runDir = path.dirname(path.resolve(tallyPath));
|
|
194
|
-
|
|
217
|
+
// v4.9 fix round 2 (council B1/C2/C1), extended by fix round 3 (C3): the
|
|
218
|
+
// run's INTENT decides which chair scale this rebuild may read, and rides
|
|
219
|
+
// onto the rebuilt document. THREE carriers, because no one of them covers
|
|
220
|
+
// every leg: the record's own `meta.intent` is what the run dir's tally.json
|
|
221
|
+
// carries (tally.js copies meta verbatim) but a hand-assembled or
|
|
222
|
+
// MCP-supplied record has none; `run.json`'s checkpoint (run.js ::
|
|
223
|
+
// runCouncil) covers that leg but can itself be absent or foreign; and the
|
|
224
|
+
// run folder's own prior verdict.json carries the key too (verdict.js ::
|
|
225
|
+
// buildVerdict writes it), which is the last document standing when the
|
|
226
|
+
// other two are missing. All three are emit-when-'task', so they combine as
|
|
227
|
+
// a DISJUNCTION — absence is no vote, not a vote for review — and
|
|
228
|
+
// absent/unreadable on all three = review, the fail-closed direction and
|
|
229
|
+
// what every pre-v4.9 run is.
|
|
230
|
+
// ⚠️ PR #200 C2 + C3: the run.json and verdict.json reads carry the SAME
|
|
231
|
+
// runId guard as their siblings — verdict.js :: readOverallVerdict and
|
|
232
|
+
// verdict.js :: readPriorVerdictSurfaces — `!runId || doc.runId === runId`, waived
|
|
233
|
+
// only when the RECORD names no run, never when the DOCUMENT does not.
|
|
234
|
+
// Without it a stale or foreign document in the folder hands this rebuild
|
|
235
|
+
// another run's intent, and intent SELECTS THE CHAIR PARSER in the
|
|
236
|
+
// readOverallVerdict call just below — so the leak can also change
|
|
237
|
+
// overallVerdict to a phrase off the wrong scale, exactly the failure the
|
|
238
|
+
// foreign-verdict.json guard exists to prevent one file over. PR #200 round-5 B3 parenthesized the whole three-carrier disjunction below before the `?` — behaviour-identical (`||` already binds tighter than `?:`), readability only.
|
|
239
|
+
const runDoc = readRun(runDir);
|
|
240
|
+
const ownRunDoc = runDoc && (!record.meta.runId || runDoc.runId === record.meta.runId);
|
|
241
|
+
const intent = ((record.meta && record.meta.intent === 'task')
|
|
242
|
+
|| (ownRunDoc && runDoc.intent === 'task')
|
|
243
|
+
|| readPriorVerdictIntent(runDir, record.meta.runId) === 'task') ? 'task' : 'review';
|
|
244
|
+
const overallVerdict = readOverallVerdict(runDir, record.meta.runId, intent);
|
|
195
245
|
// #87: tally.json carries neither seatLoss nor degrades — recover both from
|
|
196
246
|
// the run folder's verdict the same way the chair line is recovered.
|
|
197
247
|
const prior = readPriorVerdictSurfaces(runDir, record.meta.runId);
|
|
198
|
-
verdict = buildVerdict(record, decisions, { overallVerdict,
|
|
248
|
+
verdict = buildVerdict(record, decisions, { overallVerdict, intent,
|
|
199
249
|
...(prior.seatLoss ? { seatLoss: prior.seatLoss } : {}),
|
|
200
250
|
...(prior.degrades ? { degrades: prior.degrades } : {}) });
|
|
201
251
|
}
|
|
@@ -14,8 +14,11 @@ const electronMcpCheck = require('./utils/doctor-electron-mcp-check');
|
|
|
14
14
|
const localProvidersCheck = require('./utils/doctor-local-providers-check');
|
|
15
15
|
// v4.6.2 PR1 (spec §4) — the 'anthropic-base-url' check body.
|
|
16
16
|
const baseUrlCheck = require('./utils/doctor-base-url-check');
|
|
17
|
+
// B3 (council review of PR 198, issue 195) — the 'aliases' check body,
|
|
18
|
+
// including its --fix repair of fabricated bare ids. Same split rationale.
|
|
19
|
+
const aliasCheck = require('./utils/doctor-alias-check');
|
|
17
20
|
|
|
18
|
-
const
|
|
21
|
+
const { DEFAULT_MAX_AGE_MS: MAX_CATALOG_AGE_MS } = require('./utils/model-catalog'); // 24h — single source
|
|
19
22
|
|
|
20
23
|
/** #56: keep `doctor --fix`'s electron self-heal from ever hanging on a slow disk/network. */
|
|
21
24
|
const FIX_TIMEOUT_MS = 90 * 1000;
|
|
@@ -41,6 +44,10 @@ function realDeps() {
|
|
|
41
44
|
collectAliasSources: () => require('./utils/alias-audit').collectAliasSources(),
|
|
42
45
|
findStaleAliases: (s, c) => require('./utils/alias-audit').findStaleAliases(s, c),
|
|
43
46
|
findDriftedStoredAliases: (s, c) => require('./utils/alias-audit').findDriftedStoredAliases(s, c),
|
|
47
|
+
// B3: the narrow fabricated-bare-id repair class (pure detection) + the
|
|
48
|
+
// impure rewrite primitive `doctor --fix` calls when repairing one.
|
|
49
|
+
findFabricatedAliasRepairs: (s, c) => require('./utils/alias-audit').findFabricatedAliasRepairs(s, c),
|
|
50
|
+
repairAlias: (alias, newId) => aliasCheck.repairAlias(alias, newId),
|
|
44
51
|
hasOpencodeBinary: () => {
|
|
45
52
|
// Single source of truth shared with the runtime server-start guard.
|
|
46
53
|
const { ensureNodeModulesBinInPath, hasOpencodeBinary } = require('./utils/path-setup');
|
|
@@ -156,20 +163,10 @@ async function runDoctorChecks(depsOverride = {}) {
|
|
|
156
163
|
: { id: 'catalog', name: 'Model catalog', status: 'warn', message: `stale (${hrs}h old)`, hint: 'amicus models --refresh' };
|
|
157
164
|
}));
|
|
158
165
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
const stale = d.findStaleAliases(sources, catalog);
|
|
164
|
-
const drifted = d.findDriftedStoredAliases(sources, catalog);
|
|
165
|
-
if (stale.length === 0 && drifted.length === 0) {
|
|
166
|
-
return { id: 'aliases', name: 'Model aliases', status: 'ok', message: catalog.length ? 'all resolve' : 'catalog empty — not checked', hint: null };
|
|
167
|
-
}
|
|
168
|
-
const parts = [];
|
|
169
|
-
if (stale.length) { parts.push(`${stale.length} stale: ${stale.map(s => s.alias).join(', ')}`); }
|
|
170
|
-
if (drifted.length) { parts.push(`${drifted.length} drifted: ${drifted.map(s => s.alias).join(', ')}`); }
|
|
171
|
-
return { id: 'aliases', name: 'Model aliases', status: 'warn', message: parts.join('; '), hint: 'amicus models --check' };
|
|
172
|
-
}));
|
|
166
|
+
// B3: self-heals in place under --fix (fabricated bare ids only) — see
|
|
167
|
+
// utils/doctor-alias-check.js for the check body and utils/alias-audit.js's
|
|
168
|
+
// findFabricatedAliasRepairs for the detection rule.
|
|
169
|
+
checks.push(guard('aliases', 'Model aliases', () => aliasCheck.evaluateAliasesCheck(d)));
|
|
173
170
|
|
|
174
171
|
checks.push(guard('anthropic-base-url', 'ANTHROPIC_BASE_URL',
|
|
175
172
|
() => baseUrlCheck.evaluateAnthropicBaseUrl(d)));
|
package/src/cli.js
CHANGED
|
@@ -590,7 +590,7 @@ Subcommands for 'council':
|
|
|
590
590
|
[--debate] [--claude-review <file>] [--no-cost-gate] [--follow]
|
|
591
591
|
[--fallback] [--no-fallback] [--on-complete <cmd>]
|
|
592
592
|
[--template <name|path>] [--artifact <file>] [--var <k=v>]
|
|
593
|
-
[--pack <name|path>] [--tag <t>]
|
|
593
|
+
[--pack <name|path>] [--tag <t>] [--intent review|task]
|
|
594
594
|
Run the full headless council engine (v4.0).
|
|
595
595
|
Chair default: deepseek (must NOT be a bench seat).
|
|
596
596
|
--critic and --lenses are mutually exclusive.
|
|
@@ -620,6 +620,8 @@ Subcommands for 'council':
|
|
|
620
620
|
--pack <name|path> loads a saved pack (bench,
|
|
621
621
|
chair, critic/lenses, options, template);
|
|
622
622
|
explicit flags always override the pack's values.
|
|
623
|
+
--intent task marks a task-mode run (v4.9);
|
|
624
|
+
review is the default and is never stored.
|
|
623
625
|
Exit: 0 full run, 2 degraded, 1 quorum/cost/validation.
|
|
624
626
|
save <name> --models a,b,c Save a named council preset (>=2 resolvable members)
|
|
625
627
|
--json Machine-readable output
|
package/src/council/anonymize.js
CHANGED
|
@@ -35,7 +35,8 @@ const LETTERS = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ';
|
|
|
35
35
|
* `seatMap` (label -> seat id) is sparse by construction: a label lands in
|
|
36
36
|
* it only when `seats[i]` is a real bound seat that differs from its own
|
|
37
37
|
* alias — the shared emit-when-DIFFERENT predicate every other seat-emit
|
|
38
|
-
* producer uses (run-stats-entry.js
|
|
38
|
+
* producer uses (run-stats-entry.js :: buildRunStatsEntry —
|
|
39
|
+
* `seat.id !== seat.alias`). On a
|
|
39
40
|
* unique-alias bench, or when `seats` is omitted entirely, `seatMap` is
|
|
40
41
|
* `{}` — so no pre-T3.2 caller (or consumer of just `{entries, labelMap}`)
|
|
41
42
|
* can observe a change. T3.2 wired `seatMap` only as far as
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
// src/council/briefings-chair-task.js
|
|
2
|
+
'use strict';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* @module council/briefings-chair-task
|
|
6
|
+
* Chair TASK-intent templates (v4.9 W7; v4.8 design spec §5.5, closing #146):
|
|
7
|
+
* the synthesis pair, the ANSWER scale addendum, the concurrence caveat and the
|
|
8
|
+
* one-shot ANSWER-line repair prompt. `briefings-chair.js` lazy-requires this
|
|
9
|
+
* module at call time from `buildChairPacket` and `chairRepairPromptFor` — the
|
|
10
|
+
* W6 dispatcher shape — so the review path composes byte-identically by
|
|
11
|
+
* construction and the two modules can never form a load cycle.
|
|
12
|
+
*
|
|
13
|
+
* WHAT FORKS, AND WHAT DOES NOT. The packet takes exactly four seams on
|
|
14
|
+
* `intent`: the synthesis instruction (the pair below replaces CHAIR_TASK /
|
|
15
|
+
* CHAIR_TASK_NO_FINDINGS), the scale addendum, the no-tools preamble's last
|
|
16
|
+
* word ('verdict.' → 'answer.'), and this module's concurrence caveat. Ruling
|
|
17
|
+
* V11 keeps everything else ONE vocabulary — and this task records the
|
|
18
|
+
* extension the ruling did not spell out: the packet's SECTION HEADERS
|
|
19
|
+
* (`--- STAGE-1 REVIEWS (de-anonymized) ---`, `--- PEER RANKINGS …`,
|
|
20
|
+
* `--- PER-FINDING ADJUDICATIONS ---`, the R8 block), the `Review by <model>`
|
|
21
|
+
* labels and the empty-section `(none — …)` wordings are shared verbatim. A
|
|
22
|
+
* task packet is the review packet with a different instruction, a different
|
|
23
|
+
* scale and one extra caveat; nothing re-parses those headers, and one habit
|
|
24
|
+
* should read both intents.
|
|
25
|
+
*
|
|
26
|
+
* ⚠️ TWO SCALES, EACH SPELLED TWICE (memo trap 2). `CHAIR_ANSWER_VALUES` here
|
|
27
|
+
* and `parse-stage2.js :: CHAIR_ANSWERS` are independent constants, exactly as
|
|
28
|
+
* the review pair has always been. Skew either and the chair is asked for a
|
|
29
|
+
* phrase the parser cannot read — a degraded run AFTER the whole bench is paid
|
|
30
|
+
* for. Both pairs are drift-pinned in tests/council/chair-scale-drift.test.js,
|
|
31
|
+
* which also carries the named mutant ANSWERSCALEDRIFT and its measured red
|
|
32
|
+
* set. The published schema's enum is a THIRD independent spelling and is
|
|
33
|
+
* pinned in the same suite.
|
|
34
|
+
*/
|
|
35
|
+
|
|
36
|
+
/** The task chair's terminal scale (spec §5.5; ruling V4). */
|
|
37
|
+
const CHAIR_ANSWER_VALUES = ['Converged', 'Split', 'Insufficient'];
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* The chair's opening instruction when the bench raised claims (the ordinary
|
|
41
|
+
* case). Task mode asks for an ANSWER, not an assessment: the chair adopts,
|
|
42
|
+
* merges or refuses, and the disputed claims its answer still rests on are
|
|
43
|
+
* named in a RESIDUAL RISK section rather than left implicit.
|
|
44
|
+
*
|
|
45
|
+
* ⚠️ v4.9 fix round 2 (council C3) — the close carries an ESCAPE HATCH, and it
|
|
46
|
+
* is the same lesson as the no-claims twin below, one bench-shape over. That
|
|
47
|
+
* twin drops the section entirely because a bench that declared NO adjudicable
|
|
48
|
+
* claims cannot have disputed any. But this instruction runs on the commoner
|
|
49
|
+
* middle case: claims WERE raised and adjudicated, and every one of them was
|
|
50
|
+
* concurred. "The claims peers disputed" then names the empty set, and the only
|
|
51
|
+
* satisfiable reading of an unfollowable directive is to invent material —
|
|
52
|
+
* LC-10 exactly. Naming the honest line is a satisfiable instruction for that
|
|
53
|
+
* bench; deleting the section here is not an option, because unlike the twin
|
|
54
|
+
* this template cannot know at composition time whether any dispute survived.
|
|
55
|
+
*/
|
|
56
|
+
const TASK_CHAIR_SYNTHESIS =
|
|
57
|
+
'You are the council chair. Write the synthesized ANSWER across the responses, ' +
|
|
58
|
+
'rankings, and adjudications below: adopt the strongest response, merge ' +
|
|
59
|
+
'complementary ones, or refuse the premise if the bench showed it unsound. State ' +
|
|
60
|
+
'the consensus, the disagreements and which way they went, and close the synthesis ' +
|
|
61
|
+
'with a RESIDUAL RISK section — the claims peers disputed that your answer still ' +
|
|
62
|
+
'depends on, or the single line "RESIDUAL RISK: none — no load-bearing claim was ' +
|
|
63
|
+
'disputed." when that is the truth.';
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* The chair's opening instruction when the bench declared no adjudicable claims
|
|
67
|
+
* — the LC-10-shaped twin, and it carries that lesson twice.
|
|
68
|
+
*
|
|
69
|
+
* LC-10's rule is that an instruction whose only satisfiable reading is to
|
|
70
|
+
* invent material must be REPLACED, never amended: the review twin swaps
|
|
71
|
+
* CHAIR_TASK out entirely rather than appending a correction after it. Task
|
|
72
|
+
* mode inherits that, and drops one thing more — the RESIDUAL RISK close. That
|
|
73
|
+
* section is defined as "the claims peers disputed", which is precisely the set
|
|
74
|
+
* a clean bench just declared empty; asking for it here would be the same
|
|
75
|
+
* unfollowable directive in a new place.
|
|
76
|
+
*/
|
|
77
|
+
const TASK_CHAIR_SYNTHESIS_NO_CLAIMS =
|
|
78
|
+
'You are the council chair. Write the synthesized ANSWER across the responses and ' +
|
|
79
|
+
'rankings below: adopt the strongest response, merge complementary ones, or refuse ' +
|
|
80
|
+
'the premise if the bench showed it unsound. Weigh each analyst\'s standing by rank ' +
|
|
81
|
+
'position. NOTE: this bench declared NO adjudicable claims. Every analyst did the ' +
|
|
82
|
+
'work and reported no discrete claim needing adjudication, which is a valid outcome ' +
|
|
83
|
+
'— not a failed run. Synthesize on that basis: say what the responses actually ' +
|
|
84
|
+
'establish and where the bench\'s agreement is thin, and do not manufacture disputes ' +
|
|
85
|
+
'to fill the sections below.';
|
|
86
|
+
|
|
87
|
+
/**
|
|
88
|
+
* Pushed after the adjudications the chair is about to weigh (R8-style
|
|
89
|
+
* placement — the caveat is read while the votes are still in view). Peer
|
|
90
|
+
* agreement on a generative bench is correlation between models trained on
|
|
91
|
+
* overlapping priors, not independent verification, and a task chair that
|
|
92
|
+
* reads "3 agree" as evidence overstates exactly what the tier table cannot.
|
|
93
|
+
*/
|
|
94
|
+
const TASK_CONCURRENCE_CAVEAT =
|
|
95
|
+
'Peer agreement on a claim is CONCURRENCE, not verification — models correlate on ' +
|
|
96
|
+
'priors. Weigh adjudications accordingly.';
|
|
97
|
+
|
|
98
|
+
/**
|
|
99
|
+
* The ANSWER-scale addendum. Its first seven lines are
|
|
100
|
+
* `briefings-chair.js :: VERDICT_SCALE_ADDENDUM`'s, verbatim (V11: the
|
|
101
|
+
* two-closing-sections framing, the whole HARD QUESTIONS item and the
|
|
102
|
+
* "final line, alone" rule are scale-independent) — pinned as a prefix in
|
|
103
|
+
* tests/council/briefings-chair-task.test.js so the shared half cannot fork
|
|
104
|
+
* silently. Only the phrase list and its gloss are task-specific.
|
|
105
|
+
*/
|
|
106
|
+
const ANSWER_SCALE_ADDENDUM = [
|
|
107
|
+
'After your synthesis, add two closing sections:',
|
|
108
|
+
'',
|
|
109
|
+
'1. HARD QUESTIONS — three to five questions the material\'s author has probably not',
|
|
110
|
+
' asked themselves, chosen so that an unanswerable question reveals a structural gap',
|
|
111
|
+
' (not gotchas — questions whose answers should exist).',
|
|
112
|
+
'2. A final line, alone on the last line, containing ONLY the phrase — no rationale, no',
|
|
113
|
+
' dash, no trailing text of any kind — exactly one of:',
|
|
114
|
+
'',
|
|
115
|
+
' ANSWER: Converged',
|
|
116
|
+
' ANSWER: Split',
|
|
117
|
+
' ANSWER: Insufficient',
|
|
118
|
+
'',
|
|
119
|
+
' Pick one. "Converged" = the bench substantially agrees and the synthesis above is',
|
|
120
|
+
' well-supported. "Split" = material disagreement remains; the synthesis states both',
|
|
121
|
+
' positions and what would settle them. "Insufficient" = the bench\'s work cannot',
|
|
122
|
+
' support an answer — missing information, an unsound premise, or too little usable',
|
|
123
|
+
' output. Name which, in the synthesis ABOVE, not on the ANSWER line.',
|
|
124
|
+
].join('\n');
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* One-shot chair repair, task twin: the ANSWER line was missing.
|
|
128
|
+
*
|
|
129
|
+
* Mirrors `briefings-chair.js :: buildChairRepairPrompt` block for block,
|
|
130
|
+
* because LC-12 carries unchanged — the chair leg SUCCEEDED and only the
|
|
131
|
+
* terminal line is missing, so the synthesis rides along and the repair solo
|
|
132
|
+
* picks the phrase its own prose supports instead of re-deriving one from
|
|
133
|
+
* nothing.
|
|
134
|
+
* @param {{synthesis?: string}} [args]
|
|
135
|
+
*/
|
|
136
|
+
function buildTaskChairRepairPrompt({ synthesis } = {}) {
|
|
137
|
+
const text = typeof synthesis === 'string' ? synthesis.trim() : '';
|
|
138
|
+
const prior = text
|
|
139
|
+
? ['--- YOUR SYNTHESIS (verbatim — answer on THIS) ---', text,
|
|
140
|
+
'--- END OF YOUR SYNTHESIS ---'].join('\n')
|
|
141
|
+
: null;
|
|
142
|
+
return [
|
|
143
|
+
'Do NOT use any tools or read any files; everything is in this message; begin '
|
|
144
|
+
+ 'immediately with the ANSWER line.',
|
|
145
|
+
...(prior ? [prior] : []),
|
|
146
|
+
'Your synthesis was received, but the final parseable line was missing. Emit ONLY '
|
|
147
|
+
+ 'one line, exactly one of:',
|
|
148
|
+
'ANSWER: Converged',
|
|
149
|
+
'ANSWER: Split',
|
|
150
|
+
'ANSWER: Insufficient',
|
|
151
|
+
].join('\n\n');
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
module.exports = {
|
|
155
|
+
CHAIR_ANSWER_VALUES,
|
|
156
|
+
ANSWER_SCALE_ADDENDUM,
|
|
157
|
+
TASK_CHAIR_SYNTHESIS,
|
|
158
|
+
TASK_CHAIR_SYNTHESIS_NO_CLAIMS,
|
|
159
|
+
TASK_CONCURRENCE_CAVEAT,
|
|
160
|
+
buildTaskChairRepairPrompt,
|
|
161
|
+
};
|
|
@@ -13,16 +13,19 @@
|
|
|
13
13
|
// hazard above does not apply to it. Do NOT require ./briefings-stage2.
|
|
14
14
|
const { displayName } = require('./seats');
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
// v4.9 W7: the preamble's LAST WORD is the only fork ('verdict.' | 'answer.'), so
|
|
17
|
+
// the shared sentence keeps ONE spelling. This constant's value is unchanged.
|
|
18
|
+
const CHAIR_NO_TOOLS_LEAD =
|
|
17
19
|
'Do NOT use any tools or read any files; everything is in this message; ' +
|
|
18
|
-
'begin immediately with the
|
|
20
|
+
'begin immediately with the ';
|
|
21
|
+
const CHAIR_NO_TOOLS_PREAMBLE = CHAIR_NO_TOOLS_LEAD + 'verdict.';
|
|
19
22
|
|
|
20
23
|
const CHAIR_VERDICT_VALUES = ['Ship it', 'Fix these first', 'Fundamental rethink'];
|
|
21
24
|
|
|
22
25
|
/** Shared date line (spec §4.3) — prepended to every model-facing briefing. */
|
|
23
26
|
function dateLine(date) { return `Today's date is ${date}.`; }
|
|
24
27
|
|
|
25
|
-
/** Verdict-scale addendum (SEAT-BRIEFS.md § Chair
|
|
28
|
+
/** Verdict-scale addendum (SEAT-BRIEFS.md § Chair closing-scale addendum; always on headless). */
|
|
26
29
|
const VERDICT_SCALE_ADDENDUM = [
|
|
27
30
|
'After your synthesis, add two closing sections:',
|
|
28
31
|
'',
|
|
@@ -153,8 +156,12 @@ function seatKeyedOrder(order, orderSeats) {
|
|
|
153
156
|
* `rankings` and `adjudications` may both be empty — an all-clean bench (LC-10)
|
|
154
157
|
* has nothing to adjudicate, and a Stage 2 whose judges all died has nothing to
|
|
155
158
|
* rank. Each empty section says WHICH of those it is rather than rendering blank.
|
|
159
|
+
* `intent` (v4.9 W7): `'task'` composes the twins from ./briefings-chair-task;
|
|
160
|
+
* absent — or anything else, fail-closed — composes this packet byte-identically.
|
|
161
|
+
* The four seams it moves, and the V11 vocabulary it leaves shared, are listed
|
|
162
|
+
* in briefings-chair-task.js's docblock.
|
|
156
163
|
*/
|
|
157
|
-
function buildChairPacket({ reviews, rankings, adjudications, tierCounts, date, findings }) {
|
|
164
|
+
function buildChairPacket({ reviews, rankings, adjudications, tierCounts, date, findings, intent }) {
|
|
158
165
|
// ⚠️ v4.8 SI-25: all three rendering sites below are SEAT-KEYED with an ALIAS
|
|
159
166
|
// FALLBACK. Alias-keyed, a twin bench handed the chair "tier counts:
|
|
160
167
|
// {Confirmed: 1}" beside two identical `A1 — deepseek:` lines, with nothing in
|
|
@@ -203,9 +210,14 @@ function buildChairPacket({ reviews, rankings, adjudications, tierCounts, date,
|
|
|
203
210
|
const raisedCount = Object.values(tierCounts || {})
|
|
204
211
|
.reduce((s, n) => s + (typeof n === 'number' ? n : 0), 0);
|
|
205
212
|
const tiers = JSON.stringify(tierCounts);
|
|
206
|
-
|
|
213
|
+
// Lazy, AT CALL TIME (the W6 dispatcher shape) — a top-level require would bind
|
|
214
|
+
// the two chair modules into a cycle the moment the twins need a fragment back.
|
|
215
|
+
const task = intent === 'task' ? require('./briefings-chair-task') : null;
|
|
216
|
+
const parts = [CHAIR_NO_TOOLS_LEAD + (task ? 'answer.' : 'verdict.')];
|
|
207
217
|
if (date) { parts.push(dateLine(date)); }
|
|
208
|
-
parts.push(
|
|
218
|
+
parts.push(task
|
|
219
|
+
? (raisedCount === 0 ? task.TASK_CHAIR_SYNTHESIS_NO_CLAIMS : task.TASK_CHAIR_SYNTHESIS)
|
|
220
|
+
: (raisedCount === 0 ? CHAIR_TASK_NO_FINDINGS : CHAIR_TASK));
|
|
209
221
|
parts.push(
|
|
210
222
|
`Deterministic tier counts (peers-only cascade): ${tiers}`,
|
|
211
223
|
'--- STAGE-1 REVIEWS (de-anonymized) ---',
|
|
@@ -228,7 +240,10 @@ function buildChairPacket({ reviews, rankings, adjudications, tierCounts, date,
|
|
|
228
240
|
+ ' Weigh them accordingly.',
|
|
229
241
|
);
|
|
230
242
|
}
|
|
231
|
-
|
|
243
|
+
// R8's placement rule, applied again: after the votes it qualifies, before the
|
|
244
|
+
// scale — read while the adjudications are still in view.
|
|
245
|
+
if (task) { parts.push(task.TASK_CONCURRENCE_CAVEAT); }
|
|
246
|
+
parts.push(task ? task.ANSWER_SCALE_ADDENDUM : VERDICT_SCALE_ADDENDUM);
|
|
232
247
|
return parts.join('\n\n');
|
|
233
248
|
}
|
|
234
249
|
|
|
@@ -260,9 +275,19 @@ function buildChairRepairPrompt({ synthesis } = {}) {
|
|
|
260
275
|
].join('\n\n');
|
|
261
276
|
}
|
|
262
277
|
|
|
278
|
+
/**
|
|
279
|
+
* Chair repair-prompt dispatcher (v4.9 W7, the W6 shape): `'task'` asks for the
|
|
280
|
+
* ANSWER line, anything else for the VERDICT line, byte-identically. Lazy require.
|
|
281
|
+
*/
|
|
282
|
+
function chairRepairPromptFor(intent, args) {
|
|
283
|
+
return intent === 'task'
|
|
284
|
+
? require('./briefings-chair-task').buildTaskChairRepairPrompt(args)
|
|
285
|
+
: buildChairRepairPrompt(args);
|
|
286
|
+
}
|
|
287
|
+
|
|
263
288
|
module.exports = {
|
|
264
289
|
dateLine,
|
|
265
|
-
CHAIR_NO_TOOLS_PREAMBLE,
|
|
290
|
+
CHAIR_NO_TOOLS_PREAMBLE, chairRepairPromptFor,
|
|
266
291
|
CHAIR_VERDICT_VALUES,
|
|
267
292
|
VERDICT_SCALE_ADDENDUM,
|
|
268
293
|
CHAIR_TASK,
|
|
@@ -9,6 +9,23 @@
|
|
|
9
9
|
* trailing-JSON style (prose first, one fenced ```json block last, nothing
|
|
10
10
|
* after it). Every brief opens with the no-tools preamble and the date stamp
|
|
11
11
|
* (spec §4.3). No IO, no model calls.
|
|
12
|
+
*
|
|
13
|
+
* v4.9 W7 (ruling V2) — the TASK twins live IN THIS FILE rather than in a
|
|
14
|
+
* sibling module the way W6's Stage-1 twins do: the fork is one PARAGRAPH per
|
|
15
|
+
* builder, the file is well under the 300-line gate, and a sibling would have
|
|
16
|
+
* to re-import every renderer below. Each builder takes `intent` on its args
|
|
17
|
+
* object; `'task'` composes the task frame and ANYTHING ELSE — absent included
|
|
18
|
+
* — composes the review frame byte-identically, fail-closed exactly like
|
|
19
|
+
* briefings.js's Stage-1 dispatchers. run-debate.js :: runDefenseSolo and
|
|
20
|
+
* run-debate-revote.js :: runRevoteWave are the only two callers; both thread
|
|
21
|
+
* `ctx.o.intent` straight through.
|
|
22
|
+
*
|
|
23
|
+
* V11 (one vocabulary) as extended here: ONLY the frame paragraph forks. The
|
|
24
|
+
* no-tools preamble, the date line, both trailing-JSON contracts, both
|
|
25
|
+
* finding-block renderers (`- <id> [<severity>]`, the anonymized peer split,
|
|
26
|
+
* the **AMENDED** mark), the shared `repair()` and buildDebateAddendum are one
|
|
27
|
+
* spelling across both intents — which is also why parse-stage2.js ::
|
|
28
|
+
* parseDebateDefense / parseRevote are frame-neutral and W7 does not touch them.
|
|
12
29
|
*/
|
|
13
30
|
|
|
14
31
|
const { dateLine } = require('./briefings-stage2');
|
|
@@ -78,16 +95,63 @@ function findingBlockRevote(f) {
|
|
|
78
95
|
return `- ${f.id} [${f.severity}]${mark}: ${f.claim}\n Raiser's response: ${f.argument}`;
|
|
79
96
|
}
|
|
80
97
|
|
|
81
|
-
/**
|
|
82
|
-
|
|
98
|
+
/** Defense frame — REVIEW intent, verbatim from v4.0 (spec §5.3a). */
|
|
99
|
+
const DEFENSE_FRAME =
|
|
100
|
+
'You reviewed an artifact and raised the findings below. Peer reviewers ' +
|
|
101
|
+
'(anonymous) disputed them for the stated reasons. For EACH finding decide: ' +
|
|
102
|
+
'DEFEND it with evidence, AMEND it with corrected replacement text, or WITHDRAW ' +
|
|
103
|
+
'it. Withdraw anything you cannot defend with evidence — an unsupported repeat ' +
|
|
104
|
+
'of the original claim is weaker than a withdrawal.';
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Defense frame — TASK intent (v4.9 W7, ruling V2). Mirrors the review frame's
|
|
108
|
+
* structure: same three actions, same DEFENSE_CONTRACT, same block list. What
|
|
109
|
+
* a raiser defends here is a load-bearing CLAIM under its OWN answer, not a
|
|
110
|
+
* finding about someone else's artifact — the frame is the only thing that has
|
|
111
|
+
* to know that.
|
|
112
|
+
*
|
|
113
|
+
* ⚠️ PR #200 C4: the closing sentence is the review frame's CALIBRATION
|
|
114
|
+
* INCENTIVE, restored claim-worded. W7 wrote this frame fresh and dropped it,
|
|
115
|
+
* which quietly made the task debate a worse instrument than the review one:
|
|
116
|
+
* without it the cheapest move for a disputed raiser is to restate the claim,
|
|
117
|
+
* and a debate that rewards stubbornness measures conviction rather than
|
|
118
|
+
* evidence. Only 'anything' → 'any claim' changes; the rest is the review
|
|
119
|
+
* wording verbatim, because the point it makes is mode-neutral.
|
|
120
|
+
*/
|
|
121
|
+
const TASK_DEFENSE_FRAME =
|
|
122
|
+
'You produced an answer and declared the claims below as load-bearing. Peer ' +
|
|
123
|
+
'analysts (anonymous) disputed them. For each claim: defend it with your ' +
|
|
124
|
+
'strongest argument, amend it if the dispute exposed a real flaw, or withdraw it. ' +
|
|
125
|
+
'Withdraw any claim you cannot defend with evidence — an unsupported repeat ' +
|
|
126
|
+
'of the original claim is weaker than a withdrawal.';
|
|
127
|
+
|
|
128
|
+
/** Re-vote frame — REVIEW intent, verbatim from v4.0 (spec §5.3b). */
|
|
129
|
+
const REVOTE_FRAME =
|
|
130
|
+
'You previously adjudicated findings on this artifact and disputed at least ' +
|
|
131
|
+
'one of those below. The (anonymous) raiser has now responded. Re-adjudicate ' +
|
|
132
|
+
'ONLY the findings listed, in light of each response. Changing your verdict ' +
|
|
133
|
+
'when the defense is convincing is good judging, not weakness; so is holding ' +
|
|
134
|
+
'your dispute when it isn\'t.';
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Re-vote frame — TASK intent (v4.9 W7, ruling V2); same REVOTE_CONTRACT verdicts.
|
|
138
|
+
* ⚠️ PR #200 C4: closes on the review frame's judge-side calibration incentive,
|
|
139
|
+
* reused VERBATIM — nothing in it speaks of findings-vs-claims, and a judge who
|
|
140
|
+
* reads holding a dispute as strength is the same failure on either scale.
|
|
141
|
+
*/
|
|
142
|
+
const TASK_REVOTE_FRAME =
|
|
143
|
+
'You previously adjudicated claims from this bench\'s answers and disputed at ' +
|
|
144
|
+
'least one of those below. The raiser has now responded. Re-vote each claim: ' +
|
|
145
|
+
'agree / dispute / neutral. Changing your verdict ' +
|
|
146
|
+
'when the defense is convincing is good judging, not weakness; so is holding ' +
|
|
147
|
+
'your dispute when it isn\'t.';
|
|
148
|
+
|
|
149
|
+
/** One defense solo per raiser (spec §5.3a). `intent`: 'task' | absent (review). */
|
|
150
|
+
function buildDefenseBrief({ findings, date, intent }) {
|
|
83
151
|
const parts = [DEBATE_NO_TOOLS_PREAMBLE];
|
|
84
152
|
if (date) { parts.push(dateLine(date)); }
|
|
85
153
|
parts.push(
|
|
86
|
-
|
|
87
|
-
'(anonymous) disputed them for the stated reasons. For EACH finding decide: ' +
|
|
88
|
-
'DEFEND it with evidence, AMEND it with corrected replacement text, or WITHDRAW ' +
|
|
89
|
-
'it. Withdraw anything you cannot defend with evidence — an unsupported repeat ' +
|
|
90
|
-
'of the original claim is weaker than a withdrawal.',
|
|
154
|
+
intent === 'task' ? TASK_DEFENSE_FRAME : DEFENSE_FRAME,
|
|
91
155
|
findings.map(findingBlockDefense).join('\n\n'),
|
|
92
156
|
DEFENSE_CONTRACT,
|
|
93
157
|
);
|
|
@@ -95,15 +159,11 @@ function buildDefenseBrief({ findings, date }) {
|
|
|
95
159
|
}
|
|
96
160
|
|
|
97
161
|
/** One shared re-vote bundle, fanned out to disputing judges (spec §5.3b). */
|
|
98
|
-
function buildRevoteBundle({ findings, date }) {
|
|
162
|
+
function buildRevoteBundle({ findings, date, intent }) {
|
|
99
163
|
const parts = [DEBATE_NO_TOOLS_PREAMBLE];
|
|
100
164
|
if (date) { parts.push(dateLine(date)); }
|
|
101
165
|
parts.push(
|
|
102
|
-
|
|
103
|
-
'one of those below. The (anonymous) raiser has now responded. Re-adjudicate ' +
|
|
104
|
-
'ONLY the findings listed, in light of each response. Changing your verdict ' +
|
|
105
|
-
'when the defense is convincing is good judging, not weakness; so is holding ' +
|
|
106
|
-
'your dispute when it isn\'t.',
|
|
166
|
+
intent === 'task' ? TASK_REVOTE_FRAME : REVOTE_FRAME,
|
|
107
167
|
findings.map(findingBlockRevote).join('\n\n'),
|
|
108
168
|
REVOTE_CONTRACT,
|
|
109
169
|
);
|
|
@@ -116,6 +176,12 @@ function buildRevoteBundle({ findings, date }) {
|
|
|
116
176
|
* `prior` is embedded verbatim and uncapped; the absent case is stated rather
|
|
117
177
|
* than papered over with an empty block, so a model with nothing to correct is
|
|
118
178
|
* told to say so instead of left to invent a position.
|
|
179
|
+
*
|
|
180
|
+
* NO intent fork (v4.9 W7, ruling V2 — verified, not forked): a repair turn
|
|
181
|
+
* carries the trailing-JSON CONTRACT and the prior text, never a frame, so
|
|
182
|
+
* there is nothing here that speaks in findings-vs-claims. Both repair builders
|
|
183
|
+
* therefore stay one spelling for both intents; pinned in
|
|
184
|
+
* tests/council/briefings-debate.test.js ("frame-neutral in both intents").
|
|
119
185
|
* @param {string} kind 'defense' | 're-vote'
|
|
120
186
|
* @param {string} contract the trailing-JSON contract for that kind
|
|
121
187
|
* @param {Array<{code:string,detail:string}>} errors
|