Pi Background Processes
Add a background-process extension to pi whose processes can wake the agent, survive session replacement, and never outlive pi.
Background processes for pi: start them, page their logs, let them wake the agent
Where this comes from
Our team runs pi as the coding agent for a monorepo where almost every task needs a long-lived process on the side: a dev server, a test watcher, a local API, or a build that takes minutes. Today the built-in bash tool blocks until the command exits. When the model works around that with nohup ... &, the session either hangs or leaves daemons behind after pi exits. Just as often the model starts a dev server with plain bash, the server prints its banner and goes quiet, and the whole session sits there waiting. We want a proper background-process extension, and we want a process to be able to wake the agent up instead of the agent polling.
What we are asking for
A pi extension at packages/coding-agent/examples/extensions/background-processes/index.ts in this checkout of pi (@earendil-works/pi-coding-agent 0.85.1, Node 22). The file must export default function (pi: ExtensionAPI) and use only the public extension API; do not modify pi core. Keep the layout conventions of the neighbouring examples so it loads with pi -e or through DefaultResourceLoader. Ship a short README.md next to it describing the tools and the wake rules, and a unit test file under packages/coding-agent/test/ that runs offline with npm test -w packages/coding-agent. The existing coding-agent suite must keep passing unchanged; do not edit existing test files. Leave pi's build and test configuration (package manifests, tsconfig*.json, vitest.config.ts and the like) unchanged; the verifier runs the suites with it.
The interface we already agreed on
We have aligned with the harness team on tool names, parameters and result shapes, so please keep these exactly. Every tool returns a JSON object: serialize it as the tool result text and attach the same object as the result details. The one exception is bash on its normal path, described last.
bg_run—{ command: string; cwd?: string; name?: string; wake?: { ready?: string; error?: string }; cleanup?: { command: string; timeoutSec?: number } }. Startscommandthrough the configured shell and comes back at once with the process record (see below), including a freshid. The command and everything it spawns can later be stopped as one unit, and nothing about it may hold up pi's own input, output or exit. A relativecwdresolves against the session's working directory, and the record'scwdis the resolved absolute path.wake.readyandwake.errorare regular expressions matched against each output line.bg_logs—{ id: string; offset?: number; limit?: number }. Returns{ id, offset, lines, total }: a page of the process's combined stdout/stderr lines in order, the line offset actually served, and the total line count so far. A page never exceeds 64 KB of text; iflimitis omitted, serve the newest lines that fit.bg_list—{}. Returns{ processes }, every process record visible to this session, running and finished.bg_kill—{ id: string; timeoutSec?: number }. Stops the process and all of its descendants:SIGTERMfirst, thenSIGKILLaftertimeoutSec(default 5) to whatever is still alive. If the process was started withcleanup, run the cleanup command once nothing of the process tree is left, with the same working directory, bounded bycleanup.timeoutSec(default 30); its output is appended to the same log. Returns the final process record pluscleanup: { ran, exitCode, failed } | null; a failed or timed-out cleanup is reported asfailed: true, never swallowed.bg_watch—{ id: string; ready?: string | null; error?: string | null; exit?: boolean; output?: boolean }. Changes the wake rules of a running process and returns its record.nullremoves a pattern;exit: falsesilences the exit wake;output: falsesilences the output wake of an auto-backgrounded command (below).bash— the session'sbashtool must accept{ command: string; timeout?: number; stalledSec?: number }and behave as follows. The command runs in the foreground exactly as the built-in tool runs it, until one of two things happens first:- It finishes (exits, hits
timeout, or is aborted) before going silent forstalledSecseconds (default 30): return exactly what the built-in tool would have returned: the same result text, the same truncation, the same tool-error texts for a non-zero exit, atimeoutand an abort. Atimeoutshorter thanstalledSectherefore wins. No process record is created and nothing wakes the agent. - It produces no stdout/stderr bytes for
stalledSecseconds while still running: it becomes a managed background process. The tool returns within one second of the threshold, as a normal (not an error) tool result, with the process record as JSON text anddetails, withbackgrounded: trueand thestalledSecthat applied. Everything the command printed before the switch is the head of its log. From then on the process is exactly like one started withbg_run: it is listed, its logs page,bg_killandbg_watchapply, and the lifecycle expectations below hold.
- It finishes (exits, hits
A process record contains at least id, name, command, cwd, pid, state (running | exited | killed), exitCode (number or null), startedAt and endedAt (ISO-8601 strings; endedAt is null while running), logLines (total lines so far), and backgrounded (true only for a command that bash moved to the background).
How a process wakes the agent
The wake is a custom message whose customType is "background-process". Its details must contain { id, name, reasons, exitCode, matchedLine }, where reasons is a non-empty array drawn from "ready", "error", "output", "exit", exitCode is a number or null, and matchedLine is the first output line that triggered the wake or null. The message content is free-form text for the model.
This is what we expect to see when we try it:
- By default only process exit wakes the agent.
readyanderrorwake only when the corresponding pattern was supplied.outputapplies only to a command thatbashmoved to the background: the first complete output line the command prints after the switch wakes the agent, with that line asmatchedLine. readyandoutputwake at most once per process.errorwakes at most once per delivery window.exitwakes exactly once, when the process leaves therunningstate, whether it exited on its own or was killed.bg_killdoes not suppress it: a killed process delivers its single exit wake (withstate: "killed") like any other exit. Nothing wakes the agent for a process after its exit wake has been delivered, and nothing wakes it for a reason thatbg_watchsilenced.- While the agent is busy, the wake is delivered after the current assistant turn has finished executing its tool calls and before the next model request. While the agent is idle, the wake starts a new turn immediately.
- Several reasons that fire for the same process while the agent is busy may be combined into one message whose
reasonslists each of them once, or delivered as separate messages; either way each reason is reported at most once per delivery window. Several processes firing in the same window are reported in the order in which each process first fired. - The wake goes to the session that started the process if it is still the active session in this pi process; otherwise to the current active session.
Lifecycle, the part that bit us before
- Processes belong to the running pi process, not to a session.
/reload,/new,/fork, and tree navigation must not stop them, and any session in the same pi process can list, read, and kill them. - When the pi process exits, normally or on
SIGTERM/SIGINT, every managed process must be stopped with the sameSIGTERM-then-SIGKILLsequence, including children the command spawned itself. No managed process may outlive pi. ASIGTERMorSIGINTdelivered to the pi process, interactive or headless, must still end that process once its managed processes are stopped: pi exits by the signal or with a non-zero status, and never keeps running. - Every process's log and final record must be durable on disk inside the session's storage directory. A later pi process that resumes the same session must be able to
bg_listthe finished records andbg_logstheir output; those records reportstateasexitedorkilledwith the recordedexitCode, neverrunning. bg_logsshows a running process's output as it arrives, and pi's memory must not grow with the volume of output: a process that prints hundreds of megabytes must not grow pi's heap accordingly, andbg_logsmust still page through all of it.