@eir-labs/coltrane 0.24.36 → 0.24.38

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 (36) hide show
  1. package/agents/session-analyst.json +28 -0
  2. package/agents/session-review-verifier.json +31 -0
  3. package/agents/session-reviewer.json +32 -0
  4. package/dist/src/chart.js +5 -3
  5. package/dist/src/chart.js.map +1 -1
  6. package/dist/src/claude_invoker.d.ts +26 -0
  7. package/dist/src/claude_invoker.js +57 -1
  8. package/dist/src/claude_invoker.js.map +1 -1
  9. package/dist/src/cli.d.ts +1 -1
  10. package/dist/src/mcp.js +7 -1
  11. package/dist/src/mcp.js.map +1 -1
  12. package/dist/src/releases.d.ts +46 -0
  13. package/dist/src/releases.js +51 -5
  14. package/dist/src/releases.js.map +1 -1
  15. package/dist/src/reuse.d.ts +1 -1
  16. package/dist/src/server.js +26 -0
  17. package/dist/src/server.js.map +1 -1
  18. package/dist/src/version.d.ts +1 -1
  19. package/dist/src/version.js +1 -1
  20. package/domain_types/session-analysis.json +59 -0
  21. package/domain_types/session-census.json +72 -0
  22. package/domain_types/session-review-verdict.json +48 -0
  23. package/domain_types/session-review.json +86 -0
  24. package/domain_types/session-target.json +26 -0
  25. package/package.json +1 -1
  26. package/releases.json +176 -0
  27. package/skills/gig-census/fixtures/basic.json +33 -0
  28. package/skills/gig-census/fixtures/ledger.jsonl +3 -0
  29. package/skills/gig-census/fixtures/outputs/00000000-0000-4000-8000-00000000cens.jsonl +4 -0
  30. package/skills/gig-census/meta.json +20 -0
  31. package/skills/gig-census/skill.mjs +114 -0
  32. package/skills/session-census/fixtures/basic.json +10 -0
  33. package/skills/session-census/fixtures/sample.jsonl +6 -0
  34. package/skills/session-census/meta.json +25 -0
  35. package/skills/session-census/skill.mjs +141 -0
  36. package/standards/session-review-v0.json +104 -0
