aiki-cli 0.3.1 → 0.3.3

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +80 -5
  2. package/README.md +73 -15
  3. package/dist/bench/idea-v3-bench.js +104 -5
  4. package/dist/bench/idea-v3-rating.js +159 -4
  5. package/dist/cli/bench.js +5 -5
  6. package/dist/cli/index.js +12 -2
  7. package/dist/cli/resume.js +20 -0
  8. package/dist/cli/run.js +76 -5
  9. package/dist/cli/serve.js +48 -0
  10. package/dist/config/config.js +7 -2
  11. package/dist/council/view.js +64 -15
  12. package/dist/orchestration/auto-profile.js +97 -0
  13. package/dist/orchestration/claim-groups.js +56 -0
  14. package/dist/orchestration/context.js +69 -6
  15. package/dist/orchestration/decision-dossier.js +450 -89
  16. package/dist/orchestration/decision-graph.js +33 -2
  17. package/dist/orchestration/engine.js +8 -4
  18. package/dist/orchestration/evidence-origin.js +17 -0
  19. package/dist/orchestration/jsonStage.js +47 -5
  20. package/dist/orchestration/legacy-idea-adapter.js +3 -0
  21. package/dist/orchestration/modes.js +18 -10
  22. package/dist/orchestration/preflight.js +36 -3
  23. package/dist/orchestration/quick-analysis.js +29 -7
  24. package/dist/orchestration/sanitize-paths.js +10 -0
  25. package/dist/orchestration/stages/s10-render.js +196 -84
  26. package/dist/orchestration/stages/s4-analyze.js +10 -2
  27. package/dist/orchestration/stages/s4b-challenge.js +97 -0
  28. package/dist/orchestration/stages/s5-drift.js +13 -3
  29. package/dist/orchestration/stages/s6-positions.js +18 -0
  30. package/dist/orchestration/stages/s7-decision-graph.js +3 -0
  31. package/dist/orchestration/stages/s8-verify.js +66 -12
  32. package/dist/orchestration/stages/s8b-rebuttal.js +11 -4
  33. package/dist/orchestration/stages/s9-judge.js +52 -14
  34. package/dist/orchestration/stages/s9b-plan.js +227 -48
  35. package/dist/orchestration/url-sources.js +21 -0
  36. package/dist/providers/adapter-core.js +1 -1
  37. package/dist/providers/claude.js +18 -0
  38. package/dist/schemas/index.js +112 -3
  39. package/dist/serve/flight-deck.js +830 -0
  40. package/dist/serve/followup.js +50 -0
  41. package/dist/serve/frames.js +168 -0
  42. package/dist/serve/gates.js +72 -0
  43. package/dist/serve/projections.js +283 -0
  44. package/dist/serve/server.js +219 -0
  45. package/dist/serve/threads.js +145 -0
  46. package/dist/serve-ui/Five.png +0 -0
  47. package/dist/serve-ui/app.js +820 -0
  48. package/dist/serve-ui/index.html +171 -0
  49. package/dist/serve-ui/workspace.css +662 -0
  50. package/dist/skills/idea-refinement/analyst.md +5 -5
  51. package/dist/skills/idea-refinement/chair.md +18 -0
  52. package/dist/skills/idea-refinement/economics-delivery.md +11 -0
  53. package/dist/skills/idea-refinement/market-adoption.md +12 -0
  54. package/dist/skills/idea-refinement/planner.md +25 -19
  55. package/dist/skills/idea-refinement/rebuttal.md +15 -0
  56. package/dist/skills/idea-refinement/verifier.md +17 -0
  57. package/dist/storage/runs.js +2 -1
  58. package/dist/workflows/idea-refinement.js +130 -24
  59. package/package.json +2 -2
package/CHANGELOG.md CHANGED
@@ -1,18 +1,93 @@
1
1
  # Changelog
2
2
 
