the-high-council 0.6.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.
Files changed (78) hide show
  1. package/.env.example +19 -0
  2. package/CONTRIBUTING.md +41 -0
  3. package/LICENSE +21 -0
  4. package/PERSONAS.md +51 -0
  5. package/README.md +499 -0
  6. package/TROUBLESHOOTING.md +45 -0
  7. package/chains/cheap.json +35 -0
  8. package/chains/gp-judge-v1.json +80 -0
  9. package/chains/idea-open-c2.json +106 -0
  10. package/chains/mock-budget.json +15 -0
  11. package/chains/mock-debate.json +59 -0
  12. package/chains/mock-external.json +59 -0
  13. package/chains/mock-open.json +60 -0
  14. package/chains/mock-proposals.json +57 -0
  15. package/chains/mock-questions-wait.json +63 -0
  16. package/chains/mock-questions.json +63 -0
  17. package/chains/mock-relay.json +42 -0
  18. package/chains/mock-unanimous.json +42 -0
  19. package/chains/mock.json +17 -0
  20. package/chains/plan-auto-mistral.json +101 -0
  21. package/chains/plan-auto.json +131 -0
  22. package/chains/plan-cheap.json +82 -0
  23. package/chains/plan-debate-c2.json +86 -0
  24. package/chains/plan-debate-open-c2.json +98 -0
  25. package/chains/plan-debate-roles-c1.json +149 -0
  26. package/chains/plan-debate.json +116 -0
  27. package/chains/plan-fast.json +66 -0
  28. package/chains/plan-proposals-best-of-5.json +98 -0
  29. package/chains/plan-proposals.json +96 -0
  30. package/chains/plan-relay.json +83 -0
  31. package/chains/plan-thorough.json +82 -0
  32. package/chains/plan-unanimous.json +82 -0
  33. package/chains/seven-cheap.json +67 -0
  34. package/chains/seven.json +60 -0
  35. package/chains/smo-v2-cheap.json +67 -0
  36. package/chains/smo.json +50 -0
  37. package/chains/verify.json +40 -0
  38. package/package.json +55 -0
  39. package/src/board-export.js +77 -0
  40. package/src/budget.js +126 -0
  41. package/src/buys.js +44 -0
  42. package/src/cache-integrity.js +38 -0
  43. package/src/chain-lint.js +107 -0
  44. package/src/chain.js +880 -0
  45. package/src/cli.js +1031 -0
  46. package/src/cost-forecast.js +70 -0
  47. package/src/cost.js +139 -0
  48. package/src/errors.js +82 -0
  49. package/src/http-server.js +349 -0
  50. package/src/integrity.js +48 -0
  51. package/src/key-redaction.js +97 -0
  52. package/src/mcp/server.js +319 -0
  53. package/src/name-lint.js +186 -0
  54. package/src/partial-deliverable.js +38 -0
  55. package/src/peer-claim.js +98 -0
  56. package/src/personas.js +54 -0
  57. package/src/preflight.js +84 -0
  58. package/src/pricing.json +120 -0
  59. package/src/providers.js +268 -0
  60. package/src/replay.js +40 -0
  61. package/src/resume-brief.js +114 -0
  62. package/src/role-diagnostics.js +181 -0
  63. package/src/roles.js +520 -0
  64. package/src/schema-version.js +39 -0
  65. package/src/scope-freeze.js +35 -0
  66. package/src/seat-role.js +96 -0
  67. package/src/shape-rounds.js +58 -0
  68. package/src/spend.js +193 -0
  69. package/src/stage-contract.js +223 -0
  70. package/src/stage-submission.js +53 -0
  71. package/src/tie-break.js +94 -0
  72. package/src/ui/app.js +139 -0
  73. package/src/ui/index.html +43 -0
  74. package/src/ui/parse.js +101 -0
  75. package/src/ui/server.js +117 -0
  76. package/src/ui/style.css +37 -0
  77. package/src/verdict-stats.js +267 -0
  78. package/src/withdrawal-ledger.js +47 -0
