missions: phase-completion summary card (Claude Opus 4.8 synthesized)
ci / gates (push) Successful in 6s
ci / rust (push) Failing after 9s
ci / frontend (push) Successful in 26s
ci / e2e (push) Skipped
ci / publish (push) Skipped

New phase_summarizer background worker fires on any mission_phase
transition to a terminal state (completed/failed). Aggregates every
topology_runs.checkpoint.outputs[] + mission_tasks + mission_artifacts
bound to that phase and asks Claude Opus 4.8 to produce a structured
JSON card:

  { narrative, metrics, sources, tooling, next_actions }

Rendered inline on the mission page under each completed phase via
new PhaseSummaryCard component. Metrics grid is kind-specific:
research surfaces insights/sources/int_cards/artifacts, coding
surfaces cards_picked_up/commits/tests/issues, benchmark surfaces
regressions/improvements, security surfaces findings-by-severity.

New table: mission_phase_summaries (migration 0060), unique per
phase_id — regenerates on retry.
New endpoint: GET /api/missions/{id}/phases/{phase_id}/summary.

Model overridable via CLAWMATES_SUMMARIZER_MODEL. Reuses the
ANTHROPIC_API_KEY prod already carries for mission_refiner.
This commit is contained in:
Omar Sobh
2026-07-23 16:54:38 -07:00
parent 1be3430bf2
commit 5c63ef0ed3
8 changed files with 1022 additions and 0 deletions
+45
View File
@@ -480,6 +480,51 @@ pub async fn retry_phase(
Ok(Json(serde_json::json!({ "reset": true })))
}
/// GET /api/missions/{id}/phases/{phase_id}/summary — the completion
/// card produced by `phase_summarizer` for a terminal-state phase.
/// Returns 404 while the phase is still running / hasn't been
/// summarized yet.
pub async fn get_phase_summary(
State(state): State<AppState>,
Authed(user): Authed,
Path((id, phase_id)): Path<(Uuid, Uuid)>,
) -> Result<Json<Value>, ApiError> {
// Scope check.
let _ = cm_db::repo::missions::get(&state.pool, id, user.workspace_id.as_uuid())
.await?
.ok_or(ApiError::NotFound)?;
use sqlx::Row;
let row = sqlx::query(
"SELECT kind, model, narrative, metrics, sources, artifacts,
tooling, next_actions, generated_at, error
FROM mission_phase_summaries
WHERE mission_id = $1 AND phase_id = $2",
)
.bind(id)
.bind(phase_id)
.fetch_optional(&state.pool)
.await?;
let Some(r) = row else {
return Err(ApiError::NotFound);
};
let generated_at: time::OffsetDateTime = r.get("generated_at");
let payload = serde_json::json!({
"kind": r.get::<String, _>("kind"),
"model": r.get::<String, _>("model"),
"narrative": r.get::<String, _>("narrative"),
"metrics": r.get::<Value, _>("metrics"),
"sources": r.get::<Value, _>("sources"),
"artifacts": r.get::<Value, _>("artifacts"),
"tooling": r.get::<Value, _>("tooling"),
"next_actions": r.get::<Value, _>("next_actions"),
"generated_at": generated_at
.format(&time::format_description::well_known::Rfc3339)
.unwrap_or_default(),
"error": r.get::<Option<String>, _>("error"),
});
Ok(Json(payload))
}
/// GET /api/missions/{id}/runs — topology_runs bound to this mission,
/// newest first. Used by the Live tab to subscribe to per-run SSE.
pub async fn list_runs(