ADR 0026: The no-judge task lane
Status: Implemented Date: 2026-08-17 Related: ADR-0001 (the frozen-judge wall this lane deliberately does not weaken), ADR-0004 (the row/session shapes the lane reuses), ADR-0023 (resume, which is row-shaped and therefore works unchanged)
Context
Crucible had no lane for unsupervised agent chores with no objective: "consolidate the open
dependabot PRs", "fix flaky tests nightly". [judge] was required at the type level, so the
degenerate case wasn't expressible, and teams reached for weekend-scale external runners that hand
the agent credentials and keep no durable state. The gap is real and adjacent: everything such a
task needs (sandbox, broker mediation, session log, publish-on-keep, resume) already exists; only
the mandatory scoring stood in the way.
Decision
Absent [judge] = the task lane. A manifest with no [judge] table builds the engine's
TaskJudge (crucible/src/task_judge.rs), mirroring the existing absent-[world]-means-GitWorld
fallback:
measureemitsReading { valid: true, score: None, solved: false }— no command runs, no number is fabricated.decidekeeps unconditionally and never solves; the run always exits viaFinished(or budget/stop/escalation, as ever).improvedis unconditionally true, so a completed task run exits 0.objective()is"task", which is the discriminator on the wire:Start.gate == "task".
Rows stay the ordinary decision: "keep" shape with score: null, so every keep consumer
(draft-PR publish, S3 record, resume fold, kept-best restore, flow rendering) works unchanged.
skip_baseline is forced on: the iter-0 row is baseline-skipped and the segment baseline stays
at the non-finite sentinel, the pre-existing skip-baseline convention.
Composites still require [judge]: a composite exists to combine scored components. The scope
pipeline's gaming review still rejects a proposed pack with no judge. A task manifest also
rejects [search], [workflow], and [preflight] (no scores to rank, grade, or seed), and a
scored judge may not claim objective = "task" — that string is the wire discriminator. The
deploy renderer omits BROKER_MEASURE_CMD entirely for a task manifest, so the broker's
measure tool answers with its "measurement not configured" error instead of running an empty
command, and crucible deploy render prints the same task-mode warning as check and the run.
crucible check on a task manifest skips the measure probe, the editable-gate lint, and the
selftest block (including the missing-selftest warning), and instead requires a non-empty goal and
prints a loud notice: "task mode: no [judge] — every completed turn is kept and published
unscored". The engine prints the same line at run start.
Alternatives rejected
[judge] mode = "none": safer against accidentally deleting[judge]from a scored manifest (absence would then be a parse error), but it adds a concept and makes everyJudgeCfgfield conditionally required. Mitigations chosen instead: the check/run-start notices, and the fact that a frozen pack losing its[judge]changes the run identity digest, which resume already warns about loudly. If accidental omission bites in practice, this marker is the escape hatch.[agent] mode = "task": conflates proposal-policy config with run semantics and still forces the judge optional.- A separate one-shot subcommand: re-implements session Start/Row/Summary/Shutdown, publish, and resume, all of which the loop already provides.
Consequences
- The trust boundary is untouched: the agent still holds no privilege, and task output is kept commits published as a draft PR. Privileged write actions (merging a PR, closing an issue) remain broker-tool material, added case by case with server-side admission.
- Exit 0 does not mean "the chore succeeded" — it means the run completed. A task run whose every turn errored (all rows discarded on apply failure) still exits 0. Consumers must inspect rows or PR presence; a later refinement may downgrade the exit when no keep row exists.
Summary.best_scoreandSegment.baseline_scorebecameOption<f64>on the wire: the non-finite sentinel serialized tonull, which a plainf64field failed to deserialize, silently dropping those lines for task runs and skip-baseline codegen runs alike.- Downstream dashboards that read
decision == "keep"as "beat the objective" must branch ongate == "task".
Future work
- An agent-declared "done early" marker (the fixed-N iteration budget is the only terminator
today), fitting the existing
drain_turn_markersshape. - Broker write tools (
merge_prand friends) for task flavors that need more than a draft PR.