Repository navigation
Model routing: show AWF routing decisions in gh aw logs and gh aw audit #66638
Description
Summary
Workflows that use engine.model-routing now write AWF's routing decisions to sandbox/firewall/logs/api-proxy-logs/model-routing.jsonl, next to token-usage.jsonl. AWF added this file in gh-aw-firewall#9451 (first released in v0.28.35), and gh-aw's default AWF version is v0.28.37, so every routed workflow produces it. The agent artifact already uploads the whole firewall logs folder, but gh aw logs and gh aw audit never read the file. On main (679cb02), nothing under pkg/cli refers to model-routing.jsonl, model_routing, or routing_classification.
So for a routed run, audit shows tokens and credits, but it doesn't say:
- what the router classified the task as;
- which model and effort it chose, or which router version made the choice;
- whether the agent and its sub-agents actually used that model;
- how much of the cost went to the routing classifier itself.
The model routing guide (reference/model-routing.md, added in #66338) says that gh aw logs and gh aw audit "do not yet provide a dedicated routing summary", tells authors to read the JSONL files themselves, and promises CLI support later. This issue proposes that support.
Evidence: run 37548242023
I audited https://github.com/githubnext/gh-aw-routing-sandbox/actions/runs/37548242023 with gh aw audit 37548242023. It's a private test repository, with workflow Routing test: t01-explain-trivial, AWF v0.28.37, and router gh-aw-router 0.1.3. Then I compared the report with the run's model-routing.jsonl.
What audit reports:
- The engine is
engine=copilot/auto/v1.0.90. - Usage is
tokens: in=95.5k out=941 cache_read=70.0k reqs=5andaic=0.76. - The behavior fingerprint is
adaptive/moderate/selective_write/moderate/standalone. - The comparison line is
comparison: stable vs baseline 37546193453 | No action needed; this run matches the selected successful baseline closely. - The text report has no routing section. In
audit --json,model-routing.jsonlappears only in the list of downloaded files, and the step name "Prepare model-routing conversation" appears only in the job list. Neitheraudit.jsonnorrun_summary.jsonhas any routing field.
What model-routing.jsonl records for the same run (6 records):
- 1
classificationrecord: the classifier wasgithub-copilot/gpt-5.6-lunaat efforthigh, attempt 1. - 1
selectionrecord:- The objective was
cost/autoand the provider wascopilot. - The labels were
task_type: explain,scope: local, andtask_complexity: trivial, with modeeconomy. - The router selected
github-copilot/gpt-5.6-lunaat efforthigh(selected_id: choice-0017), with endpoint/responses. - There were 34 ranked choices, 34 eligible choices, and a catalogue overlap of 34. Classification wasn't degraded, and selection took 4.9 s.
- The record also has
conversation_sha256,interaction_id: 37548242023-1,github_repository, andgithub_workflow_ref.
- The objective was
- 4
requestrecords: all hadrouted: as_selectedandoutcome: completedwith status 200. All 4request_ids match rows intoken-usage.jsonl.
What audit gets wrong or leaves out:
-
Classifier cost is counted in the agent's totals.
token-usage.jsonlhas 5 rows, whilemodel-routing.jsonlhas only 4 requests. The extra row haspurpose: "routing_classification"and cost 0.059 AIC.auditcounts it inreqs=5andaic=0.76, so about 8% of this run's cost was routing overhead, and the report doesn't show it. -
The "stable" baseline used a different route. The baseline 37546193453 had the same labels (
explain/local/trivial,economy), but it ran on AWF v0.28.35 with router 0.1.2 and selectedgpt-5.6-lunaatmedium. This run used AWF v0.28.37 and router 0.1.3 and selectedgpt-5.6-lunaathigh. Credits were about the same (0.76), so "stable" is fair for cost, but the report doesn't mention that the router version and the selected effort changed. Baseline selection inpkg/cli/audit_comparison.goscores runs by behavior fingerprint only. It knows nothing about the route. -
The route mix across runs is lost.
auditdownloaded nine baselines of the same routing-test family. Their routing logs show that the selected model drives the cost:Labels (type / complexity) Mode Selected AIC explain / trivial economy gpt-5.6-luna:medium 0.72 to 0.76 chore / easy economy gpt-5.6-luna:high 1.87 fix / hard robust gpt-5.6-sol:xhigh 41.8 plan / hard balanced gpt-5.6-sol:high 48.7 implement / medium robust gpt-5.6-sol:max 89.5 implement / medium robust claude-opus-5:high 462 plan / hard robust claude-opus-5:max 618 plan / hard balanced claude-opus-5:xhigh 933 None of this appears in
auditorlogs. You can only get it by reading the JSONL files by hand. -
Deviations are hidden, and old records report them incorrectly. In the three Claude baselines (AWF v0.28.35), every request was logged as
routed: deviated.- Most of these deviations are only
["endpoint"], because AWF before v0.28.39 counted the endpoint difference as a deviation (gh-aw-firewall#9505, fixed by gh-aw-firewall#9507). - Some are real effort deviations:
["effort","endpoint"], whereclaude-opus-5ran atlowinstead of the selectedmaxorxhigh. Examples are run 37402330643 (62 of 95 requests) and run 37505153056 (15 of 101). auditdoesn't show either kind of deviation.
- Most of these deviations are only
Record format
AWF's docs/api-proxy-sidecar.md (section "Model routing audit log") is the source of truth, and gh-aw should read the file loosely rather than copy the schema. The fields observed in these runs are listed below. Some names differ from the earlier planning notes.
- Every record:
_schema(for examplemodel-routing/v0.28.37),timestamp,event: "model_routing", andstage. classification:purpose,attempt,classifier_model, andclassifier_effort.selection:- the configuration:
objective(goal,mode) andprovider; - the classification:
labels(task_type,scope,task_complexity),mode,classifier_model,classifier_effort,classifier_attempts,degraded_classification, anddegraded_reason; - the choice:
selected_id,selected_provider,selected_model,selected_effort,wire_model, andendpoint; - the candidates:
ranked_choices,eligible_choices, andcatalogue_overlap; - the router:
router(name,version) andlatency_ms; - the context:
conversation_sha256,interaction_id,github_repository, andgithub_workflow_ref.
- the configuration:
failure: acodeanddetail.request:routed(as_selected,deviated, orunobserved),deviations, andunavailable;method,pathname, andprovider;- the
requested_*andselected_*model, effort, provider, and endpoint; request_id,outcome(completed,rejected,failed, oraborted), and the HTTPstatus.
Since AWF v0.28.43 (gh-aw-firewall#9523), request records also have requested_endpoint and upstream_endpoint, which differ when the proxy translated the request between Responses and Chat Completions. The file contains no prompt or conversation text.
Proposed plan
-
Find and parse the routing log.
- Find
model-routing.jsonlnext totoken-usage.jsonl, reusing the search inpkg/cli/token_usage_find.go, which already covers thesandbox/firewall/logsandsandbox/firewall/auditlayouts. - Parse it leniently: ignore unknown fields and stages, and accept older schema versions.
- If the file is missing, report "not routed" (or "routing log not available" when the AWF config enabled routing). Don't treat it as an error.
- Find
-
Add a routing section to the run summary and audit report.
- Add it to
RunSummaryinpkg/cli/logs_models.goand to the report inpkg/cli/audit_report.go, in both text and JSON output. - Show the status (selected, failed, or not routed) and the objective.
- Show the labels and mode, the classifier model, effort, and attempts, and whether classification was degraded and why.
- Show the selected model, effort, and endpoint, the top three ranked choices, the router name and version, and the selection latency.
- Show request counts by
routedandoutcome, and list the models and efforts that deviated. - On failure, show the failure
codeanddetail, so an exit code 78 can be explained from the report.
- Add it to
-
Split the cost. Join
requestrecords totoken-usage.jsonlbyrequest_idand split credits and tokens into three parts:- the classifier, from rows with
purpose: "routing_classification"; - traffic that used the selected model;
- traffic that deviated from it, such as sub-agents or overrides.
This shows how much of a run's cost was the classifier, the routed model, and everything else. When reading records from AWF before v0.28.39, count a request whose only deviation is
endpointas using the selected model, and say so in the output. - the classifier, from rows with
-
Use the route in comparisons.
- Add the selected model, effort, mode, and router version to the run's comparison data.
- When the current run and the baseline selected a different model, effort, or router version, say so in the comparison line, even if the cost is about the same. Don't call two runs "stable" without noting that they ran on different routes.
- Optionally, prefer baselines with the same labels.
-
Show the route mix across runs in
gh aw logs. Aggregate the routes across the downloaded runs and show which labels led to which selection, with run counts and total and average credits for each selection. Also show the total classifier cost and the share of traffic that deviated. -
Keep the routing log in the fallback artifact.
- Add
model-routing.jsonlto the smallagent-output-fallbackartifact. Today that artifact carries only the threetoken-usage.jsonlpath variants (pkg/workflow/compiler_yaml_artifacts.go), using the same ARC/DinD path rewrite. Then routing data is still available when theagentartifact is missing or too large. aw-prompts/user.txtis already in theagentartifact (Include split prompts in the unified agent session #66011). The routing conversation is that text sent as a single user message, soauditcan recompute the SHA-256 and check it against the selection'sconversation_sha256. This confirms the router saw the expected prompt without AWF storing any prompt text.
- Add
-
Add tests.
- Parser tests with fixture files for these cases:
- a selection;
- a degraded classification;
- a
failurerecord; - no file;
- an older AWF v0.28.35 Claude selection where only the endpoint deviated;
- a real effort deviation;
- a log with unknown fields.
- Golden output tests for
logsandauditin text and JSON, for both routed and non-routed runs. - A comparison test where the route differs but the cost doesn't.
- Compiler snapshot tests for the fallback artifact path list.
- Parser tests with fixture files for these cases:
-
Update the docs.
reference/model-routing.md:- Replace the Observability paragraph that says
gh aw logsandgh aw audit"do not yet provide a dedicated routing summary" and tells authors to read the JSONL files themselves. Describe instead the routing section inaudit, the cost split (classifier, selected model, deviated), the route-aware comparison, and the route mix inlogs, with a short example of the report. - Keep the link to AWF's schema documentation, for authors who need the full field list.
- Update the troubleshooting rows (exit code 78 /
failure,degraded_reason,routed: "deviated") to say whereauditnow shows each one. - Explain how audit treats endpoint-only deviations in records from AWF before v0.28.39.
- Replace the Observability paragraph that says
reference/audit.md:- Add the routing section to the list of report sections for
gh aw audit <run-id-or-url>. - Add the new routing fields to "JSON output schemas".
- Explain that the comparison now notes route changes.
- Under "
gh aw logs --format <fmt>", describe the route mix in the logs output.
- Add the routing section to the list of report sections for
reference/cost-management.md:- In "Monitoring Costs with
gh aw logs", explain how to read credits per selection and the split between the classifier, the selected model, and deviated traffic for routed runs. - Add a link to the model routing page from "AI Credits with Dynamic Model Selectors".
- In "Monitoring Costs with
reference/artifacts.md:- List
model-routing.jsonlnext totoken-usage.jsonlin the API proxy log directory listing, and add its schema to the JSON Schemas table, linking to AWF's docs if AWF doesn't publish a schema file. - List it in the
agent-output-fallbackcontents once step 6 lands. - Fix the existing claim that
token-usage.jsonlis only in a separatefirewall-audit-logsartifact. This run uploaded onlyactivation,agent,agent-output-fallback,usage,safe-outputs-items, andinfo. Both JSONL files came from theagentartifact atsandbox/firewall/logs/api-proxy-logs/.
- List
Out of scope
- Changing AWF's record format or the router's selection behavior.
- Adding routing to the per-run
$GITHUB_STEP_SUMMARYtoken table. That can be a follow-up issue.
Related
- Fixed issues: Model routing: the routing-conversation step reads a prompt path that doesn't exist in the agent job #66221/Fix model routing to read the agent-job task prompt #66290 (routing reads
user.txt), Model routing: routed workflows run stale AWF and router images regardless of the AWF version #66226/Route model-routing workflows to version-matched, pinned images #66291 (current images and the AWF v0.28.37 default), gh-aw-firewall#9505/[aw] agentic workflows out of sync #9507 (endpoint-only deviations), and Docs: add a model routing guide forengine.model-routing#66337/Docs: add experimental Copilot model-routing guide #66338 (the routing guide that defers CLI support to this issue). - gh-aw-firewall#9447/Purge orphaned .campaign.g.md files and their lock files with --purge flag #9451 added the
model-routing.jsonllog in AWF.
Activity
pelikhan commentedon Oct 7, 2026
@copilot also integrate model routing messages in agent unified session file.