feat(agents): make delete permanent, and expose the census
deploy / test (push) Successful in 4m25s
deploy / build (push) Successful in 5m10s

A soft delete marked the row and left it. The agent stayed in the table forever,
kept appearing on any surface that forgot `deleted_at IS NULL`, and deleting it
again did nothing — the decision was recorded and never honoured. Two agents on
this deployment had been in that state since June.

`deleted` is now a fifth lifecycle state, collected with NO grace window: a
human already decided, months ago. It takes usage_events with it, which is the
explicit trade — the alternative is rows that outlive the decision to delete
them.

Two endpoints, because this was previously only answerable by reading the
database by hand:

  GET  /api/claws/lifecycle        the census: who is active, completed,
                                   orphaned, deleted — and what is reapable
  POST /api/claws/lifecycle/sweep  run the reap now, rather than waiting out
                                   the hourly timer for a decision already made

Verified end to end: census reported both as `deleted`/`reapable`, the sweep
returned {"reaped":2,"failed":0}, and agents and usage_events both went to 0.

The safety property is unchanged and re-asserted by a new test: adding `deleted`
did not make `owned` or `active` reapable.

Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
Omar Sobh
2026-08-14 18:04:43 -07:00
co-authored by Claude Opus 5
parent ccbc387f4b
commit b290025fc4
3 changed files with 126 additions and 29 deletions
+31 -23
View File
@@ -1,59 +1,55 @@
//! REST API for Clawmates (spec §13). One route resource per module.
pub mod benchmark_runner;
pub mod agent_lifecycle;
pub mod agent_names;
pub mod auto_merge;
pub mod benchmark_runner;
pub mod beszel;
pub mod brain_seed;
pub mod cleanup_sweeper;
pub mod agent_names;
pub mod mission_gc;
pub mod container_exec;
pub mod corpus;
mod error;
pub mod evaluator;
pub mod evaluator_tools;
mod extract;
pub mod fleet;
pub mod fleet_herdr;
pub mod harvest;
pub mod level_up;
pub mod library;
mod mcp_door;
mod mcp_skills;
pub mod mission_orchestrator;
pub mod mission_refiner;
pub mod auto_merge;
pub mod corpus;
pub mod harvest;
pub mod library;
pub mod mission_delivery;
pub mod mission_events;
pub mod microvm_client;
pub mod microvm_executor;
pub mod microvm_turn_executor;
pub mod subscription;
pub mod vm_placement;
pub mod vm_stop_gate;
pub mod vm_tool_tap;
pub mod mission_delivery;
pub mod mission_events;
pub mod mission_fs;
pub mod mission_gc;
pub mod mission_orchestrator;
pub mod mission_outputs;
pub mod papers;
pub mod phase_config;
pub mod session_executor;
pub mod repo_digest;
pub mod runtime_preflight;
pub mod validator_preflight;
pub mod mission_plan;
pub mod mission_refiner;
pub mod mission_roster;
pub mod mission_runtime;
pub mod mission_workspace;
pub mod node_rules;
pub mod papers;
pub mod phase_config;
pub mod phase_runner;
pub mod root_copy;
pub mod phase_summarizer;
pub mod quota;
mod recursive_exec;
pub mod repo_digest;
pub mod root_copy;
mod routes;
pub mod runtime_preflight;
mod runtime_provision;
pub mod security_scan;
pub mod session_executor;
pub mod skills_loader;
pub mod subscription;
pub mod swarm;
pub mod task_card_parser;
pub mod task_card_worker;
@@ -61,6 +57,10 @@ pub mod team_template_loader;
pub mod tool_versions;
mod topology_exec;
pub mod topology_worker;
pub mod validator_preflight;
pub mod vm_placement;
pub mod vm_stop_gate;
pub mod vm_tool_tap;
pub mod workflow_registry;
use axum::routing::{delete, get, patch, post};
@@ -243,6 +243,11 @@ pub fn router(state: AppState) -> Router {
.route("/api/user/me", get(routes::identity::me))
.route("/api/claws", post(routes::claws::create))
.route("/api/claws/batch-delete", post(routes::claws::batch_delete))
.route("/api/claws/lifecycle", get(routes::claws::lifecycle_census))
.route(
"/api/claws/lifecycle/sweep",
post(routes::claws::lifecycle_sweep),
)
.route("/api/claws/{id}", patch(routes::claws::patch))
.route("/api/claws/{id}", delete(routes::claws::delete))
.route("/api/claws/{id}/model", patch(routes::claws::set_model))
@@ -510,7 +515,10 @@ pub fn router(state: AppState) -> Router {
"/api/missions/refine-draft",
post(routes::missions::refine_draft),
)
.route("/api/missions/{id}/merge", post(routes::missions::merge_branch))
.route(
"/api/missions/{id}/merge",
post(routes::missions::merge_branch),
)
.route(
"/api/missions/{id}/artifacts/{artifact_id}/content",
get(routes::missions::artifact_content),