#13167: feat(agents): dispatch Claude Code CLI runs as persistent, resumable subagents
app: web-ui
gateway
commands
agents
stale
Cluster:
Session Management and Fixes
## feat(agents): dispatch Claude Code CLI runs as persistent, resumable subagents
### Summary
This PR enables a workflow for dispatching Claude Code CLI processes as kept subagents that run in specific project directories, pick up MCP tools via `.mcp.json`, and support iterative follow-up — all driven by the existing lifecycle/announce pipeline without cron or polling.
Spawn a subagent into a git worktree → it discovers project-specific MCP servers → it completes and announces its result → you follow up to refine → the cycle continues with full prior context.
### Motivation
The built-in subagent system and CLI backends (`claude-cli`) already support spawning headless Claude Code runs. With `cleanup: "keep"`, sessions persist and can be resumed. But two gaps prevented this from being useful for multi-turn coding workflows:
1. **No way to follow up.** The announce message didn't include the child session key, the subagent didn't expect follow-ups, and async sends silently dropped results. The main agent had no structured path to iterate on a subagent's work.
2. **No way to control where the process runs.** The CLI process always launched in the agent's configured workspace directory (`~/.openclaw/workspace` by default). If your project has a `.mcp.json` that provides MCP tools, the subagent never sees them — and telling it to `cd` to your project breaks MCP discovery since servers are initialized relative to the process `cwd` at startup.
### Changes
#### Working directory separation (`cwd`)
The `sessions_spawn` tool now accepts an optional `cwd` parameter that controls where the CLI/embedded process actually runs, independently from the agent's workspace directory.
- **`workspaceDir`** — the agent's home for memory, bootstrap files, skills, and state. Resolved from agent config as before.
- **`cwd`** — where the process runs. Defaults to `workspaceDir` when not specified. When set, only affects `spawn()` in the CLI runner and `process.chdir()` / `createAgentSession({ cwd })` in the embedded runner.
Nothing is written into the `cwd` directory by the agent framework — no `AGENTS.md`, `SOUL.md`, or other scaffolding. The typical workflow:
1. Create a git worktree for the task
2. Spawn a subagent with `cwd` pointing at the worktree
3. The Claude CLI process starts in the worktree, discovers `.mcp.json`, and gets project-specific MCP tools
4. Follow up as needed, clean up the worktree when done
**Files:** `sessions-spawn-tool.ts` (schema + passthrough), `agent.ts` (protocol schema), `server-methods/agent.ts` (handler), `agent/types.ts` (opts), `agent.ts` (command), `cli-runner.ts` (spawn), `pi-embedded-runner/run.ts` + `run/attempt.ts` + `run/params.ts` + `run/types.ts` (embedded runner)
#### Follow-up messaging to kept subagent sessions
When a subagent is spawned with `cleanup: "keep"`, the main agent can now send follow-up messages that build on the subagent's full prior context. Three things were missing:
**Announce message includes follow-up key** (`subagent-announce.ts`)
When `cleanup === "keep"`, the announce trigger now appends the child session key and a hint that `sessions_send` can continue the conversation.
**Subagent system prompt is context-aware** (`subagent-announce.ts`, `sessions-spawn-tool.ts`)
`buildSubagentSystemPrompt` gains an optional `resumable` parameter. When the spawn tool passes `resumable: true`, the subagent prompt swaps "Be ephemeral" for "Resumable — your session persists after completion. You may receive follow-up messages."
**Async follow-ups register for announce delivery** (`sessions-send-tool.ts`)
When `sessions_send` dispatches async (`timeoutSeconds: 0`) to a subagent session key, it now calls `registerSubagentRun` — the same registration that `sessions_spawn` uses. This wires follow-ups into the existing lifecycle → announce pipeline so results are delivered back automatically.
#### CLI output available for announce flows (`agent.ts`, `chat.ts`)
CLI-backed runs don't write to the gateway chat store, so `readLatestAssistantReply` returned nothing when the announce flow tried to read the subagent's output. Now, CLI output is injected via `chat.inject` before emitting the lifecycle event. The handler accepts `createIfMissing: true` so transcripts can be bootstrapped on first write.
### Design rationale
The `sessions_send` follow-up path reuses the full subagent lifecycle pipeline (`registerSubagentRun` → lifecycle listener → `runSubagentAnnounceFlow` → `finalizeSubagentCleanup`) rather than building a parallel delivery mechanism. Each follow-up generates a unique `runId`, so multiple concurrent follow-ups to different subagents are tracked independently.
The `cwd` / `workspaceDir` separation is intentionally minimal — `cwd` only affects the process working directory, not skills resolution, bootstrap file loading, or any other agent state. This keeps the security model intact: the operator controls the workspace via config, while `cwd` lets the orchestrating agent point tasks at specific project directories.
### Testing
```bash
pnpm build && pnpm check && pnpm test
```
Type-check clean, formatter clean, all 252 tests pass (41 test files).
**Verified manually:**
- `sessions_spawn` with `cleanup: "keep"` — announce message includes the follow-up session key
- `sessions_send` to the child session key — subagent resumes with full prior context
- `sessions_spawn` with `cwd` pointing at a project with `.mcp.json` — CLI process discovers MCP servers
- Spawning a *new* subagent and asking "What's the last thing I asked you?" — correctly reports no history, confirming session isolation
- Async `sessions_send` results announced back automatically via the lifecycle flow
### Sign-Off
- **AI-assisted**: Yes — developed collaboratively with Claude (Opus 4.6)
- **Testing**: Type-check clean, all existing tests pass, manual verification confirmed above
Most Similar PRs
#19136: feat(claude-code): implement spawn mode for Claude Code sub-agents
by botverse · 2026-02-17
82.3%
#9049: fix: prevent subagent stuck loops and ensure user feedback
by maxtongwang · 2026-02-04
76.0%
#20661: feat: agent lifecycle & parenting primitives
by brancante · 2026-02-19
73.6%
#16247: feat(agents): declarative agent definitions for sessions_spawn
by zerone0x · 2026-02-14
73.5%
#20072: feat(sessions_spawn): add sessionKey param to reuse sub-agent sessions
by Be1Human · 2026-02-18
72.7%
#11788: feat: inter-agent communication via CLI scripts
by jingkang0822 · 2026-02-08
72.4%
#19316: feat(agents): add agentic engineering workflows and /workflow command
by ranausmanai · 2026-02-17
71.7%
#13303: feat(subagents): replace silent boolean with announce enum ('user'|...
by ivalsaraj · 2026-02-10
71.6%
#13990: feat: add subagent_progress tool for sub-agent progress reporting
by caprihan · 2026-02-11
71.1%
#23166: fix(agents): restore subagent announce chain from #22223
by tyler6204 · 2026-02-22
70.8%