3
- ## Unreleased
3
+ ## Unreleased — v7 adaptive deliberation
4
+
5
+ ### Added
6
+ - **`aiki run idea-refinement --mode auto`** — a deterministic quick-vs-council routing decision made
7
+ before any provider call. Escalates to `council` when the prompt supplies URLs or an evidence pack,
8
+ hits a regulated/financial/security topic, requests deliverables beyond a decision, uses research
9
+ wording, or runs long (>120 words); otherwise `quick`. Prints the resolved mode + reason and persists
10
+ it to `meta.auto_decision`. No learned routing (§22): the rules are fixed and inspectable. `meta.mode`
11
+ stays `quick|council`; legacy metadata still parses and explicit modes keep their existing artifacts.
12
+ - **One-call auto fast path (v7 Phase C).** A short DECISION-only request with a clear decision verb
13
+ and subject skips the two model preflight readings and builds the same typed contract deterministically.
14
+ Ambiguous prompts keep the existing quick path; explicit quick/council runs are unchanged. Reports label
15
+ the result: `Single-pass analysis; council escalation was not required.`
16
+ - **Structural auto escalation (v7 Phase D).** Validated analyst output now triggers a deterministic
17
+ failover for unsupported decisive claims, model-knowledge STOP/PIVOT claims, failed calculations,
18
+ contradictory supplied evidence, or load-bearing disagreement. Auto runs use 1–4 calls: two existing
19
+ preflight readings, one primary analysis, and at most one slim claim-only challenger call. The receipt
20
+ records every trigger; explicit quick/council paths remain unchanged.
21
+ - **Per-call token accounting (v7 Phase A).** Every call records normalized input/output token usage —
22
+ provider-reported for Claude, a labeled `chars/4` estimate otherwise — summed into `meta.usage_totals`
23
+ and shown as a `Tokens:` line in the dossier and terminal summary. Purely additive; behavior unchanged.
24
+
25
+ ## Unreleased — v6 council integrity
26
+
27
+ Root-cause pass driven by exact replay of run `20260717-1219` (`plan/AIKI-v6-council-integrity-plan.md`);
28
+ every fix is pinned by tests on the real payloads that failed live (`test/fixtures/f740-*`).
29
+
30
+ ### Fixed
31
+ - **A parseable plan is never discarded again.** The planner's complete feature backlog and
32
+ implementation plan were replaced by "deliverables unavailable" over a 13-character headline
33
+ overflow with no repair budget. `jsonCall` now applies deterministic salvage before failing when
34
+ repair is unavailable; lossy coercion clips over-max strings at a word boundary; empty optional
35
+ strings are dropped losslessly (they carry no information). Partial planner answers are kept and
36
+ flagged `plan_partial` instead of vanishing.
37
+ - **The chair's judgment is no longer overruled by code.** The `PARTIAL/UNVERIFIABLE ⇒ must be
38
+ UNRESOLVED` rule (which flattened 5 nuanced rulings and forced a 125s Opus repair) is deleted;
39
+ ruling HOLDS against positively CONTRADICTED evidence remains fatal.
40
+ - **S4 schema-ceremony repairs are free now.** Empty rationale strings validate via lossless
41
+ coercion (was a 267s paid repair); genuinely lossy defects salvage without a call when the budget
42
+ is tight (repairs stay gated behind a 3-call reserved tail: chair + planner + one tail repair).
43
+ - Chair prose is no longer regex-mangled ("confirmed-unverified" stays the chair's own words);
44
+ enum rewrites apply to UPPERCASE leaks only. A `NOT_COMPUTABLE` payback block is omitted instead
45
+ of announced. Rendered Markdown/HTML never contain local filesystem usernames.
46
+
47
+ ### Added
48
+ - **Local council workspace:** `aiki serve` opens a loopback-only three-pane browser workspace with cached
49
+ provider status, decision history, server-enforced file/URL/spend gates, live SSE stage and call telemetry,
50
+ cancellation, path-free verdict cards, and responsive desktop/mobile layouts.
51
+ - Completed decisions accept one-call follow-up questions labeled `no council`; failed or cancelled runs with
52
+ cached calls can be resumed after an explicit spend review. Settings atomically persist provider model pins
53
+ and role assignments without exposing credentials, local paths, or arbitrary JSON to the browser.
54
+ - A dependency-free hosted replay, real workspace screenshot, and timed three-minute demo script provide a
55
+ safe submission path when judges do not have the three provider CLIs installed.
56
+ - **Semantic claim join at zero extra calls:** the S8 verifier may emit `claim_groups` over an
57
+ anonymous all-claims index; validated groups (unknown ids and single-provider groups dropped)
58
+ overlay CONSENSUS/DISAGREEMENT states through the existing state machine. Cross-provider
59
+ paraphrases — the reason every report said "0 consensus · 0 disputes" — now join.
60
+ - **Evidence origin honesty:** every card is classified EXTERNAL / USER_MATERIAL / MODEL_KNOWLEDGE;
61
+ confidence's evidence-quality term counts external only; the user's own material renders as
62
+ "consistent with your materials (not independently checked)", never VERIFIED; the Sources section
63
+ states how many independent external sources the run actually had.
64
+ - **Blocked-source permission prompt:** when a supplied URL cannot be read, an interactive run now
65
+ asks right in the terminal before any paid call — `Proceed without it? [y/N]` — the same
66
+ allow/deny pattern provider CLIs use for internet or file access. Deny prints how to proceed
67
+ (paste the page text into the prompt, or `--allow-blocked-sources`); headless runs fail fast
68
+ with the same instructions. (The previous behavior burned a full council budget around a 403'd
69
+ page.)
70
+
71
+ ## 0.3.2 — 2026-07-17
4
72
 
5
73
  ### Added
6
74
  - Public URL snapshots for idea prompts, including an npm registry adapter, private-network rejection,
7
- auditable `FETCHED` / `BLOCKED` / `FAILED` status, and a research-mode gate that stops before model calls
75
+ auditable `FETCHED` / `BLOCKED` / `FAILED` status, and a full-council gate that stops before model calls
8
76
  when a supplied source cannot be read.
9
77
  - Requested-output planning: prompts that ask for a feature list or implementation plan now carry that
10
78
  requirement through the decision contract, the existing planner call, machine JSON, Markdown, and HTML.
11
79
 
12
80
  ### Changed
13
- - Explicit research wording selects research mode deterministically when `--mode` is absent; an explicit
14
- mode remains authoritative. Preflight asks zero to four questions and may not repeat facts already present
15
- in the user prompt or a fetched source.
81
+ - **Report v4 answer-first decisions** reports now lead with the requested deliverables and chair reasoning,
82
+ substitute readable claim labels for internal ids, dedupe and cap claim-named conditions, frame/cap risks,
83
+ summarize each council seat (including a plain-language `weak_seat` warning), and distinguish FACTUAL
84
+ claims from JUDGMENT calls so verification priority and coverage describe checkable facts honestly.
85
+ Deliverable detection now combines model-backed readings with a widened fallback that hears requests for
86
+ standout or "ultra-level" features.
87
+ - `council` now includes the complete source-investigation path, 8–10-call allowance, 12-call repair budget,
88
+ and 45-minute deadline. `research` remains accepted as a compatibility alias with identical behavior.
89
+ Preflight asks zero to four questions and may not repeat facts already present in the user prompt or a
90
+ fetched source.
16
91
  - Reader-facing report sections use human claim text instead of internal graph ids such as `G1`; raw ids
17
92
  remain in machine artifacts and the Markdown technical audit.
18
93
 
package/README.md CHANGED
@@ -12,7 +12,7 @@
12
12
  <img alt="Node ≥ 20" src="https://img.shields.io/badge/node-%E2%89%A5%2020-brightgreen.svg">
13
13
  <img alt="Local-first, no API keys" src="https://img.shields.io/badge/local--first-no%20API%20keys-informational.svg">
14
14
  <img alt="Read-only orchestration" src="https://img.shields.io/badge/orchestration-read--only-success.svg">
15
- <img alt="Tests" src="https://img.shields.io/badge/tests-381%20passing-success.svg">
15
+ <img alt="Tests" src="https://img.shields.io/badge/tests-579%20passing-success.svg">
16
16
  </p>
17
17
 
18
18
  ---
@@ -30,7 +30,21 @@ It does two jobs, well:
30
30
  aiki is **not** a general assistant. Trivia and chat are routed away, not answered — a council adds cost, not
31
31
  accuracy, when there's one right answer.
32
32
 
33
- **Jump to:** [Why](#why) · [What's new](#whats-new-in-030) · [How it works](#how-aiki-works-no-apis) · [Benchmarks](#benchmarks) · [Requirements](#requirements) · [Install](#install) · [Quickstart](#quickstart) · [The two workflows](#the-two-workflows) · [Example](#example-a-real-idea-run) · [Configuration](#configuration) · [Sessions & resume](#sessions--resume) · [Safety](#safety-model) · [Costs & limits](#costs--limits)
33
+ **Jump to:** [Workspace](#local-council-workspace) · [Why](#why) · [What's new](#whats-new-in-030) · [How it works](#how-aiki-works-no-apis) · [Benchmarks](#benchmarks) · [Requirements](#requirements) · [Install](#install) · [Quickstart](#quickstart) · [The two workflows](#the-two-workflows) · [Example](#example-a-real-idea-run) · [Configuration](#configuration) · [Sessions & resume](#sessions--resume) · [Safety](#safety-model) · [Costs & limits](#costs--limits)
34
+
35
+ ---
36
+
37
+ ## Local council workspace
38
+
39
+ Open the live local workspace with:
40
+
41
+ ```bash
42
+ npm i -g aiki-cli && aiki serve
43
+ ```
44
+
45
+ `aiki serve` opens a loopback-only browser workspace with decision history, explicit file/URL/spend approval,
46
+ live council stages, a verdict card, one-call follow-ups, and per-role model settings. It makes no provider
47
+ calls until you approve and convene a run.
34
48
 
35
49
  ---
36
50
 
@@ -50,9 +64,13 @@ stop copy-pasting between them by hand.
50
64
  - **Evidence-grounded decisions.** Supply local sources with `--evidence`; Aiki records their paths and
51
65
  hashes, checks freshness and citations, independently verifies selected load-bearing claims, and shows the
52
66
  remaining coverage gaps before the chair.
53
- - **Three bounded modes.** Use `quick` for one structured analyst, `council` for bounded multi-model
54
- deliberation, or `research` for current-fact work. Explicit research wording selects `research`
55
- deterministically; `--mode` always overrides it.
67
+ - **Two bounded behaviors.** Use `quick` for one structured analyst or `council` for the full multi-model
68
+ decision council with source investigation. `research` remains accepted as an alias for `council`.
69
+ `--mode auto` deterministically selects the entry policy from the prompt (URLs, an evidence pack, a
70
+ regulated/financial/security topic, requested deliverables, research wording, or a long input require
71
+ preflight) and prints the one-line reason — never a learned choice. A clear, short decision question starts
72
+ with one call; structural gaps in the typed output add the two readings and, only when useful, one targeted
73
+ challenger call (1–4 total). Use explicit `council` when you want the full multi-model council every time.
56
74
  - **A decision dossier, not an essay.** Reports lead with the recommendation, verified evidence coverage,
57
75
  decisive facts, first action, strongest counter-case, and critical unknowns. Financial and threshold-heavy
58
76
  decisions can include graph-anchored numbers, payback, option commitments, and a go/no-go tripwire.
@@ -181,7 +199,7 @@ Upgrade an existing installation:
181
199
 
182
200
  ```bash
