diff --git a/src/agents/cli-runner/prepare.test.ts b/src/agents/cli-runner/prepare.test.ts index c92e07024ec2..b4bbc7ccc6c5 100644 --- a/src/agents/cli-runner/prepare.test.ts +++ b/src/agents/cli-runner/prepare.test.ts @@ -277,7 +277,7 @@ describe("prepareCliRunContext", () => { requestedProvider: "openai", requestedModel: "mock-1", stage: "fallback", - fallbackReason: "overloaded", + fallbackReason: "rate_limit", }, }), ).finally(preparedRunAdmission.close); @@ -285,7 +285,7 @@ describe("prepareCliRunContext", () => { expect(decisionWork).toHaveLength(1); expect(decisionWork[0]?.receipt).toMatchObject({ action: { summary: "Requested openai/mock-1; selected test-cli/mock-2." }, - decision: { reasonCode: "model_route_selected_after_fallback" }, + decision: { reasonCode: "rate_limit" }, }); }); diff --git a/src/agents/embedded-agent-runner/run/attempt-dispatch-preparation.ts b/src/agents/embedded-agent-runner/run/attempt-dispatch-preparation.ts index a56a742b4dbf..87e1f9b47d90 100644 --- a/src/agents/embedded-agent-runner/run/attempt-dispatch-preparation.ts +++ b/src/agents/embedded-agent-runner/run/attempt-dispatch-preparation.ts @@ -226,8 +226,9 @@ export async function prepareAndDispatchEmbeddedRunAttempt(input: { selectionMode: runtime.lastProfileId && runtime.lastProfileId === lockedProfileId ? "explicit" : "automatic", credentialProfileId: runtime.lastProfileId, - fallbackSelected: params.modelRoutingProvenance?.stage === "fallback", - fallbackReason: params.modelRoutingProvenance?.fallbackReason ?? fallbackReason, + fallbackSelected: + params.modelRoutingProvenance?.stage === "fallback" || Boolean(fallbackReason), + fallbackReason: params.modelRoutingProvenance?.fallbackReason, }); const dispatchedAttempt = await dispatchEmbeddedRunAttempt({ params, diff --git a/src/agents/model-fallback.run-embedded.e2e.test.ts b/src/agents/model-fallback.run-embedded.e2e.test.ts index d75a422c4625..8171cf2b5dda 100644 --- a/src/agents/model-fallback.run-embedded.e2e.test.ts +++ b/src/agents/model-fallback.run-embedded.e2e.test.ts @@ -563,7 +563,7 @@ describe("runWithModelFallback + runEmbeddedAgent failover behavior", () => { }, { action: { summary: "Requested openai/mock-1; selected groq/mock-2." }, - decision: { reasonCode: "model_route_selected_after_fallback" }, + decision: { reasonCode: "overloaded" }, }, ]); expect(observedModelRoutingProvenance).toMatchObject([ diff --git a/src/agents/model-routing-decision.test.ts b/src/agents/model-routing-decision.test.ts index 38d2a3ef86b7..2339e3ff79de 100644 --- a/src/agents/model-routing-decision.test.ts +++ b/src/agents/model-routing-decision.test.ts @@ -45,7 +45,7 @@ describe("admitted model routing decisions", () => { token, receipt: { action: { family: "model-routing", operation: "explicit-selection" }, - decision: { reasonCode: "model_route_selected_after_fallback" }, + decision: { reasonCode: "rate_limit" }, enforcement: { coverageState: "attribution-only" }, }, refs: { diff --git a/src/agents/model-routing-decision.ts b/src/agents/model-routing-decision.ts index b731e1b13491..721bea0abb45 100644 --- a/src/agents/model-routing-decision.ts +++ b/src/agents/model-routing-decision.ts @@ -4,6 +4,7 @@ import { truncateUtf16Safe } from "@openclaw/normalization-core/utf16-slice"; import { recordExecutionDecisionWork } from "../audit/execution-decision-work.js"; import type { ExecutionIdentityAdmissionToken } from "../audit/execution-identity-admission.js"; import { redactSensitiveText } from "../logging/redact.js"; +import type { FailoverReason } from "./failover/signal.js"; type ModelRoutingSelectionMode = "automatic" | "explicit"; @@ -21,7 +22,7 @@ export function recordAdmittedModelRoutingDecision(params: { selectionMode: ModelRoutingSelectionMode; credentialProfileId?: string; fallbackSelected?: boolean; - fallbackReason?: string | null; + fallbackReason?: FailoverReason | null; occurredAt?: number; }): boolean { if (!params.token) { @@ -32,8 +33,9 @@ export function recordAdmittedModelRoutingDecision(params: { const selectedRef = boundedModelRef(params.selectedProvider, params.selectedModel); const credentialProfileId = params.credentialProfileId?.trim(); const hasCredentialOwner = Boolean(credentialProfileId); - const fallbackSelected = - params.fallbackSelected === true || Boolean(params.fallbackReason?.trim()); + const reasonCode = + params.fallbackReason ?? + (params.fallbackSelected ? "model_route_selected_after_fallback" : "model_route_selected"); return recordExecutionDecisionWork({ workVersion: 1, token: params.token, @@ -48,9 +50,7 @@ export function recordAdmittedModelRoutingDecision(params: { }, decision: { outcome: "allowed", - reasonCode: fallbackSelected - ? "model_route_selected_after_fallback" - : "model_route_selected", + reasonCode, }, enforcement: { coverageState: hasCredentialOwner ? "attribution-only" : "unknown",