Skip to content

Preserve AWF ranked choices and request/failure metadata in unified sessions - #69848

Merged
SivaKesava1 merged 4 commits into
mainfrom
copilot/g-2-g-11-preserve-candidate-rankings
Oct 18, 2026
Merged

SivaKesava1 merged 4 commits into
mainfrom
copilot/g-2-g-11-preserve-candidate-rankings

Conversation

Copilot AI commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

AWF already records the router's ranked candidates, the pool counts, the request endpoints and HTTP status, and the failure phase. The unified session collector dropped all of it, so a reader of usage/aw_session.jsonl couldn't see which candidates were ranked or the endpoint/status context for deviations and failures. This PR adds those fields to the existing firewall.model_routing projection; no new event or source is added.

Projection (actions/setup/js/unified_session_payload.cjs)

  • Nine fields are added, accepting both snake_case and camelCase:

    Unified AWF source Notes
    rankedChoices ranked_choices Each entry becomes {rank, id?, model, effort}. rank is the 1-based array position. An explicit null effort is kept. id appears only if supplied (current AWF doesn't persist it). Any other property on an entry is dropped.
    eligibleChoices eligible_choices Count supplied by AWF; never inferred from the array length.
    catalogueOverlap catalogue_overlap
    selectedEndpoint selected_endpoint Also kept on request records. The existing endpoint field is unchanged.
    requestedEndpoint / upstreamEndpoint requested_endpoint / upstream_endpoint Neither is substituted for the other.
    unavailable unavailable AWF's list of request-telemetry dimensions it didn't observe (e.g. model, effort), not unavailable models. Kept only if it is an array of strings.
    status status HTTP status, independent of outcome.
    phase phase AWF failure phase (primary/bootstrap), distinct from provenance.phase.
  • Absent stays absent: no zero, empty array or null is added for missing data. A supplied zero, null effort or empty array is kept.

  • Malformed scalars dropped: if the counts, endpoints, status or phase hold an object, that field is omitted, so nested payloads can't pass through.

  • Private fields still excluded: conversation_sha256, detail, method and pathname, including their camelCase spellings.

  • No per-candidate predictions: nothing beyond the ranking, the selected choice and observed results is recorded.

Example from a recorded selection (run 38984582061):

"rankedChoices": [
  { "rank": 1, "model": "github-copilot/gpt-5.6-luna", "effort": "high" },
  { "rank": 10, "model": "github-copilot/claude-haiku-4.5", "effort": null }
],
"eligibleChoices": 12, "catalogueOverlap": 12, "endpoint": "/responses"

(Truncated; the record has 12 entries in AWF order.)

Types and schema

  • Adds the fields and a new ModelRoutingRankedChoice type to ModelRoutingData in types/unified_session.d.ts.
  • docs/public/schemas/unified-session.schema.json is regenerated with the existing generator (npm run schema:session).

Specification

  • T-UAS-070: adds a field table, ordering and omission rules, and the privacy exclusions.
  • Other sections: updates the Section 3 summary row and the Section 9 test matrix row.
  • Version: 1.11.0 → 1.12.0, building on Resolve model aliases the same way on every engine #69487's T-UAS-074. session.format stays 1.

Tests (unified_session_payload.test.cjs)

  • Cases: the six cases named in the issue, each run with snake_case and camelCase input.
  • Checks: every case checks idempotence, JSON round trips and that the input isn't modified.
  • Inputs:
    • A minimized, sanitized copy of run 38984582061's agent/model-routing.jsonl.
    • The codex-routed-provider-unavailable and copilot-budget-exhausted-steering golden logs and sessions, read directly.
    • Variants explicitly labelled as compatibility or privacy cases (not recorded telemetry): an id on a candidate, the new fields omitted, sentinel values in the private fields.

Not changed

  • Audit output: gh aw audit output is unchanged, as the issue decided.
  • Golden fixtures: the golden session files are captured data, so a collector change doesn't regenerate them. Running make update-model-routing-golden changed no fixtures or expected*.json files.

Known failures that also occur on main

  • TestSessionParserSourcesIncludeLocalDependencies
  • The symlink and hardlink tests in copilot_sdk_mcp_config.test.cjs
  • Timeouts in work_queue_control_performance.test.cjs
  • collect_usage_artifact_files_test.sh

…essions

Co-authored-by: SivaKesava1 <11771739+SivaKesava1@users.noreply.github.com>
…erve-candidate-rankings

Co-authored-by: SivaKesava1 <11771739+SivaKesava1@users.noreply.github.com>
@SivaKesava1
SivaKesava1 marked this pull request as ready for review October 18, 2026 20:52
Copilot AI max review requested due to automatic review settings October 18, 2026 20:52
@SivaKesava1 SivaKesava1 added the smoke-routing Run the model-routing smoke workflows on this PR label Oct 18, 2026
@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

✅ Smoke Copilot SDK Routed Claude completed successfully!

Generated by Smoke Copilot SDK Routed Claude for #69848

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

✅ Smoke Copilot Routed Claude completed successfully!

Generated by Smoke Copilot Routed Claude for #69848

@github-actions github-actions Bot removed the smoke-routing Run the model-routing smoke workflows on this PR label Oct 18, 2026
@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

✅ PR Code Quality Reviewer completed the code quality review.

🔎 Code quality review by PR Code Quality Reviewer

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

✅ Smoke Pi Routed Claude completed successfully!

Generated by Smoke Pi Routed Claude for #69848

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

✅ Ponytail Reviewer completed successfully!

Ponytail Reviewer

Outcome: reported_no_action

The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible.

Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.

Generated from Ponytail Reviewer · gpt56 · 7.57 AIC · ⌖ 5.28 AIC · ⊞ 14.6K · ◷

Generated by Ponytail Reviewer for #69848

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

✅ Smoke Copilot Routed Steering completed successfully!

Generated by Smoke Copilot Routed Steering for #69848

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

✅ Test Quality Sentinel completed test quality analysis.

Test Quality Sentinel

Outcome: reported_no_action

The agent completed and reported no action. A no-op output alone does not verify that all required work was completed or that publication was possible.

Agent messages are omitted from this comment. The category uses structured run status, not interpretation of agent prose.

Generated from Test Quality Sentinel · sonnet50 · 14.2 AIC · ⌖ 15.1 AIC · ◇ 6.58 AIC · ⊞ 12.6K · ◷

🧪 Test quality analysis by Test Quality Sentinel

@github-actions

Copy link
Copy Markdown
Contributor

Comment Memory

Peek at saved memory (pr-code-quality-reviewer)
reviewed_at: 2026-10-18T20:57:00Z
review_event: COMMENT
top_themes:
  - malformed ranked-choice telemetry should be dropped, not projected as placeholder entries
files_reviewed:
  - actions/setup/js/types/unified_session.d.ts
  - actions/setup/js/unified_session_payload.cjs
  - actions/setup/js/unified_session_payload.test.cjs
  - docs/public/schemas/unified-session.schema.json
  - docs/src/content/docs/specs/unified-agent-session-specification.md
comment_count: 1

Note

This comment is managed by comment memory.

Expand the saved memory block to view or edit the persistent context for this thread.
Edit only the text inside the backtick fences; workflow metadata and the footer are regenerated automatically.

Learn more about comment memory

🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt54 · 51.8 AIC · ⌖ 6.24 AIC · ⊞ 12.7K · ◷
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict

I didn't find a blocking issue in the changed lines.

Notes

Static review only: I checked the projection changes, schema/type updates, and the new regression coverage around AWF model-routing metadata. I did not execute tests or runtime validation in this review.

🔎 Code quality review by PR Code Quality Reviewer · copilot · gpt54 · 51.8 AIC · ⌖ 6.24 AIC · ⊞ 12.7K
Comment /review to run again

Comment thread actions/setup/js/unified_session_payload.cjs
@SivaKesava1
SivaKesava1 merged commit f274332 into main Oct 18, 2026
4 of 5 checks passed
@SivaKesava1
SivaKesava1 deleted the copilot/g-2-g-11-preserve-candidate-rankings branch October 18, 2026 21:08

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔵 Needs a closer look

The published session-contract and privacy-boundary changes warrant maintainer sign-off.

1 open issue
What changed in this PR

Preserves AWF routing metadata in unified sessions so downstream readers can inspect candidate rankings and request/failure context.

Changes:

  • Adds ranked choices, pool counts, endpoints, telemetry availability, HTTP status, and failure phase.
  • Updates contracts and regression coverage while retaining session format 1.
File Description
docs/​src/​content/​docs/​specs/​unified-agent-session-specification.md Documents metadata and privacy rules in version 1.12.0.
docs/​public/​schemas/​unified-session.schema.json Adds routing fields and ranked-choice schema.
actions/​setup/​js/​unified_session_payload.test.cjs Adds recorded-data and compatibility cases.
actions/​setup/​js/​unified_session_payload.cjs Projects metadata and filters malformed values.
actions/​setup/​js/​types/​unified_session.d.ts Declares routing fields and ranked-choice type.

Review effort: Max

Comment on lines +580 to +582
if (Object.hasOwn(data, "unavailable") && !(Array.isArray(data.unavailable) && data.unavailable.every(value => typeof value === "string"))) delete data.unavailable;
for (const key of ["eligibleChoices", "catalogueOverlap", "selectedEndpoint", "requestedEndpoint", "upstreamEndpoint", "status", "phase"]) {
if (data[key] !== null && typeof data[key] === "object") delete data[key];

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /tdd and /codebase-design (via pr-triage: new_feature classification). The core projection logic (normalizeRankedChoices, the malformed-scalar-drop loop, the unavailable guard) is well-structured and the PR description's documented behaviors (absence-stays-absent, malformed-scalar-dropped, private-field exclusion) are mostly backed by the 300-line test suite using real golden fixtures — a strong practice.

📋 Key Themes & Highlights

Key Themes

  • Test coverage gaps on negative paths: the malformed-scalar-drop loop (7 fields), the unavailable type guard, and normalizeRankedChoices' non-array fallback are all documented behaviors in the PR description but have no corresponding test exercising the "drop" branch. Only the happy-path (valid data) and omitted-field cases are currently tested.
  • Test/production alias duplication: SOURCE_FIELDS in the test file duplicates EVENT_FIELDS["firewall.model_routing"] from production code; a future alias change could drift silently between the two.

Positive Highlights

  • ✅ Idempotence, JSON round-trip, and input-immutability are checked on every projected record via the shared project() helper — a solid regression-test pattern.
  • ✅ Real golden fixtures (recorded AWF runs) are used for both selection and request/failure stages, in both snake_case and camelCase, giving good confidence the projection matches actual AWF output shapes.
  • ✅ Privacy exclusions (conversation_sha256, detail, method, pathname, and nested candidate payload fields) are explicitly tested with sentinel values and a JSON.stringify scan.
  • ✅ Spec (T-UAS-070), schema, and types are updated consistently alongside the implementation.

Note: investigation was static-only — vitest could not run in this sandbox due to a blocked npm registry (self-signed cert error), so findings are based on code and test-file reading rather than executed assertions.

@copilot please address the review comments above.

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · copilot · sonnet50 · 133.1 AIC · ⌖ 16.2 AIC · ⊞ 10.1K
Comment /matt to run again

else data.rankedChoices = rankedChoices;
}
// AWF lists unobserved request-telemetry dimensions here, not unavailable models.
if (Object.hasOwn(data, "unavailable") && !(Array.isArray(data.unavailable) && data.unavailable.every(value => typeof value === "string"))) delete data.unavailable;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The guard that drops unavailable unless it is an array of strings has no negative-case test. Current coverage only supplies a valid array or omits the field entirely.

💡 Suggested tests
for (const bad of ["not-an-array", { nested: true }, ["model", 42]]) {
  const projected = project(routingEvent({ ...SMOKE_REQUEST, unavailable: bad }));
  expect(projected.data).not.toHaveProperty("unavailable");
}

The mixed-type array case (["model", 42]) is the most likely regression target since .every(value => typeof value === "string") is easy to accidentally weaken (e.g. to .some) without a failing test catching it.

@copilot please address this.

// AWF lists unobserved request-telemetry dimensions here, not unavailable models.
if (Object.hasOwn(data, "unavailable") && !(Array.isArray(data.unavailable) && data.unavailable.every(value => typeof value === "string"))) delete data.unavailable;
for (const key of ["eligibleChoices", "catalogueOverlap", "selectedEndpoint", "requestedEndpoint", "upstreamEndpoint", "status", "phase"]) {
if (data[key] !== null && typeof data[key] === "object") delete data[key];

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] The malformed-scalar-drop loop (dropping eligibleChoices, catalogueOverlap, selectedEndpoint, requestedEndpoint, upstreamEndpoint, status, phase when they are objects) is explicitly documented in the PR description ("Malformed scalars dropped"), but no test in unified_session_payload.test.cjs exercises it for any of these 7 fields.

💡 Suggested test
it.each(["eligibleChoices", "catalogueOverlap", "selectedEndpoint", "requestedEndpoint", "upstreamEndpoint", "status", "phase"])("drops %s when AWF supplies an object instead of a scalar", key => {
  const snakeKey = SOURCE_FIELDS[key][0];
  const record = { ...SMOKE_SELECTION, [snakeKey]: { nested: true } };
  const projected = project(routingEvent(record));
  expect(projected.data).not.toHaveProperty(key);
});

Without this, a future refactor of the drop loop (e.g. an off-by-one in the field list, or an accidental removal) would ship silently, since nothing currently fails if the loop is deleted entirely.

@copilot please address this.

* @param {any} choices
* @returns {import("./types/unified_session").ModelRoutingRankedChoice[] | undefined}
*/
function normalizeRankedChoices(choices) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/tdd] normalizeRankedChoices returns undefined for any non-array input (string, number, object), deleting rankedChoices entirely per the caller at line 576. This fallback path is untested; all current test inputs supply either a valid array or omit the field.

