3.6 KiB
3.6 KiB
id, title, status, wave, depends_on, plan, spec
| id | title | status | wave | depends_on | plan | spec | |
|---|---|---|---|---|---|---|---|
| task-04-post-start-gating | Office post-start failure gates on profile mode | done | 2 |
|
docs/plans/no-silent-model-fallback/plan.md | docs/specs/agents/requirements/no-silent-model-fallback.md |
Task 4 — Post-start failure: strict escalate / fallback one-shot / legacy requeue
Change
HandlePostStartFailure in
apps/backend/internal/office/scheduler/routing_lifecycle.go already
receives agent *models.AgentInstance. After routingerr.Classify:
!classified.FallbackAllowed→ unchanged (return false, escalate).agent.AutoFallback→ unchanged (requeue to next candidate viaapplyPostStartFallback).agent.FallbackModel != ""→ one-shot retry with the fallback model:- Persist the override: new nullable column
runs.fallback_model(models.RungainsFallbackModelOverride *string; repo methodSetRunFallbackModelOverrideinapps/backend/internal/office/repository/sqlite/run_routing.go; migration in the runs schema file — check where office run migrations live, e.g.repository/sqlite/migrate list). - Requeue (existing
RequeueForNextCandidate). - Next dispatch (
dispatch_routing.go): whenrun.FallbackModelOverride != nil, build one candidate fromrun.ResolvedExecutionProfileID+run.ResolvedProviderID+ the override, skipping the resolver (or via aResolveOptionsforce), and clear the override after the attempt so a subsequent failure escalates to the terminal explicit-failure path. Counts as a normal attempt row (recordAttemptStart) — respectsMaxAttemptsPerRun. - On success,
handleLaunchSuccessrecordsResolvedModel = fallback.
- Persist the override: new nullable column
- otherwise (strict) → return
(false, nil)so the caller escalates toHandleAgentFailure— the run fails explicitly (no requeue).
Error mapping: helper (in routingerr or the scheduler) that renders an
actionable message for model/auth codes (model_unavailable,
auth_required, missing_credentials, subscription_required,
provider_unavailable) — e.g. "Model unavailable: the configured model <id> is no longer available. Change the model in the agent profile." Used
when the strict path writes the run failure message (and reused by task 03
for session-start failures).
Acceptance
- Test: strict profile + fallback-allowed failure →
HandlePostStartFailurereturns(false, nil); run reaches the terminal failure path; failure message contains the actionable copy. - Test: auto-fallback profile → existing requeue-to-next-candidate behavior preserved (regression).
- Test: fallback-model profile → override persisted + requeued; next dispatch launches the single forced candidate (same provider, fallback model); the override is cleared after one attempt.
- Test: forced candidate fails too → run fails explicitly (no second requeue, no resolver candidates).
- Test: forced candidate succeeds →
ResolvedModel= fallback; health scopes marked healthy. - Test:
MaxAttemptsPerRunstill enforced with the forced candidate.
Verification
make -C apps/backend test ./internal/office/scheduler/...
make -C apps/backend test ./internal/office/repository/...
Risks
- The forced candidate path must not bypass the attempt cap or the route-cycle baseline semantics.
- Find the runs-schema migration location and follow its conventions; existing runs get NULL override (no behavior change).
excludedFromAttempts/continuationLaunchContextmust treat the forced attempt like any other (exclude-set unchanged — the forced candidate is the only candidate, so exclusion is moot).