183
201
  npm install -g aiki-cli@latest
184
- aiki --version # 0.3.0
202
+ aiki --version # 0.3.3
185
203
  ```
186
204
 
187
205
  From source:
@@ -198,9 +216,46 @@ npm link # puts `aiki` on your PATH (or run directly: node dist/cl
198
216
 
199
217
  ```bash
200
218
  cd /path/to/your/project
201
- aiki # opens the interactive home screen
219
+ aiki serve # opens the local council workspace in your browser
220
+ aiki # or open the interactive terminal home screen
202
221
  ```
203
222
 
223
+ ## The serve workspace
224
+
225
+ `aiki serve` is the recommended way to use Aiki. It starts a **loopback-only** web workspace (bound to
226
+ `127.0.0.1`, never exposed to your network) and opens it in your browser. **No provider is called until you
227
+ type a request and approve the run** — opening the workspace is free.
228
+
229
+ ```bash
230
+ aiki serve # binds the first free port in 4173–4183 and opens the browser
231
+ aiki serve --port 4180 # pin a specific port
232
+ aiki serve --no-open # start the server but don't auto-open the browser
233
+ ```
234
+
235
+ **What you'll see and do:**
236
+
237
+ 1. **Sessions rail (left).** Every past run, newest first. Click one to reopen its full decision brief;
238
+ runs that were killed or timed out are flagged **Resume**.
239
+ 2. **Composer (center).** Describe an idea to stress-test, or ask for a code review, and press **Enter**.
240
+ Plain text never spends anything on its own — you always get a convene step first.
241
+ 3. **Approval gates (inline).** Before any paid work, Aiki asks — per action — to read a **file**, fetch a
242
+ **URL**, or **spend** provider calls. Each gate offers **allow once**, **allow for this session**, or
243
+ **deny**. Nothing runs until you approve.
244
+ 4. **Live council.** A pipeline timeline shows each stage; the provider seat cards (Claude, Codex, Gemini)
245
+ light up as they work, with a running **call counter and budget bar** so you always see the spend.
246
+ 5. **Verdict card.** When the council finishes: a plain-language banner (go / proceed-with-conditions /
247
+ stop / inconclusive), a **confidence meter**, the anchored validation plan, expandable reasoning and
248
+ evidence, and a **Copy summary** button.
249
+ 6. **Cost receipt.** Per-provider call counts plus the token/cost receipt for the run, against your budget.
250
+ 7. **Follow-ups.** After a run completes you can ask a follow-up question — answered in **exactly one
251
+ read-only call, no new council** — or **Re-convene** to run a fresh full council on a revised question.
252
+ 8. **Settings.** Choose the model each provider uses and override per-role assignments (judge, verifier,
253
+ analyst, the two review seats, the follow-up responder). It shows the effective value vs. your local
254
+ override and never exposes credentials, keys, or paths.
255
+
256
+ Everything the workspace shows is backed by the same schema-validated, on-disk audit trail as the headless
257
+ CLI, so nothing on screen can drift from what was actually stored.
258
+
204
259
  ## Use Aiki For
205
260
 
206
261
  - Code review: "review this branch", "check this patch", "find bugs in this diff".
@@ -235,8 +290,9 @@ Plain text is never charged silently — you get a confirm step before any run s
235
290
  ```bash
