Skip to content

Pi sub-agent model.resolution stays 'exact' when the model fails the /reflect liveness check #70384

Closed

Description

@SivaKesava1

Problem

When a Pi sub-agent model fails Pi's /reflect liveness check, the unified session still records that sub-agent's model.resolution as successful. Pi aborts startup, but usage/aw_session.jsonl says the model resolved.

Seen in https://github.com/github/gh-aw/actions/runs/39325995892 (Smoke Pi Routed, workflow_dispatch, 19 Oct 2026, branch copilot/add-session-contract-checks from #70294, head 893ffe1377ec1997588ac17dfdbbcb8ecb499a3f).

Evidence

Audited with a dev build of main (gh aw dev-b68bd31219): gh aw audit 39325995892 --no-baseline --json.

  • smoke-pi-routed.md declares the inline sub-agent mini-whoami with model: copilot/gpt-5.4-mini. Copilot no longer offers gpt-5.4-mini; [model-inventory] Model alias inventory - action required - 2026-10-19 #70367 lists this pin as model_unavailable.
  • The audit reports model_unavailable (high): Pi startup rejected model "gpt-5.4-mini" on provider "copilot" before inference. Same-run awf-reflect.json confirms models_fetch_complete=true; the configured copilot endpoint advertises 36 model(s), but not "gpt-5.4-mini".
  • The /reflect capture (sandbox/firewall/awf-reflect.json in the agent artifact) has models_fetch_complete: true and one configured endpoint, copilot, with 36 models. It includes claude-haiku-4.5 and gpt-5.6-luna, but not gpt-5.4-mini.
  • agent-stdio.log (the main model was routed: inference routing: mode=awf-routed model=gpt-5.6-luna):
    sub-agent haiku-whoami: requested=copilot/claude-haiku-4.5 resolved=github-copilot/claude-haiku-4.5
    sub-agent mini-whoami: requested=copilot/gpt-5.4-mini resolved=github-copilot/gpt-5.4-mini
    awf-reflect: model availability confirmed (provider=copilot, model=claude-haiku-4.5)
    warning: awf-reflect model availability check failed (provider=copilot, model=gpt-5.4-mini)
    fatal: Pi model "gpt-5.4-mini" is not advertised by the configured copilot proxy endpoint; The requested model is not supported: 'copilot/gpt-5.4-mini' is not available in the workflow's live model catalog. ...
    [pi-harness] attempt 1: process exit event exitCode=1
    
    The agent made no model requests and exited with code 1.
  • Despite that failure, agent/model-resolution.json and the session's model.resolution events contain:
    {"engine":"pi","role":"mini-whoami","configured_model":"copilot/gpt-5.4-mini","resolved_model":"gpt-5.4-mini","provider":"copilot","status":"exact","cause":null}
  • The smoke's U3 check (Add unified-session contract checks (U1–U3) to smoke assertions #70294) passed on that record while E1, R3, R4, S1–S3 and U1 failed:
    PASS U3 model.resolution recorded for haiku-whoami: copilot/claude-haiku-4.5 -> claude-haiku-4.5 (exact), mini-whoami: copilot/gpt-5.4-mini -> gpt-5.4-mini (exact)
    

Cause

Line numbers below are from main at b68bd31219.

  • actions/setup/js/pi_subagent_config.cjs (preparePiSubagents, ~L87) records the sub-agent's resolution (recordModelResolution(... status: resolution.status ...)) as soon as resolveConfiguredModel returns. For a non-alias ID, that step returns exact without checking whether the model is live.
  • actions/setup/js/pi_models_json.cjs (sub-agent loop, ~L538) then calls validatePiModelAvailability for each sub-agent model. That call throws a ModelAliasResolutionError with reason: "model_unavailable" for a model the endpoint doesn't list, but it doesn't replace the earlier exact record.
  • The main model path in the same file (~L445–452) already handles this case. It wraps validatePiModelAvailability in a try/catch, records status: "failed", cause: error.reason, resolved_model: null, and then rethrows. The sub-agent path has no such handling.

Proposed solution

Make the sub-agent path match the main path. In the sub-agent loop in pi_models_json.cjs, when validatePiModelAvailability throws a ModelAliasResolutionError:

  1. Record that sub-agent role's model.resolution with status: "failed", cause: error.reason (model_unavailable), resolved_model: null and provider: error.provider || null. Use the same configured_model that preparePiSubagents recorded. recordModelResolution replaces records with the same engine and role (resolve_model_alias.cjs ~L687), so the earlier exact record goes away.
  2. Rethrow the error.

Do not change the main-model path or the exact meaning used by the shared resolver (resolveConfiguredModel).

Implementation notes (from reading the code):

  • Don't copy the main path's !routingRequired guard. This run was routed, and the sub-agent records were still written. The failed sub-agent record has to be written in both routed and unrouted runs.
  • The loop skips repeated model IDs (seen). If two sub-agents use the same dead model, only the first one gets checked. Mark every sub-agent role that uses the failing modelId as failed. Otherwise the second role keeps its exact record.

Acceptance cases

Add these tests to actions/setup/js/pi_models_json.test.cjs, next to fails closed with model_unavailable for a retired exact id. Build the fixture from the real /reflect capture of run 39325995892: the agent artifact's sandbox/firewall/awf-reflect.json, which doesn't list gpt-5.4-mini. Store it with the existing fixtures, for example actions/setup/js/fixtures/awf-reflect-pi-routed-39325995892.json. Keep models_fetch_complete and endpoints exactly as captured; don't invent a reflect shape.

  1. records failed model_unavailable for a sub-agent pinned to a model missing from /reflect (run 39325995892): a sub-agent pinned to copilot/gpt-5.4-mini with that /reflect data. Pi startup still fails as it does today. The role's model.resolution is failed / model_unavailable / resolved_model: null, and no exact record remains for that role.
  2. keeps exact resolution for a sub-agent on a live model: a sub-agent on copilot/claude-haiku-4.5 is unchanged and stays exact.
  3. marks only the unavailable sub-agent failed when one of two sub-agents is live: one live and one dead sub-agent. The live one stays exact, the dead one becomes failed, and startup still fails. Test both declaration orders, because the loop stops at the first failure.
  4. Main-model failure path unchanged: the existing main-path tests, including fails closed with model_unavailable for a retired exact id, still pass without changes.

End to end

On the PR branch, run smoke-pi-routed. It's still pinned to gpt-5.4-mini until that pin is replaced separately. Expected result: it fails at startup, its session shows mini-whoami as failed / model_unavailable, and U3 now fails and reports the cause, for example FAIL U3 role mini-whoami status is failed (cause model_unavailable), expected exact or resolved .... List the run URL in the PR.

Scope

  • Keep the PR to this one fix. Don't change files or behavior unrelated to it.
  • Before fixing a failing check, compare it with the same check on main. If it also fails on main, don't fix it in this PR. Merge main once main is fixed, and note in the PR which checks are known failures on main.
  • This applies even if a bot comment asks you to fix a failing check: fix only failures caused by this PR's changes.
  • Don't change any workflow's model pin. Dead pins, including smoke-pi-routed's mini-whoami: copilot/gpt-5.4-mini, are tracked in [model-inventory] Model alias inventory - action required - 2026-10-19 #70367.
  • Don't change the smoke assertions (smoke_model_routing_assertions.cjs, including U1–U3).

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions