Skip to content

agent followup (resume) drops the profile's provider pin, so a resumed child is validated against the session's active provider #6046

Description

@Hmbown

Symptom

Resuming a parked/interrupted sub-agent with the agent tool's followup action fails with a route error that names a provider the caller never selected:

model "<pinned model>" is not served by direct provider deepseek

The child's own route receipt still reports the pinned provider, so the receipt and the executed route disagree.

Reproduction — 3/3 across three different providers

Profile Pinned route (per its own receipt) start (cold) followup (resume)
consultant openai-codex / gpt-5.6-sol reached the Responses API; failed on a rate limit failed: not served by direct provider deepseek
reviewer xai / grok-4.6 ran normally for many minutes failed: not served by direct provider deepseek
general zai / GLM-5.3 ran normally failed: not served by direct provider deepseek

Observed: agent_6cc0446eagent_52b116e6; agent_401e22fcagent_ec759da8; agent_85eb7e46agent_b53d1214. Every resume failed in one step, ~45–70 ms — it never reached a provider.

Control: a cold start with the same reviewer profile, dispatched immediately afterwards, resolved correctly and ran. So the pin is honored on start and dropped on followup.

start with resume_from="<parked id>" also dispatched successfully where followup on the same id failed — a possible workaround, though that is only evidence it got past route resolution, not that it is correct.

Mechanism

The config has top-level provider = "deepseek"; the profiles pin both provider and model. On resume the model pin survives but the provider falls back to the session's active provider. deepseek is a StrictDirect provider, and the resolver rejects a selector matching another provider's offering at crates/config/src/route/resolver.rs:608 (selector_matches_other_provider_offering), producing RouteError::ForeignModelForDirectProvider (crates/config/src/route/errors.rs:53).

Not yet located: the exact line in the resume path where the provider pin is lost. The behavior and the error site are pinned; the drop site is not. Reporting it anyway because the repro is deterministic and cheap.

Why no test catches it

crates/tui/src/tools/subagent/tests.rs:20638parked_followup_reuses_successor_and_preserves_route_authority_and_lineage — looks like it covers exactly this, and passes. It asserts:

assert_eq!(spec.child_route.as_ref(), Some(&saved_route));

But its fixture pins provider_id: "deepseek", identical to the stub runtime's default provider. So (a) it asserts the successor's recorded receipt, not the route execution actually uses — and the receipt is the half that works; and (b) with the pinned provider equal to the fallback provider, a fallback is indistinguishable from correct behavior. The fixture masks the exact variable under test.

Why it matters

  • The receipt disagrees with the executed route, so any diagnostic built on the receipt is wrong.
  • The error names a provider the user never selected, sending debugging in the wrong direction.
  • Resume is the documented recovery path for every parked or interrupted child, so this is not an edge case.

Fix direction

Resolve the child's provider from the saved child_route/profile on the resume path with the same authority as start, rather than from the session's active provider. Then make the guard test pin a provider different from the session default and assert the route execution actually uses, not only the receipt.

Related

Same family as #6032 (capability decided by hardcoded tables), #6035 (model pins don't propagate), #6036 (Fleet and agent profile stored twice), #6037 (members over-pin their model).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions