|
| 1 | +# Background Agents — Design & Roadmap |
| 2 | + |
| 3 | +A maintainer-oriented map of the **background-agent** subsystem in protoCLI: |
| 4 | +what is already wired, what is intentionally deferred, and how we plan to |
| 5 | +close the gap with upstream `qwen-code`. |
| 6 | + |
| 7 | +The goal of this document is not to specify a feature — much of the |
| 8 | +infrastructure already exists. It is to give us a single pass at the |
| 9 | +**shape** of the work so we can sequence the next set of ports without |
| 10 | +re-reading the upstream diff every time. |
| 11 | + |
| 12 | +--- |
| 13 | + |
| 14 | +## 1. What "background agent" means here |
| 15 | + |
| 16 | +Two related but distinct things travel under the same name in our fork: |
| 17 | + |
| 18 | +1. **Background shell tasks** — `run_shell_command` invoked with |
| 19 | + `is_background: true`. The process is detached, output is streamed to a |
| 20 | + file under `<projectTempDir>/<sessionId>/tasks/<taskId>.output`, and a |
| 21 | + `task_id` is returned to the model. These are the kind of tasks the user |
| 22 | + sees with `/bg list` today. |
| 23 | + |
| 24 | +2. **Background subagents** — full `AgentCore` instances running in a |
| 25 | + separate execution context, communicating with the parent through the |
| 26 | + progress event bus. A subagent may itself spawn background shells; the |
| 27 | + two systems compose. |
| 28 | + |
| 29 | +Both share the same lifecycle vocabulary (`running` → `completed` / |
| 30 | +`failed` / `killed`) and surface through the same UI hooks. The split |
| 31 | +matters for porting because upstream's recent work mostly extends path 2 |
| 32 | +(headless / SDK / resume) while leaving path 1 stable. |
| 33 | + |
| 34 | +--- |
| 35 | + |
| 36 | +## 2. Current state in the fork |
| 37 | + |
| 38 | +### Core (ported and live) |
| 39 | + |
| 40 | +| File | LOC | Role | |
| 41 | +| -------------------------------------------------------------------------------------- | -------------- | -------------------------------------------------------------------------------------------------------------- | |
| 42 | +| `packages/core/src/backgroundShells/registry.ts` | 126 | `BackgroundShellRegistry`: tracks long-running shells, pub/sub listeners, `drainPendingNotifications()` | |
| 43 | +| `packages/core/src/backgroundShells/{types,diskOutput,notifications,watcher,index}.ts` | ~340 | Types, disk-tail capture, completion notifications, process-lifecycle watcher | |
| 44 | +| `packages/core/src/utils/backgroundProgressEmitter.ts` | 190 | Singleton typed event bus: `agent_started`, `agent_round`, `agent_tool_call`, `agent_finished`, `agent_failed` | |
| 45 | +| `packages/core/src/agents/background-store.ts` | 75 | `~/.proto/agents/background.json` persistence with 24h prune | |
| 46 | +| `packages/core/src/tools/bg-stop.ts` | 168 | `BgStopTool`: SIGTERM → SIGKILL on shell task PIDs | |
| 47 | +| `packages/core/src/tools/task-stop.ts` | (file present) | `TaskStopTool`: agent-level stop (separate from shell stop) | |
| 48 | +| `packages/core/src/tools/shell.ts` | — | `is_background: true` parameter; spawns detached, captures to disk | |
| 49 | +| `packages/core/src/agents/runtime/agent-headless.ts` | (file present) | Headless `AgentCore` execution path | |
| 50 | + |
| 51 | +### CLI / UI (ported and live) |
| 52 | + |
| 53 | +| File | Role | |
| 54 | +| --------------------------------------------------------- | -------------------------------------------------------------------------------------- | |
| 55 | +| `packages/cli/src/ui/hooks/useBackgroundAgentProgress.ts` | Subscribes to `backgroundProgressEmitter`, exposes `activeAgents[]` and `lastFinished` | |
| 56 | +| `packages/cli/src/ui/commands/bgCommand.ts` | `/bg list` — running + recent shell tasks with status, duration, output path, PID | |
| 57 | +| `packages/cli/src/ui/AppContainer.tsx` | Surfaces `lastFinished?.hitLimit` warnings into the conversation history | |
| 58 | +| `packages/cli/src/ui/components/StatusBar.tsx` | Renders `activeAgents` count | |
| 59 | + |
| 60 | +### What this gives us today |
| 61 | + |
| 62 | +- The model can fire-and-forget shells and look at output files later. |
| 63 | +- The model can stop a runaway shell via `bg_stop`. |
| 64 | +- The user sees a count in the status bar and a one-time warning when an |
| 65 | + agent hits its turn/time budget. |
| 66 | +- Sessions resume cleanly because shell registry state is in-memory |
| 67 | + per-session and the persistent `background-store.json` is best-effort. |
| 68 | + |
| 69 | +### What this does **not** give us yet |
| 70 | + |
| 71 | +- No model-facing way to send a message into a running subagent. |
| 72 | +- No UI for "what is each background agent doing right now" beyond a count. |
| 73 | +- No throttled streaming of subagent output back to the parent. |
| 74 | +- No cross-session resume of a background agent that was alive when the |
| 75 | + session ended. |
| 76 | +- `/tasks` (the upstream-managed pool view) is absent; `/bg list` is our |
| 77 | + thinner stand-in. |
| 78 | + |
| 79 | +--- |
| 80 | + |
| 81 | +## 3. Upstream gap (April–May 2026) |
| 82 | + |
| 83 | +The upstream PRs we have not yet ported, ordered by approximate dependency: |
| 84 | + |
| 85 | +| Upstream PR | Title | What it adds | Dependency | |
| 86 | +| ----------- | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------- | ------------------------------------------------------ | |
| 87 | +| **#3076** | background subagents with headless and SDK support | Headless agent runner + SDK task events | (foundation; partially landed via `agent-headless.ts`) | |
| 88 | +| **#3379** | headless support and SDK task events for background agents | Event surface for SDK consumers | builds on #3076 | |
| 89 | +| **#3471** | model-facing agent control (`task_stop`, `send_message`, per-agent transcript) | New tools the model can call to manage running agents | builds on #3076 | |
| 90 | +| **#3488** | background-agent UI — pill, combined dialog, detail view | TUI surface: pill in status, combined dialog, per-agent detail | builds on #3471 | |
| 91 | +| **#3642** | managed background shell pool with `/tasks` command | Pool view + `/tasks` slash command | independent | |
| 92 | +| **#3684** | event monitor tool with throttled stdout streaming (Phase C) | `event_monitor` tool — stream subagent stdout back to parent at controlled rate | builds on #3471 | |
| 93 | +| **#3687** | wire background shells into the `task_stop` tool | Unifies `bg_stop` + `task_stop` so the model has one stop verb | needs both stops merged conceptually | |
| 94 | +| **#3739** | background agent resume and continuation | Cross-session resume of interrupted agents | builds on #3471, #3684 | |
| 95 | + |
| 96 | +**Skip list** (already-decided exclusions): |
| 97 | + |
| 98 | +- All `vscode-ide-companion` schema fragments — package deleted. |
| 99 | +- `auto-memory` integration points — un-ported subsystem; PRs that touch |
| 100 | + `MemoryDialog` or `isAutoMemPath` need that stripped out. |
| 101 | +- Anything that imports `BackgroundTaskRegistry` (an upstream symbol that |
| 102 | + was never in the diff we picked up). Where upstream uses it, we use our |
| 103 | + `BackgroundShellRegistry`. |
| 104 | + |
| 105 | +--- |
| 106 | + |
| 107 | +## 4. Proposed phasing |
| 108 | + |
| 109 | +The dependency chain suggests three phases. Each phase is a single PR |
| 110 | +unless noted; each is sized to stay under the "path of least resistance" |
| 111 | +bar we have been holding for the rest of this fork's port work. |
| 112 | + |
| 113 | +### Phase A — model can talk to its running agents |
| 114 | + |
| 115 | +Land the upstream agent-control surface so the model has a real grammar |
| 116 | +for managing background work. |
| 117 | + |
| 118 | +- **Port #3471** (model-facing agent control): `send_message` + per-agent |
| 119 | + transcript. Reconcile with our existing `task-stop.ts`. |
| 120 | +- **Port #3687** (unify shells into task_stop): collapse `bg_stop` into |
| 121 | + `task_stop` if it doesn't break our tool registry expectations. If it |
| 122 | + does, keep both and document the split. |
| 123 | +- **Port #3684** (event monitor tool with throttled streaming): adds the |
| 124 | + primary mechanism the parent uses to actually consume subagent output. |
| 125 | + |
| 126 | +Risk: our `BackgroundShellRegistry` and upstream's task-pool shape may |
| 127 | +have diverged. Expect a non-trivial reconciliation in the registry's |
| 128 | +public methods. |
| 129 | + |
| 130 | +Effort estimate: **medium-large**. Two cherry-picks plus a glue PR. |
| 131 | + |
| 132 | +### Phase B — user can see what's happening |
| 133 | + |
| 134 | +Once the model has a control surface, expose it. |
| 135 | + |
| 136 | +- **Port #3488** (UI: pill, combined dialog, detail view). This will |
| 137 | + conflict heavily with our existing StatusBar + AppContainer because we |
| 138 | + have our own pill there for `lastFinished`. Resolve by keeping our hook |
| 139 | + shape and only adopting upstream's components where they don't depend |
| 140 | + on un-ported state. |
| 141 | +- **Port #3642** (`/tasks` command). Decision: replace `/bg list` with |
| 142 | + `/tasks`, or keep both and have `/bg` alias to `/tasks`? |
| 143 | + |
| 144 | +Risk: TUI churn. Snapshot tests will break. Voice / recap state has to |
| 145 | +keep working in the same component tree. |
| 146 | + |
| 147 | +Effort estimate: **medium**. One UI PR plus the slash-command PR. |
| 148 | + |
| 149 | +### Phase C — agents survive session boundaries |
| 150 | + |
| 151 | +The headline upstream feature. |
| 152 | + |
| 153 | +- **Port #3739** (resume / continuation): persist enough agent state on |
| 154 | + disk so a freshly-started session can re-attach to running agents. Our |
| 155 | + `background-store.ts` already persists _that_ an agent ran; this PR |
| 156 | + extends it to the agent's transcript and pending tool calls. |
| 157 | + |
| 158 | +Risk: this touches `chatRecordingService` and `sessionService`. Both |
| 159 | +have local divergence. Expect a careful merge. |
| 160 | + |
| 161 | +Effort estimate: **large**. Likely the biggest single port left. |
| 162 | + |
| 163 | +--- |
| 164 | + |
| 165 | +## 5. Cross-cutting concerns |
| 166 | + |
| 167 | +### Settings |
| 168 | + |
| 169 | +Upstream's bg-agent settings have grown into their own block. We already |
| 170 | +have an `agents.*` section (Arena/Team/Swarm) plus a flat `backgroundModel`. |
| 171 | +Before Phase A lands, decide whether to nest under `agents.background.*` |
| 172 | +or keep flat. **Recommendation:** nest, and migrate `backgroundModel` to |
| 173 | +`agents.background.model` with a back-compat read. |
| 174 | + |
| 175 | +### Naming |
| 176 | + |
| 177 | +We have **two** stop tools: `bg_stop` (shell) and `task_stop` (agent). |
| 178 | +Upstream is collapsing these. Pick a direction now so Phase A doesn't |
| 179 | +have to revisit it. **Recommendation:** keep the split until Phase A's |
| 180 | +`#3687` port forces the unification — premature consolidation rarely |
| 181 | +pays off in this codebase. |
| 182 | + |
| 183 | +### Persistence layout |
| 184 | + |
| 185 | +`~/.proto/agents/background.json` is shared by anything that wants to |
| 186 | +remember an agent existed. If Phase C extends it to full transcripts, we |
| 187 | +should move from a single JSON to a directory-of-files layout to avoid |
| 188 | +rewriting hundreds of KB on every checkpoint. **Recommendation:** keep |
| 189 | +the simple JSON until Phase C makes it actually painful. |
| 190 | + |
| 191 | +### LiteLLM / gateway |
| 192 | + |
| 193 | +All agent control surfaces assume the standard generate-content path. |
| 194 | +Our gateway layer has its own quirks (thinking-tag stripping, max_tokens |
| 195 | +ceilings). Validate Phase A against `protolabs/fast` and `protolabs/smart` |
| 196 | +before merging. **Recommendation:** add a smoke test that runs a |
| 197 | +background subagent with the gateway in CI. |
| 198 | + |
| 199 | +--- |
| 200 | + |
| 201 | +## 6. Open questions for the team |
| 202 | + |
| 203 | +1. **Do we need `/tasks` as a name, or is `/bg` good enough?** Aliasing |
| 204 | + is cheap; choosing the wrong primary name and renaming later is not. |
| 205 | +2. **Should the per-agent detail view be a dialog or a separate route?** |
| 206 | + Upstream picks dialog. Our DialogManager already has 8+ dialogs and |
| 207 | + is starting to feel crowded. |
| 208 | +3. **Cross-session resume scope:** do we attempt to resume _any_ |
| 209 | + interrupted agent, or only those flagged as resumable? The latter is |
| 210 | + safer; the former is what the headline feature looks like. |
| 211 | +4. **SDK surface:** upstream's task-event SDK exposes a public API for |
| 212 | + third-party tools to subscribe. We have no SDK consumers today. Worth |
| 213 | + the maintenance cost to port the surface, or strip it on the way in? |
| 214 | + |
| 215 | +--- |
| 216 | + |
| 217 | +## 7. Out of scope for this document |
| 218 | + |
| 219 | +- Detailed code-level design for any single phase. That belongs in a |
| 220 | + follow-up doc per phase. |
| 221 | +- Performance work on the existing registry. Today's footprint is fine; |
| 222 | + revisit if Phase C's persistence changes that. |
| 223 | +- Anything about Arena, Team, or Swarm. Those are different agent |
| 224 | + systems that live alongside the background path; see |
| 225 | + `sub-agents-design.md`. |
| 226 | + |
| 227 | +--- |
| 228 | + |
| 229 | +## Appendix: file inventory at time of writing |
| 230 | + |
| 231 | +``` |
| 232 | +packages/core/src/ |
| 233 | +├── agents/ |
| 234 | +│ ├── background-store.ts # 75 LOC, persistence |
| 235 | +│ ├── runtime/agent-headless.ts # headless execution |
| 236 | +│ └── runtime/agent-interactive.ts |
| 237 | +├── backgroundShells/ |
| 238 | +│ ├── registry.ts # 126 LOC, central registry |
| 239 | +│ ├── watcher.ts # 109 LOC, lifecycle |
| 240 | +│ ├── diskOutput.ts # 125 LOC, file capture |
| 241 | +│ ├── notifications.ts # 54 LOC, completion |
| 242 | +│ └── types.ts # 44 LOC |
| 243 | +├── tools/ |
| 244 | +│ ├── bg-stop.ts # 168 LOC, shell-level stop |
| 245 | +│ ├── task-stop.ts # agent-level stop |
| 246 | +│ └── shell.ts # is_background: true entry point |
| 247 | +└── utils/ |
| 248 | + └── backgroundProgressEmitter.ts # 190 LOC, event bus |
| 249 | +
|
| 250 | +packages/cli/src/ui/ |
| 251 | +├── commands/bgCommand.ts # 84 LOC, /bg list |
| 252 | +├── hooks/useBackgroundAgentProgress.ts # 127 LOC |
| 253 | +├── components/StatusBar.tsx # active-agent count |
| 254 | +└── AppContainer.tsx # lastFinished hit-limit warnings |
| 255 | +``` |
| 256 | + |
| 257 | +Last reviewed: 2026-05-02 (before Phase A planning). |
0 commit comments