# `keep_processes` sidecar

> Opt-in mechanism for agents that spawn detached children themselves and need to leave them running past their own exit. The companion to engine-owned [managed-spawn](managed-spawn.md).

## When to use `keep_processes`

| Need | Use this |
|---|---|
| Long-running dev server / emulator the engine should own across restarts | [managed-spawn](managed-spawn.md) |
| Short-lived helper the *same* agent needs alive past exit (e.g. `gradle --daemon` for the next gradle invocation) | `meta.keep_processes` |
| Executable outside the managed-spawn allowlist | `meta.keep_processes` |
| Service with no healthcheck | `meta.keep_processes` |
| One-shot script that exits on its own | Neither |

Both can coexist on the same WI. There's no plan to deprecate `keep_processes`; a future cleanup PR can revisit if managed-spawn fully subsumes its use cases.

## Sidecar contract

Set `meta.keep_processes: true` on the WI; agent writes `agents/<id>/keep-pids.json` before exit:

```jsonc
{
  "pids": [12345],                 // ≤5 entries
  "purpose": "gradle daemon for follow-up test WI",
  "cwd": "D:/repos/my-android-app", // MUST be a real git worktree
  "ports": [8080],                  // advisory, engine doesn't bind
  "expires_at": "2026-04-29T00:00:00Z", // TTL ≤1440 min; default 60
  "written_by": "ripley",
  "wi_id": "W-mpqtg16a0007f5bb"
}
```

`buildKeepProcessesHint` bakes the Windows-correct detached-spawn pattern (`spawn(..., { detached: true, stdio: 'ignore' }).unref()` plus the `start /B` fallback on cmd-only shells) into the agent prompt automatically — agents should not hand-roll the detach call.

## Lifecycle

- **Validation:** sidecar shape + workdir checked in `onAgentClose`. Failure → non-retryable `failure_class: invalid-keep-processes-{workdir,schema}`.
- **Sweep:** [`engine/keep-process-sweep.js`](../engine/keep-process-sweep.js) reaps at boot and every 30 ticks. Dead PIDs are pruned; expired TTL → process killed via `shared.killGracefully` then `killImmediate`.
- **No healthcheck:** unlike managed-spawn, `keep_processes` does not gate WI completion on first-healthy. If the child crashes immediately the agent still succeeds — by design (ad-hoc helper semantics).

## Caps

- **PIDs:** ≤5 per sidecar.
- **TTL:** ≤1440 min (24h). Default 60.
- **Cwd:** must exist and resolve to inside a git worktree (`requireGitWorkdir: true`). Monorepo subdirs walk up to `gitWorktreeMaxParentDepth` (default 6).

See also: [`CLAUDE.md`](../CLAUDE.md) → **Agent Spawn**, [managed-spawn.md](managed-spawn.md).
