Skip to content

Complete runner/collector-written routing analytics in aw_session.jsonl (G-1/G-5/G-6/G-9/G-12) #70668

Closed

Description

@SivaKesava1

Problem

Complete the runner- and collector-written routing analytics record in usage/aw_session.jsonl in one focused change: configured policy, counterfactual baseline pricing, harness/run identity, and identifier-only upstream/request errors.

These are the remaining G-1, G-5, G-6, G-9 and G-12 items from the Data gaps section of #69507. #69725 and #69754 are closed; do not reopen their candidate-ranking/request-context and inventory work. The inspected sessions already retain ranked choices and model inventories.

The CAO model-routing page in githubnext/gh-cao reads unified sessions, not the separate agent artifacts. Its adapter already recognizes model.config, model.baseline_estimate and firewall.upstream_error; the queries consume the routing record. This issue is about supplying that record, not changing CAO.

Verified evidence: October 20, 2026

Audited all three supplied runs on Windows using a development binary built from gh-aw main at 70e15c7ff659c05c2f4466aa172bb910d28847a2, reporting dev-70e15c7ff6, rather than the installed stable v0.89.21:

gh-aw-main.exe audit <run-id> --repo github/gh-aw --no-baseline --timeout 4m --artifacts usage,agent,info --output <per-run-directory> --json

All three audit commands completed successfully; that is collection success, not the workflows' conclusions. Read each usage/aw_session.jsonl, the original agent artifact's final runtime/aw_info.json / agent/aw_info.json, compiled workflow configuration, and firewall evidence. In these artifacts the proxy JSONL files are under sandbox/firewall/logs/api-proxy-logs/, not sandbox/firewall/audit/api-proxy-logs/; sandbox/firewall/audit/awf-resolved-config.json was inspected too.

Run / created at (UTC) Workflow revision Observed result
39508154330, 22:26:46 smoke-copilot-sdk-routed, main, 70e15c7ff6 Success, SDK, AWF v0.28.70. Selected gpt-5.6-luna on /responses; the declared claude-haiku-4.5 sub-agent completed on /chat/completions. Six ranked choices and one inventory survive in the session.
39506475007, 22:13:11 smoke-copilot, copilot/expand-experiment-model-expressions, 9c8c823adb Failure, CLI, unrouted, AWF v0.28.62. Main auto resolved to claude-sonnet-5.5, using the session-wide completions API. A sub-agent request for gpt-6.1-sol failed with an AWF translation 400 involving frequency_penalty and /responses. Audit reports subagent_failed.
39499464895, 21:19:52 smoke-copilot-sdk-routed-claude, copilot/fix-copilot-sub-agent-models, b9f7f48a3b Failure, SDK, AWF v0.28.62. Selected claude-haiku-4.5 on /chat/completions, then failed closed because declared sub-agent mini-whoami requested unavailable gpt-5.4-mini. model.resolution records role: mini-whoami, status: failed, cause: model_unavailable. No main-agent inference request followed selection. This is not the CLI translation 400.

These failures explain why recording policy and harness identity must not depend on a successful conversation or routing selection. Fixing their runtime causes is outside this issue.

Gap verification

Gap What the actual sessions/artifacts contain
G-9 / G-12 All three workflow.info records say engineId: copilot and omit engineHarness, workflowRef and serverUrl. Correction to the original design: runAttempt: "1" already exists in all three, as do workflowId, workflowCallId and callerWorkflowRef. Preserve these; do not describe attempt identity as wholly missing. workflowId already holds the executing workflow's full reference.
G-1 Zero model.config events in all three. The routed run's compiled configuration has goal cost, mode economy, and allowed routing model gpt-5.6-luna (emitted to AWF as candidateModels: ["github-copilot/gpt-5.6-luna"]). Selection's resulting mode is balanced, not the configured economy. The Claude fixture likewise configures cost/economy with claude-haiku-4.5, but selection's resulting mode is unknown. The CLI fixture has no routing configuration or selection. All three actual AWF configurations have maxAiCredits: 1000, wired through the repository-default budget expression with fallback 1000, not explicit frontmatter. Policy allow/block lists, budget/source and a resolved no-routing baseline are not published as one session configuration record.
G-6 Zero model.baseline_estimate events in all three. The successful routed fixture has four main-agent requests, one sub-agent request and one classifier request in agent-phase proxy accounting; the session additionally contains two detection requests. A sum over all agent-phase usage would overcount the baseline.
G-5 None of the three original agent artifacts contains upstream-errors.jsonl, and none supplies the new error_code fields. The two failed runs predate the AWF feature; the v0.28.70 run has successful inference requests. Therefore these runs cannot demonstrate preservation of the new upstream fields. Source inspection establishes the lossy projection; use the upstream change's tests for this acceptance case, not an invented historical record.

