Door governor: runtime-agent judge (Kimi-as-judge on the subscription)
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

The §15 governor can now be judged by a ZeroClaw runtime agent instead of a
server-side registry/API model. ZeroClawDriveExecutor::judge drives an agent
with the governor prompt and parses ALLOW/DENY (fail-open). mcp_door routes to
it when CLAWMATES_JUDGE_MODEL=runtime:<alias>.

This unblocks Kimi-as-judge with NO Kimi Platform key: Kimi runs on the
membership via kimi_cli, so CLAWMATES_JUDGE_MODEL=runtime:judge_kimi makes the
coding agent the governor. (Same path works for any subscription-only model.)
cm-runtime re-exports judge_model. clippy clean.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-18 09:42:46 -07:00
co-authored by Claude Opus 4.8
parent 2fcec622fb
commit 3402a3b56d
3 changed files with 30 additions and 2 deletions
+12 -1
View File
@@ -153,7 +153,18 @@ async fn policy_decide(
category.map(|c| c.as_str()).unwrap_or("none"), category.map(|c| c.as_str()).unwrap_or("none"),
serde_json::to_string(args).unwrap_or_default() serde_json::to_string(args).unwrap_or_default()
); );
let (allow, reason) = state.runtime.judge(system, &request).await; // `CLAWMATES_JUDGE_MODEL=runtime:<alias>` routes the governor through a
// ZeroClaw runtime agent (e.g. Kimi via kimi_cli on the subscription) —
// no platform API key needed. Otherwise the server-side registry judge.
let judge_model = cm_runtime::judge_model();
let (allow, reason) = if let Some(alias) = judge_model.strip_prefix("runtime:") {
match crate::topology_exec::ZeroClawDriveExecutor::from_env() {
Ok(exec) => exec.judge(alias.trim(), system, &request).await,
Err(e) => (true, format!("governor unreachable (fail-open): {e}")),
}
} else {
state.runtime.judge(system, &request).await
};
if !allow { if !allow {
return PolicyOutcome::Deny(format!("governor agent vetoed — {reason}")); return PolicyOutcome::Deny(format!("governor agent vetoed — {reason}"));
} }
+17
View File
@@ -179,6 +179,23 @@ impl ZeroClawDriveExecutor {
Ok(outcome) Ok(outcome)
} }
/// Use a runtime agent as a governance judge: drive `alias` with the judge
/// prompt and parse the verdict (`DENY` anywhere ⇒ deny, else allow). This
/// lets a **subscription-only** model (e.g. Kimi via `kimi_cli`) be the judge
/// with no platform API key — the registry/SDK path GLM and Kimi can't take.
/// Fail-open (returns `(true, …)`) so a judge outage never halts agents.
pub async fn judge(&self, alias: &str, system: &str, user: &str) -> (bool, String) {
let prompt = format!("{system}\n\n{user}");
match self.drive(alias, &prompt).await {
Ok(outcome) => {
let text = outcome.output.trim().to_string();
let allow = !text.to_uppercase().contains("DENY");
(allow, text)
}
Err(e) => (true, format!("governor unreachable (fail-open): {e}")),
}
}
/// Read frames until a terminal (`done`/`error`/`approval_request`) event. /// Read frames until a terminal (`done`/`error`/`approval_request`) event.
async fn drain<S>(ws: &mut S) -> Result<TurnOutcome, OrchestratorError> async fn drain<S>(ws: &mut S) -> Result<TurnOutcome, OrchestratorError>
where where
+1 -1
View File
@@ -11,6 +11,6 @@ mod tools;
pub use events::{RunEventBody, RunEventEnvelope}; pub use events::{RunEventBody, RunEventEnvelope};
pub use outbox::{drain_once, spawn_drainer, EmailSender, LettreSender, SmtpConfig}; pub use outbox::{drain_once, spawn_drainer, EmailSender, LettreSender, SmtpConfig};
pub use runtime::{ProviderRegistry, Runtime, RuntimeConfig, RuntimeError, StartedRun}; pub use runtime::{judge_model, ProviderRegistry, Runtime, RuntimeConfig, RuntimeError, StartedRun};
pub use sandboxes::SandboxManager; pub use sandboxes::SandboxManager;
pub use tools::{ClockNow, EmailSend, Tool, ToolContext, ToolRegistry}; pub use tools::{ClockNow, EmailSend, Tool, ToolContext, ToolRegistry};