Skip to content

Commit aca698e

Browse files
authored
tribunal v0.0.3: detached-runtime handoff invariants + handoff-durability eval - Merge pull request #19 from a-tokyo/feature/tribunal-0.0.3-detached-handoff
tribunal v0.0.3: detached-runtime handoff invariants + handoff-durability eval
1 parent 18ec0b0 commit aca698e

34 files changed

Lines changed: 1939 additions & 15 deletions

.claude-plugin/marketplace.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@
3232
"source": "./",
3333
"skills": ["./skills/tribunal"],
3434
"strict": false,
35-
"version": "0.0.2",
35+
"version": "0.0.3",
3636
"description": "Doer to verifier-panel to consensus delivery verification for any artifact (code slices, plans, documents, audits). An orchestrator freezes acceptance criteria before implementation, dispatches a doer, then convenes a context-walled panel of independent verifiers — including an adversary with a must-oppose mandate — for evidence-anchored review adjudicated to SHIP / SHIP_WITH_CAVEATS / ITERATE / BLOCK / ESCALATE. Platform-agnostic; degrades to sequential fresh-context sessions without subagents."
3737
},
3838
{

benchmarks/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ they **don't ship** when a skill is installed (`npx skills add …` pulls only t
66
| skill | benchmark |
77
|-------|-----------|
88
| [production-grade](production-grade/) | same-model uplift (with skill vs without): engineering rigor, code size, correctness |
9-
| [tribunal](tribunal/) | same-model A/B (tribunal panel vs single pass): cross-file defect recall, build-and-verify composite, + a deterministic operative-skill propagation-fidelity check |
9+
| [tribunal](tribunal/) | same-model A/B (tribunal panel vs single pass): cross-file defect recall, build-and-verify composite, + deterministic operative-skill propagation-fidelity and artifact handoff-durability checks |
1010
| [app-ai-guardrails](app-ai-guardrails/) | same-model uplift (with skill vs bare) scaffolding a greenfield repo: deterministic `guardrail_score` + `all_gates_pass` across 6 stacks (Next.js, NestJS, Django, Go, Rust, Spring Boot), with teeth probes and honest env-failure/thin-cell disclosure |
1111
| [create-skill-autoresearch](create-skill-autoresearch/) | end-to-end factory A/B/C (bare vs official skill-creator vs full 5-phase factory) building the same skill from one brief; the produced skill is executed on held-out cases and scored against a deterministic answer key |
1212

benchmarks/tribunal/README.md

Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ real subagents — not a single `promptfoo` command. Three suites:
1111
| [`recall-task/`](recall-task/) | cross-file defect recall on a 6-module TypeScript codebase (cause in one file, failure in another) + correct verdict + zero false positives | deterministic recall vs `ANSWER-KEY.md` (O1/O2) + blind LLM judge for process (P1–P7), per [`judge-rubric.md`](judge-rubric.md) |
1212
| [`build-task/`](build-task/) | build-and-verify a 3-slice CLI against a 17-criterion spec; catch a seeded spec contradiction | deterministic AC re-execution (`JUDGING.md`) + blind judge for process |
1313
| [`propagation-fidelity/`](propagation-fidelity/) | does an **operative skill** (e.g. `production-grade`) actually reach the doer and panel, and is the tribunal skill never nested? | fully deterministic — string/structure checks on the dispatched prompts, self-tested offline |
14+
| [`handoff-durability/`](handoff-durability/) | is the **artifact** handed over by a fetchable address rather than a working-tree path, and do the round index and doer budget travel in the handoff? | fully deterministic — string/structure checks on the dispatched prompts and the doer's report, self-tested offline |
1415

1516
## Method
1617

@@ -71,6 +72,50 @@ Under the pre-edit skill, even a capable orchestrator carried the standard to th
7172
only and to **none** of the panel — so the panel would score against a weaker bar than
7273
the work was built to. That is the exact gap the change closes.
7374

75+
### Handoff durability (tribunal ≥ v0.0.3)
76+
77+
Deterministic, not a score: the doer is told to materialize the artifact durably and
78+
report a **fetchable address**; that address — not a working-tree path — reaches every
79+
verifier; and the round index plus remaining doer budget travel in the handoff rather
80+
than in orchestrator context. See `handoff-durability/`.
81+
82+
Production provenance for the failure mode: on a live detached run a doer completed its
83+
slice but left the spec uncommitted, and the orchestrator had to catch it and re-dispatch
84+
([lqa-app#3506](https://github.com/LeadingQuality/lqa-app/pull/3506)). On a shared
85+
filesystem that is fragile; under a torn-down or namespace-isolated sandbox the panel
86+
would score a path it cannot open.
87+
88+
Live before/after, same model and task in both arms, the skill version the only variable
89+
(counts are runs passing each metric):
90+
91+
| Skill version | model | materialize | address reported | verifiers carrying it | budget carried | `handoff_durability` |
92+
|---|---|---|---|---|---|---|
93+
| `v0.0.2` | haiku (n=3) | 0/3 | 0/3 | 0/3 | 0/3 | **0/3** |
94+
| `v0.0.3` | haiku (n=3) | 2/3 | **3/3** | **3/3** | 0/3 | 0/3 |
95+
| `v0.0.2` | sonnet (n=3) | 1/3 | 1/3 | 0/3 | 1/3 | **0/3** |
96+
| `v0.0.3` | sonnet (n=3) | **3/3** | **3/3** | **3/3** | **3/3** | **3/3** |
97+
98+
**No cell regresses**; every metric is equal or better under `v0.0.3`.
99+
100+
The load-bearing figure is `v0.0.2`/sonnet **propagation 0/3**: under the old skill a
101+
capable orchestrator sometimes had its doer commit and report a SHA unprompted (1/3 on
102+
both) and *still* pointed the panel at a working tree every single time. That is
103+
invariant 1's latent flaw — "new-file paths" assuming a shared filesystem — reproduced
104+
under controlled conditions, and `v0.0.3` takes it to 3/3.
105+
106+
Honest negative: `budget_carried` is 0/3 on haiku in **both** arms, so haiku shows no
107+
composite PASS despite its address chain going 0/3 → 3/3. Invariant 6's "every dispatch
108+
states the round index and the remaining budget" under-guides smaller models; sonnet
109+
complies 3/3, haiku not at all. Reported as a finding rather than fixed by relaxing the
110+
metric.
111+
112+
`n=3` per cell is small and the metrics are binary — these support "the address reliably
113+
reaches the panel under `v0.0.3` and unreliably under `v0.0.2`", not a precise rate. The
114+
arms run on haiku and sonnet, the tiers where a skill is load-bearing (same reasoning as
115+
the suites above: a skill that helps a frontier model may under-guide a small one). All 12
116+
attempted captures scored: no env failures, no extraction failures. Full per-run data and
117+
method: [`handoff-durability/`](handoff-durability/).
118+
74119
## Reading the numbers
75120

76121
Honest caveats, straight from the harness analysis:
@@ -95,12 +140,31 @@ fixtures don't involve an operative skill, so it isn't expected to move recall/c
95140
— but those suites do **not** test it. The `propagation-fidelity/` eval is the targeted
96141
test for the new behavior; re-running recall/build for regression is a follow-up.
97142

143+
### Scope of these numbers vs. the v0.0.3 change
144+
145+
Same shape, same honesty. `v0.0.3`'s handoff invariants are additive — the doer is asked
146+
for an address in addition to the diff, and the counters are restated in each dispatch —
147+
and none of the recall/build fixtures involves a second sandbox, so the change is not
148+
expected to move recall/composite. Those suites were **not** re-run, and no figure above
149+
has been restated or softened to accommodate the new version. `handoff-durability/` is
150+
the targeted test; re-running recall/build for regression remains a follow-up.
151+
152+
Note the arms differ from the recall/build suites: `handoff-durability/` and
153+
`propagation-fidelity/` both A/B **one skill version against another** rather than skill
154+
against no skill, because both measure an additive protocol change that has no bare-arm
155+
counterpart.
156+
98157
## Reproduce
99158

100159
```bash
101160
# Deterministic, offline — no API key needed:
102161
node propagation-fidelity/selftest.mjs # the checker is sound
103162
node propagation-fidelity/check.mjs <dispatched> --skill production-grade
163+
node handoff-durability/selftest.mjs # checker + dispatch extractor are sound
164+
node handoff-durability/check.mjs <dispatched>
165+
166+
# Live handoff-durability arms (needs a headless credential; see that suite's README):
167+
handoff-durability/arms/run-arm.sh v003 sonnet v003-sonnet-1
104168

105169
# Quality suites (agent-orchestrated, needs an agent with parallel subagents):
106170
# 1. recall-task: give an agent recall-task/prompt.md + recall-task/spec.md +
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# Runtime dispatch capture (live runs) — never committed.
2+
dispatched*/
3+
# Run dirs / symlinks and the per-run results ledger.
4+
runs/
5+
results/
6+
# Headless OAuth token minted with `claude setup-token` — a secret.
7+
.auth-token
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
# Handoff-durability eval
2+
3+
`recall-task/` and `build-task/` measure verification **quality**; `propagation-fidelity/`
4+
measures whether an **operative skill** reaches the subagents. None of them looks at how
5+
the **artifact itself** is handed over.
6+
7+
Tribunal `v0.0.3` made that a protocol matter: the doer materializes the artifact durably
8+
and reports a **fetchable address**; the orchestrator hands that address — not a
9+
working-tree path — to every verifier; and the round index plus remaining doer budget
10+
travel in the handoff rather than living in orchestrator context. This eval is its
11+
dedicated test.
12+
13+
Production provenance: on a live detached run, a doer completed its slice but left the
14+
spec uncommitted, and the orchestrator had to notice and re-dispatch
15+
([lqa-app#3506](https://github.com/LeadingQuality/lqa-app/pull/3506)). On a shared
16+
filesystem that is merely fragile; under a torn-down or namespace-isolated sandbox the
17+
verifiers would have scored a path they could not open.
18+
19+
## What it asserts
20+
21+
Given the prompts the orchestrator actually dispatched, plus the doer's report:
22+
23+
1. **Materialize** — the doer prompt instructs durable materialization (commit / push /
24+
publish) *and* reporting the artifact's address.
25+
2. **Address reported** — the doer's report contains a fetchable address, not only
26+
working-tree paths.
27+
3. **Address propagated** — every verifier prompt carries *that* address.
28+
4. **Budget carried** — the round index **and** the remaining doer-dispatch budget appear
29+
in the dispatch, or in a durable ledger the run wrote (`.tribunal/`). Invariant 6 names
30+
two counters, so a round cap alone (`"round 1/3"`) does not satisfy the budget half.
31+
The orchestrator's closing summary does **not** count either: the whole point is that
32+
it survives the orchestrator.
33+
34+
The check is **deterministic** — string/structure assertions on the prompts, no LLM judge,
35+
no network. `check.mjs`'s header records exactly what counts as an address (SHA forms,
36+
branch/ref, URI, PR) and, deliberately, that a path never *dis*qualifies anything: real
37+
doers report an address **and** the paths, so every assertion is presence-of-address.
38+
39+
```bash
40+
node check.mjs <dispatched-dir>
41+
```
42+
43+
Prints `METRIC handoff_durability=1|0` plus `doer_materialize_instruction`,
44+
`artifact_address_reported`, `verifier_count`, `verifier_address_propagation` and
45+
`budget_carried`; exits non-zero on any failure.
46+
47+
## Self-test (offline, no API)
48+
49+
```bash
50+
node selftest.mjs
51+
```
52+
53+
Five fixtures, and the self-test asserts **which metric** each one breaks — not merely
54+
that it exits non-zero, since a checker that fails everything wholesale would pass a naive
55+
exit-code test while being useless on a live run:
56+
57+
| fixture | breaks | story |
58+
|---|---|---|
59+
| `pass/` || committed, address propagated to both verifiers, budget carried |
60+
| `pass-address-forms/` || the report and the dispatches spell the same address differently (backticked URI vs bare, `refs/heads/x` vs `origin/x`) — equivalent spellings are one address, not a broken handoff |
61+
| `fail-no-address/` | `doer_materialize_instruction`, `artifact_address_reported`, `verifier_address_propagation` | the **pre-`v0.0.3`** dispatch: nothing asked the doer to materialize, so the report offers only paths |
62+
| `fail-ephemeral-path/` | `verifier_address_propagation` | the doer *did* commit, but the panel was pointed at its working tree |
63+
| `fail-budget-not-carried/` | `budget_carried` | durable handoff, but the counters live only in orchestrator context |
64+
65+
Fixtures are written by the same hand as the regexes, so a green self-test is necessary
66+
and not sufficient. Two extra hand-written captures in a deliberately different voice
67+
(short SHAs in backticks, "iteration 2 of 3", "3 dispatches left") were run against the
68+
checker during development; both were initially misjudged and the matcher was widened —
69+
line-wrap tolerance, `Committed`/`pushed` anchors, round synonyms — until each landed on
70+
the correct verdict. Repeat that exercise before trusting a new capture batch.
71+
72+
## Live before/after
73+
74+
```bash
75+
# once: mint a headless credential (the fake HOME cannot see the keychain)
76+
claude setup-token > .auth-token # gitignored
77+
78+
# one capture (arm is the ONLY variable: v002 = git show origin/main, v003 = working tree)
79+
arms/run-arm.sh v003 sonnet v003-sonnet-1
80+
node check.mjs runs/v003-sonnet-1/dispatched
81+
82+
# or the whole matrix, scored, resumable (re-invoke with the same tag to continue):
83+
arms/run-batch.sh 1 b1
84+
```
85+
86+
Keep concurrency at 1. Three concurrent runs — each a doer plus a 3-lens panel doing real
87+
work — exhausted the account session allowance in minutes, and a run cut off mid-flight
88+
produces a truncated capture that scores like a clean FAIL.
89+
90+
`arms/run-arm.sh` copies exactly one tribunal `SKILL.md` into `$HOME/.claude/skills/tribunal`
91+
inside an isolated fake HOME — the only placement Claude Code reliably discovers, and the
92+
reason this is **not** done with `--append-system-prompt-file`. Containment (isolated
93+
`HOME`/`XDG_*`/`GIT_CONFIG_GLOBAL`, `GIT_CEILING_DIRECTORIES`, `env -i` allowlist, stdin
94+
from `/dev/null`, `perl alarm` timeout, `exit 75` on env failure) is inherited from
95+
[`create-skill-autoresearch/arms/run-arm.sh`](../../create-skill-autoresearch/arms/run-arm.sh);
96+
read that file's comments before changing this one. The work dir gets its own `git init`,
97+
without which the v003 arm would fail for the wrong reason.
98+
99+
`extract-dispatch.mjs` recovers the dispatched prompts from the `stream-json` transcript
100+
(envelope verified against a live capture; it handles both synchronous dispatch, where the
101+
tool_result is the report, and asynchronous dispatch, where the tool_result is only a launch
102+
acknowledgement and the report arrives as subagent events under `parent_tool_use_id`),
103+
so — unlike `propagation-fidelity/`, which asks the agent to save its own prompts — the
104+
agent is never told its dispatch is under inspection. The raw transcript is kept, so a
105+
parser fix can be replayed offline without re-spending a capture.
106+
107+
**Ship gate.** `v0.0.3` ships only if no cell regresses against `v0.0.2` — better or not
108+
at all. A mixed result is a fix list, not a footnote.
109+
110+
### Results
111+
112+
Same task, same model, same prompt; the tribunal `SKILL.md` version is the only variable.
113+
Counts are runs passing each metric, out of the cell's `n`.
114+
115+
| cell | n | materialize | address reported | propagated | budget | **PASS** |
116+
|---|---|---|---|---|---|---|
117+
| `v0.0.2` haiku | 3 | 0/3 | 0/3 | 0/3 | 0/3 | **0/3** |
118+
| `v0.0.3` haiku | 3 | 2/3 | **3/3** | **3/3** | 0/3 | 0/3 |
119+
| `v0.0.2` sonnet | 3 | 1/3 | 1/3 | 0/3 | 1/3 | **0/3** |
120+
| `v0.0.3` sonnet | 3 | **3/3** | **3/3** | **3/3** | **3/3** | **3/3** |
121+
122+
**No cell regresses**; every metric is equal or better under `v0.0.3`. The gate passes.
123+
124+
The load-bearing number is `v0.0.2` sonnet **propagation 0/3**. Under the old skill a
125+
capable orchestrator *sometimes* had its doer commit and report a SHA unprompted (1/3 on
126+
both) — and still passed that address to the panel **zero times out of three**. That is
127+
invariant 1's latent flaw reproduced under controlled conditions: the artifact was
128+
reachable, and the verifiers were pointed at a working tree anyway. `v0.0.3` takes it to
129+
3/3. The edit is not teaching the doer to commit; it is closing the gap between the
130+
doer's commit and the panel's ability to fetch it.
131+
132+
**Honest negative — `budget_carried` on haiku is 0/3 in BOTH arms.** Invariant 6 says
133+
every dispatch states the round index and the remaining budget. Sonnet complies (3/3);
134+
haiku states neither, and its runs are single-dispatch, so the haiku cell shows no
135+
composite PASS despite the address chain going 0/3 → 3/3. The instruction under-guides
136+
smaller models. That is a finding about the skill's wording, not a defect in the
137+
measurement, and it is reported rather than resolved by relaxing the conjunction —
138+
a benchmark tuned to its own desired result measures nothing.
139+
140+
**Reading these numbers.** `n=3` per cell is small, and the metrics are binary, so a
141+
single run moves a cell by a third. The claim these support is "the address reliably
142+
reaches the panel under `v0.0.3` and unreliably under `v0.0.2`", not a precise rate.
143+
`v0.0.2`'s own spread — one sonnet run scoring 1/1/0/1 and another 0/0/0/0 — is itself
144+
the point: the old skill leaves the handoff to chance.
145+
146+
**Tiers.** The matrix is haiku and sonnet — the tiers where a skill is load-bearing, per
147+
the same reasoning as the suite's other evals: a skill that helps a frontier model may
148+
under-guide a small one, so the small ones are where the wording has to work. Add a larger
149+
tier with `MATRIX="haiku:3 sonnet:3 opus:1" ./arms/run-batch.sh`.
150+
151+
**12 of 12 attempted captures scored** — no env failures and no extraction failures in
152+
the reported batch. Runs killed by an account usage limit, a network outage, or the
153+
timeout were discarded and re-run, never scored; `results/metrics.csv` holds the per-run
154+
data behind every figure above.
155+
156+
## Scope — what this can and cannot show
157+
158+
Claude Code's subagents share a filesystem with the orchestrator, so this harness
159+
**cannot** demonstrate the *consequence* of a path-based handoff: here a path usually
160+
still resolves. (Dispatch itself may well be asynchronous — verified during development —
161+
but that changes when the report arrives, not whether the path is reachable.) That is by
162+
design. This eval measures the **form** of the handoff in the dispatched prompts, which is
163+
host-neutral and observable on any runtime — the same reason `propagation-fidelity/` works
164+
on prompts rather than outcomes. The consequence is evidenced separately by the field case
165+
above, on a detached sandboxed runtime.
166+
167+
Both arms are tribunal-vs-tribunal (`v0.0.2` vs `v0.0.3`), not bare-vs-skill, mirroring how
168+
`propagation-fidelity/` frames pre-`v0.0.2` against `v0.0.2`. Stated here because
169+
[`docs/benchmarking.md`](../../../docs/benchmarking.md) requires the method be declared
170+
rather than implied.

0 commit comments

Comments
 (0)