The compiled budget expression is vars.GH_AW_DEFAULT_MAX_AI_CREDITS || '1000'; the observed value alone does not distinguish an equal-valued repository default from the literal fallback.

Useful original artifact links for reproducible fixtures:

Run usage agent
39508154330 12303814398 12304048825
39506475007 12304237333 12303457750
39499464895 12300520074 12298393979

Possible solution

1. Runner-owned identity and configured policy

Update actions/setup/js/generate_aw_info.cjs and the runner step that persists model.config, with only the directly required compiler-to-runner metadata wiring.

For workflow.info, add engineHarness, workflowRef and serverUrl, and preserve/test the existing runAttempt. Emit copilot-cli versus copilot-sdk from the actual compiler/runner harness configuration, with one stable value per supported harness, never by guessing from engineId, a workflow name or model output. Capture run attempt and server URL from runner context.

Keep the executing and calling workflow references distinct. workflowRef should retain the compiler-wired executing workflow reference already represented by workflowId; callerWorkflowRef retains GITHUB_WORKFLOW_REF, which can identify a different top-level caller for reusable workflows. Do not regress the existing identity contract.

Write one model.config per run, from runner-owned aw_info.json and the compiled configuration, including when routing is disabled or selection fails:

  • routing: { enabled, goal, mode, allowedModels }, retaining configured policy rather than the selected classification's resulting mode.
  • models: { allowed, blocked }, keeping policy separate from the runtime inventory and routing candidate pool.
  • maxAiCredits and maxAiCreditsSource (frontmatter or default), using the effective resolved budget and recorded configuration provenance, not inferring the source from its numeric value. Defaults may be resolved through repository variables.
  • baselineModel: the concrete configured/default main model the run would use without routing, after existing alias resolution. This is neither the literal auto alias nor automatically the router's chosen model.

Capture baseline metadata without changing any harness's resolution or selection behavior. If a historical source cannot establish it, leave it unknown; do not relabel the routed workflow.info.model as the counterfactual baseline. Policy/model/source strings must be identifier-shaped metadata; exclude prompts, transcripts, secrets and free-form diagnostic text.

2. Collector-owned baseline estimate

In the conclusion collector, reprice each main-agent request's recorded token usage at baselineModel using the existing computeInferenceAIC in actions/setup/js/model_costs.cjs. Preserve the input/cache/reasoning accounting semantics and count a request once, not both its native and proxy accounting copies.

Emit model.baseline_estimate for routed runs, clearly marked estimated: true, with baseline identity, an AIC estimate/subtotal, priced and unpriced request counts, and explicit completeness. Exclude sub-agent, routing-classifier and detection requests. Use deterministic runner-owned harness/proxy evidence for attribution; a differing model/endpoint, xInitiator: agent, or model_routing.outcome alone is not proof of a main/sub-agent role. Unattributable or unpriceable evidence must not silently produce a complete total.

Check pricing availability before interpreting the helper's result: the current cost helper returns zero when pricing is unavailable. Unknown pricing means unpriced, never zero cost. A priced subtotal with unpriced requests must not be presented as a complete estimate.

Proposed unrouted policy: omit model.baseline_estimate for explicitly unrouted runs; retain their model.config with routing.enabled: false and resolved baselineModel. Document this choice. Missing estimates in older sessions remain unknown, not zero or evidence that routing was disabled.

For the successful routed fixture, the four main-request proxy token tuples (input, output, cache_read, cache_write) are (3,175,0,18516), (3,61,18516,339), (3,210,18855,76) and (3,60,18931,228). Keep the original records in fixtures rather than reconstructing JSONL from these tuples. The claude-haiku-4.5 sub-agent and classifier are separate requests.

3. Preserve identifier-only proxy errors

github/gh-aw-firewall#10211, shipped in AWF v0.28.67 on October 19, already writes identifier-only error_code, error_type and error_param on upstream-error records, and error_code, error_type, error_source on failed routing request records. Current gh-aw main defaults to AWF v0.28.70; no AWF change is required.

Update the collector's runtime mapping and actions/setup/js/unified_session_payload.cjs:

  • Map proxy upstream-error records to firewall.upstream_error, retaining errorCode, errorType, errorParam, HTTP status, provider, model and request ID.
  • Retain errorCode, errorType and errorSource on projected firewall.model_routing request events; preserve upstream versus awf.
  • Explicitly allowlist/validate identifiers. Never copy response_body, response headers or messages, and never recover codes by scraping historical error text.

The current runtime mapping leaves upstream errors as generic firewall.event. Its payload projection retains generic runtime fields and omits the new request error identifiers. Fix both boundaries rather than changing audit findings.

