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.
- package/CHANGELOG.md +80 -5
- package/README.md +73 -15
- package/dist/bench/idea-v3-bench.js +104 -5
- package/dist/bench/idea-v3-rating.js +159 -4
- package/dist/cli/bench.js +5 -5
- package/dist/cli/index.js +12 -2
- package/dist/cli/resume.js +20 -0
- package/dist/cli/run.js +76 -5
- package/dist/cli/serve.js +48 -0
- package/dist/config/config.js +7 -2
- package/dist/council/view.js +64 -15
- package/dist/orchestration/auto-profile.js +97 -0
- package/dist/orchestration/claim-groups.js +56 -0
- package/dist/orchestration/context.js +69 -6
- package/dist/orchestration/decision-dossier.js +450 -89
- package/dist/orchestration/decision-graph.js +33 -2
- package/dist/orchestration/engine.js +8 -4
- package/dist/orchestration/evidence-origin.js +17 -0
- package/dist/orchestration/jsonStage.js +47 -5
- package/dist/orchestration/legacy-idea-adapter.js +3 -0
- package/dist/orchestration/modes.js +18 -10
- package/dist/orchestration/preflight.js +36 -3
- package/dist/orchestration/quick-analysis.js +29 -7
- package/dist/orchestration/sanitize-paths.js +10 -0
- package/dist/orchestration/stages/s10-render.js +196 -84
- package/dist/orchestration/stages/s4-analyze.js +10 -2
- package/dist/orchestration/stages/s4b-challenge.js +97 -0
- package/dist/orchestration/stages/s5-drift.js +13 -3
- package/dist/orchestration/stages/s6-positions.js +18 -0
- package/dist/orchestration/stages/s7-decision-graph.js +3 -0
- package/dist/orchestration/stages/s8-verify.js +66 -12
- package/dist/orchestration/stages/s8b-rebuttal.js +11 -4
- package/dist/orchestration/stages/s9-judge.js +52 -14
- package/dist/orchestration/stages/s9b-plan.js +227 -48
- package/dist/orchestration/url-sources.js +21 -0
- package/dist/providers/adapter-core.js +1 -1
- package/dist/providers/claude.js +18 -0
- package/dist/schemas/index.js +112 -3
- package/dist/serve/flight-deck.js +830 -0
- package/dist/serve/followup.js +50 -0
- package/dist/serve/frames.js +168 -0
- package/dist/serve/gates.js +72 -0
- package/dist/serve/projections.js +283 -0
- package/dist/serve/server.js +219 -0
- package/dist/serve/threads.js +145 -0
- package/dist/serve-ui/Five.png +0 -0
- package/dist/serve-ui/app.js +820 -0
- package/dist/serve-ui/index.html +171 -0
- package/dist/serve-ui/workspace.css +662 -0
- package/dist/skills/idea-refinement/analyst.md +5 -5
- package/dist/skills/idea-refinement/chair.md +18 -0
- package/dist/skills/idea-refinement/economics-delivery.md +11 -0
- package/dist/skills/idea-refinement/market-adoption.md +12 -0
- package/dist/skills/idea-refinement/planner.md +25 -19
- package/dist/skills/idea-refinement/rebuttal.md +15 -0
- package/dist/skills/idea-refinement/verifier.md +17 -0
- package/dist/storage/runs.js +2 -1
- package/dist/workflows/idea-refinement.js +130 -24
- 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
|
|
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
|
-
-
|
|
14
|
-
|
|
15
|
-
|
|
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-
|
|
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
|
-
- **
|
|
54
|
-
|
|
55
|
-
|
|
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.
|
|
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
|
|
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
|
|
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
|
|
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
|
|
273
|
-
`--mode research`
|
|
274
|
-
|
|
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
|
|
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);
|