236
291
  aiki run idea-refinement "a fridge-photo-to-recipe app for busy parents"
237
292
  aiki run idea-refinement "an early idea" --mode quick # one structured analyst; no council claim
293
+ aiki run idea-refinement "Should I use Postgres or MySQL?" --mode auto # starts at 1 call; structural gaps may escalate to 4
238
294
  aiki run idea-refinement ./idea.md
239
- aiki run idea-refinement ./idea.md --mode research --evidence ./research/ # grounded, source-verifying council
295
+ aiki run idea-refinement ./idea.md --mode council --evidence ./research/ # grounded, source-verifying council
240
296
  aiki run code-review --base main # review this branch vs main
241
297
  aiki run code-review --diff ./changes.patch # review a patch file
242
298
  aiki run code-review --cheap # Gemini+Codex review, Claude judges only disputes (~⅓ the Opus)
@@ -251,7 +307,7 @@ reads provider credential directories.
251
307
 
252
308
  Public `http(s)` links in an idea are snapshotted once before model calls and stored in
253
309
  `00a-url-sources.json`; every preflight reader and analyst sees that same text. npm package links use the
254
- registry metadata API. A snapshot records `FETCHED`, `BLOCKED`, or `FAILED` honestly. In research mode,
310
+ registry metadata API. A snapshot records `FETCHED`, `BLOCKED`, or `FAILED` honestly. In full council mode,
255
311
  an unreadable supplied link stops before paid calls and asks for pasted text or a public export instead of