Suggested test

Add cases where ranked_choices is a string, a number, or a plain object, and assert the projected data has no rankedChoices property.

Also worth a quick case for a non-string id (e.g. id: 123) on a candidate entry, to confirm it is silently dropped rather than coerced; the current typeof choice.id === string check suggests this is intended but it is not currently verified.

@copilot please address this.

const GOLDEN_DIR = fileURLToPath(new URL("../../../pkg/cli/testdata/model_routing_golden/", import.meta.url));
const ROUTING_LOG = "sandbox/firewall/logs/api-proxy-logs/model-routing.jsonl";
const NEW_FIELDS = ["rankedChoices", "eligibleChoices", "catalogueOverlap", "selectedEndpoint", "requestedEndpoint", "upstreamEndpoint", "unavailable", "status", "phase"];
const SOURCE_FIELDS = {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[/codebase-design] SOURCE_FIELDS (lines 925-933) duplicates the alias mapping already defined in the production EVENT_FIELDS["firewall.model_routing"] object in unified_session_payload.cjs. If an alias is added or renamed in production, this test fixture will silently drift and keep passing against the stale list rather than the real contract.

Suggestion

Export EVENT_FIELDS (or an alias slice of it) from unified_session_payload.cjs for test use, or derive SOURCE_FIELDS from it at test setup time, so the test fails loudly if production aliases change without a matching test update.

@copilot please address this.

@github-actions

Copy link
Copy Markdown
Contributor

🎉 This pull request is included in a new release.

Release: v0.92.6

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Preserve AWF candidate rankings and request/failure metadata in unified sessions (G-2/G-11)

3 participants