Skip to content

feat(token-id-capture): record served response IDs - #2675

Merged
ananthsub merged 1 commit into
mainfrom
ansubramania/terminal-attribution-response-id
Aug 27, 2026
Merged

ananthsub merged 1 commit into
mainfrom
ansubramania/terminal-attribution-response-id

Conversation

@ananthsub

@ananthsub ananthsub commented Aug 23, 2026 •

Copy link
Copy Markdown
Contributor

What does this PR do?

This PR records the response envelope ID on each captured model call. The ID is a lightweight receipt that an agent can retain while token IDs and log probabilities remain in the configured token sink.

  • Adds the optional TokenEntry.response_id field to the existing version 1 record schema.
  • Records the top-level ID returned by the Responses, Chat Completions, or Messages endpoint.
  • Reuses the underlying Responses ID in the Anthropic conversion so the client-visible ID matches the captured ID.

Response ID flow

sequenceDiagram
    participant Agent as Agent harness
    participant Capture as Capture middleware
    participant Route as Model API route
    participant Model as Model server
    participant Sink as Token sink

    Agent->>Capture: Request /ng-rollout/{rollout_id}/training-token-capture/v1/{dialect}
    Capture->>Capture: Strip prefix and set rollout_id + model_call_id
    Capture->>Route: Forward dialect request with capture context
    Route->>Model: Generate response
    Model-->>Route: NeMoGymResponse(id, output, token metadata)
    Route->>Sink: TokenEntry(model_call_id, response_id=id, token arrays, output items)
    alt Responses or Chat Completions
        Route-->>Agent: Client response carries the recorded id
    else Anthropic Messages
        Route->>Route: Convert response and preserve response.id as message.id
        Route-->>Agent: Message response carries the recorded id
    end
Loading

The response ID and token arrays are written to the same TokenEntry. The agent only needs to retain the response ID; later code can use it to locate the corresponding captured model call.

This is the first PR in the terminal-attribution stack. #2676 uses the recorded ID as one witness when resolving the model call that produced the verified response.

Checklist

  • I have read the contributing guidelines.
  • The change is focused; unrelated "drive-by" edits are tracked as separate issues/PRs.
  • Tests added or updated and pass locally, or N/A for docs-only / non-code changes (so CI unit/server checks pass when applicable).
  • Pre-commit checks pass locally (pre-commit run --all-files) (so CI lint/format/copyright pass).
  • All commits have DCO sign-off (git commit -s) (so the DCO check passes).

@copy-pr-bot

copy-pr-bot Bot commented Aug 23, 2026

Copy link
Copy Markdown

Auto-sync is disabled for draft pull requests in this repository. Workflows must be run manually.

Contributors can view more details about this message here.

@ananthsub ananthsub changed the title ansubramania/terminal attribution response id feat(token-id-capture): record served response IDs Aug 23, 2026
@ananthsub
ananthsub requested a review from pthombre August 24, 2026 03:38
@ananthsub
ananthsub force-pushed the ansubramania/terminal-attribution-response-id branch from 10a0211 to a9b69c7 Compare August 24, 2026 03:45
@ananthsub
ananthsub marked this pull request as ready for review August 24, 2026 04:03
@ananthsub
ananthsub requested a review from Glorf August 24, 2026 14:00
@github-actions github-actions Bot added the sla:review-overdue Review response is over the one-business-day SLA label Aug 25, 2026
@ananthsub
ananthsub force-pushed the ansubramania/terminal-attribution-response-id branch from a9b69c7 to ed44fd2 Compare August 25, 2026 18:19
@ananthsub
ananthsub force-pushed the ansubramania/terminal-attribution-response-id branch from ed44fd2 to 80154ce Compare August 25, 2026 18:27
…ed call

TokenEntry gains response_id so terminal attribution can match an agent's
final response to the captured tokens for that model call. The capture stack
is not operational yet, so the initial schema remains version 1.

The Anthropic Messages mapping reuses the inner Responses envelope id instead
of minting msg_<uuid>. This keeps the client-visible id aligned with the value
recorded during capture.

Signed-off-by: Ananth Subramaniam <ansubramania@nvidia.com>
@ananthsub
ananthsub force-pushed the ansubramania/terminal-attribution-response-id branch from 80154ce to 6a91461 Compare August 27, 2026 19:21
@ananthsub

Copy link
Copy Markdown
Contributor Author

/ok to test 6a91461

@ananthsub

Copy link
Copy Markdown
Contributor Author

/claude review

@claude

claude Bot commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

SHIP β€” LGTM, no reliability concerns.

