Skip to content

Model routing: let sub-agents use their own models by emitting routing.candidateModels #66231

Closed

Description

@SivaKesava1

Summary

In a routed workflow, gh-aw sends engine.model-routing.allowed-models to AWF as the request
policy, apiProxy.allowedModels, which is enforced on every request. The routing selection
itself is advisory: a request for a different model is admitted and logged as deviated, as long
as the policy permits it. Because gh-aw makes the policy exactly the router's candidate list,
nothing outside that list is permitted. An inline sub-agent that declares its own model: outside
allowed-models is rejected by the model policy with 403 model_policy_violation. Routing isn't
what rejects it.

AWF now separates the two (gh-aw-firewall#9380, fixed by #9382): apiProxy.routing.candidateModels
limits the router and classifier, and allowedModels remains the request policy.

gh-aw doesn't emit candidateModels yet, so the only workaround is to add every sub-agent model to
allowed-models. That also makes those models routing candidates for the main task.

Evidence

  • Local reproduction (2 Oct, AWF b777912b). AWF ran with the allowedModels and alias map
    that gh-aw compiles for a routed workflow, with allowed-models set to gpt-5.6-luna,
    gpt-6-sol, claude-sonnet-4.6, and claude-haiku-4.5. The requests a sub-agent with its own
    model would make got these results:

    Sub-agent model Result
    claude-haiku-4.5 (in the list) 200
    alias small, resolved to claude-haiku-4.5 200
    alias opus (no allowed match) 403 model_policy_violation
    gpt-5.4-mini (not in the list) 403 model_policy_violation
  • In gh-aw's own workflows, 70 inline sub-agent blocks set model:. Most use the aliases
    small (36) and large (7).

Cause

  • buildAWFConfig in pkg/workflow/awf_config_build.go handles routed workflows like this:
    • it replaces the workflow's model policy with the routing candidates
      (resolveModelRoutingAllowedModels, intersected with models.allowed and models.blocked by
      intersectModelRoutingPolicy);
    • it writes the result as apiProxy.allowedModels;
    • AWFModelRoutingConfig in pkg/workflow/awf_config.go has no candidateModels field.
  • extractEffectiveMarkdown in pkg/workflow/compiler_orchestrator_tools.go extracts the inline
    sub-agents (parser.ExtractInlineSubAgents) but only logs their count. Their model: values
    aren't available when the AWF config is built.

Proposed plan

  1. Emit routing.candidateModels.
    • Add a candidateModels field to AWFModelRoutingConfig.
    • Fill it with the provider-qualified allowed-models, intersected with models.allowed and
      models.blocked as today.
    • The router and classifier then stay limited to allowed-models.
  2. Widen the request policy to the declared sub-agent models.
    • Set apiProxy.allowedModels to the routing candidates plus the models that inline
      sub-agents declare in model:, still intersected with the workflow's models.allowed and
      models.blocked.
    • Keep the sub-agent models from extractEffectiveMarkdown (and from imported agent files, if
      they declare models) on the workflow data so the AWF config builder can use them.
    • For aliases such as small, add the alias's resolved patterns from the model alias map, so
      AWF's alias resolution can find an admitted model.
    • This keeps the cost bound explicit: only models the workflow names somewhere are permitted.
  3. Warn at compile time. If a sub-agent's model can't be admitted, for example because
    models.blocked excludes it or an alias resolves to nothing, emit a compiler warning naming the
    sub-agent and the model. Don't let it fail at run time with 403.
  4. Gate on AWF version.
  5. Tests.
    • Compile a routed workflow with inline sub-agents that use a concrete model, an alias, and a
      blocked model.
    • Assert that routing.candidateModels equals the candidates, that allowedModels adds the
      sub-agent models, that the blocked one produces a warning, and that an old AWF version falls
      back.

Notes

  • Copilot CLI's built-in sub-agents (for example Explore) inherit the routed model, so they're
    unaffected. This issue is about sub-agents that declare their own model.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions