← Back to PRs

#13167: feat(agents): dispatch Claude Code CLI runs as persistent, resumable subagents

by gyant open 2026-02-10 05:24 View on GitHub →
app: web-ui gateway commands agents stale
## 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