Topology P-zc 1A: ZeroClawDriveExecutor — real role-agents over /ws/chat
ci / gates (push) Has been cancelled
ci / rust (push) Has been cancelled
ci / sandbox-k8s (push) Has been cancelled
ci / frontend (push) Has been cancelled
ci / e2e (push) Has been cancelled

cm-orchestrator owns the topology graph; each turn now drives a real ZeroClaw
role-agent in a container via the proven gateway drive recipe, instead of a
tool-free cm-llm call.

- crates/cm-api/src/topology_exec.rs: ZeroClawDriveExecutor impl TurnExecutor —
  pair (POST /pair + X-Pairing-Code, token cached) -> ws /ws/chat?agent=<alias>
  -> send {type:message,content} -> drain chunk/done/approval_request/error.
  approval_request is recorded as a BLOCKED GatedAction, never auto-approved (§15).
  Role->alias via ZEROCLAW_AGENT_MAP, fallback ZEROCLAW_DEFAULT_AGENT (scout).
- POST /api/topologies/run {task,graph} -> execute() -> RunRecord, persisted
  best-effort to the existing topology_runs table (no migration). compare stays
  tool-free. from_env() is read in-handler so cm-api still boots unset.
- deploy/clawmates-runtime: example config now declares a tool-free multi-agent
  role-cast; README documents the ZEROCLAW_* knobs + run endpoint.

tokio-tungstenite 0.26 (already in lock) + dev axum `ws` for the hermetic test.
3 lib tests green, clippy clean, SQLX_OFFLINE build clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-16 12:52:54 -07:00
co-authored by Claude Opus 4.8
parent 7baf2082d0
commit 6d77a0acc1
7 changed files with 518 additions and 9 deletions
+37 -1
View File
@@ -5,7 +5,7 @@
use axum::extract::{Path, State};
use axum::Json;
use cm_orchestrator::{compare, Comparison, JudgeScorer, ProviderExecutor};
use cm_orchestrator::{compare, execute, Comparison, JudgeScorer, ProviderExecutor, RunRecord};
use cm_topology::{build, classify, heuristics, Classification, TopologyGraph, TopologyKind};
use serde::{Deserialize, Serialize};
use time::format_description::well_known::Rfc3339;
@@ -113,6 +113,42 @@ pub async fn compare_topologies(
Ok(Json(cmp))
}
/// Request body for executing a single topology on a real agent container.
#[derive(Deserialize)]
pub struct RunRequest {
pub task: String,
pub graph: TopologyGraph,
}
/// `POST /api/topologies/run` — execute one topology by driving real ZeroClaw
/// role-agents (in a container) for each turn, returning the run journal. The
/// orchestrator owns the graph; agents are tool-free behind the Clawmates MCP
/// door, so §15 holds by construction. Result is persisted best-effort to the
/// existing `topology_runs` table.
pub async fn run_topology(
State(state): State<AppState>,
Authed(user): Authed,
Json(req): Json<RunRequest>,
) -> Result<Json<RunRecord>, ApiError> {
let executor =
crate::topology_exec::ZeroClawDriveExecutor::from_env().map_err(|_| ApiError::Internal)?;
let record = execute(&req.graph, &req.task, &executor)
.await
.map_err(|_| ApiError::Internal)?;
if let Ok(value) = serde_json::to_value(&record) {
let _ = cm_db::repo::topology_runs::insert(
&state.pool,
Uuid::now_v7(),
user.workspace_id,
&req.task,
&value,
)
.await;
}
Ok(Json(record))
}
/// A saved comparison run, summarized.
#[derive(Serialize)]
pub struct RunSummary {