feat(agents): classify agents by lifecycle and reap the finished and orphaned
deploy / test (push) Successful in 4m42s
deploy / build (push) Successful in 5m7s

A mission mints a crew, and the only thing that reaped one was DELETING the
mission. A mission that merely completed left its agents in the roster forever,
and a crew whose reap was skipped or failed left agents bound to nothing —
indistinguishable in the UI from the operator's own staff.

Four states, from one query:

  owned      no agent_template_link row   → hand-created. NEVER reaped.
  active     on a running/draft mission   → working right now. Kept.
  completed  every mission terminal       → reaped after a 24h grace.
  orphaned   minted, bound to nothing     → reaped.

The discriminator is `agent_template_link`, which mission_orchestrator writes
per minted claw. This matters more than it looks: verified on live data, a
hand-created agent and an orphaned crew member both have ZERO team links and are
structurally identical by binding alone. Judging orphanhood by "no team" would
delete the user's workforce. Provenance is the only honest signal.

The grace window exists because the results view, the World's 24h replay and
"who did this work?" all read the crew AFTER the run ends; reaping on the
terminal transition deletes the answer exactly when the question gets asked. A
completed crew with no usable timestamp is KEPT — a missing date must never read
as "old enough to delete".

Also fixes the delete summary, which reported how many claws were FOUND rather
than purged: "reaped 4 claw(s)" was printed by a delete that purged none, which
is precisely the log you would read while wondering why the agents are still
there. It now reports purged / kept / FAILED, and failed > 0 is the orphan case.

Verified against live data — all four states observed, including the two that
look alike.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-14 10:55:56 -07:00
co-authored by Claude Opus 5
parent eb120a10dd
commit a494634f81
4 changed files with 310 additions and 9 deletions
+10
View File
@@ -401,6 +401,16 @@ async fn run() -> Result<(), String> {
// row has never deleted a directory — which is why the gateway, the smallest
// disk in the fleet, accumulates mission trees that nothing reclaims.
cm_api::mission_gc::spawn(pool.clone(), std::time::Duration::from_secs(3600));
// Agent lifecycle: reap crews whose missions finished (after a 24h grace so
// the results view can still show who did the work) and crews left bound to
// nothing. Never touches an agent without an `agent_template_link` row —
// that is the operator's own staff, which looks identical to an orphan if
// you judge by team membership alone.
cm_api::agent_lifecycle::spawn(
pool.clone(),
runtime.clone(),
std::time::Duration::from_secs(3600),
);
// Fleet backstop: a node whose heartbeats stop (without a clean channel
// close) goes offline within ~28s even if its control channel hangs.
cm_api::fleet::spawn_node_sweeper(pool.clone(), std::time::Duration::from_secs(8), 20);