You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Copilot SDK mode: routed model passed unqualified, declared sub-agents never loaded, and one wire API shared by every Copilot model #67507
In Copilot SDK mode (engine.copilot-sdk: true), the session that gh-aw builds for the Copilot runtime has four gaps. Together they mean a routed workflow under org billing fails outright, and a declared sub-agent never runs on its own model.
The session model is passed unqualified. With BYOK providers configured, the Copilot runtime only uses a provider when the model is written as <provider>/<model>. A bare name falls back to Copilot's built-in inference. Inside AWF that path has no token, so the session fails before any model call with No GitHub OAuth token or Copilot HMAC key provided. When routing is off, the harness adds the copilot/ prefix and SDK mode works under org billing (smoke-copilot-sub-agents uses copilot-requests: write and passes). The failure happens whenever AWF model routing supplies the model, because the prefix is skipped when a routing selection exists. The fallback model and the model taken from /reflect are also bare, so those paths fail the same way.
Declared sub-agents are never loaded. The session config sets neither customAgents nor enableConfigDiscovery, and the SDK's default for enableConfigDiscovery is false. The inline agents that gh-aw writes to .github/agents/*.agent.md are therefore ignored. When the prompt asks for delegation, the main agent quietly runs the built-in general-purpose agent on its own model. This is why smoke-copilot-sub-agents has never exercised its Haiku agent.
Bare agent models are rejected. If config discovery is turned on, the agent file loads, but its frontmatter model (claude-haiku-4.5) is rejected because only provider-qualified names are offered.
Every Copilot model shares one wire API.resolveMultiProviderFromReflect builds one SDK provider per AWF endpoint with a single wireApi, which is inferred from the configured model or overridden by the routing selection. A Claude sub-agent under a GPT main model is then sent on /responses, and AWF rejects it because that model only supports /chat/completions.
model-routing on, with allowed-models: [gpt-5.6-luna]
The workflow declares an inline sub-agent file-summarizer with model: claude-haiku-4.5, and the task asks the main agent to delegate to it.
The custom-driver runs use .github/drivers/xfam_sdk_driver.cjs in the sandbox repo. It calls gh-aw's own runWithCopilotSDK and changes only the providers, models, session model and customAgents before the session starts. Its [xfam] lines in agent-stdio.log record every model call (isByok, model, result) and every sub-agent event. A green agent step does not show that the sub-agent worked. In several runs the main agent recovered by doing the work itself or by switching the sub-agent to another model. The evidence below therefore comes from the [xfam] lines and from sandbox/firewall/logs/api-proxy-logs/token-usage.jsonl.
The "Ingest agent output" failure in all of these runs is unrelated. It is the separate memory_schema_contract.cjs setup regression on main.
This is the only run where "Execute GitHub Copilot CLI" fails. All four attempts fail about 200 ms after session.start:
[copilot-sdk-driver] [sdk-driver] creating session with model="gpt-5.6-luna" providers=1 models=4
session.error ... "Execution failed: InvalidArg, No GitHub OAuth token or Copilot HMAC key provided"
The CLI process log shows Received session.create request: {"model":"gpt-5.6-luna", ...}, with no provider prefix.
The harness had resolved the provider correctly: inference routing: mode=awf-routed model=gpt-5.6-luna effort=xhigh wire_api=responses and sdk-mode(multi): resolved provider="copilot" ... wireApi="responses" ... baseUrl="http://api-proxy:10002".
token-usage.jsonl contains only the routing classifier request (purpose=routing_classification, gpt-5.6-luna /responses 200). No agent request reached the api-proxy.
Custom driver experiments (same workflow, only the provider configuration changes)
Main model prefixed (copilot/gpt-5.6-luna), one provider copilot:responses, customAgents=0
The session works, but no declared agent exists: subagent.started agent=general-purpose model=copilot/gpt-5.6-luna. Every request is gpt-5.6-luna /responses.
The agent file loads. Its bare model is rejected, and the qualified Haiku model then fails on the wrong wire API. The main agent retries file-summarizer on copilot/gpt-5-mini.
Split providers, models mapped by supported_endpoints, declared agents passed as customAgents
Works. file-summarizer runs on Haiku over /chat/completions.
In the discovery run, the bare model is rejected. The listed models are also duplicated:
Model 'claude-haiku-4.5' is not available. Available models: copilot/claude-haiku-4.5, copilot/gpt-5-mini, copilot/gpt-5.4-mini, copilot/gpt-5.6-luna, copilot/claude-haiku-4.5, ...
The qualified Haiku model is then sent with the session's single wire API:
[xfam] subagent.started agent=file-summarizer model=copilot/claude-haiku-4.5
[xfam] model.call_final_result model=claude-haiku-4.5 isByok=true result=http_400
[xfam] session.error ... 400 Cannot translate Copilot request feature 'include' between Responses and Chat Completions. Routing model "claude-haiku-4.5" to /chat/completions is incompatible: this request needs /responses to preserve 'include'
[xfam] subagent.failed agent=file-summarizer model=copilot/claude-haiku-4.5
[xfam] subagent.started agent=file-summarizer model=copilot/gpt-5-mini
In the working run, the driver maps each model from /reflectrouting_models[].supported_endpoints:
AWF's token-usage.jsonl for the same session shows claude-haiku-4.5 on /chat/completions (200, twice) and gpt-5.6-luna on /responses (200). The run's usage/aw_session.jsonl already contains subagent.started, subagent.configured, subagent.selected and subagent.completed events with provider-qualified models, plus firewall.token_usage per model and path.
The second declared agent, quick-checker, has model: small. That is an alias, not a catalog model, so the driver left it on the session model.
gh-aw's own smoke runs
They do not silently bypass the api-proxy.
smoke-copilot-sdk 37862583327 uses a secret token and model: copilot/gpt-5.4. The CLI process log has 4 model.call_start events and 4 connections to api-proxy, with no direct Copilot host. These match 4 /responses requests in token-usage.jsonl.
smoke-copilot-sub-agents 37863614500 uses org billing with session model copilot/gpt-5.3-codex. It has 3 calls, 3 api-proxy connections and 3 matching requests. Runs 37550743356 and 37246639853 look the same.
Both smoke tests pass because their model is provider-qualified and routing is off. Neither one exercises routing, an unqualified model or a real sub-agent.
The stock driver does not persist model.call_final_result, because CAPTURED_EPHEMERAL_EVENTS adds only model.call_failure. As a result, isByok cannot be read from stock artifacts.
smoke-copilot-sub-agents does not test sub-agents. In 37863614500, the requests are all gpt-5.3-codex and no sub-agent was invoked. Its checks compare the agents' text replies, which any model can produce. It also has two problems that should be tracked separately and should not block this fix:
The rendered prompt contains every {{#if experiments.sub_agent_strategy == ...}} block, unevaluated. The agent followed the first block ("Do not call any sub-agent"). Its own report listed all agents as not_invoked with Overall status: FAIL and an unrendered variant name, yet the run was green. The same unrendered prompt appears in 37550743356 and 37246639853.
The nano-whoami agent declares gpt-5-nano, which the Copilot endpoint does not offer in that run's /reflect model list.
copilot_harness.cjs. The copilot/ prefix is applied only when there is no modelRoutingSelection. That guard was added in Support AWF task-level model routing in the Copilot engine #64593, so a routed COPILOT_MODEL reaches the SDK driver bare. The harness also overwrites the primary provider's wireApi with the routing endpoint, which forces every model on that provider onto one wire API.
copilot_sdk_driver.cjsmain. The driver chooses the routed COPILOT_MODEL, then the applyModelFallback result, then multiProviderConfig.model. Nothing qualifies any of the three.
copilot_sdk_session.cjsrunWithCopilotSDK. The session is created with that model, falling back to resolveModelWithFallback, with no customAgents and no enableConfigDiscovery.
awf_reflect.cjsresolveMultiProviderFromReflect. It builds one provider per endpoint with a single wireApi, even though routing_models[].supported_endpoints gives the wire APIs for each model. That data is present in /reflect whether or not routing is on.
model_fallback.cjs.GH_AW_MODEL_FALLBACK is a bare model name.
Intended behavior
One SDK provider per wire API for the Copilot endpoint. Build a responses provider and a completions provider, both pointing at the same api-proxy base URL. Map each Copilot model to one of them using /reflectrouting_models[].supported_endpoints.
A model that supports both wire APIs keeps today's inference.
The routed model goes on the wire API from the routing selection.
Other endpoints (openai, anthropic) stay as they are.
Provider-qualified models everywhere. The session model is passed as <provider>/<model>, including the routed COPILOT_MODEL, the GH_AW_MODEL_FALLBACK model and the model taken from /reflect. A bare model can no longer fall through to built-in inference inside AWF.
Declared sub-agents are loaded. The workflow's sub-agents (inline agents and .github/agents/*.agent.md) are passed to the session as customAgents. Name, description, prompt and tools come from the agent files, and each agent's model is provider-qualified with the same mapping. A model alias such as small either resolves to a concrete catalog model or leaves the agent on the session model. An unknown model produces a clear warning instead of a silent general-purpose fallback.
Sub-agent activity is recorded. Sub-agent model, usage and outcome events stay in usage/aw_session.jsonl, the unified session file. Audit and logs must attribute sub-agent spend to the sub-agent's model, which means normalizing the new provider-qualified names (for example copilot-completions/claude-haiku-4.5) to the served model.
model.call_final_result is captured. Persisting it, with isByok and the result, in the stock driver's captured events would make this class of problem visible from the standard artifacts.
Files and functions that change
actions/setup/js/awf_reflect.cjs: resolveMultiProviderFromReflect (per-wire-API Copilot providers and per-model mapping), using the existing getCatalogModelEntry, inferWireApiForModel and endpointBaseUrl helpers.
actions/setup/js/copilot_harness.cjs: the model qualification guard and the primary-provider wireApi override near the resolveMultiProviderFromReflect call, and buildCopilotSDKChildEnv.
actions/setup/js/copilot_sdk_driver.cjs: main, so that the routed, fallback and reflect models are qualified.
actions/setup/js/copilot_sdk_session.cjs: the runWithCopilotSDK session config (qualified model, customAgents), plus CAPTURED_EPHEMERAL_EVENTS.
actions/setup/js/copilot_sdk_multi_provider.cjs: parseMultiProviderJson, if the multi-provider shape needs to carry the per-model provider.
actions/setup/js/model_fallback.cjs: callers of resolveModelWithFallback and applyModelFallback in SDK mode.
actions/setup/js/extract_inline_sub_agents.cjs: reuse its agent parsing to build customAgents from the files that interpolate_prompt.cjs writes.
actions/setup/js/copilot_workflow_events.cjs and the unified session writer: keep the subagent.* events and their models in usage/aw_session.jsonl.
pkg/cli/token_usage_subagent_session.go, token_usage_subagent.go and model_identity.go: attribution with provider-qualified sub-agent models. Add a fixture next to pkg/cli/testdata/subagent_attribution/copilot-cross-family/ that uses qualified names.
.github/workflows/smoke-copilot-sub-agents.md: the assertions (see Tests).
Tests
Unit tests in awf_reflect.test.cjs, driven by a /reflect fixture that contains routing_models[].supported_endpoints, for:
the provider split
per-model provider mapping
routed-model and fallback-model qualification
alias handling
Unit tests in copilot_sdk_driver.test.cjs and the session tests that the session model is never bare and that declared agents arrive as customAgents with qualified models.
smoke-copilot-sub-agents should assert from usage/aw_session.jsonl and token-usage.jsonl that each declared sub-agent ran on its own model and wire API. Checking only that the run passed, or that the reply text names a model, is not enough.
Acceptance
In SDK mode under org billing, two workflows complete successfully: one with a GPT main model and a declared Claude sub-agent, and one the other way round. In both:
The sub-agent's requests go through the api-proxy on its own model and wire API.
Nothing falls back to the main model.
Audit shows the sub-agent's model.
Scope
This issue covers SDK mode only. CLI mode (the default) is not covered and still depends on github/copilot-cli#5103 (BYOK sub-agents always use the session's wire API).
Related: #67485 (CLI-mode wire API selection from AWF endpoint data) and #67460 (haiku sub-agent 'include' failure).
Problem
In Copilot SDK mode (
engine.copilot-sdk: true), the session that gh-aw builds for the Copilot runtime has four gaps. Together they mean a routed workflow under org billing fails outright, and a declared sub-agent never runs on its own model.<provider>/<model>. A bare name falls back to Copilot's built-in inference. Inside AWF that path has no token, so the session fails before any model call withNo GitHub OAuth token or Copilot HMAC key provided. When routing is off, the harness adds thecopilot/prefix and SDK mode works under org billing (smoke-copilot-sub-agents usescopilot-requests: writeand passes). The failure happens whenever AWF model routing supplies the model, because the prefix is skipped when a routing selection exists. The fallback model and the model taken from/reflectare also bare, so those paths fail the same way.customAgentsnorenableConfigDiscovery, and the SDK's default forenableConfigDiscoveryis false. The inline agents that gh-aw writes to.github/agents/*.agent.mdare therefore ignored. When the prompt asks for delegation, the main agent quietly runs the built-ingeneral-purposeagent on its own model. This is why smoke-copilot-sub-agents has never exercised its Haiku agent.claude-haiku-4.5) is rejected because only provider-qualified names are offered.resolveMultiProviderFromReflectbuilds one SDK provider per AWF endpoint with a singlewireApi, which is inferred from the configured model or overridden by the routing selection. A Claude sub-agent under a GPT main model is then sent on/responses, and AWF rejects it because that model only supports/chat/completions.Setup
9e123795f5, compiled with--gh-aw-refengine: copilotwithcopilot-sdk: true@github/copilot-sdk1.0.16, AWF v0.28.50permissions: copilot-requests: write)model-routingon, withallowed-models: [gpt-5.6-luna]file-summarizerwithmodel: claude-haiku-4.5, and the task asks the main agent to delegate to it.The custom-driver runs use
.github/drivers/xfam_sdk_driver.cjsin the sandbox repo. It calls gh-aw's ownrunWithCopilotSDKand changes only the providers, models, session model andcustomAgentsbefore the session starts. Its[xfam]lines inagent-stdio.logrecord every model call (isByok, model, result) and every sub-agent event. A green agent step does not show that the sub-agent worked. In several runs the main agent recovered by doing the work itself or by switching the sub-agent to another model. The evidence below therefore comes from the[xfam]lines and fromsandbox/firewall/logs/api-proxy-logs/token-usage.jsonl.The "Ingest agent output" failure in all of these runs is unrelated. It is the separate
memory_schema_contract.cjssetup regression on main.Evidence
Stock gh-aw SDK driver fails: 38077047915
This is the only run where "Execute GitHub Copilot CLI" fails. All four attempts fail about 200 ms after
session.start:Received session.create request: {"model":"gpt-5.6-luna", ...}, with no provider prefix.inference routing: mode=awf-routed model=gpt-5.6-luna effort=xhigh wire_api=responsesandsdk-mode(multi): resolved provider="copilot" ... wireApi="responses" ... baseUrl="http://api-proxy:10002".token-usage.jsonlcontains only the routing classifier request (purpose=routing_classification, gpt-5.6-luna/responses200). No agent request reached the api-proxy.Custom driver experiments (same workflow, only the provider configuration changes)
copilot/gpt-5.6-luna), one providercopilot:responses,customAgents=0subagent.started agent=general-purpose model=copilot/gpt-5.6-luna. Every request is gpt-5.6-luna/responses.copilot-responses:responsesandcopilot-completions:completions,customAgents=0general-purposeoncopilot-responses/gpt-5.6-luna, and Haiku never runs.enableConfigDiscovery=truecopilot/gpt-5-mini.enableConfigDiscovery=truecopilot-responses/gpt-5-miniinstead of Haiku.supported_endpoints, declared agents passed ascustomAgents/chat/completions.In the discovery run, the bare model is rejected. The listed models are also duplicated:
The qualified Haiku model is then sent with the session's single wire API:
In the working run, the driver maps each model from
/reflectrouting_models[].supported_endpoints:AWF's
token-usage.jsonlfor the same session shows claude-haiku-4.5 on/chat/completions(200, twice) and gpt-5.6-luna on/responses(200). The run'susage/aw_session.jsonlalready containssubagent.started,subagent.configured,subagent.selectedandsubagent.completedevents with provider-qualified models, plusfirewall.token_usageper model and path.The second declared agent,
quick-checker, hasmodel: small. That is an alias, not a catalog model, so the driver left it on the session model.gh-aw's own smoke runs
They do not silently bypass the api-proxy.
model: copilot/gpt-5.4. The CLI process log has 4model.call_startevents and 4 connections toapi-proxy, with no direct Copilot host. These match 4/responsesrequests intoken-usage.jsonl.copilot/gpt-5.3-codex. It has 3 calls, 3 api-proxy connections and 3 matching requests. Runs 37550743356 and 37246639853 look the same.Both smoke tests pass because their model is provider-qualified and routing is off. Neither one exercises routing, an unqualified model or a real sub-agent.
The stock driver does not persist
model.call_final_result, becauseCAPTURED_EPHEMERAL_EVENTSadds onlymodel.call_failure. As a result,isByokcannot be read from stock artifacts.smoke-copilot-sub-agents does not test sub-agents. In 37863614500, the requests are all gpt-5.3-codex and no sub-agent was invoked. Its checks compare the agents' text replies, which any model can produce. It also has two problems that should be tracked separately and should not block this fix:
{{#if experiments.sub_agent_strategy == ...}}block, unevaluated. The agent followed the first block ("Do not call any sub-agent"). Its own report listed all agents asnot_invokedwithOverall status: FAILand an unrendered variant name, yet the run was green. The same unrendered prompt appears in 37550743356 and 37246639853.nano-whoamiagent declaresgpt-5-nano, which the Copilot endpoint does not offer in that run's/reflectmodel list.Root cause in the code (at 9e12379)
copilot_harness.cjs. Thecopilot/prefix is applied only when there is nomodelRoutingSelection. That guard was added in Support AWF task-level model routing in the Copilot engine #64593, so a routedCOPILOT_MODELreaches the SDK driver bare. The harness also overwrites the primary provider'swireApiwith the routing endpoint, which forces every model on that provider onto one wire API.copilot_sdk_driver.cjsmain. The driver chooses the routedCOPILOT_MODEL, then theapplyModelFallbackresult, thenmultiProviderConfig.model. Nothing qualifies any of the three.copilot_sdk_session.cjsrunWithCopilotSDK. The session is created with that model, falling back toresolveModelWithFallback, with nocustomAgentsand noenableConfigDiscovery.awf_reflect.cjsresolveMultiProviderFromReflect. It builds one provider per endpoint with a singlewireApi, even thoughrouting_models[].supported_endpointsgives the wire APIs for each model. That data is present in/reflectwhether or not routing is on.model_fallback.cjs.GH_AW_MODEL_FALLBACKis a bare model name.Intended behavior
/reflectrouting_models[].supported_endpoints.<provider>/<model>, including the routedCOPILOT_MODEL, theGH_AW_MODEL_FALLBACKmodel and the model taken from/reflect. A bare model can no longer fall through to built-in inference inside AWF..github/agents/*.agent.md) are passed to the session ascustomAgents. Name, description, prompt and tools come from the agent files, and each agent's model is provider-qualified with the same mapping. A model alias such assmalleither resolves to a concrete catalog model or leaves the agent on the session model. An unknown model produces a clear warning instead of a silentgeneral-purposefallback.usage/aw_session.jsonl, the unified session file. Audit and logs must attribute sub-agent spend to the sub-agent's model, which means normalizing the new provider-qualified names (for examplecopilot-completions/claude-haiku-4.5) to the served model.model.call_final_resultis captured. Persisting it, withisByokand the result, in the stock driver's captured events would make this class of problem visible from the standard artifacts.Files and functions that change
actions/setup/js/awf_reflect.cjs:resolveMultiProviderFromReflect(per-wire-API Copilot providers and per-model mapping), using the existinggetCatalogModelEntry,inferWireApiForModelandendpointBaseUrlhelpers.actions/setup/js/copilot_harness.cjs: the model qualification guard and the primary-providerwireApioverride near theresolveMultiProviderFromReflectcall, andbuildCopilotSDKChildEnv.actions/setup/js/copilot_sdk_driver.cjs:main, so that the routed, fallback and reflect models are qualified.actions/setup/js/copilot_sdk_session.cjs: therunWithCopilotSDKsession config (qualified model,customAgents), plusCAPTURED_EPHEMERAL_EVENTS.actions/setup/js/copilot_sdk_multi_provider.cjs:parseMultiProviderJson, if the multi-provider shape needs to carry the per-model provider.actions/setup/js/model_fallback.cjs: callers ofresolveModelWithFallbackandapplyModelFallbackin SDK mode.actions/setup/js/extract_inline_sub_agents.cjs: reuse its agent parsing to buildcustomAgentsfrom the files thatinterpolate_prompt.cjswrites.actions/setup/js/copilot_workflow_events.cjsand the unified session writer: keep thesubagent.*events and their models inusage/aw_session.jsonl.pkg/cli/token_usage_subagent_session.go,token_usage_subagent.goandmodel_identity.go: attribution with provider-qualified sub-agent models. Add a fixture next topkg/cli/testdata/subagent_attribution/copilot-cross-family/that uses qualified names..github/workflows/smoke-copilot-sub-agents.md: the assertions (see Tests).Tests
awf_reflect.test.cjs, driven by a/reflectfixture that containsrouting_models[].supported_endpoints, for:copilot_sdk_driver.test.cjsand the session tests that the session model is never bare and that declared agents arrive ascustomAgentswith qualified models.usage/aw_session.jsonlandtoken-usage.jsonlthat each declared sub-agent ran on its own model and wire API. Checking only that the run passed, or that the reply text names a model, is not enough.Acceptance
In SDK mode under org billing, two workflows complete successfully: one with a GPT main model and a declared Claude sub-agent, and one the other way round. In both:
Scope
This issue covers SDK mode only. CLI mode (the default) is not covered and still depends on github/copilot-cli#5103 (BYOK sub-agents always use the session's wire API).
Related: #67485 (CLI-mode wire API selection from AWF endpoint data) and #67460 (haiku sub-agent
'include'failure).