feat(llm): the subscription is the default provider, with a recorded fallback chain
Two changes so an empty metered account stops being a platform outage. 1. `build_provider` prefers the subscription token over ANTHROPIC_API_KEY. A bare model name resolves to whatever this returns, so making it the subscription means no server-side call can reach the metered key by construction — rather than by a source-grep test that already missed four call sites once. The metered key remains a fallback and now warns loudly when it is the one in use; boot no longer requires it at all. 2. `complete_with_fallback` walks a declared chain when a model has no capacity: opus -> haiku -> glm:glm-4.7 by default, overridable via CLAWMATES_MODEL_FALLBACK, empty to disable. Measured on gw-04 today: opus and sonnet return 429 on the subscription while haiku, GLM and Kimi all return 200, so a capped window no longer means "the planner is gone". The chain returns the model that ANSWERED, and every caller persists it — mission_plan_proposals.author_model, mission_team_proposals.author_model, and the swarm's step role. A plan drafted by the third link and filed as an opus plan is a silent quality change, which is the failure shape this project keeps paying for. Two negative controls hold the design: the chain never retries the model that just failed as its own fallback, and it steps down ONLY for a capacity failure — walking it on a malformed prompt would ask three models the same bad question and report the third one's confusion. Co-Authored-By: Claude Opus 5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5
parent
ee5a939ce6
commit
9c9439a271
@@ -115,7 +115,10 @@ pub async fn suggest(
|
||||
// `Runtime::complete` with a bare model name resolves to the default
|
||||
// provider, which is the pay-as-you-go key; this planner died with
|
||||
// "credit balance is too low" while missions on the same box ran fine.
|
||||
let raw = crate::subscription::complete_or(
|
||||
// `author_model` is what ANSWERED, not what was asked for. When opus is
|
||||
// capped the chain steps down to haiku and then to GLM, and a plan drafted
|
||||
// by the third link but filed as an opus plan is a silent quality change.
|
||||
let (raw, author_model) = crate::subscription::complete_with_fallback(
|
||||
&state.runtime,
|
||||
ROSTER_SYSTEM,
|
||||
&prompt,
|
||||
@@ -124,12 +127,12 @@ pub async fn suggest(
|
||||
false,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
eprintln!("mission {id}: roster proposal failed: {e}");
|
||||
// A rate-limited subscription is a 503 the operator can act on, not
|
||||
// a 500 that reads as "this server is broken".
|
||||
crate::subscription::as_api_error(&e)
|
||||
})?;
|
||||
.map_err(|e| {
|
||||
eprintln!("mission {id}: roster proposal failed: {e}");
|
||||
// A rate-limited subscription is a 503 the operator can act on, not
|
||||
// a 500 that reads as "this server is broken".
|
||||
crate::subscription::as_api_error(&e)
|
||||
})?;
|
||||
|
||||
// A model that answered with prose around its JSON has still answered; a
|
||||
// model that answered with nothing usable has not, and that is a refusal
|
||||
@@ -158,7 +161,7 @@ pub async fn suggest(
|
||||
id,
|
||||
ws.as_uuid().to_owned(),
|
||||
&stored,
|
||||
PLANNER_MODEL,
|
||||
&author_model,
|
||||
)
|
||||
.await
|
||||
.map_err(|e| {
|
||||
@@ -167,7 +170,7 @@ pub async fn suggest(
|
||||
})?;
|
||||
eprintln!(
|
||||
"mission_roster: mission {id} — {} proposed {} member(s): {}",
|
||||
PLANNER_MODEL,
|
||||
author_model,
|
||||
roster.members.len(),
|
||||
roster
|
||||
.members
|
||||
@@ -180,7 +183,7 @@ pub async fn suggest(
|
||||
Ok(Json(ProposalResponse {
|
||||
id: pid,
|
||||
roster: stored,
|
||||
author_model: PLANNER_MODEL.to_string(),
|
||||
author_model,
|
||||
status: "proposed".into(),
|
||||
}))
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user