256
312
  letting the council guess or grill you for facts that should have come from the link.
257
313
 
@@ -269,10 +325,9 @@ only the disputes → report.
269
325
  complementary analyst lanes → deterministic claim/evidence graph audit → only decision-critical verification
270
326
  or rebuttal → evidence-linked chair → validation planner. The report is a graph-backed **decision dossier**,
271
327
  not an essay.
272
- Choose `--mode quick` for one structured analyst, `--mode council` for the full decision council, or
273
- `--mode research` for source-grounded current-fact work. Without a flag, the default is council unless the
274
- request explicitly asks to research, browse, look up, or check current sources; that conservative rule is
275
- deterministic, and an explicit flag wins:
328
+ Choose `--mode quick` for one structured analyst or `--mode council` for the full source-investigating decision
329
+ council. `--mode research` is retained as a compatibility alias and runs the same council. Without a flag, the
330
+ default is the full council:
276
331
 
277
332
  - a **reader-first decision card** — recommendation, verified evidence coverage, decisive facts, first action,
278
333
  strongest counter-case, and critical unknowns before audit detail
@@ -360,9 +415,12 @@ This is the part that makes aiki trustworthy to point at a real repo:
360
415
  ## Costs & limits
361
416
 
362
417
  - **Runs cost real model calls** against your existing CLI subscriptions/quota. Idea refinement is nominally
363
- **3 calls in quick**, **6–8 in council**, or **8–10 in research** (schema repairs can add calls within the
364
- mode-aware budget); code review is about **5**. `aiki run` shows the mode, range, budget, and reserved
418
+ **3 calls in quick** or **8–10 in the full council** (`research` is the same behavior; schema repairs can add
419
+ calls within the mode-aware budget); code review is about **5**. `aiki run` shows the mode, range, budget, and reserved
365
420
  chair/planner calls before asking to confirm (skip with `--yes`).
421
+ - **Every run reports token usage.** The decision report and the serve cost receipt show input/output tokens
422
+ and reported cost per run — provider-reported where the CLI returns it, a labeled estimate otherwise — so
423
+ you see the real spend, not just the call count.
366
424
  - **Not a general assistant.** Questions and "explore my whole codebase" requests are redirected, not answered
