feat(missions): surface goal conditions and per-pass verdicts in the UI
Makes the completion evaluator usable and observable.
- GET /api/missions/{id}/phases/{phase_id}/evaluations returns every verdict
for a phase, newest pass first, scoped like the summary endpoint.
- MissionPhase gains done_when / max_iterations / iteration, so the phase card
can show what the phase is working toward and which pass it is on.
- PhaseStatus gains 'evaluating' (amber) -- the state between "runs finished"
and "phase done" that only conditioned phases enter.
- New PhaseGoalStrip renders on the phase card, and renders NOTHING for phases
without a condition so unconditioned missions look exactly as before. It
polls only while the phase is running or being judged.
- Mission wizard step 2 gains the condition + a max-passes field.
Two deliberate emphases in the UI:
The evaluator's `reason` is the most prominent element, because it is both the
explanation of why a phase iterated and the literal text handed back to the
agents as guidance -- it is what tells an operator whether the condition is
written well.
The hint copy states the constraint that actually governs whether a condition
works: the judge cannot run commands, it only reads what the agents wrote, so
the condition has to be provable from their output. "cargo test reported 0
failures" works; "the code is well factored" does not. Getting this wrong is
the difference between a phase that converges and one that burns every pass.
An evaluator error is rendered distinctly from a negative verdict, so a judge
outage doesn't read as a judgement on the work.
Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
f848248fac
commit
fe57ce4ed1
@@ -59,6 +59,13 @@ pub struct MissionPhase {
|
||||
pub order_idx: i32,
|
||||
pub status: String,
|
||||
pub config: Value,
|
||||
/// Completion condition. `None` = the phase completes as soon as its runs
|
||||
/// finish, with no evaluation (the pre-conditions behaviour).
|
||||
pub done_when: Option<String>,
|
||||
/// Upper bound on passes; 1 means run once.
|
||||
pub max_iterations: i32,
|
||||
/// Which pass the phase is on, 0-based.
|
||||
pub iteration: i32,
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
pub started_at: Option<OffsetDateTime>,
|
||||
#[serde(with = "time::serde::rfc3339::option")]
|
||||
@@ -410,6 +417,7 @@ pub async fn phases_for(pool: &PgPool, mission_id: Uuid) -> Result<Vec<MissionPh
|
||||
use sqlx::Row;
|
||||
let rows = sqlx::query(
|
||||
"SELECT id, mission_id, kind, order_idx, status, config,
|
||||
done_when, max_iterations, iteration,
|
||||
started_at, completed_at
|
||||
FROM mission_phases WHERE mission_id = $1
|
||||
ORDER BY order_idx ASC",
|
||||
@@ -426,6 +434,9 @@ pub async fn phases_for(pool: &PgPool, mission_id: Uuid) -> Result<Vec<MissionPh
|
||||
order_idx: r.get("order_idx"),
|
||||
status: r.get("status"),
|
||||
config: r.get("config"),
|
||||
done_when: r.get("done_when"),
|
||||
max_iterations: r.get("max_iterations"),
|
||||
iteration: r.get("iteration"),
|
||||
started_at: r.get("started_at"),
|
||||
completed_at: r.get("completed_at"),
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user