llm: named-provider registry — judges & topology nodes on GLM/Kimi

Adds a multi-provider registry so judges and topology execution can run on
providers beyond the default. cm-config gains [[llm.providers]] (name, base_url,
api_key_env) — all OpenAI-compatible (GLM, Kimi/Moonshot). The server builds an
Arc<dyn LlmProvider> per entry (OpenAiCompatProvider) keyed by name; a missing
key is skipped with a warning, not a boot failure. cm-runtime RuntimeConfig
carries a ProviderRegistry; Runtime::resolve_provider("<name>:<model>") selects a
registry provider (else the default). The door governor (Runtime::judge) and the
topology compare endpoint both resolve through it, so CLAWMATES_JUDGE_MODEL and
CLAWMATES_TOPOLOGY_EXEC_MODEL accept "glm:glm-4.6" / "kimi:kimi-k2".

Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
Omar Sobh
2026-06-17 04:17:14 -07:00
co-authored by Claude Opus 4.8
parent 325038e806
commit 2ec8832ef6
5 changed files with 88 additions and 10 deletions
+11 -7
View File
@@ -90,15 +90,19 @@ pub async fn compare_topologies(
Authed(user): Authed,
Json(req): Json<CompareRequest>,
) -> Result<Json<Comparison>, ApiError> {
let provider = state.runtime.provider();
// Execution turns run on the configured default model (sonnet); the judge
// uses the stronger judge model (default claude-opus-4-8).
let model = state.runtime.model().to_string();
let judge_model =
// Execution turns run on the exec model (default = configured model, e.g.
// sonnet); the judge uses the judge model (default claude-opus-4-8). Either
// can name a registry provider as "<name>:<model>" (e.g. "glm:glm-4.6",
// "kimi:kimi-k2") to run on GLM/Kimi instead.
let exec_spec = std::env::var("CLAWMATES_TOPOLOGY_EXEC_MODEL")
.unwrap_or_else(|_| state.runtime.model().to_string());
let (exec_provider, exec_model) = state.runtime.resolve_provider(&exec_spec);
let judge_spec =
std::env::var("CLAWMATES_JUDGE_MODEL").unwrap_or_else(|_| "claude-opus-4-8".to_string());
let (judge_provider, judge_model) = state.runtime.resolve_provider(&judge_spec);
let executor =
ProviderExecutor::new(provider.clone(), model, state.runtime.max_tokens());
let scorer = JudgeScorer::new(provider, judge_model, 16);
ProviderExecutor::new(exec_provider, exec_model, state.runtime.max_tokens());
let scorer = JudgeScorer::new(judge_provider, judge_model, 16);
let cmp = compare(&req.graphs, &req.task, &executor, &scorer)
.await
.map_err(|_| ApiError::Internal)?;