Skip to content

agentHost: preserve BYOK state through tool continuations - #331749

Open
sgent-epic wants to merge 7 commits into
microsoft:mainfrom
sgent-epic:codex/fix-agent-host-stateful-marker
Open

agentHost: preserve BYOK state through tool continuations#331749
sgent-epic wants to merge 7 commits into
microsoft:mainfrom
sgent-epic:codex/fix-agent-host-stateful-marker

Conversation

@sgent-epic

Copy link
Copy Markdown

Summary

  • retain provider response IDs only while tool calls are pending
  • recover the Copilot SDK immediate tool-result replay by supplying previous_response_id and forwarding only the matching tool outputs
  • scope pending state by authenticated SDK session, vendor, and model
  • preserve explicit continuation IDs and clear recovered state after a terminal response

Why

The bundled Copilot SDK currently replays the full Responses history after a tool call and omits previous_response_id. Extension-provided stateful models then receive no stateful_marker and reject the continuation.

The integration test reproduces the SDK request shape directly. Before the proxy change, it failed because the second request replayed the message, reasoning, and tool call with no response ID. It now reaches the provider with the original response ID and only the tool output.

Fixes #329283

Testing

  • npm run compile-client
  • scripts\test.bat --run src\vs\platform\agentHost\test\node\byokLmProxyService.test.ts
  • scripts\test-integration.bat --run src\vs\platform\agentHost\test\node\providerIntegration\copilotByokResponses.integrationTest.ts
  • precommit hygiene
  • git diff --check upstream/main...HEAD

Copilot AI balanced review requested due to automatic review settings August 20, 2026 05:06

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.

Pull request overview

Preserves BYOK provider state through Copilot SDK tool continuations, addressing #329283.

Changes:

  • Tracks pending response IDs by session, vendor, and model.
  • Recovers matching tool-output continuations without overriding explicit state.
  • Adds unit and SDK integration coverage.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
byokLmProxyService.ts Implements continuation recovery and scoped state tracking.
byokLmProxyService.test.ts Tests recovery, scoping, explicit IDs, and cleanup.
copilotByokResponses.integrationTest.ts Verifies SDK tool continuation behavior end-to-end.

💡 Configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread src/vs/platform/agentHost/node/copilot/byokLmProxyService.ts Outdated
@sgent-epic
sgent-epic force-pushed the codex/fix-agent-host-stateful-marker branch 2 times, most recently from e547f44 to 9886540 Compare August 20, 2026 14:23
@sgent-epic

Copy link
Copy Markdown
Author

Hi Vritant Bhardwaj (@vritant24), I hope you're doing well. Is there any way this could be prioritized? It's a blocker for wider piloting of agents in VS Code at Epic. I'm personally really liking the VS Code environment over other options, and I'd like to enable other folks to give it a try as well. Thanks for your time!

@sgent-epic
sgent-epic force-pushed the codex/fix-agent-host-stateful-marker branch 3 times, most recently from 4ec345b to 67cd0c4 Compare August 20, 2026 19:35
}

private _updateToolContinuation(state: ByokLmProxyState, key: string, result: IByokLmChatResult): void {
if (!result.responseId) {

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

result.responseId is not always evidence that the downstream response is resumable.

  • Explicit-ZDR Responses requests use store: false and deliberately remove previous_response_id.
  • The Responses processor still emits the returned ID as stateful_marker.
  • This proxy then caches that ID and replaces the SDK replay with only tool outputs.
  • On the next request, the ZDR endpoint drops the injected marker, leaving orphaned tool outputs without the original history.

Suggested fix:

  • Define stateful_marker as “resumable provider state”: do not emit it for explicit-ZDR/non-stored responses.
  • With no marker, IByokLmChatResult.responseId remains absent and this proxy preserves the full stateless replay.
  • Do not infer this from the local body.store: the SDK intentionally sends store: false to this facade even when the downstream provider is stateful.
  • Add a ZDR Responses tool-continuation test verifying that no marker is cached and the full replay reaches the provider.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in bf9576442cb. responseId is now explicitly an opt-in resumable-state contract. The component that knows whether a response is resumable owns whether to report it. The proxy does not infer this from store, and the regression verifies that absence preserves full replay without previous_response_id.

// A session can disappear after receiving a tool call, so keep abandoned
// continuations from growing for the lifetime of the shared proxy.
state.delete(key);
state.set(key, { responseId: result.responseId, calls });

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

This retains only one continuation per (sessionId, vendor, modelId), but root, background, and subagent trajectories share the same session-scoped provider token.

A normal nested flow can therefore lose the parent state even without an HTTP race:

parent tool call   -> stores resp_parent
subagent response  -> overwrites/deletes the same entry
parent tool result -> recovery misses -> strict stateful provider rejects it

Suggested fix:

  • Retain multiple pending responses and match trailing tool outputs against the exact call-ID/kind set.
  • Recover only when exactly one candidate in the same session/vendor/model scope matches.
  • Remove only the matched predecessor after a successful continuation; unrelated terminal responses must not clear other entries.
  • Keep memory bounded with a flat global limit of 256 pending responses, not unbounded arrays per scope, and store only IDs/kinds rather than history.
  • Add an interleaving test with parent and subagent continuations using the same session/vendor/model.

@sgent-epic sgent-epic Aug 20, 2026

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 6a805a3423f. The proxy now keeps a flat globally bounded set of candidates, resumes only a unique exact call-ID/kind match within the same scope, and removes only the matched predecessor after a successful bridge call. Tests cover parent/subagent interleaving, ambiguous matches, and the 256-entry bound.

@sgent-epic
sgent-epic force-pushed the codex/fix-agent-host-stateful-marker branch from 6a805a3 to 83216ae Compare August 20, 2026 23:15
@sgent-epic
sgent-epic force-pushed the codex/fix-agent-host-stateful-marker branch from 83216ae to e7008ba Compare August 20, 2026 23:33
@sgent-epic

Copy link
Copy Markdown
Author

Hi Vritant Bhardwaj (@vritant24), please let me know if there are any changes required. Thanks!

@vritant24

Copy link
Copy Markdown
Member

Thanks for the update. The parent/subagent collision looks fixed now. I think two things remain:

  • The ZDR path still emits every Responses ID as a statefulMarker, even though responses created with store: false cannot be resumed. The new test assumes responseId is already absent, but the real producer still supplies one. Could we suppress the marker for ZDR responses and test that path directly?

  • Explicit continuations do not clear their cached entry. We only delete state when a continuation was implicitly recovered, so a successful request with previous_response_id leaves stale state behind. Could we remove the matching entry after that request succeeds, while retaining it on errors?

The latest rebase doesn’t appear to change either path.

@sgent-epic

Copy link
Copy Markdown
Author

Thanks, Vritant Bhardwaj (@vritant24). Both are addressed in e2cee5c. ZDR responses no longer emit markers, with direct ZDR/stored coverage. Explicit continuations now remove matching cached state after success while retaining it on errors. Full compile and focused tests pass.

@sgent-epic
sgent-epic force-pushed the codex/fix-agent-host-stateful-marker branch from e2cee5c to abd344d Compare August 21, 2026 23:22

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Verified the diff. Changes align with project standards.

@sgent-epic
sgent-epic force-pushed the codex/fix-agent-host-stateful-marker branch 2 times, most recently from d509d36 to 7a302ea Compare August 24, 2026 16:38
@sgent-epic

Copy link
Copy Markdown
Author

Hi Vritant Bhardwaj (@vritant24) can we run the test suites again? Thanks!

@sgent-epic
sgent-epic force-pushed the codex/fix-agent-host-stateful-marker branch 2 times, most recently from 0d98e35 to 8922007 Compare August 25, 2026 01:41
@sgent-epic
sgent-epic requested a balanced review from Copilot August 25, 2026 01:42

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.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated no new comments.

@sgent-epic

Copy link
Copy Markdown
Author

Hi Vritant Bhardwaj (@vritant24), can you please help me with the next steps? Apologies for the nagging, but it's a blocker for us. TIA!

@sgent-epic
sgent-epic force-pushed the codex/fix-agent-host-stateful-marker branch from 8922007 to 05a545b Compare August 25, 2026 20:48
@sgent-epic
sgent-epic force-pushed the codex/fix-agent-host-stateful-marker branch from 05a545b to 8188e25 Compare August 27, 2026 13:30
@sgent-epic

Copy link
Copy Markdown
Author

Hi Vritant Bhardwaj (@vritant24) is there anything else I need to do? Can you please approve the workflows to run?

@sgent-epic
sgent-epic force-pushed the codex/fix-agent-host-stateful-marker branch from 7f3cc34 to 769a7b6 Compare August 31, 2026 21:00
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.

[Agent Host BYOK] Responses reasoning continuation loses its stateful marker

5 participants