Add background work to chat state for shells and subagents - #482
Conversation
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.
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>; |
There was a problem hiding this comment.
Wondering if we can use this for the attached/detached badge, e.g. "vscode.copilot.shellAttachment": "detached".
Keep both BackgroundWork and ChangesSummary in the generated Rust state import, then regenerate the clients.
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.
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.
roblourens
left a comment
There was a problem hiding this comment.
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
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.
Match microsoft/agent-host-protocol#482 at d513c6fb, which says background work lists only active work.
roblourens I agree , it seems like copilot app already has ui for listing background activities where at least background shell and subagent shows up:
|
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.
|
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:
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:
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. |
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.
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.
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.
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.
…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.


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
ChatState.backgroundWork: work that keeps running after the tool call that started it returns and will resume the chat when it finishes. Likechangesets, it stays offChatSummary, so it isn't broadcast to every session subscriber; clients read it by subscribing to the chat.BackgroundWorkunion keyed bykind, with two kinds:shell: its plain-textcommand, plus an optionalterminalURI for its output. Hosts SHOULD setterminalwhenever they can show that output. It stays optional because some hosts can't point at a live terminal yet.subagent: itschat, the same chat the spawning tool call'sToolResultSubagentContent.resourcepoints to, instead of repeating the subagent's state.id,label,startedAt, and_metaacross 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_metarather than a shared field, and other kinds, subagents included, don't carry it.chat/backgroundWorkSet(upsert byid) andchat/backgroundWorkRemoved(remove byid, no-op when absent), introduced in0.9.0.idis opaque and unique within the chat across kinds, likeinputNeededentries.id. Go'sChatState.BackgroundWorkis a pointer, so a list emptied by removals still encodes as[].Validation
npm run generateandnpm testatdc9ff6b0: passed (505 tests, 100% reducer coverage). Everynpm run generate:<lang>step that CI runs (schema, typescript, go, rust, swift, kotlin, dotnet, docs, metadata) then produced no diff.gofmt -l,go vet ./..., andgo test ./...inclients/go: passed. Without the Go fix, the two unknown-kind fixtures fail.cargo fmt --all -- --checkandcargo test -p ahpinclients/rust: passed. Kotlin and .NET weren't built locally.dc9ff6b0passed every job, including the generated-code, formatting, and test checks for Go, Rust, Swift, Kotlin, .NET, and TypeScript.Inspirations from
*Kinddiscriminant, unexported base interface, and opaque cross-kindidfollowSessionInputRequest,SessionInputRequestKind, andSessionInputRequestBase.sessionInputRequestIDand its counterparts in the other clients. Readingidfrom unrecognized kinds follows the Rustsession_input_request_idand .NETSessionInputRequestIdhelpers.terminalis the same subscribable terminal URI asToolResultTerminalContent.resource, and clients tell plain-text output from a pty byTerminalState.isPty. Keeping it optional follows the doctrine's preference for optional fields.chatis the spawn edge thatToolResultSubagentContent.resourcealready exposes and the worker'sChatOriginrecords in reverse.ChatSummaryfollowsChatState.changesets, which clients also only get by subscribing to the chat.SessionState.inputNeeded, whose entries are removed once the underlying request resolves.Set/Removednaming and reducer behavior follow the keyed-collection convention ingeneral-instructions.instructions.md._metafollowscustomizations.mdand the doctrine's explicit escape hatches.