Reviewed all 4 changed files against base. This is a small, additive change that threads the served envelope id through token capture:

  • TokenEntry.response_id is a new optional field (= None, default), fully backward-compatible under extra="allow": old records deserialize to None, and keeping TOKEN_ENTRY_RECORD_SCHEMA_VERSION at 1 is correct since the addition is optional and non-semantic.
  • capture_tokens records payload.get("id") and never mints β€” consistent with the stated invariant. Capture runs on the inner response (_invoke_responses/_invoke_chat_completions) before the Anthropic conversion drops token fields, so ordering is right.
  • responses_to_anthropic_response reuses the inner Responses id on the outer Messages envelope, with the prior msg_<uuid> mint retained as a fallback when response.id is empty. The Anthropic SDK's Message.id is a plain str (no prefix validation), so reusing a resp_/chatcmpl_ id validates cleanly β€” the join holds across the responses, chat, and messages dialects.
  • No async/HTTP concerns; no other TokenEntry consumer needs updating. Tests exercise all three routes plus the converter and the missing-id / newer-record cases, asserting real behavior.

@ananthsub
ananthsub merged commit 731e8de into main Aug 27, 2026
36 of 37 checks passed
@ananthsub
ananthsub deleted the ansubramania/terminal-attribution-response-id branch August 27, 2026 21:24
ananthsub added a commit that referenced this pull request Aug 27, 2026
## What does this PR do?

This PR identifies the captured model call that produced the response
scored by the verifier, then anchors trajectory selection to that call.
It depends on #2675.

- Normalizes model-authored content across Responses, Chat Completions,
and Anthropic message shapes.
- Resolves the terminal model call from an explicit call ID, the served
response ID, and content fingerprints.
- Requires independent witnesses to agree and abstains when evidence is
missing, duplicated, or contradictory.
- Selects the root-to-terminal token chain, excluding auxiliary calls,
sub-agent branches, and later retries.
- Preserves the existing strict single-chain behavior when no terminal
call can be attributed.
- Records the attribution result and masking reason in rollout metrics.

## Terminal attribution flow

```mermaid
flowchart TD
    Result["Completed /run result<br/>response scored by verifier"] --> Finalize["finalize_rollout_token_capture"]
    Store["Frozen TokenEntry snapshot<br/>one entry per captured model call"] --> Finalize
    Finalize --> Resolve["resolve_terminal"]

    Explicit["Explicit terminal model_call_id<br/>when supplied"] --> Agreement
    ResponseId["Response ID witness<br/>result.response.id = TokenEntry.response_id"] --> Agreement
    Content["Content witness<br/>cross-dialect fingerprint match"] --> Agreement
    Resolve --> Explicit
    Resolve --> ResponseId
    Resolve --> Content

    Agreement{"Do all available witnesses<br/>identify the same token sequence?"}
    Agreement -- Yes --> Terminal["Resolved terminal model_call_id"]
    Agreement -- No witnesses or disagreement --> Abstain["No attribution"]

    Terminal --> Builder["Build token-prefix forest<br/>and select root-to-terminal chain"]
    Builder --> Intact{"Is the terminal chain intact?"}
    Intact -- Yes --> Deliver["Deliver verified chain unmasked<br/>exclude auxiliary calls and sibling branches"]
    Intact -- No --> Mask["Mask rollout<br/>known terminal is not safely deliverable"]

    Abstain --> Strict["Use existing strict single-chain policy"]
    Strict --> Unique{"Exactly one unambiguous chain?"}
    Unique -- Yes --> Deliver
    Unique -- No --> Mask
```

The response ID does not directly determine a trajectory. It identifies
a captured `TokenEntry`, whose `model_call_id` anchors selection in the
token-prefix forest. Content and explicit-ID witnesses independently
check that join. A disagreement fails closed instead of allowing one
witness to override another.

## Known limitations

- Agent observation bundles are not yet consumed as an additional
terminal witness.
- Token-prefix reconstruction can still split legitimate multi-call tool
loops when a harness reserializes structured turns. Terminal attribution
chooses the verified root; it does not restore missing token continuity.
- Hermes currently sends auxiliary model-server requests through the
capture prefix. The middleware treats those requests as missing
generation captures and masks an otherwise complete rollout. #2678
tracks that separate completeness bug.

## Checklist

- [x] I have read the [contributing
guidelines](https://docs.nvidia.com/nemo/gym/latest/contribute/development-setup).
- [x] The change is focused; unrelated "drive-by" edits are tracked as
separate issues/PRs.
- [x] Tests added or updated and pass locally, or N/A for docs-only /
non-code changes (so CI unit/server checks pass when applicable).
- [x] Pre-commit checks pass locally (`pre-commit run --all-files`) (so
CI lint/format/copyright pass).
- [x] All commits have DCO sign-off (`git commit -s`) (so the DCO check
passes).

---------

Signed-off-by: Kajal Jain <kajalj@nvidia.com>
Signed-off-by: ananthsub <ananth.subramaniam@gmail.com>
Co-authored-by: kajalj22 <kajalj@nvidia.com>

This branch was previously deployed

1 inactive deployment
public β€” 6a91461a Deployed Aug 27, 2026 by copy-pr-bot[bot] via release / finalize / notify #2131
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

sla:review-overdue Review response is over the one-business-day SLA

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant