Skip to content

Add background work to chat state for shells and subagents - #482

Merged
Anthony Kim (anthonykim1) merged 16 commits into
mainfrom
anthonykim1/chat-background-work
Oct 1, 2026
Merged

Anthony Kim (anthonykim1) merged 16 commits into
mainfrom
anthonykim1/chat-background-work

Conversation

@anthonykim1

@anthonykim1 Anthony Kim (anthonykim1) commented Sep 29, 2026 •

Copy link
Copy Markdown
Contributor

Replaces: #475, following the discussion there.
Related: #473. Its interoperability audit found a background task catalog that copilotd exposes only through _meta; this makes background work typed chat state.
Related: microsoft/vscode#336348, microsoft/vscode#337684
Used by: microsoft/vscode#338723

  • Add ChatState.backgroundWork: work that keeps running after the tool call that started it returns and will resume the chat when it finishes. Like changesets, it stays off ChatSummary, so it isn't broadcast to every session subscriber; clients read it by subscribing to the chat.
  • Model entries as a non-exhaustive BackgroundWork union keyed by kind, with two kinds:
    • shell: its plain-text command, plus an optional terminal URI for its output. Hosts SHOULD set terminal whenever they can show that output. It stays optional because some hosts can't point at a live terminal yet.
    • subagent: its chat, the same chat the spawning tool call's ToolResultSubagentContent.resource points to, instead of repeating the subagent's state.
  • Share id, label, startedAt, and _meta across kinds. Shells can be attached (tied to the agent's lifetime) or detached (outliving it). That's provider-specific, so it goes in the shell's _meta rather than a shared field, and other kinds, subagents included, don't carry it.
  • Leave out a status. Every entry is unfinished work, and hosts remove entries when the work finishes instead of marking them done.
  • Add server-only chat/backgroundWorkSet (upsert by id) and chat/backgroundWorkRemoved (remove by id, no-op when absent), introduced in 0.9.0. id is opaque and unique within the chat across kinds, like inputNeeded entries.
  • Keep the list independent of turns. An entry stays listed whether or not the turn that started it is still open, since attached shells can keep that turn open, and turn completion, cancellation, steering, and truncation don't clear it.
  • Register both kinds in all five client generators, and update each client's hand-written reducer. Every client keeps entries of kinds it doesn't recognize, and can replace or remove them by id. Go's ChatState.BackgroundWork is a pointer, so a list emptied by removals still encodes as [].
  • Regenerate the schema and clients, and add reducer fixtures for adding a shell and a subagent, replacing an entry (which adds its terminal), removing one entry while keeping the other, absent remove, and an unknown kind being kept, replaced, and removed.
  • Document the list in the state model guide, and add a change fragment.
Validation
  • npm run generate and npm test at dc9ff6b0: passed (505 tests, 100% reducer coverage). Every npm run generate:<lang> step that CI runs (schema, typescript, go, rust, swift, kotlin, dotnet, docs, metadata) then produced no diff.
  • gofmt -l, go vet ./..., and go test ./... in clients/go: passed. Without the Go fix, the two unknown-kind fixtures fail.
  • cargo fmt --all -- --check and cargo test -p ahp in clients/rust: passed. Kotlin and .NET weren't built locally.
  • The CI workflow on dc9ff6b0 passed every job, including the generated-code, formatting, and test checks for Go, Rust, Swift, Kotlin, .NET, and TypeScript.

Inspirations from

Chats list work that will resume them when it finishes, as a non-exhaustive union of background shells and subagents, independent of turn lifetime. Hosts update it with chat/backgroundWorkSet and chat/backgroundWorkRemoved and mirror it into session chat summaries.
Drop the subagent kind for now. The kind list is non-exhaustive, so subagents and other kinds can be added later without breaking clients.
The Go, Kotlin, and Swift reducers now read the common id from kinds they don't recognize, as Rust and .NET already do. A newer host's entries are kept on set and can be removed, matching the documented behavior. Add shared fixtures for both.
Every listed entry is unfinished work, and hosts remove finished work instead of marking it done. Copilot never reports a background shell as idle, so the field had no producer yet. An optional status can be added later without breaking clients.
@anthonykim1 Anthony Kim (anthonykim1) changed the title Add chat-owned background work Add background work to chat state, starting with shells Sep 29, 2026
Nothing sets or reads it yet. What a background shell's terminal should point to belongs with output streaming, and an optional field can be added then without breaking clients.
/** ISO 8601 timestamp when the work started. */
startedAt: string;
/** Provider-specific metadata, such as how a shell's lifetime is tied to its agent. */
_meta?: Record<string, unknown>;

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Wondering if we can use this for the attached/detached badge, e.g. "vscode.copilot.shellAttachment": "detached".

Comment thread types/channels-chat/state.ts
Comment thread types/channels-chat/state.ts
Keep both BackgroundWork and ChangesSummary in the generated Rust state import, then regenerate the clients.
@anthonykim1
Anthony Kim (anthonykim1) marked this pull request as draft September 29, 2026 22:51
Comment thread types/channels-chat/state.ts Outdated
Addresses review thread #482 (comment).
A shell entry now carries an optional terminal URI, so clients have something to open to check on the shell. It's optional because some hosts can't point at a live terminal for every shell yet.
Adding the background work id helper split sessionInputRequestID from its doc comment. Move the comment back and give the new helper its own.
Addresses review thread #482 (comment).
A background subagent is now its own kind, pointing at the subagent's chat instead of repeating its state. That's the same chat the spawning tool call's subagent content points to.
Addresses review thread #482 (comment).
Attached shells can keep the turn that started them open, so "outside the current turn" was wrong. Describe background work as work that keeps running after its tool call returns, and document that shells can be attached or detached, with that detail kept in the shell's _meta instead of the shared fields.
Anthony Kim (anthonykim1) added a commit to microsoft/vscode that referenced this pull request Sep 29, 2026
Match microsoft/agent-host-protocol#482: background shells can link their terminal, and background subagents are their own kind that points at the subagent's chat. The Background Shells pill still shows shells only.
@anthonykim1 Anthony Kim (anthonykim1) changed the title Add background work to chat state, starting with shells Add background work to chat state for shells and subagents Sep 29, 2026
@anthonykim1
Anthony Kim (anthonykim1) marked this pull request as ready for review September 30, 2026 00:10

@roblourens roblourens left a comment

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.

Would be worth doing a review of the UI across various apps, especially copilot app, and ensure it fits their needs.

It's been a bit since I did that and I'm not sure what the latest is

Comment thread types/channels-chat/state.ts
Comment thread types/channels-chat/state.ts Outdated
Addresses review thread #482 (comment).
Hosts remove an entry once its work ends rather than marking it completed. The state model guide already said so; say it on the field too.
Anthony Kim (anthonykim1) added a commit to microsoft/vscode that referenced this pull request Sep 30, 2026
Match microsoft/agent-host-protocol#482 at d513c6fb, which says background work lists only active work.
@anthonykim1

Anthony Kim (anthonykim1) commented Sep 30, 2026 •

Copy link
Copy Markdown
Contributor Author

Would be worth doing a review of the UI across various apps, especially copilot app, and ensure it fits their needs.

roblourens I agree , it seems like copilot app already has ui for listing background activities where at least background shell and subagent shows up:

Screenshot 2026-09-30 at 8 35 55 AM Screenshot 2026-09-30 at 8 34 41 AM

Bring in chat move and reorder (#484). Keep chat/backgroundWorkSet and chat/backgroundWorkRemoved alongside chat/movableChanged in the action types, generator action lists, and every client reducer. Use main's Rust actions import with BackgroundWork added, then regenerate the schemas and clients.
@ellismg

Copy link
Copy Markdown
Member

From the Copilot App side, I think we could adopt this model for AHP sessions as an active background-work inventory. With the proposal as written, we should be able to:

  • show currently active shells and subagents in our background activity UI;
  • preserve that inventory across navigation and reconnects through ChatSummary.backgroundWork;
  • open a shell terminal when terminal is available;
  • open a subagent chat and use its chat state for details such as current activity.

This would be a useful improvement over what we can represent for AHP sessions today. I do not think the remaining items need to block this PR, but matching the full experience we currently provide for local sessions would require some additive protocol extensions in future versions:

  • Attached versus detached shells: This affects more than presentation in the App. Running attached shells keep a session in the Working state, while detached shells remain visible but do not. It would be helpful to eventually standardize an attachmentMode-style field rather than relying on a provider-specific _meta key.
  • Idle subagents: We show a distinct Idle state for multi-turn agents waiting for another message. We may be able to derive this from the referenced child chat, but the contract should clarify that an idle-but-addressable subagent remains in backgroundWork until it completes or is no longer participating.
  • Originating tool-call correlation: Our local UI associates background work with the transcript row that started it. An optional { turnId, toolCallId } origin would let clients make that association without scanning historical tool results and matching resource URIs.
  • Completion information: The local UI retains completed, failed, stopped, and cancelled work, including duration and shell exit code. backgroundWorkRemoved currently tells us only that an item disappeared. A future optional completion payload containing the outcome, completion timestamp, and exit code would let clients retain an accurate completed-work row while keeping backgroundWork itself active-only.
  • Controls: Local sessions can stop or cancel active work and clear completed entries. This proposal explicitly does not add process controls, so those could be introduced separately through capabilities and an operation keyed by the background-work ID.

Overall, this proposal looks sufficient for us to build the active inventory and navigation portions for AHP sessions. The items above would be follow-up work needed for closer parity with the local-session experience, and they appear compatible with the current design as additive protocol changes.

Anthony Kim (anthonykim1) added a commit to microsoft/vscode that referenced this pull request Sep 30, 2026
Take agent-host-protocol at c53aef24, the head of #482 after merging main, so this PR and #482 can merge back to back. That also brings in chat move and reorder from #484: reject moveChat with InvalidParams, since clients may only move chats that advertise movable: true and this host never does.
Comment thread types/channels-chat/state.ts Outdated
Comment thread types/channels-chat/state.ts Outdated
@anthonykim1
Anthony Kim (anthonykim1) marked this pull request as draft September 30, 2026 23:02
Addresses review thread #482 (comment).
Background work may still be running when it notifies the chat, so drop "will resume this chat when it finishes" along with the tool call that started it. Say instead that an entry may have been started by an earlier turn rather than the activeTurn, and match the BackgroundWork doc and the state model guide.
@anthonykim1
Anthony Kim (anthonykim1) marked this pull request as ready for review September 30, 2026 23:43
Anthony Kim (anthonykim1) added a commit to microsoft/vscode that referenced this pull request Sep 30, 2026
Take agent-host-protocol at ab694bfd, the head of microsoft/agent-host-protocol#482. The only change is the shortened backgroundWork doc text from its review.
Addresses review thread #482 (comment).
Clients only show background work for a chat they have opened, so mirroring it into ChatSummary would broadcast every change to all session subscribers for no reader. Keep it on ChatState next to changesets, which is also absent from the summary, and drop the session/chatUpdated mirroring from the docs, the Go, Swift, and Rust reducers, and the reducer fixtures.
@anthonykim1
Anthony Kim (anthonykim1) marked this pull request as draft October 1, 2026 00:56
82d07d5 removed ChatSummary.backgroundWork but left references to it in
the hand-written .NET and Kotlin chatUpdated reducers, a Rust test
literal, and Go's hand-written PartialChatSummary, which broke the
dotnet, kotlin, and rust CI jobs.
Anthony Kim (anthonykim1) added a commit to microsoft/vscode that referenced this pull request Oct 1, 2026
microsoft/agent-host-protocol#482 dropped ChatSummary.backgroundWork, so
session subscribers no longer receive every chat's background work. The
Agents window pill now reads ChatState.backgroundWork through the active
session's chat subscription, like the editor window already did, and the
state manager no longer mirrors the list into chat summaries.

Syncs the protocol to microsoft/agent-host-protocol@dc9ff6b0.
@anthonykim1
Anthony Kim (anthonykim1) marked this pull request as ready for review October 1, 2026 01:06
@anthonykim1
Anthony Kim (anthonykim1) merged commit 4248be7 into main Oct 1, 2026
9 checks passed
@anthonykim1
Anthony Kim (anthonykim1) deleted the anthonykim1/chat-background-work branch October 1, 2026 15:46
Anthony Kim (anthonykim1) added a commit to microsoft/vscode that referenced this pull request Oct 1, 2026
…t input (#338723)

* Publish chat background work and show background shells

Publish Copilot's running and waiting shells and running background subagents as AHP chat background work, and project the shells onto the Agents window's Background Shells pill.

* Publish only background shells as background work

Follow the protocol change that starts background work with shells only. The provider still skips kinds it doesn't render, since newer hosts can send more.

* Stop publishing background shell status

Follow the protocol change that drops status from background work. Every listed shell is unfinished, and Copilot never reports a shell as idle. The pill now shows the attached or detached mode and elapsed time.

* Ignore background work in the read shell E2E snapshot

The async shell is now published as background work from a separate task-list read, so its timing against the tool calls isn't part of this scenario.

* Drop terminal from the background shell work protocol copy

Follow the protocol change that removes the unused terminal field. Nothing in VS Code set or read it.

* Show background shells in the editor window's chat

Move the Background Shells pill control and the background work projection into the workbench so the editor window's Agent Host chat input can show the same pill as the Agents window. Share the pill's accessibility help between both windows.

* Agent Host: reconcile background work after a Copilot client restart

Addresses review feedback on stale shells after a client restart or crash. A session that replaced another started with an empty baseline, so shells the new runtime no longer reported stayed listed. The host now passes the chat's current background work when a watch attaches, and the session removes entries missing from its first read.

* Agent Host: keep reading background work after an abort

Addresses review feedback on the abort race. Aborting a turn bumped the task status revision without queuing a read, so a read in flight was discarded and the detached shell poll could stop for good, since detached shells report no exit on their own. The session now reads again once the abort settles.

* Sync background work protocol with AHP

Match microsoft/agent-host-protocol#482: background shells can link their terminal, and background subagents are their own kind that points at the subagent's chat. The Background Shells pill still shows shells only.

* Publish background subagents as background work

Running background agents are published with their chat. Idle ones have already reported back, so they're left out. Shells don't set a terminal yet; that waits for output streaming.

* Key background shell rows by the protocol id

Addresses review thread #338723 (comment).
Rows used Copilot's shell ID from _meta as their identity, which the protocol doesn't guarantee is unique. Key rows by the entry id instead, and show Copilot's shell ID separately, only when the agent reports one.

* Keep background work out of the retained subagent E2E snapshot

Running background subagents are now published as background work. Those actions come from a separate task-list read, so their timing against the subagent's tool calls isn't part of this scenario, and CI's snapshot comparison failed on every platform. Ignore them here, as the read-shell scenario already does.

* Remove a stray E2E snapshot artifact

A failing local run left this .actual file next to the retained background subagent snapshot, and it was committed by mistake. Hygiene rejects it, and the test runner logs when it deletes it, which fails the no-console-output check.

* Sync the background work docs with AHP

Match microsoft/agent-host-protocol#482 at d513c6fb, which says background work lists only active work.

* Show only Detached on background shells

Attached is the common case, so an unlabeled shell reads as attached and only detached shells carry a label, matching the GitHub app.

* Sync the AHP protocol with microsoft/agent-host-protocol#482

Take agent-host-protocol at c53aef24, the head of #482 after merging main, so this PR and #482 can merge back to back. That also brings in chat move and reorder from #484: reject moveChat with InvalidParams, since clients may only move chats that advertise movable: true and this host never does.

* Sync the shorter background work description from AHP

Take agent-host-protocol at ab694bfd, the head of microsoft/agent-host-protocol#482. The only change is the shortened backgroundWork doc text from its review.

* Read background shells from chat state instead of chat summaries

microsoft/agent-host-protocol#482 dropped ChatSummary.backgroundWork, so
session subscribers no longer receive every chat's background work. The
Agents window pill now reads ChatState.backgroundWork through the active
session's chat subscription, like the editor window already did, and the
state manager no longer mirrors the list into chat summaries.

Syncs the protocol to microsoft/agent-host-protocol@dc9ff6b0.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

4 participants