feat(viz): what the agents actually did, as structured events
The World could draw a mission's shape but nothing about the work. The
detail existed only as prose in checkpoint.log and model output, where a
tool name is indistinguishable from an agent *talking about* a tool — so
it was never parsed, deliberately. `mission_events` is the structured
channel that replaces it.
Three taps, one table:
- Container tier: the `_ => {}` at the end of topology_exec's typed frame
stream now matches `tool_call` and reads the tool's JSON ARGUMENTS for a
path. Never the prose summary — a path scraped from a sentence would put
files on the map that no agent opened, and the test proves a Grep whose
summary says "src/main.rs" produces no file touch. The frame name itself
is unverified, so the same commit ships an unmatched-frame-type
histogram: a tap that matches nothing looks exactly like a mission that
used no tools, and this is how one gw-04 run names the real frame.
- microVM tier: a `PostToolUse` hook, the seam vm_stop_gate already proved
fires under `claude -p`. It copies stdin to /root/tap and exits 0
unconditionally — a non-zero PostToolUse hook talks back to the model,
which would turn the observer into a participant. Drained before collect,
since the VM is destroyed moments later.
- Phase transitions: five identical copies of the pending→running UPDATE
became one `mark_phase_running`, and `close_finished_phases` grew
RETURNING. Its CASE decides each phase's status inside SQL from rows the
statement does not change, so it cannot be re-derived afterwards without
writing that CASE twice — without RETURNING it emits zero phase.completed
and reports success.
The settings.json hazard the plan called out: the stop gate wrote the
WHOLE document, so a second hook writer would have silently erased it and
a coding phase would then complete having written nothing — the exact
failure the gate exists to catch. There is now one composer,
`vm_tool_tap::guest_settings`, one writer, and a source-walk test that
fails if anything else writes a settings document.
`mission_events.run_id` carries no FK on purpose: phase_runner DELETEs
topology_runs on retry, and a cascade would erase a phase's whole history
the moment it retried — silently, since a cascade is not an error.
world.rs streams it with a cursor that separates backfill from motion.
Everything already in the table when a subscriber arrives is drawn as
settled history; only what lands afterwards animates. Otherwise opening a
finished mission replays an hour of tool calls as a burst storm.
Bounded twice: 400 events per phase (enforced inside the INSERT, since
two concurrent taps would each read a count below the cap) and a 7-day
retention sweep in mission_gc.
Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
c2fa8067e1
commit
9e61e3ba35
@@ -18,6 +18,18 @@ pub fn claw_alias(claw_id: Uuid) -> String {
|
||||
format!("claw_{}", claw_id.simple())
|
||||
}
|
||||
|
||||
/// The claw behind a runtime alias, or `None` if it is not one of ours.
|
||||
///
|
||||
/// The inverse of [`claw_alias`], and it lives beside it so the two cannot
|
||||
/// drift — a changed prefix breaks the round-trip test rather than quietly
|
||||
/// returning `None` for every agent and dropping their attribution.
|
||||
///
|
||||
/// `None` is the honest answer for `scout` and the other configured aliases
|
||||
/// that are not claws: they have no row in `agents` to point at.
|
||||
pub fn claw_from_alias(alias: &str) -> Option<Uuid> {
|
||||
Uuid::parse_str(alias.trim().strip_prefix("claw_")?).ok()
|
||||
}
|
||||
|
||||
/// Map a claw's chosen model to a configured provider alias.
|
||||
///
|
||||
/// Claude models resolve to `claude_cli.default`, which spawns the real
|
||||
@@ -435,4 +447,19 @@ mod tests {
|
||||
let id = Uuid::nil();
|
||||
assert_eq!(claw_alias(id), "claw_00000000000000000000000000000000");
|
||||
}
|
||||
|
||||
/// The alias must round-trip, and must NOT invent a claw for one of the
|
||||
/// configured non-claw aliases.
|
||||
///
|
||||
/// The failure this guards is silent both ways: a broken round-trip drops
|
||||
/// every tool call's agent attribution (files appear, nobody moves), and a
|
||||
/// too-eager parse would attribute work to a claw id that matches no row.
|
||||
#[test]
|
||||
fn an_alias_round_trips_to_its_claw_and_nothing_else_does() {
|
||||
let id = Uuid::from_u128(0x0198_2f11_7ac0_7d51_9c3e_44a1_09b2_5e77);
|
||||
assert_eq!(claw_from_alias(&claw_alias(id)), Some(id));
|
||||
assert_eq!(claw_from_alias("scout"), None);
|
||||
assert_eq!(claw_from_alias("claude_cli.default"), None);
|
||||
assert_eq!(claw_from_alias("claw_not-a-uuid"), None);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user