Background agent idle notifications falsely report multi-turn agents as complete
- Dominant language
- TypeScript
- Stars
- 193k
- Forks
- 42.4k
- PR merge metrics
- PR metrics pending
Description
## Description
When a multi-turn background agent finishes one turn and becomes idle, VS Code renders a system notification saying:
> Background agent `` is complete
This is a terminal claim, but `agent_idle` is a nonterminal lifecycle state: the agent is waiting for another message and can be resumed with `write_agent`. If the parent resumes the agents, the UI correctly shows their new turns as in progress while the earlier permanent messages still say that those same agents are complete.
This produces the contradictory state shown in the attached reproduction: three subagent pills are in progress while three messages state that the agents are complete.
## Reproduction
1. Start several background, multi-turn subagents.
2. Have each subagent finish its first turn by requesting additional input (`NEEDS_INPUT` in this reproduction).
3. Allow the resulting `agent_idle` notifications to reach the parent session.
4. Resume the same agents with `write_agent`.
5. Observe the parent response while the resumed agents are running.
## Actual behavior
The parent response contains permanent messages such as:
- `Background agent proj1-readme is complete`
- `Background agent proj2-readme is complete`
- `Background agent proj3-readme is complete`
At the same time, the corresponding subagent pills show active/in-progress turns.
## Expected behavior
An `agent_idle` notification must not be presented as terminal agent completion. It should describe a nonterminal state, for example that the agent finished its current turn and is waiting for follow-up.
Terminal completion/failure wording should be reserved for `agent_completed`.
## Verified event timeline
Times below are UTC from the Agent Host debug bundle:
| Time | Event |
|---|---|
| `00:59:44` | Three background agents emit `subagent.started`. |
| `00:59:49`–`00:59:50` | Their initial turns finish after returning `NEEDS_INPUT`; `subagent.completed` is emitted for those turns. |
| `00:59:53` | Three `system.notification` events arrive with `kind.type = "agent_idle"`. |
| `00:59:57` | `read_agent` reports all three agents as `idle (waiting for messages)`. |
| `01:00:03` | The parent sends follow-up messages to the same agent IDs with `write_agent`. |
| `01:00:05` | Agent Host logs `Resuming subagent turn` for the first agent. |
| `01:00:35` | `read_agent` explicitly reports that resumed agent as `status: running`. |
| `01:01:00` | Agent Host logs `Resuming subagent turn` for the other two agents. |
This rules out the notification merely arriving late after a resume: each idle notification was valid when generated. The problem is that VS Code translates that nonterminal transition into terminal wording and persists it after the agent resumes.
## Root cause
[`buildCopilotSystemNotification`](https://github.com/microsoft/vscode/blob/main/src/vs/platform/agentHost/node/copilot/copilotSystemNotification.ts) handles both `agent_completed` and `agent_idle`. The `agent_idle` branch currently translates the event to:
```ts
"Background agent {0} is complete"
```
[`CopilotAgentSession`](https://github.com/microsoft/vscode/blob/main/src/vs/platform/agentHost/node/copilot/copilotAgentSession.ts) then appends that text to the parent turn as a `ResponsePartKind.SystemNotification`. This response part is historical and permanent.
When a follow-up is sent, [`AgentSideEffects._resumeSubagentSession`](https://github.com/microsoft/vscode/blob/main/src/vs/platform/agentHost/node/agentSideEffects.ts) correctly starts a new child turn. The live subagent state therefore changes back to running, but nothing revises or retracts the earlier terminal-looking notification in the parent response.
The result is not an incorrect running-state reducer or a stale SDK notification. It is a lifecycle semantic mismatch at the system-notification translation boundary:
- SDK/runtime state: `agent_idle` = the current turn finished; the multi-turn agent is parked and can resume.
- VS Code presentation: `agent_idle` = the agent is complete.
The completion-oriented copy was introduced by #323805, changing the previous `is idle` wording to `is complete`.
## Suggested fix direction
- Reserve `completed`/`failed` wording for `agent_completed`.
- Give `agent_idle` explicitly nonterminal wording, such as `Background agent finished its turn and is waiting for follow-up`.
- Keep the notification useful to the parent agent—it still needs to wake/read the child result—but do not describe the reusable agent as terminal.
- Ensure history replay uses the same corrected translation.
## Regression coverage
Add an end-to-end lifecycle regression covering this complete sequence:
1. A multi-turn subagent becomes idle and emits `agent_idle`.
2. The parent receives the system notification.
3. The parent sends `write_agent` to the same agent.
4. `subagent_resumed` starts a new active child turn.
5. The parent transcript contains no terminal completion claim for that still-reusable/running agent.
Existing tests cover the pieces independently—`agent_idle` translation and resuming a previously completed child turn—but not the contradictory combined scenario.
## Diagnostics
An Agent Host debug-log bundle reproducing the sequence is available if needed. It contains SDK events, AHP traffic, Agent Host logs, process logs, renderer logs, and the screenshot of the contradictory UI state.
Contributor guide
Assessment
This issue has not been assessed yet.