@@ -0,0 +1,33 @@
1
+ {
2
+ "id": "basic",
3
+ "description": "contract: the census counts what a gig DID \u2014 one amend round for the chair that sealed the same type twice, the failed verdict, the departure, and the spend \u2014 from records shipped with the skill.",
4
+ "input": {
5
+ "gig-target": {
6
+ "gig_id": "00000000-0000-4000-8000-00000000cens",
7
+ "outputs_dir": "skills/gig-census/fixtures/outputs",
8
+ "ledger_path": "skills/gig-census/fixtures/ledger.jsonl"
9
+ }
10
+ },
11
+ "assertions": [
12
+ {
13
+ "path": "gig_id",
14
+ "op": "equals",
15
+ "value": "00000000-0000-4000-8000-00000000cens"
16
+ },
17
+ {
18
+ "path": "chairs.0.amend_rounds",
19
+ "op": "equals",
20
+ "value": 1
21
+ },
22
+ {
23
+ "path": "failed_verdicts",
24
+ "op": "equals",
25
+ "value": 1
26
+ },
27
+ {
28
+ "path": "spend.completed",
29
+ "op": "equals",
30
+ "value": true
31
+ }
32
+ ]
33
+ }
@@ -0,0 +1,3 @@
1
+ {"kind": "chair_spend", "gig_id": "00000000-0000-4000-8000-00000000cens", "role": "make"}
2
+ {"kind": "chair_spend", "gig_id": "00000000-0000-4000-8000-00000000cens", "role": "check"}
3
+ {"kind": "gig", "gig_id": "00000000-0000-4000-8000-00000000cens", "status": "complete"}
@@ -0,0 +1,4 @@
1
+ {"id": "o1", "gig_id": "00000000-0000-4000-8000-00000000cens", "from_role": "make", "agent_slug": "maker", "domain_type": "artifact", "created_at": "2026-09-19T00:00:00Z", "cost_usd": 1.5, "model": "model-a", "data": {"value": "v1"}}
2
+ {"id": "o2", "gig_id": "00000000-0000-4000-8000-00000000cens", "from_role": "check", "agent_slug": "checker", "domain_type": "verdict", "created_at": "2026-09-19T00:01:00Z", "cost_usd": 0.25, "model": "model-b", "data": {"pass": false}}
3
+ {"id": "o3", "gig_id": "00000000-0000-4000-8000-00000000cens", "from_role": "make", "agent_slug": "maker", "domain_type": "artifact", "created_at": "2026-09-19T00:02:00Z", "cost_usd": 1.0, "model": "model-a", "data": {"value": "v2", "departures": [{"from_step": "P1", "instead": "left red", "why": "the law's fixture could not build"}]}}
4
+ {"id": "o4", "gig_id": "00000000-0000-4000-8000-00000000cens", "from_role": "check", "agent_slug": "checker", "domain_type": "verdict", "created_at": "2026-09-19T00:03:00Z", "cost_usd": 0.25, "model": "model-b", "data": {"pass": true}}
@@ -0,0 +1,20 @@
1
+ {
2
+ "slug": "gig-census",
3
+ "version": 1,
4
+ "skill_type": "analysis",
5
+ "permission": { "tier": 1 },
6
+ "determinism_ratio": 1.0,
7
+ "input_type": "gig-target",
8
+ "output_type": "gig-census",
9
+ "output_schema": {
10
+ "type": "object",
11
+ "properties": {
12
+ "gig_id": { "type": "string" },
13
+ "chairs": { "type": "array" },
14
+ "verdicts": { "type": "array" },
15
+ "departures": { "type": "array" },
16
+ "spend": { "type": "object" }
17
+ },
18
+ "required": ["gig_id", "chairs", "spend"]
19
+ }
20
+ }
@@ -0,0 +1,114 @@
1
+ // What a gig ACTUALLY did (determinism 1.0): read the sealed outputs and the ledger rows for one
2
+ // gig_id and count them. This is the evidence half of reconciliation, and it exists so the question
3
+ // "what happened" is never put to the agent that did it — a seat asked to recall its own run will
4
+ // answer with a story, and a story is what a ledger must not accrue.
5
+ //
6
+ // What the agent CAN answer — why, and what it would do differently — is the other half, and it is a
7
+ // hypothesis marked as one, never a finding.
8
+ import { readFileSync, existsSync } from "node:fs";
9
+ import { join } from "node:path";
10
+ import { homedir } from "node:os";
11
+
12
+ const readJsonl = (path) => {
13
+ if (!existsSync(path)) return [];
14
+ const out = [];
15
+ for (const line of readFileSync(path, "utf8").split("\n")) {
16
+ const t = line.trim();
17
+ if (!t) continue;
18
+ try { out.push(JSON.parse(t)); } catch { /* a torn line is not a record */ }
19
+ }
20
+ return out;
21
+ };
22
+
23
+ const ms = (a, b) => (a && b ? Math.max(0, Date.parse(b) - Date.parse(a)) : null);
24
+
25
+ export default async function run(input) {
26
+ const target = (input && typeof input === "object" && input["gig-target"]) || input || {};
27
+ const gig_id = target.gig_id ? String(target.gig_id) : "";
28
+ if (!gig_id) throw new Error("gig-census: no `gig_id` in the input");
29
+
30
+ const outputsDir = target.outputs_dir
31
+ ? String(target.outputs_dir)
32
+ : join(process.env.COLTRANE_OUTPUTS_DIR ?? join(homedir(), ".eir", "coltrane_outputs"), "outputs");
33
+ const ledgerPath = target.ledger_path ? String(target.ledger_path) : join(process.cwd(), ".coltrane", "ledger.jsonl");
34
+
35
+ const outPath = join(outputsDir, `${gig_id}.jsonl`);
36
+ if (!existsSync(outPath)) throw new Error(`gig-census: no sealed outputs for gig "${gig_id}" at ${outPath}`);
37
+ const records = readJsonl(outPath);
38
+
39
+ // Every sealed record, in order, by the chair that sealed it. A role appearing more than once is an
40
+ // AMEND ROUND, which is the single most informative fact about how a gig went.
41
+ const byRole = new Map();
42
+ const seals = [];
43
+ for (const r of records) {
44
+ const role = r.from_role ?? "(none)";
45
+ const slot = byRole.get(role) ?? { role, agent: r.agent_slug, seals: 0, cost_usd: 0, models: new Set(), types: new Set(), perType: new Map() };
46
+ slot.seals += 1;
47
+ // An AMEND ROUND is the same chair sealing the same TYPE again — not the same chair sealing two
48
+ // different types once each. A builder that seals a change-set and a primer in one round is not a
49
+ // builder that ran twice, and counting it as one would put a failure in the ledger that never
50
+ // happened.
51
+ if (r.domain_type) slot.perType.set(r.domain_type, (slot.perType.get(r.domain_type) ?? 0) + 1);
52
+ if (typeof r.cost_usd === "number") slot.cost_usd += r.cost_usd;
53
+ if (r.model) slot.models.add(r.model);
54
+ if (r.domain_type) slot.types.add(r.domain_type);
55
+ byRole.set(role, slot);
56
+ const d = r.data ?? {};
57
+ seals.push({
58
+ at: r.created_at, role, agent: r.agent_slug, type: r.domain_type,
59
+ cost_usd: typeof r.cost_usd === "number" ? r.cost_usd : null,
60
+ model: r.model ?? null,
61
+ ...(typeof d.pass === "boolean" ? { pass: d.pass } : {}),
62
+ ...(Array.isArray(d.departures) && d.departures.length > 0 ? { departures: d.departures.length } : {}),
63
+ });
64
+ }
65
+
66
+ // A verdict that failed, and the departures a maker recorded rather than bridging: the two places a
67
+ // gig says out loud that it did not go cleanly.
68
+ const verdicts = records
69
+ .filter((r) => typeof (r.data ?? {}).pass === "boolean")
70
+ .map((r) => ({ at: r.created_at, role: r.from_role, pass: r.data.pass }));
71
+ const departures = [];
72
+ for (const r of records) {
73
+ for (const dep of (r.data ?? {}).departures ?? []) {
74
+ departures.push({ role: r.from_role, from_step: dep.from_step ?? null, instead: dep.instead ?? null, why: dep.why ?? null });
75
+ }
76
+ }
77
+
78
+ const primers = records
79
+ .filter((r) => r.domain_type === "seat-primer")
80
+ .map((r) => ({ role: r.from_role, agent: r.data?.agent_slug, area: r.data?.area, context_tokens: r.data?.context_tokens ?? null, frontier: r.data?.frontier ?? null, files: (r.data?.files ?? []).length }));
81
+
82
+ const ledgerRows = readJsonl(ledgerPath).filter((row) => row.gig_id === gig_id);
83
+ const spendRows = ledgerRows.filter((row) => row.kind === "chair_spend");
84
+ const gigRows = ledgerRows.filter((row) => row.kind === "gig");
85
+
86
+ const first = records[0]?.created_at ?? null;
87
+ const last = records[records.length - 1]?.created_at ?? null;
88
+ const sealedCost = records.reduce((a, r) => a + (typeof r.cost_usd === "number" ? r.cost_usd : 0), 0);
89
+
90
+ return {
91
+ source: `gig-census://${gig_id}`,
92
+ gig_id,
93
+ span: { first, last, duration_ms: ms(first, last) },
94
+ chairs: [...byRole.values()].map((s) => ({
95
+ role: s.role, agent: s.agent, seals: s.seals,
96
+ amend_rounds: Math.max(0, Math.max(...[...s.perType.values()], 1) - 1),
97
+ cost_usd: Number(s.cost_usd.toFixed(4)),
98
+ models: [...s.models], output_types: [...s.types],
99
+ })),
100
+ seals,
101
+ verdicts,
102
+ failed_verdicts: verdicts.filter((v) => v.pass === false).length,
103
+ departures,
104
+ primers,
105
+ spend: {
106
+ sealed_cost_usd: Number(sealedCost.toFixed(4)),
107
+ chair_spend_rows: spendRows.length,
108
+ gig_rows: gigRows.length,
109
+ // A gig row missing while sealed outputs exist means the gig did not complete — the absence IS
110
+ // the signal (#236), so it is reported rather than defaulted to "complete".
111
+ completed: gigRows.length > 0,
112
+ },
113
+ };
114
+ }
@@ -0,0 +1,10 @@
1
+ {
2
+ "id": "basic",
3
+ "description": "contract: the census counts a transcript held in the package itself — records, assistant turns, the tool histogram, and the operator's messages with repeats collapsed.",
4
+ "input": { "session-target": { "transcript": "skills/session-census/fixtures/sample.jsonl" } },
5
+ "assertions": [
6
+ { "path": "records", "op": "equals", "value": 6 },
7
+ { "path": "turns.assistant", "op": "equals", "value": 2 },
8
+ { "path": "operator_messages.0.count", "op": "equals", "value": 2 }
9
+ ]
10
+ }
@@ -0,0 +1,6 @@
1
+ {"type": "user", "timestamp": "2026-09-19T00:00:00Z", "message": {"content": "do the thing"}}
2
+ {"type": "assistant", "timestamp": "2026-09-19T00:00:01Z", "message": {"id": "m1", "usage": {"input_tokens": 10, "output_tokens": 5, "cache_read_input_tokens": 100}, "content": [{"type": "tool_use", "name": "Bash", "input": {}}]}}
3
+ {"type": "user", "timestamp": "2026-09-19T00:00:02Z", "message": {"content": [{"type": "tool_result", "content": "ok"}]}}
4
+ {"type": "assistant", "timestamp": "2026-09-19T00:00:03Z", "message": {"id": "m2", "usage": {"input_tokens": 12, "output_tokens": 6}, "content": [{"type": "tool_use", "name": "Bash", "input": {}}]}}
5
+ {"type": "user", "timestamp": "2026-09-19T00:00:04Z", "message": {"content": "do the thing"}}
6
+ {"type": "user", "timestamp": "2026-09-19T00:00:05Z", "message": {"content": "<system-reminder>ignored</system-reminder>"}}
@@ -0,0 +1,25 @@
1
+ {
2
+ "slug": "session-census",
3
+ "version": 1,
4
+ "skill_type": "analysis",
5
+ "permission": { "tier": 1 },
6
+ "determinism_ratio": 1.0,
7
+ "input_type": "session-target",
8
+ "output_type": "session-census",
9
+ "output_schema": {
10
+ "type": "object",
11
+ "properties": {
12
+ "transcript": { "type": "string" },
13
+ "bytes": { "type": "number" },
14
+ "records": { "type": "number" },
15
+ "span": { "type": "object" },
16
+ "turns": { "type": "object" },
17
+ "tools": { "type": "array" },
18
+ "operator_messages": { "type": "array" },
19
+ "interruptions": { "type": "number" },
20
+ "denials": { "type": "array" },
21
+ "errors": { "type": "array" }
22
+ },
23
+ "required": ["transcript", "records", "turns", "tools", "operator_messages"]
24
+ }
25
+ }
@@ -0,0 +1,141 @@
1
+ // The deterministic half of a session review (determinism 1.0): read one Claude Code transcript and
2
+ // COUNT it. No model, no judgement — every number here is a fact a reviewer can be held to, and the
3
+ // review seat downstream may claim nothing this census does not carry.
4
+ //
5
+ // A transcript is JSONL, one record per line, and large (this session's is 23MB), so it is streamed
6
+ // line by line and never held whole. What comes out is small enough for a seat to read.
7
+ import { createReadStream, statSync } from "node:fs";
8
+ import { createInterface } from "node:readline";
9
+
10
+ const TOOL_BUCKET = (name) =>
11
+ name.startsWith("mcp__") ? `mcp:${name.split("__")[1] ?? "?"}` : name;
12
+
13
+ export default async function run(input) {
14
+ // A ROOT skill chair is handed the gig payload as the runtime holds it: keyed by type slug
15
+ // (`{"session-target": {transcript}}`). A chair fed by upstream outputs gets the merged data
16
+ // instead. Accept both rather than depending on where in a standard this chair happens to sit.
17
+ const target = (input && typeof input === "object" && input["session-target"]) || input || {};
18
+ const path = target && target.transcript ? String(target.transcript) : "";
19
+ // A census that cannot read its transcript THROWS: returning an explanation object would fail the
20
+ // seal's schema check instead, and the caller would read "required property 'records' missing"
21
+ // rather than the reason. Fail with the reason.
22
+ if (!path) throw new Error("session-census: no `transcript` path in the input");
23
+
24
+ let bytes = 0;
25
+ try {
26
+ bytes = statSync(path).size;
27
+ } catch {
28
+ throw new Error(`session-census: cannot read "${path}"`);
29
+ }
30
+
31
+ const tools = new Map();
32
+ const operatorByText = new Map();
33
+ const denials = [];
34
+ const errors = [];
35
+ let records = 0;
36
+ let assistantTurns = 0;
37
+ let firstTs = null;
38
+ let lastTs = null;
39
+ let interruptions = 0;
40
+ let inputTokens = 0;
41
+ let outputTokens = 0;
42
+ let cacheRead = 0;
43
+ let cacheWrite = 0;
44
+ let maxContext = 0;
45
+ const seenMessageIds = new Set();
46
+
47
+ const rl = createInterface({ input: createReadStream(path, { encoding: "utf8" }), crlfDelay: Infinity });
48
+ for await (const line of rl) {
49
+ if (!line.trim()) continue;
50
+ let r;
51
+ try { r = JSON.parse(line); } catch { continue; }
52
+ records += 1;
53
+ const ts = typeof r.timestamp === "string" ? r.timestamp : null;
54
+ if (ts) { if (firstTs === null) firstTs = ts; lastTs = ts; }
55
+
56
+ if (r.type === "user") {
57
+ const c = r.message?.content;
58
+ // A STRING content is the operator typing; a list is a tool result coming back.
59
+ if (typeof c === "string") {
60
+ const text = c.trim();
61
+ // The harness injects its own user-shaped records (command output, reminders). An operator
62
+ // message is what is left once those are excluded — named, so the exclusion is auditable.
63
+ const injected =
64
+ text.startsWith("<command-") || text.startsWith("<local-command") ||
65
+ text.startsWith("[Request interrupted") || text.includes("<system-reminder>") ||
66
+ // Harness-delivered, not typed by the operator: a background task reporting in, and a message
67
+ // from another session. Counting either as the operator's would credit them with decisions
68
+ // nobody made.
69
+ text.startsWith("<task-notification>") || text.startsWith("<cross-session-message");
70
+ if (text.startsWith("[Request interrupted")) interruptions += 1;
71
+ // A repeated message (a cron firing the same prompt) is ONE decision said many times, not many
72
+ // decisions: collapse it to a count, first and last seen. Otherwise the census is mostly echo,
73
+ // and a seat reading it would weigh the loop's cadence as if it were the operator's attention.
74
+ if (!injected && text) {
75
+ const key = text.length > 400 ? `${text.slice(0, 400)}…` : text;
76
+ const prior = operatorByText.get(key);
77
+ if (prior) { prior.count += 1; prior.last = ts; }
78
+ else operatorByText.set(key, { text: key, count: 1, first: ts, last: ts });
79
+ }
80
+ }
81
+ }
82
+
83
+ if (r.type === "assistant") {
84
+ const msg = r.message ?? {};
85
+ const id = msg.id;
86
+ if (id && !seenMessageIds.has(id)) {
87
+ seenMessageIds.add(id);
88
+ assistantTurns += 1;
89
+ const u = msg.usage ?? {};
90
+ inputTokens += u.input_tokens ?? 0;
91
+ outputTokens += u.output_tokens ?? 0;
92
+ cacheRead += u.cache_read_input_tokens ?? 0;
93
+ cacheWrite += u.cache_creation_input_tokens ?? 0;
94
+ const ctx = (u.input_tokens ?? 0) + (u.cache_read_input_tokens ?? 0) + (u.cache_creation_input_tokens ?? 0);
95
+ if (ctx > maxContext) maxContext = ctx;
96
+ }
97
+ for (const b of msg.content ?? []) {
98
+ if (b && b.type === "tool_use" && typeof b.name === "string") {
99
+ const k = TOOL_BUCKET(b.name);
100
+ tools.set(k, (tools.get(k) ?? 0) + 1);
101
+ }
102
+ }
103
+ }
104
+
105
+ // A tool result carrying a refusal or an error is evidence a reviewer should not have to find.
106
+ if (r.type === "user" && Array.isArray(r.message?.content)) {
107
+ for (const b of r.message.content) {
108
+ if (!b || b.type !== "tool_result") continue;
109
+ const text = typeof b.content === "string" ? b.content : JSON.stringify(b.content ?? "");
110
+ if (/permission[^\n]{0,40}denied|Blocked by classifier/i.test(text)) {
111
+ denials.push({ at: ts, text: text.slice(0, 300) });
112
+ } else if (b.is_error) {
113
+ errors.push({ at: ts, text: text.slice(0, 300) });
114
+ }
115
+ }
116
+ }
117
+ }
118
+
119
+ return {
120
+ // The Signal core requires a non-empty `source`: what this reading is OF. For a census that is the
121
+ // transcript it counted, named so a sealed census can be traced back to the file it read.
122
+ source: `session-census://${path}`,
123
+ transcript: path,
124
+ bytes,
125
+ records,
126
+ span: { first: firstTs, last: lastTs },
127
+ turns: {
128
+ assistant: assistantTurns,
129
+ input_tokens: inputTokens,
130
+ output_tokens: outputTokens,
131
+ cache_read_tokens: cacheRead,
132
+ cache_write_tokens: cacheWrite,
133
+ max_context_tokens: maxContext,
134
+ },
135
+ tools: [...tools.entries()].sort((a, b) => b[1] - a[1]).map(([name, calls]) => ({ name, calls })),
136
+ operator_messages: [...operatorByText.values()],
137
+ interruptions,
138
+ denials,
139
+ errors: errors.slice(0, 40),
140
+ };
141
+ }
@@ -0,0 +1,104 @@
1
+ {
2
+ "slug": "session-review-v0",
3
+ "domain": "session-review",
4
+ "agent_slugs": [
5
+ "session-analyst",
6
+ "session-reviewer",
7
+ "session-review-verifier"
8
+ ],
9
+ "phases": [
10
+ {
11
+ "name": "census",
12
+ "chairs": [
13
+ {
14
+ "role": "census",
15
+ "skill_slug": "session-census",
16
+ "depends_on": [],
17
+ "input_contract": [
18
+ "session-target"
19
+ ],
20
+ "output_contract": [
21
+ "session-census"
22
+ ],
23
+ "required_skills": []
24
+ }
25
+ ]
26
+ },
27
+ {
28
+ "name": "analyse",
29
+ "chairs": [
30
+ {
31
+ "role": "analyse",
32
+ "agent_slug": "session-analyst",
33
+ "depends_on": [
34
+ "census"
35
+ ],
36
+ "input_contract": [
37
+ "session-census"
38
+ ],
39
+ "output_contract": [
40
+ "session-analysis"
41
+ ],
42
+ "required_skills": [],
43
+ "turn_reserve": 4
44
+ }
45
+ ]
46
+ },
47
+ {
48
+ "name": "review",
49
+ "chairs": [
50
+ {
51
+ "role": "write-review",
52
+ "agent_slug": "session-reviewer",
53
+ "depends_on": [
54
+ "census",
55
+ "analyse"
56
+ ],
57
+ "input_contract": [
58
+ "session-census",
59
+ "session-analysis"
60
+ ],
61
+ "output_contract": [
62
+ "session-review"
63
+ ],
64
+ "required_skills": [],
65
+ "turn_reserve": 4
66
+ }
67
+ ]
68
+ },
69
+ {
70
+ "name": "check",
71
+ "chairs": [
72
+ {
73
+ "role": "check-review",
74
+ "agent_slug": "session-review-verifier",
75
+ "depends_on": [
76
+ "census",
77
+ "write-review"
78
+ ],
79
+ "input_contract": [
80
+ "session-census",
81
+ "session-review"
82
+ ],
83
+ "output_contract": [
84
+ "session-review-verdict"
85
+ ],
86
+ "required_skills": [],
87
+ "turn_reserve": 4
88
+ }
89
+ ]
90
+ }
91
+ ],
92
+ "status": "draft",
93
+ "input_types": [
94
+ "session-target"
95
+ ],
96
+ "output_types": [
97
+ "session-census",
98
+ "session-analysis",
99
+ "session-review",
100
+ "session-review-verdict"
101
+ ],
102
+ "max_examine_rounds": 2,
103
+ "description": "A formal review of one working session, from a census counted by code rather than from anyone's memory. The input is a session-target: the path to a Claude Code transcript. The census chair is SKILL-BACKED (session-census, determinism 1.0, no model): it streams the .jsonl — 23MB and 13,000 records for the session this was built on — and emits counts, the tool histogram, the token classes and peak context, the operator's own messages with identical ones collapsed to a count, the interruptions, the denials and the errors. Nothing a model has to summarize, and nothing a model may contradict. An INTERPRET seat sits between the counts and the prose — composeStandard refuses a CREATE seat fed only by sensing, and it is right: the patterns (where the cost went, what was done twice, where what was ASKED FOR diverges from what the session SPENT ITSELF ON) are a separate judgement from the prose that reports them. The review seat then writes what the session was: the arc, the measured cost, what went wrong, the operator's decisions quoted from their own words, and every claim paired with the census path behind it. The verify seat resolves each path, checks each quote VERBATIM against the operator's messages, and names what the census carries that the review ignored; a failing verdict re-runs the writer with the verdict fed back. A review that flatters the work is a failed review, and a quote the operator never wrote is the failure the verify seat exists to catch."
104
+ }