@@ -0,0 +1,82 @@
1
+ {
2
+ "name": "plan-unanimous",
3
+ "description": "Same seats as plan-cheap, but every critic must independently sign off on the SAME draft before it's done (his call, 2026-09-07) - not 'first lab to pass it, stop'. Each round is a full panel pass (all 5 critics), so 3 rounds here means up to 15 critic calls, not 5. A round that isn't unanimous revises against the union of every objection any lab raised, then the whole panel reviews again.",
4
+ "maxRounds": 3,
5
+ "signoff": "unanimous",
6
+ "estimate": {
7
+ "promptTokens": 16000,
8
+ "draftTokens": 16000,
9
+ "critiqueTokens": 2000
10
+ },
11
+ "seats": {
12
+ "criteria": {
13
+ "provider": "anthropic",
14
+ "model": "claude-sonnet-5",
15
+ "maxTokens": 8000,
16
+ "extra": {
17
+ "thinking": {
18
+ "type": "disabled"
19
+ }
20
+ }
21
+ },
22
+ "builder": {
23
+ "provider": "anthropic",
24
+ "model": "claude-sonnet-5",
25
+ "maxTokens": 40000,
26
+ "extra": {
27
+ "thinking": {
28
+ "type": "disabled"
29
+ }
30
+ }
31
+ },
32
+ "reviser": {
33
+ "provider": "anthropic",
34
+ "model": "claude-sonnet-5",
35
+ "maxTokens": 40000,
36
+ "extra": {
37
+ "thinking": {
38
+ "type": "disabled"
39
+ }
40
+ }
41
+ },
42
+ "critics": [
43
+ {
44
+ "provider": "together",
45
+ "model": "Qwen/Qwen3.5-9B",
46
+ "maxTokens": 20000,
47
+ "extra": {
48
+ "chat_template_kwargs": {
49
+ "enable_thinking": false
50
+ }
51
+ },
52
+ "lab": "qwen"
53
+ },
54
+ {
55
+ "provider": "zai",
56
+ "model": "glm-4.7-flash",
57
+ "maxTokens": 20000,
58
+ "extra": {
59
+ "thinking": {
60
+ "type": "disabled"
61
+ }
62
+ }
63
+ },
64
+ {
65
+ "provider": "cohere",
66
+ "model": "command-r7b-12-2024",
67
+ "maxTokens": 4096
68
+ },
69
+ {
70
+ "provider": "google",
71
+ "model": "gemini-3.6-flash",
72
+ "maxTokens": 20000
73
+ },
74
+ {
75
+ "provider": "openrouter",
76
+ "model": "meta-llama/llama-3.3-70b-instruct",
77
+ "maxTokens": 20000,
78
+ "lab": "meta"
79
+ }
80
+ ]
81
+ }
82
+ }
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "seven-cheap",
3
+ "description": "Six labs, each lab's cheap model, one seat per lab: Qwen, GLM, Cohere, Google and Llama on the panel, Anthropic in the builder seat. (Named for the seven-lab shape it was cut down from - the roster below is the real one.) Builder and reviser stay on the cheap Anthropic seat because the deliverable here is a plan/prompt, not code - the actual build happens afterward in Claude Code on the existing subscription, not through this chain. No finalist seat for the same reason: the human takes the plan to Claude Code themselves.",
4
+ "maxRounds": 5,
5
+ "stopOnPass": true,
6
+ "estimate": {
7
+ "promptTokens": 4000,
8
+ "draftTokens": 6000,
9
+ "critiqueTokens": 1200
10
+ },
11
+ "seats": {
12
+ "criteria": {
13
+ "provider": "anthropic",
14
+ "model": "claude-haiku-4-5-20251001",
15
+ "maxTokens": 2000
16
+ },
17
+ "builder": {
18
+ "provider": "anthropic",
19
+ "model": "claude-haiku-4-5-20251001",
20
+ "maxTokens": 8000
21
+ },
22
+ "reviser": {
23
+ "provider": "anthropic",
24
+ "model": "claude-haiku-4-5-20251001",
25
+ "maxTokens": 8000
26
+ },
27
+ "critics": [
28
+ {
29
+ "provider": "together",
30
+ "model": "Qwen/Qwen3.5-9B",
31
+ "maxTokens": 20000,
32
+ "extra": {
33
+ "chat_template_kwargs": {
34
+ "enable_thinking": false
35
+ }
36
+ },
37
+ "lab": "qwen"
38
+ },
39
+ {
40
+ "provider": "zai",
41
+ "model": "glm-4.7-flash",
42
+ "maxTokens": 20000,
43
+ "extra": {
44
+ "thinking": {
45
+ "type": "disabled"
46
+ }
47
+ }
48
+ },
49
+ {
50
+ "provider": "cohere",
51
+ "model": "command-r7b-12-2024",
52
+ "maxTokens": 4096
53
+ },
54
+ {
55
+ "provider": "google",
56
+ "model": "gemini-3.6-flash",
57
+ "maxTokens": 20000
58
+ },
59
+ {
60
+ "provider": "openrouter",
61
+ "model": "meta-llama/llama-3.3-70b-instruct",
62
+ "maxTokens": 20000,
63
+ "lab": "meta"
64
+ }
65
+ ]
66
+ }
67
+ }
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "seven",
3
+ "description": "Seven frontier labs, one seat each, as originally sketched. Kept so the cost and the benefit can be measured against the two-lab chain rather than argued about.",
4
+ "maxRounds": 5,
5
+ "stopOnPass": true,
6
+ "estimate": {
7
+ "promptTokens": 4000,
8
+ "draftTokens": 6000,
9
+ "critiqueTokens": 1200
10
+ },
11
+ "seats": {
12
+ "criteria": {
13
+ "provider": "anthropic",
14
+ "model": "claude-opus-5",
15
+ "maxTokens": 2000
16
+ },
17
+ "builder": {
18
+ "provider": "anthropic",
19
+ "model": "claude-opus-5",
20
+ "maxTokens": 16000
21
+ },
22
+ "reviser": {
23
+ "provider": "anthropic",
24
+ "model": "claude-opus-5",
25
+ "maxTokens": 16000
26
+ },
27
+ "critics": [
28
+ {
29
+ "provider": "openai",
30
+ "model": "gpt-5",
31
+ "maxTokens": 20000
32
+ },
33
+ {
34
+ "provider": "google",
35
+ "model": "gemini-2.5-pro",
36
+ "maxTokens": 20000
37
+ },
38
+ {
39
+ "provider": "xai",
40
+ "model": "grok-4",
41
+ "maxTokens": 20000
42
+ },
43
+ {
44
+ "provider": "deepseek",
45
+ "model": "deepseek-chat",
46
+ "maxTokens": 20000
47
+ },
48
+ {
49
+ "provider": "groq",
50
+ "model": "llama-3.3-70b-versatile",
51
+ "maxTokens": 20000
52
+ }
53
+ ],
54
+ "finalist": {
55
+ "provider": "anthropic",
56
+ "model": "claude-opus-5",
57
+ "maxTokens": 16000
58
+ }
59
+ }
60
+ }
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "smo-v2-cheap",
3
+ "description": "Long-form building-plan pass for SMO v2. Builder/reviser/criteria upgraded from Haiku to Sonnet 5 (his call - this is the seat that actually writes the plan, worth paying for). 5 critic seats, cheap-tier, no xAI/Grok. Mistral parked (2026-09-07: account-side 429 across three keys/workspaces, unresolved on their end) - add back once it clears, nothing else needs to change. Criteria are generated fresh, not hand-written, because this is a new plan, not a description of existing SMO.",
4
+ "maxRounds": 5,
5
+ "stopOnPass": true,
6
+ "estimate": {
7
+ "promptTokens": 16000,
8
+ "draftTokens": 12000,
9
+ "critiqueTokens": 2000
10
+ },
11
+ "seats": {
12
+ "criteria": {
13
+ "provider": "anthropic",
14
+ "model": "claude-sonnet-5",
15
+ "maxTokens": 2000
16
+ },
17
+ "builder": {
18
+ "provider": "anthropic",
19
+ "model": "claude-sonnet-5",
20
+ "maxTokens": 24000
21
+ },
22
+ "reviser": {
23
+ "provider": "anthropic",
24
+ "model": "claude-sonnet-5",
25
+ "maxTokens": 24000
26
+ },
27
+ "critics": [
28
+ {
29
+ "provider": "together",
30
+ "model": "Qwen/Qwen3.5-9B",
31
+ "maxTokens": 20000,
32
+ "extra": {
33
+ "chat_template_kwargs": {
34
+ "enable_thinking": false
35
+ }
36
+ },
37
+ "lab": "qwen"
38
+ },
39
+ {
40
+ "provider": "zai",
41
+ "model": "glm-4.7-flash",
42
+ "maxTokens": 20000,
43
+ "extra": {
44
+ "thinking": {
45
+ "type": "disabled"
46
+ }
47
+ }
48
+ },
49
+ {
50
+ "provider": "cohere",
51
+ "model": "command-r7b-12-2024",
52
+ "maxTokens": 4096
53
+ },
54
+ {
55
+ "provider": "google",
56
+ "model": "gemini-3.6-flash",
57
+ "maxTokens": 20000
58
+ },
59
+ {
60
+ "provider": "openrouter",
61
+ "model": "meta-llama/llama-3.3-70b-instruct",
62
+ "maxTokens": 20000,
63
+ "lab": "meta"
64
+ }
65
+ ]
66
+ }
67
+ }
@@ -0,0 +1,50 @@
1
+ {
2
+ "name": "smo",
3
+ "description": "The SMO vertical-slice test run. Same seats as 'verify', but the acceptance criteria are hand-written rather than generated - see Docs/SmoTestRun.md section 3.",
4
+ "maxRounds": 2,
5
+ "stopOnPass": true,
6
+ "estimate": {
7
+ "promptTokens": 16000,
8
+ "draftTokens": 9000,
9
+ "critiqueTokens": 1500
10
+ },
11
+ "seats": {
12
+ "criteria": {
13
+ "provider": "anthropic",
14
+ "model": "claude-haiku-4-5-20251001",
15
+ "maxTokens": 1000
16
+ },
17
+ "builder": {
18
+ "provider": "anthropic",
19
+ "model": "claude-opus-5",
20
+ "maxTokens": 24000
21
+ },
22
+ "reviser": {
23
+ "provider": "anthropic",
24
+ "model": "claude-opus-5",
25
+ "maxTokens": 24000
26
+ },
27
+ "critics": [
28
+ {
29
+ "provider": "openai",
30
+ "model": "gpt-5",
31
+ "maxTokens": 20000
32
+ },
33
+ {
34
+ "provider": "google",
35
+ "model": "gemini-2.5-pro",
36
+ "maxTokens": 20000
37
+ }
38
+ ]
39
+ },
40
+ "criteria": [
41
+ "The deliverable is the production bible itself, not a plan or outline of one.",
42
+ "It covers exactly the named slice, and explicitly states what is out of scope.",
43
+ "Every factual claim about SMO is traceable to the source description, and gaps are marked 'not specified in source' rather than filled in.",
44
+ "Every design decision originating in the deliverable is marked as a proposal with a stated reason.",
45
+ "It contains an itemised, countable asset list.",
46
+ "It contains acceptance criteria that decide the slice is done.",
47
+ "It contains no schedule, timeline, headcount or cost estimate.",
48
+ "It contains no praise of the project and no pitch language."
49
+ ]
50
+ }
@@ -0,0 +1,40 @@
1
+ {
2
+ "name": "verify",
3
+ "description": "The recommended default. Two labs, a hard 2-round cap. Most of the measurable gain, a fraction of the cost.",
4
+ "maxRounds": 2,
5
+ "stopOnPass": true,
6
+ "estimate": {
7
+ "promptTokens": 4000,
8
+ "draftTokens": 6000,
9
+ "critiqueTokens": 1200
10
+ },
11
+ "seats": {
12
+ "criteria": {
13
+ "provider": "anthropic",
14
+ "model": "claude-opus-5",
15
+ "maxTokens": 2000
16
+ },
17
+ "builder": {
18
+ "provider": "anthropic",
19
+ "model": "claude-opus-5",
20
+ "maxTokens": 16000
21
+ },
22
+ "reviser": {
23
+ "provider": "anthropic",
24
+ "model": "claude-opus-5",
25
+ "maxTokens": 16000
26
+ },
27
+ "critics": [
28
+ {
29
+ "provider": "openai",
30
+ "model": "gpt-5",
31
+ "maxTokens": 20000
32
+ },
33
+ {
34
+ "provider": "google",
35
+ "model": "gemini-2.5-pro",
36
+ "maxTokens": 20000
37
+ }
38
+ ]
39
+ }
40
+ }
package/package.json ADDED
@@ -0,0 +1,55 @@
1
+ {
2
+ "name": "the-high-council",
3
+ "version": "0.6.0",
4
+ "description": "The High Council: a multi-model planning harness. One builder, independent critics from other labs, checkable acceptance criteria, a debate on the record, and a hard stop.",
5
+ "type": "module",
6
+ "license": "MIT",
7
+ "bin": {
8
+ "council": "src/cli.js"
9
+ },
10
+ "scripts": {
11
+ "run": "node src/cli.js",
12
+ "cost": "node src/cost.js",
13
+ "dry": "node src/cli.js --dry-run",
14
+ "ui": "node src/ui/server.js",
15
+ "mcp": "node src/mcp/server.js",
16
+ "http-server": "node src/http-server.js",
17
+ "test": "node --test"
18
+ },
19
+ "engines": {
20
+ "node": ">=20"
21
+ },
22
+ "dependencies": {
23
+ "@modelcontextprotocol/sdk": "^1.30.0",
24
+ "express": "^4.22.2",
25
+ "zod": "^4.6.4"
26
+ },
27
+ "files": [
28
+ "src/",
29
+ "chains/",
30
+ "README.md",
31
+ "TROUBLESHOOTING.md",
32
+ "CONTRIBUTING.md",
33
+ "PERSONAS.md",
34
+ "LICENSE",
35
+ ".env.example"
36
+ ],
37
+ "repository": {
38
+ "type": "git",
39
+ "url": "git+https://github.com/muad-yasin/the-high-council-mcp.git"
40
+ },
41
+ "homepage": "https://muad-yasin.github.io/the-high-council-mcp/",
42
+ "bugs": {
43
+ "url": "https://github.com/muad-yasin/the-high-council-mcp/issues"
44
+ },
45
+ "keywords": [
46
+ "mcp",
47
+ "model-context-protocol",
48
+ "llm",
49
+ "multi-agent",
50
+ "planning",
51
+ "claude",
52
+ "ai",
53
+ "cli"
54
+ ]
55
+ }
@@ -0,0 +1,77 @@
1
+ // v5 §1 candidate 8: a run's proposals, debate, replies and verdict as one
2
+ // self-contained HTML file - no external assets, no template-engine
3
+ // dependency, no server. Same "interactivity without JavaScript" discipline
4
+ // docs/demo.html already uses: collapsible sections are plain
5
+ // <details>/<summary>, inline CSS only.
6
+ const esc = s => String(s ?? '')
7
+ .replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;').replace(/"/g, '&quot;');
8
+
9
+ function proposalSection(p, posts, replies) {
10
+ const on = posts.filter(x => x.on === p.id);
11
+ const re = replies.filter(r => r.id === p.id);
12
+ const status = p.withdrawn
13
+ ? `WITHDRAWN by ${esc(p.lab)}${p.replaced_by ? ` in favour of ${esc(p.replaced_by)}` : ''}`
14
+ : p.amended ? 'AMENDED by its author after debate' : 'stands';
15
+ const boardItems = on.map(x => `<li>${esc(x.by)} - ${esc(x.stance)}${x.merge_with ? ` with ${esc(x.merge_with)}` : ''}: ${esc(x.text)}</li>`)
16
+ .concat(re.map(r => `<li>${esc(p.lab)} (author) - ${esc(r.action)}: ${esc(r.text)}</li>`))
17
+ .join('\n') || '<li>(no posts)</li>';
18
+
19
+ return `<details>
20
+ <summary>${esc(p.id)} (${esc(p.lab)}) - ${status}</summary>
21
+ <p><strong>Title:</strong> ${esc(p.title)}</p>
22
+ <p><strong>Serves:</strong> ${esc(p.serves)}</p>
23
+ <p><strong>What:</strong> ${esc(p.what)}</p>
24
+ <p><strong>Why:</strong> ${esc(p.why)}</p>
25
+ <p><strong>How:</strong> ${esc(p.how)}</p>
26
+ <p><strong>Acceptance test:</strong> ${esc(p.acceptance_test)}</p>
27
+ <p><strong>Board:</strong></p>
28
+ <ul>
29
+ ${boardItems}
30
+ </ul>
31
+ </details>`;
32
+ }
33
+
34
+ /**
35
+ * A run's report.json as one self-contained HTML string: no external
36
+ * assets, no <script>, no server. Never throws on a partial report -
37
+ * missing proposals/debate/signoff just render as empty sections.
38
+ */
39
+ export function renderBoardHtml(report) {
40
+ const proposals = report.proposals || [];
41
+ const posts = report.debate?.posts || [];
42
+ const replies = report.debate?.replies || [];
43
+ const signoff = report.signoff || [];
44
+
45
+ const proposalsHtml = proposals.length
46
+ ? proposals.map(p => proposalSection(p, posts, replies)).join('\n')
47
+ : '<p>No proposal debate ran this run.</p>';
48
+
49
+ const verdictItems = signoff.map(s =>
50
+ `<li>${esc(s.provider)}: ${s.signedOff === null ? 'abstained' : s.signedOff ? 'signed off' : 'objected'}</li>`).join('\n') || '<li>(no panel signoff recorded)</li>';
51
+
52
+ return `<!doctype html>
53
+ <html lang="en">
54
+ <head>
55
+ <meta charset="utf-8">
56
+ <title>Debate board${report.runId ? ` - ${esc(report.runId)}` : ''}</title>
57
+ <style>
58
+ body { font-family: system-ui, sans-serif; max-width: 900px; margin: 2rem auto; padding: 0 1rem; color: #1a1a1a; }
59
+ summary { cursor: pointer; font-weight: 600; margin: 0.5rem 0; }
60
+ details { border: 1px solid #ddd; border-radius: 6px; padding: 0.5rem 1rem; margin-bottom: 0.75rem; }
61
+ h1, h2 { border-bottom: 1px solid #eee; padding-bottom: 0.25rem; }
62
+ </style>
63
+ </head>
64
+ <body>
65
+ <h1>Debate board${report.runId ? ` - ${esc(report.runId)}` : ''}</h1>
66
+ <p>Chain: ${esc(report.chain || 'unknown')}</p>
67
+ <h2>Verdict</h2>
68
+ <p>${report.passed ? 'PASSED - every lab signed off' : 'OPEN OBJECTIONS'}</p>
69
+ <ul>
70
+ ${verdictItems}
71
+ </ul>
72
+ <h2>Proposals</h2>
73
+ ${proposalsHtml}
74
+ </body>
75
+ </html>
76
+ `;
77
+ }
package/src/budget.js ADDED
@@ -0,0 +1,126 @@
1
+ #!/usr/bin/env node
2
+ // Prices a whole-run token budget across a roster of models.
3
+ //
4
+ // node src/budget.js --per-model 10000000 --roster frontier --split 80
5
+ //
6
+ // Unlike --dry-run, which prices one pass through a chain from per-stage
7
+ // assumptions, this answers the blunt question: if each seat chews through
8
+ // N tokens over a whole run, what does the run cost?
9
+
10
+ import { readFileSync } from 'node:fs';
11
+ import { fileURLToPath } from 'node:url';
12
+ import { dirname, join } from 'node:path';
13
+
14
+ const here = dirname(fileURLToPath(import.meta.url));
15
+ const PRICES = JSON.parse(readFileSync(join(here, 'pricing.json'), 'utf8'));
16
+
17
+ export const ROSTERS = {
18
+ // Seven labs, top model from each. The seven-frontier-lab harness as asked for.
19
+ frontier: [
20
+ 'anthropic/claude-opus-5',
21
+ 'openai/gpt-5',
22
+ 'google/gemini-2.5-pro',
23
+ 'xai/grok-4',
24
+ 'mistral/mistral-large-latest',
25
+ 'deepseek/deepseek-reasoner',
26
+ 'together/Qwen/Qwen2.5-72B-Instruct-Turbo',
27
+ ],
28
+ // The frontier roster with the Anthropic seat dropped from Opus 5 to
29
+ // Sonnet 5. Same seven labs; only the most expensive seat changes.
30
+ 'frontier-sonnet': [
31
+ 'anthropic/claude-sonnet-5',
32
+ 'openai/gpt-5',
33
+ 'google/gemini-2.5-pro',
34
+ 'xai/grok-4',
35
+ 'mistral/mistral-large-latest',
36
+ 'deepseek/deepseek-reasoner',
37
+ 'together/Qwen/Qwen2.5-72B-Instruct-Turbo',
38
+ ],
39
+ // Seven labs again, but each lab's small model. Same structure, same
40
+ // independence between labs, roughly a tenth of the bill.
41
+ cheap: [
42
+ 'anthropic/claude-haiku-4-5-20251001',
43
+ 'openai/gpt-5-mini',
44
+ 'google/gemini-2.5-flash',
45
+ 'deepseek/deepseek-chat',
46
+ 'groq/llama-3.3-70b-versatile',
47
+ 'mistral/mistral-small-latest',
48
+ 'together/meta-llama/Llama-3.3-70B-Instruct-Turbo',
49
+ ],
50
+ // The shape worth actually running: one expensive builder, cheap critics.
51
+ // Output tokens are what cost money, and only the builder writes at length.
52
+ mixed: [
53
+ 'anthropic/claude-sonnet-5',
54
+ 'openai/gpt-5-mini',
55
+ 'google/gemini-2.5-flash',
56
+ 'deepseek/deepseek-chat',
57
+ 'groq/llama-3.3-70b-versatile',
58
+ 'mistral/mistral-small-latest',
59
+ 'together/meta-llama/Llama-3.3-70B-Instruct-Turbo',
60
+ ],
61
+ };
62
+
63
+ // Repeated input is billed at a fraction of base price when a provider's
64
+ // prompt cache hits. The multiplier differs per lab (Anthropic reads at 0.1x
65
+ // and writes at 1.25x, OpenAI and Google land near 0.1x-0.25x). One number
66
+ // here is a deliberate simplification; treat cached figures as a floor.
67
+ const CACHE_READ_MULTIPLIER = 0.1;
68
+
69
+ export function priceRoster({ perModel, roster, inputShare, cacheHitRate = 0 }) {
70
+ const rows = roster.map(key => {
71
+ const p = PRICES[key];
72
+ const input = perModel * inputShare;
73
+ const output = perModel * (1 - inputShare);
74
+ if (!p) return { key, input, output, usd: null };
75
+ const cached = input * cacheHitRate;
76
+ const fresh = input - cached;
77
+ const usd = (fresh / 1e6) * p.in
78
+ + (cached / 1e6) * p.in * CACHE_READ_MULTIPLIER
79
+ + (output / 1e6) * p.out;
80
+ return { key, input, output, usd, rate: p };
81
+ });
82
+ const total = rows.reduce((s, r) => s + (r.usd || 0), 0);
83
+ return { rows, total, tokens: perModel * roster.length };
84
+ }
85
+
86
+ const usd = n => n >= 100 ? `$${n.toFixed(0)}` : n >= 1 ? `$${n.toFixed(2)}` : `$${n.toFixed(4)}`;
87
+ const M = n => `${(n / 1e6).toFixed(1)}M`;
88
+
89
+ function table({ perModel, roster, inputShare, cacheHitRate, label }) {
90
+ const { rows, total, tokens } = priceRoster({ perModel, roster, inputShare, cacheHitRate });
91
+ const w = Math.max(...rows.map(r => r.key.length));
92
+ console.log(`\n${label}`);
93
+ console.log(` ${M(perModel)} tokens per model, ${roster.length} models, ${M(tokens)} total`);
94
+ console.log(` split ${Math.round(inputShare * 100)}% input / ${Math.round((1 - inputShare) * 100)}% output` +
95
+ (cacheHitRate ? `, ${Math.round(cacheHitRate * 100)}% of input served from prompt cache` : ', no prompt caching'));
96
+ console.log('');
97
+ for (const r of rows.sort((a, b) => (b.usd || 0) - (a.usd || 0))) {
98
+ const price = r.rate ? `${r.rate.in}/${r.rate.out}` : 'unpriced';
99
+ console.log(` ${r.key.padEnd(w)} ${price.padStart(12)} per Mtok ${(r.usd === null ? 'unpriced' : usd(r.usd)).padStart(9)}`);
100
+ }
101
+ console.log(` ${''.padEnd(w)} ${''.padStart(12)} ${usd(total).padStart(9)} per run`);
102
+ return total;
103
+ }
104
+
105
+ if (import.meta.url === `file://${process.argv[1]}`) {
106
+ const argv = process.argv.slice(2);
107
+ const arg = (n, d) => { const i = argv.indexOf(`--${n}`); return i === -1 ? d : argv[i + 1]; };
108
+ const perModel = Number(arg('per-model', 10_000_000));
109
+ const share = Number(arg('split', 80)) / 100;
110
+ const cache = Number(arg('cache', 0)) / 100;
111
+ const which = arg('roster', null);
112
+
113
+ const names = which ? [which] : Object.keys(ROSTERS);
114
+ const totals = {};
115
+ for (const n of names) {
116
+ totals[n] = table({
117
+ perModel, roster: ROSTERS[n], inputShare: share, cacheHitRate: cache,
118
+ label: `ROSTER: ${n}`,
119
+ });
120
+ }
121
+ if (names.length > 1) {
122
+ console.log('\nRatio to the cheapest roster:');
123
+ const min = Math.min(...Object.values(totals));
124
+ for (const [n, t] of Object.entries(totals)) console.log(` ${n.padEnd(9)} ${usd(t).padStart(9)} ${(t / min).toFixed(1)}x`);
125
+ }
126
+ }
package/src/buys.js ADDED
@@ -0,0 +1,44 @@
1
+ #!/usr/bin/env node
2
+ // "What does $N actually buy?" - the inverse of budget.js.
3
+ //
4
+ // budget.js answers: given a token budget, what does it cost.
5
+ // This answers: given money, how much real work is that.
6
+ //
7
+ // node src/buys.js --usd 43 --roster cheap --split 90
8
+
9
+ import { ROSTERS, priceRoster } from './budget.js';
10
+
11
+ // Concrete units, so a token count means something.
12
+ const UNITS = {
13
+ 'page of prose (~500 words)': 670,
14
+ 'long design document': 12_000,
15
+ 'one deliverable through the 2-lab verify chain (worst case)': 54_400,
16
+ 'one SMO-style production bible through the verify chain': 130_000,
17
+ 'a full 10-request blind benchmark (4 outputs each)': 130_000 * 40,
18
+ };
19
+
20
+ const argv = process.argv.slice(2);
21
+ const arg = (n, d) => { const i = argv.indexOf(`--${n}`); return i === -1 ? d : argv[i + 1]; };
22
+ const usdBudget = Number(arg('usd', 43));
23
+ const rosterName = arg('roster', 'cheap');
24
+ const share = Number(arg('split', 90)) / 100;
25
+ const roster = ROSTERS[rosterName];
26
+
27
+ // Price 1M tokens per seat to get the roster's blended rate.
28
+ const probe = priceRoster({ perModel: 1e6, roster, inputShare: share });
29
+ const usdPerMtokTotal = probe.total / (probe.tokens / 1e6);
30
+
31
+ const tokens = (usdBudget / usdPerMtokTotal) * 1e6;
32
+
33
+ console.log(`\nRoster: ${rosterName} (${roster.length} models, ${Math.round(share * 100)}% input / ${Math.round((1 - share) * 100)}% output)`);
34
+ console.log(`Blended rate: $${usdPerMtokTotal.toFixed(3)} per million tokens across the whole roster\n`);
35
+ console.log(`$${usdBudget} buys ${(tokens / 1e6).toFixed(1)}M tokens of total traffic.\n`);
36
+ console.log(`That is, in units you can picture:\n`);
37
+ const w = Math.max(...Object.keys(UNITS).map(k => k.length));
38
+ for (const [label, cost] of Object.entries(UNITS)) {
39
+ const n = tokens / cost;
40
+ // Force en-US grouping; the default locale turns 5823 into "5.823".
41
+ const shown = n >= 100 ? Math.round(n).toLocaleString('en-US') : n >= 1 ? n.toFixed(1) : n.toFixed(2);
42
+ console.log(` ${label.padEnd(w)} ${String(shown).padStart(9)}`);
43
+ }
44
+ console.log(`\nOr per unit: one deliverable through the verify chain costs $${(usdPerMtokTotal * 54_400 / 1e6).toFixed(3)} on this roster.`);