367
425
  — aiki reviews a *diff* and vets a *stated idea*.
368
426
  - **Analysis, not advice.** Every report is a decision aid. Verify before acting.
@@ -3,15 +3,18 @@ import { access, mkdir, readFile, readdir, rename, writeFile } from 'node:fs/pro
3
3
  import { dirname, isAbsolute, join, relative, resolve, sep } from 'node:path';
4
4
  import { z } from 'zod';
5
5
  import { executeRun } from '../orchestration/engine.js';
6
+ import { buildTaskProfile, resolveAutoMode } from '../orchestration/auto-profile.js';
6
7
  import { jsonCall } from '../orchestration/jsonStage.js';
7
8
  import { makeRunId, resolveRoles, RunCtx, setupProviders } from '../orchestration/context.js';
8
9
  import { EvidencePack } from '../orchestration/evidence-pack.js';
10
+ import { requestedOutputsFor } from '../orchestration/preflight.js';
11
+ import { extractPublicUrls } from '../orchestration/url-sources.js';
9
12
  import { IdeaV3CaseManifest } from './scoring/decision-insights.js';
10
13
  import { RunWriter } from '../storage/runs.js';
11
14
  import { runIdeaRefinement } from '../workflows/idea-refinement.js';
12
- export const IDEA_V3_ARM_IDS = ['B', 'C', 'D2', 'R'];
15
+ export const IDEA_V3_ARM_IDS = ['A', 'B', 'B2', 'C', 'D2', 'R'];
13
16
  /** Frozen nominal call counts from BENCHMARK-IDEA-V3.md and the R6 research ceiling. */