Because these three runs lack an upstream-error fixture, use the shapes and cases from the tests merged in github/gh-aw-firewall#10211 at b5c683e116470e0aa579ec9bf30a675fcdb66f76: upstream-log.test.js has the captured Copilot invalid_reasoning_effort record; provider-error-fields.test.js covers optional code/type/param identifiers; upstream-response.test.js and routing-persistence.test.js cover upstream- and AWF-originated failed requests. Preserve real optional-field omissions.

4. Specify the additive session contract

Update docs/src/content/docs/specs/unified-agent-session-specification.md alongside the writers, collector projection/types and directly related tests. Give each new event and field its writer (runner, collector or proxy), field definitions, provenance, applicability/completeness and an "absent means unknown" rule. An explicitly recorded false, empty policy list or zero is not the same as an absent value.

All attribution must come from runner- or proxy-written evidence, never model_routing.outcome or agent-authored outcome reports. Preserve older sessions and the existing candidate/inventory contracts.

Acceptance cases

Each case must be a named test. Check in appropriately sanitized fixtures from the linked real artifacts, keeping their actual envelopes, optional fields and attribution; do not invent proxy/native record shapes. Use the explicitly identified AWF tests only where these runs lack the necessary error record.

Named test Required assertions
workflow_info_records_sdk_and_cli_identity SDK fixtures emit engineHarness: copilot-sdk; the CLI fixture emits copilot-cli. Existing runAttempt matches attempt 1. workflowRef matches each run's full executing .lock.yml@refs/heads/... reference, and serverUrl is https://github.com. Preserve workflowId, workflowCallId and the separate caller-reference semantics.
model_config_records_routed_compiled_policy Run 39508154330 emits exactly one configuration record with enabled routing, goal cost, configured mode economy, allowedModels corresponding to compiled gpt-5.6-luna, budget 1000 and source default. Do not substitute selection mode balanced. Retain model allow/block policy when supplied.
model_config_records_unrouted_resolved_baseline Run 39506475007 emits exactly one configuration record with routing.enabled: false and baselineModel: claude-sonnet-5.5, from recorded main-model resolution rather than literal auto.
model_config_survives_selection_or_startup_failure The Claude SDK fixture retains configured cost/economy, allowed claude-haiku-4.5 and budget/source despite the later mini-whoami resolution failure. Exercise runner publication before selection using this fixture's real compiled inputs; no synthetic proxy record is needed.
baseline_estimate_reprices_only_main_requests On the routed usage fixture, reprice only the four main-agent requests. The sum matches computeInferenceAIC applied to those same token counts and baseline pricing, with explicit cache semantics, estimated: true, and correct priced/unpriced counts. Exclude the sub-agent, classifier and detection usage, and do not double-count native/proxy copies.
baseline_estimate_reports_unknown_pricing_and_coverage Missing baseline pricing increments unpriced coverage, never emits a complete zero estimate. Partial priced sums are explicitly incomplete; ambiguous request attribution is not silently included. Test/document the unrouted omission policy.
upstream_error_preserves_identifiers_without_payloads Using the identified upstream test shapes, preserve supplied errorCode, errorType, errorParam, HTTP status, provider, model and request ID in firewall.upstream_error; absent optional identifiers remain absent. Exclude response_body, headers and messages.
failed_request_preserves_error_source The upstream-origin fixture retains errorSource: upstream; the AWF-origin fixture retains errorSource: awf, along with supplied error code/type, on the projected request event.
older_sessions_keep_missing_routing_data_unknown The three unmodified older sessions still parse. Missing new metadata/events are unknown, not false, empty policy, zero cost or proof of a particular harness.
routing_analytics_smokes_end_to_end On the PR branch, a reviewer dispatches smoke-copilot-sdk-routed and smoke-copilot. Both resulting aw_session.jsonl files contain the new applicable configuration/identity fields; the routed run contains its estimate/completeness record. Verify new error projections when a corresponding source error exists, not by requiring an unrelated failure. Attach the run/artifact links and check the actual files, not only job conclusions.

Scope

  • Keep the PR to this one change: writing the routing analytics data listed above into aw_session.jsonl, plus its spec entries and tests. Do not change files or behavior unrelated to it.
  • Do not change AWF, the CAO page, audit's findings, the harnesses' model resolution, or workflow model pins. Do not add the x-awf-caller request header (separate work) or safe-output outcome events (separate work).
  • Before fixing any failing check, compare it with the same check on main. If it also fails on main, do not 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, including PR Sous Chef, asks to fix a failing check: fix only failures caused by this PR's changes.

Metadata

Metadata

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions