Skip to content

agentHost: dedupe no-op root state actions#314597

Merged
roblourens merged 6 commits into
mainfrom
roblou/agents/prevent-root-state-update-loop
May 6, 2026
Merged

agentHost: dedupe no-op root state actions#314597
roblourens merged 6 commits into
mainfrom
roblou/agents/prevent-root-state-update-loop

Conversation

@roblourens
Copy link
Copy Markdown
Member

Skip envelope emission in AgentHostStateManager._applyAndEmit when a root action's reducer output deeply equals the previous root state.

Why

rootReducer always allocates a new state object, even for no-op RootConfigChanged actions (it just spreads the existing values). Combined with RootStateSubscription.onDidChange firing on every applied envelope, that meant any contribution that re-published an unchanged root config value would trigger an event, react to it, and re-publish — an infinite loop. The recent #314391 fix worked around one instance of this in agentHostTerminalContribution.ts, but the underlying server-side behavior was still wrong.

What

  • In _applyAndEmit, when the action is a root action, run the reducer, deep-compare with the previous root state via objects.equals, and bail out (no envelope, no serverSeq bump) when unchanged.
  • Add a unit test covering: first dispatch emits, repeat with same primitive value is deduped, deeply-equal nested object value is deduped, and a real change still emits.

Notes for reviewers

  • The fix is intentionally scoped to root actions. The reported bug is rootState-only; session actions (e.g. deltas) effectively always carry a real change.
  • src/vs/platform/agentHost/common/state/protocol/reducers.ts is auto-generated from the protocol repo by scripts/sync-agent-host-protocol.ts, so the dedupe lives at the call site rather than inside the reducer.
  • The client-side guard in agentHostTerminalContribution.ts from Avoid infinite loop in syncing agent host config #314391 is left in place as defense in depth (it still avoids an unnecessary RPC).

(Written by Copilot)

roblourens and others added 2 commits May 5, 2026 10:02
Localized to the sessions provider
Skip envelope emission in AgentHostStateManager when a root action's reducer output deeply equals the previous root state. This prevents a feedback loop where a contribution re-publishes a root config value that already matches: the server would still fire a RootStateSubscription change, the contribution would react and re-dispatch, and so on.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copilot AI review requested due to automatic review settings May 6, 2026 00:14
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR prevents infinite update loops in the agent host protocol by skipping action-envelope emission for root actions whose reducer output is deeply equal to the previous root state. This reduces unnecessary rootState change events and avoids feedback cycles when the same root config values are re-published.

Changes:

  • Deduplicate no-op root actions in AgentHostStateManager._applyAndEmit via deep equality and skip envelope emission/serverSeq bump when unchanged.
  • Add a unit test verifying RootConfigChanged no-op dispatches do not emit envelopes, including deep-equal object values.
  • Refactor agent-provider icon mapping out of session.ts into BaseAgentHostSessionsProvider.
Show a summary per file
File Description
src/vs/sessions/services/sessions/common/session.ts Removes exported iconForAgentProvider helper (moved to provider implementation).
src/vs/sessions/contrib/agentHost/browser/baseAgentHostSessionsProvider.ts Adds private icon mapping for agent providers used when syncing session types from root state.
src/vs/platform/agentHost/node/agentHostStateManager.ts Adds deep-equality no-op detection for root actions to avoid emitting envelopes when root state is unchanged.
src/vs/platform/agentHost/test/node/agentHostStateManager.test.ts Adds coverage ensuring no-op root config actions do not emit envelopes, including deep-equal nested values.

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 1

Comment thread src/vs/platform/agentHost/node/agentHostStateManager.ts Outdated
Per review feedback, only RootConfigChanged is dispatched as a true no-op (its reducer spreads values even when the patch matches). Other root actions like RootActiveSessionsChanged fire frequently (every turn start/complete) and always carry a real value, so the deep-compare was wasted work for them.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 6, 2026

Screenshot Changes

Base: 154bcccd Current: d2bb64c2

Changed (12)

chat/input/chatInput/Default/Dark
Before After
before after
chat/input/chatInput/Default/Light
Before After
before after
chat/input/chatInput/WithArtifacts/Dark
Before After
before after
chat/input/chatInput/WithArtifacts/Light
Before After
before after
chat/input/chatInput/WithFileChanges/Dark
Before After
before after
chat/input/chatInput/WithTodos/Light
Before After
before after
chat/input/chatInput/WithTodosAndFileChanges/Dark
Before After
before after
chat/input/chatInput/WithArtifactsAndFileChanges/Dark
Before After
before after
chat/input/chatInput/WithArtifactsAndFileChanges/Light
Before After
before after
chat/widget/chatWidget/SimpleQA/Light
Before After
before after
chat/widget/chatWidget/PendingToolApproval/Dark
Before After
before after
chat/widget/chatWidget/PendingToolApproval/Light
Before After
before after

Instead of running the reducer and post-comparing output, inspect the
RootConfigChanged action's patch against the current config values before
calling rootReducer. This avoids allocating a new state object entirely for
no-op dispatches, rather than allocating and discarding it.

(Written by Copilot)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot's findings

  • Files reviewed: 4/4 changed files
  • Comments generated: 3

Comment thread src/vs/platform/agentHost/node/agentHostStateManager.ts Outdated
Comment thread src/vs/platform/agentHost/test/node/agentHostStateManager.test.ts
roblourens and others added 2 commits May 5, 2026 18:41
- Merge-mode pre-check computes the merged result and deep-compares against
  current values, so a patch adding a new key (even with value undefined) is
  not mistakenly treated as a no-op
- Test now asserts serverSeq does not advance on deduped (no-op) actions

(Written by Copilot)

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
@roblourens roblourens marked this pull request as ready for review May 6, 2026 01:43
@roblourens roblourens enabled auto-merge (squash) May 6, 2026 01:43
@roblourens roblourens merged commit dc53363 into main May 6, 2026
40 of 41 checks passed
@roblourens roblourens deleted the roblou/agents/prevent-root-state-update-loop branch May 6, 2026 04:35
@vs-code-engineering vs-code-engineering Bot added this to the 1.120.0 milestone May 6, 2026
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.

3 participants