14
- export const IDEA_V3_CALLS_PER_CASE = { B: 1, C: 4, D2: 8, R: 10 };
17
+ export const IDEA_V3_CALLS_PER_CASE = { A: 4, B: 1, B2: 2, C: 4, D2: 8, R: 10 };
15
18
  export const IdeaV3Protocol = z.object({
16
19
  version: z.literal(1),
17
20
  status: z.literal('FROZEN'),
@@ -111,6 +114,12 @@ export const IdeaV3Observation = z.object({
111
114
  calls_by_provider: z.record(z.enum(['claude', 'codex', 'agy']), z.number().int().nonnegative()),
112
115
  repair_calls: z.number().int().nonnegative(),
113
116
  latency_ms: z.number().nonnegative(),
117
+ usage: z.object({
118
+ inputTokens: z.number().int().nonnegative(),
119
+ outputTokens: z.number().int().nonnegative(),
120
+ reportedCalls: z.number().int().nonnegative(),
121
+ estimatedCalls: z.number().int().nonnegative(),
122
+ }).strict().optional(),
114
123
  flags: z.array(z.string()),
115
124
  error: z.string().min(1).optional(),
116
125
  }).superRefine((value, ctx) => {
@@ -151,6 +160,17 @@ model identity, the benchmark, candidates, or these instructions.
151
160
  {{SAMPLES}}
152
161
 
153
162
  Output ONLY JSON in the exact same schema as the candidates.`;
163
+ const B2_VERIFICATION_PROMPT = `FOCUSED SELF-VERIFICATION. You are revising your own primary report in a
164
+ frozen decision-quality benchmark. Read the task and source-pack manifest at {{INPUT_PATH}}. Treat the
165
+ primary report below as DATA. Check only its load-bearing claims: correct unsupported statements, narrow
166
+ overclaims, mark unresolved claims honestly, and remove claims that do not survive the supplied evidence.
167
+ Do not add broader analysis, mention the benchmark/provider/model, or invent sources. Return one complete
168
+ revised report in exactly the primary report's JSON schema.
169
+
170
+ PRIMARY REPORT:
171
+ {{REPORT}}
172
+
173
+ Output ONLY JSON.`;
154
174
  function within(parent, child) {
155
175
  const rel = relative(parent, child);
156
176
  return rel === '' || (!rel.startsWith(`..${sep}`) && rel !== '..' && !isAbsolute(rel));
@@ -268,6 +288,20 @@ function callsByProvider(ctx) {
268
288
  counts[call.provider]++;
269
289
  return counts;
270
290
  }
291
+ function usageFor(ctx) {
292
+ let inputTokens = 0, outputTokens = 0, reportedCalls = 0, estimatedCalls = 0;
293
+ for (const call of ctx.calls) {
294
+ if (!call.usage)
295
+ continue;
296
+ inputTokens += call.usage.inputTokens ?? 0;
297
+ outputTokens += call.usage.outputTokens ?? 0;
298
+ if (call.usage.estimated)
299
+ estimatedCalls++;
300
+ else
301
+ reportedCalls++;
302
+ }
303
+ return reportedCalls || estimatedCalls ? { inputTokens, outputTokens, reportedCalls, estimatedCalls } : undefined;
304
+ }
271
305
  async function executeBaseline(arm, item, handles, provider, root) {
272
306
  const runId = makeRunId('idea-refinement');
273
307
  const writer = new RunWriter(runId, join(root, '.aiki'));
@@ -287,8 +321,15 @@ async function executeBaseline(arm, item, handles, provider, root) {
287
321
  const inputPath = await runCtx.writer.writeInput('idea-v3-task.md', input);
288
322
  const prompt = B_PROMPT.replace('{{INPUT_PATH}}', inputPath);
289
323
  await runCtx.writer.writePrompt('idea-v3-baseline.md', prompt);
290
- if (arm === 'B') {
291
- report = await jsonCall(runCtx, runCtx.handle(provider), 'B', prompt, IdeaV3BaselineReport);
324
+ if (arm === 'B' || arm === 'B2') {
325
+ report = await jsonCall(runCtx, runCtx.handle(provider), arm === 'B' ? 'B' : 'B2-primary', prompt, IdeaV3BaselineReport);
326
+ if (arm === 'B2') {
327
+ const verification = B2_VERIFICATION_PROMPT
328
+ .replace('{{INPUT_PATH}}', inputPath)
329
+ .replace('{{REPORT}}', JSON.stringify(report, null, 2));
330
+ await runCtx.writer.writeRaw('B2-verification.prompt.txt', verification);
331
+ report = await jsonCall(runCtx, runCtx.handle(provider), 'B2-verify', verification, IdeaV3BaselineReport);
332
+ }
292
333
  }
293
334
  else {
294
335
  const samples = [];
@@ -302,6 +343,7 @@ async function executeBaseline(arm, item, handles, provider, root) {
302
343
  await runCtx.writer.writeRaw('idea-v3-baseline-report.json', JSON.stringify(report, null, 2));
303
344
  await runCtx.writer.writeText('final-report', renderBaselineReport(report));
304
345
  });
346
+ const usage = usageFor(ctx);
305
347
  const base = {
306
348
  case_id: item.id,
307
349
  arm,
@@ -310,12 +352,59 @@ async function executeBaseline(arm, item, handles, provider, root) {
310
352
  calls_by_provider: callsByProvider(ctx),
311
353
  repair_calls: ctx.calls.filter((call) => call.stage.endsWith('-repair')).length,
312
354
  latency_ms: Date.now() - started,
355
+ ...(usage ? { usage } : {}),
313
356
  flags: [...ctx.flags],
314
357
  };
315
358
  return IdeaV3Observation.parse(outcome.ok && report
316
359
  ? { ...base, status: 'ok', report_markdown: renderBaselineReport(report) }
317
360
  : { ...base, status: 'error', error: `${outcome.error?.code ?? 'CRASH'}: ${outcome.error?.message ?? 'baseline produced no report'}` });
318
361
  }
362
+ async function executeAuto(item, handles, root) {
363
+ const runId = makeRunId('idea-refinement');
364
+ const writer = new RunWriter(runId, join(root, '.aiki'));
365
+ const input = benchmarkInput(item);
366
+ const evidencePack = await caseEvidencePack(item);
367
+ const resolved = resolveAutoMode(buildTaskProfile(input, {
368
+ urlCount: extractPublicUrls(input).length,
369
+ hasEvidencePack: !!evidencePack,
370
+ requestedOutputs: requestedOutputsFor(input),
371
+ }));
372
+ const ctx = new RunCtx({
373
+ runId,
374
+ workflow: 'idea-refinement',
375
+ mode: resolved.mode,
376
+ handles,
377
+ roles: resolveRoles('idea-refinement', handles.map((handle) => handle.id)),
378
+ writer,
379
+ cwd: writer.dir,
380
+ budget: 7,
381
+ deadlineMs: 45 * 60 * 1000,
382
+ evidencePack,
383
+ autoDecision: {
384
+ resolved: resolved.mode,
385
+ reasons: resolved.reasons,
386
+ ...(resolved.fastPath ? { fast_path: true } : {}),
387
+ },
388
+ });
389
+ const started = Date.now();
390
+ const outcome = await executeRun(ctx, input, runIdeaRefinement);
391
+ const usage = usageFor(ctx);
392
+ const base = {
393
+ case_id: item.id,
394
+ arm: 'A',
395
+ run_id: runId,
396
+ calls: ctx.calls.length,
397
+ calls_by_provider: callsByProvider(ctx),
398
+ repair_calls: ctx.calls.filter((call) => call.stage.endsWith('-repair')).length,
399
+ latency_ms: Date.now() - started,
400
+ ...(usage ? { usage } : {}),
401
+ flags: [...ctx.flags],
402
+ };
403
+ if (!outcome.ok) {
404
+ return IdeaV3Observation.parse({ ...base, status: 'error', error: `${outcome.error?.code}: ${outcome.error?.message}` });
405
+ }
406
+ return IdeaV3Observation.parse({ ...base, status: 'ok', report_markdown: await readFile(join(outcome.dir, 'final-report.md'), 'utf8') });
407
+ }
319
408
  async function executeResearch(item, handles, root, frozenRoles) {
320
409
  const runId = makeRunId('idea-refinement');
321
410
  const writer = new RunWriter(runId, join(root, '.aiki'));
@@ -332,6 +421,7 @@ async function executeResearch(item, handles, root, frozenRoles) {
332
421
  });
333
422
  const started = Date.now();
334
423
  const outcome = await executeRun(ctx, benchmarkInput(item), runIdeaRefinement);
424
+ const usage = usageFor(ctx);
335
425
  const base = {
336
426
  case_id: item.id,
337
427
  arm: 'R',
@@ -340,6 +430,7 @@ async function executeResearch(item, handles, root, frozenRoles) {
340
430
  calls_by_provider: callsByProvider(ctx),
341
431
  repair_calls: ctx.calls.filter((call) => call.stage.endsWith('-repair')).length,
342
432
  latency_ms: Date.now() - started,
433
+ ...(usage ? { usage } : {}),
343
434
  flags: [...ctx.flags],
344
435
  };
345
436
  if (!outcome.ok) {
@@ -353,6 +444,9 @@ function parseArms(arms, set) {
353
444
  throw new Error('at least one idea-v3 arm is required');
354
445
  if (set === 'holdout' && unique.includes('D2'))
355
446
  throw new Error('D2 is build-set diagnostic only and has no holdout weight');
447
+ if (set === 'holdout' && unique.some((arm) => arm === 'A' || arm === 'B2')) {
448
+ throw new Error('Phase F arms are build-set validation only and cannot change the frozen holdout protocol');
449
+ }
356
450
  return unique;
357
451
  }
358
452
  const CAMPAIGN_NAME = /^idea-v3-(build|holdout)-(claude|codex|agy)-\d{4}-\d{2}-\d{2}\.json$/;
@@ -447,9 +541,12 @@ export async function runIdeaV3Bench(opts = {}) {
447
541
  const prior = resolved.prior?.observations ?? [];
448
542
  const done = new Set(prior.map((item) => `${item.case_id}:${item.arm}`));
449
543
  const handles = opts.handles ?? await setupProviders(protocol?.models);
450
- if (!handles.some((handle) => handle.id === baselineProvider) && arms.some((arm) => arm === 'B' || arm === 'C')) {
544
+ if (!handles.some((handle) => handle.id === baselineProvider) && arms.some((arm) => arm === 'B' || arm === 'B2' || arm === 'C')) {
451
545
  throw new Error(`baseline provider ${baselineProvider} is unavailable`);
452
546
  }
547
+ if (arms.includes('A') && new Set(handles.map((handle) => handle.id)).size < 2) {
548
+ throw new Error('A requires at least two providers for protocol-comparable auto escalation');
549
+ }
453
550
  if (arms.includes('R') && new Set(handles.map((handle) => handle.id)).size < 3) {
454
551
  throw new Error('R requires all three frozen providers for a protocol-comparable run');
455
552
  }
@@ -466,6 +563,8 @@ export async function runIdeaV3Bench(opts = {}) {
466
563
  const execute = opts.execute ?? (async (target) => {
467
564
  if (target.arm === 'D2')
468
565
  return d2ByCase.get(target.case.id);
566
+ if (target.arm === 'A')
567
+ return executeAuto(target.case, handles, root);
469
568
  if (target.arm === 'R')
470
569
  return executeResearch(target.case, handles, root, protocol?.roles);
471
570
  return executeBaseline(target.arm, target.case, handles, baselineProvider, root);