feat(insights): derive a run outcome from observed facts instead of the agent's self-grade (v0.321.0) - #592
Merged
Merged
Conversation
…he agent's self-grade (v0.321.0)
Step 1 of docs/insights-revisit.md, and the thing Step 0's deletions owed a
replacement for.
src/edge/outcome.ts classifies each run from facts the OS observed itself — the
process crashed, the run made no tool calls, an unattended run died in seconds, a
task closed while this run held it, another run had to pick the same task up
afterwards — and carries the `basis` that decided it, so any number traces back to
its evidence. `report` remains one input among several; a human's 👍/👎 outranks
everything (and covers 1 run in 512, which is exactly why it can't be the metric).
Two framing calls did more work than any single rule. The unit is a CONVERSATION,
not a run: a `poke:` resume continues an existing transcript, so scoring rows
counts one job several times. And not everything is scorable: a person who opens
an interactive session and closes the pane has not produced a failure, so those
leave the denominator instead of quietly counting as not-success — 134 of 443
conversations on the live corpus.
Live 30-day instapods corpus: unknown 6% (was ~40%), and 28 derived failures
against the 1 the fleet self-reported in the same window. The metric can move.
Two of the rules were bought by the falsifier rather than designed:
· died-early — unattended runs split by wall-clock: 2m+ → 96% reported an
outcome, 30-120s → 84%, <30s → 0 of 44. Those are quota/auth deaths ("You've
hit your weekly limit", "401 OAuth access token has expired") — this fleet's
most common real failure, and structurally impossible to self-report because
the agent is what stopped existing. 19 in 30 days, previously invisible.
· human-session — v1 scored a person closing their own pane as `abandoned`; the
blind labels called four of those completed successes.
Falsifier: 35 conversations sampled stratified by basis and labelled blind from
transcripts alone. v1 scored 50% exact against a 43% always-success baseline —
above the bar, but not by enough to build on, which is what surfaced both rules
above. After them: 63% against a 32% baseline. The rules were revised AFTER seeing
v1's errors, so 63% is partly fitted to 28 rows; the honest unfitted number is 50%,
and a fresh blind sample is owed before Step 2 leans on this. Both numbers, the
confusion counts and the 30% coverage gap are recorded in the doc.
The Stop-hook half of the plan was not built. Once runs were classified, most of
the 40% hole turned out not to be missing information — it was unscorable
interactive sessions, plus runs that were already decidable and could never have
self-reported anyway. `unknown` landed at 6% without touching the teardown path,
so the hook is deferred with the reasoning recorded rather than built on spec.
Nothing consumes this yet; nothing is written or stamped. It is a pure read
evaluated as-of a time, so a task that reopens tomorrow changes yesterday's
verdict. Pinned by scripts/outcome-derivation-test.cjs (23 assertions, added to
test:governance), including the property that the metric must move when work fails
and not when reporting discipline changes.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01AiTKjtdcF4ESbuYVWj2CfU
vikasprogrammer
force-pushed
the
feat/insights-step1
branch
from
August 8, 2026 12:00
01dddb0 to
155feaf
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Step 1 of
docs/insights-revisit.md— the replacement Step 0's deletions owed.src/edge/outcome.tsclassifies each run from facts the OS observed itself, each verdict carrying thebasisthat decided it so a number always traces back to its evidence.reportstays one input among several; a human's 👍/👎 outranks everything (and covers 1 run in 512 — exactly why it can't be the metric).Two framing calls did more work than any rule: the unit is a conversation (a
poke:resume continues a transcript, so scoring rows counts one job several times) and not everything is scorable (a person closing their own pane is not a failure — those leave the denominator instead of quietly counting as not-success).Live 30-day instapods corpus — 443 conversations
Exit criteria met: unknown <10%, and the failure rate has variance (9% derived vs 0.3% self-reported).
The falsifier bit, and that's the interesting part
35 conversations sampled stratified by basis, labelled blind from transcripts only (
outcome-label-sample.cjs→outcome-labels.json→outcome-label-score.cjs).v1: 50% exact vs a 43% always-success baseline. Above the bar, but not enough to build on. The disagreements were clustered and bought two rules:
died-early— unattended runs split by wall-clock: 2m+ → 96% report, 30–120s → 84%, <30s → 0 of 44. Those are quota/auth deaths (You've hit your weekly limit,401 … token has expired) — this fleet's most common real failure, and structurally impossible to self-report because the agent is what stopped existing. 19 found in 30 days.human-session— v1 scored a person closing their own paneabandoned; the labels called four of those completed successes.After both: 63% exact vs a 32% baseline (19 judged, 9 declared unscorable, 7 unlabelable — no transcript on this box).
⚠ The 63% is partly fitted. Labels were blind, but the rules were revised after seeing which rows v1 missed. The honest unfitted number is 50%, and a fresh blind sample is owed before Step 2 leans on this. Also a real coverage gap: the OS declines to judge 30% of conversations (every interactive session) — fine for Steps 2–5, which are about unattended work; not fine for a fleet-wide claim.
What I did not build
The Stop-hook half. The plan assumed the 40% hole had to be closed at the source. Once runs were classified, most of it wasn't missing information — 134 unscorable interactive sessions, plus 30 noops and 19 early deaths that are fully decidable from what's already recorded and could never have self-reported anyway. That left 6% without touching the teardown path. Deferred with the reasoning in the doc, not cancelled: if Step 2's cards are blocked by the residual 20, the hook is the fix.
Those residual 20 did substantial work (11–95 tool calls) and left no verdict — the most interesting runs in the corpus, and no observable fact decides them.
Verification
scripts/outcome-derivation-test.cjs— 23 assertions, added totest:governance, covering every rule, the precedence edges that cost a wrong answer (a task smoke test finishing in 8s must not read asdied-early), conversation folding, and the property that the metric must move when work fails and not when reporting discipline changes.Full
test:governancegreen.npm run typecheckandcd web && npm run buildclean. Nothing consumes the module yet; nothing is written or stamped — it's a pure read evaluated as-of a time.🤖 Generated with Claude Code
https://claude.ai/code/session_01AiTKjtdcF4ESbuYVWj2CfU