Run long shell commands (test suites, builds, linters) as detached background jobs so your pi agent session stays unblocked and its context stays clean. Output lands on disk — the full log plus a trailing exit marker — so nothing large ever enters the conversation; the command returns immediately. When the job finishes, pi-background-run wakes the live agent session so it proactively reads a condensed digest of the results and continues — no polling, no human intervention.
Built as a pi extension. No
shell runner, no poller, no sidecar files — the extension spawns the job in-process,
detects completion via the child exit event, and calls pi.sendUserMessage to wake
the agent. The log file is self-describing (full output + a trailing
__BGRUN_EXIT__=N marker), so exit codes survive pi restarting.
pi install npm:pi-background-runOr the scoped alias (same code, permanent namespace claim):
pi install npm:@stablekernel/pi-background-runRestart pi after install so the extension loads.
| Tool | Purpose |
|---|---|
bgrun |
Launch a command detached in the background. Optional name gives the job a short human-readable label. Returns started: <job-id> immediately. Wakes the session automatically on completion. |
bgstatus |
Show job status. With an id: any job's state + exit code. Without: this session's running jobs (finished jobs hidden by default — pass includeDone: true or set showCompletedJobs). Jobs from other sessions are only listed when adoptForeignJobs is enabled. |
bgtail |
Print the last N lines of a job's log (default 40), condensed for context: ANSI escapes stripped, repeated lines collapsed, long lines and total size capped. Pass raw: true to skip condensing. |
bgclean |
Remove old job logs. Default scope: this session's jobs only — other sessions' logs are untouched. Pass all: true to sweep the whole shared jobs dir. Retention: cleanupDays config (7 days). Never removes a running job's log. |
Human-facing mirrors of the read/clean tools, usable directly in the TUI
without asking the agent (registered via pi.registerCommand — a separate
registration from the agent tools above, which is why tools alone never show
up as / commands):
| Command | Purpose |
|---|---|
/bgstatus [id] [done] |
One job's status by id, or the session listing (done/all includes finished jobs). |
/bgtail <id> [lines] |
Tail a job's log (condensed, same as the tool). |
/bgclean [days] [all] |
Remove old logs — session-scoped by default; all sweeps every session's. |
/bgrun is deliberately not a command — starting jobs (and reacting to their
wake messages) is the agent's workflow.
bgkill— not implemented; usebashwithkill(job ids end in the child pid) if you ever need to stop a running job.bgwait— not implemented; the wake mechanism makes blocking on a job unnecessary in the normal flow.
agent calls bgrun(command: "make test-short", name: "unit-tests")
→ extension resolves log path: <jobsDir>/<slug>-<ts>-<pid>.log (default ~/.pi-bgrun/jobs/)
→ spawn('sh', ['-c', '<cmd>; ec=$?; printf "\\n__BGRUN_EXIT__=%d\\n" "$ec"; exit $ec'],
{ stdio: ['ignore', logFd, logFd], detached: true }).unref()
→ records job in-memory + appends a bgrun-job entry to the session
→ returns "started: <job-id>"
child 'exit' event fires:
→ extension records exit code, appends a done entry
→ pi.sendUserMessage(wake) when idle (triggers a turn)
or pi.sendUserMessage(wake, { deliverAs: 'followUp' }) when busy
→ ctx.ui.notify(...) — toast for the human
→ ctx.ui.setWidget("bgrun", ...) — updates/clears the live status widget
The child writes the log directly via its own stdout fd (no pipe to pi), so the job
survives pi crashing and the log completes on disk. The trailing
__BGRUN_EXIT__=N marker makes the log self-describing — bgstatus recovers the
exit code even after a restart.
Two-tier read model — the log file stays complete on disk for deep analysis; only bounded digests ever enter the conversation:
- Quick peek:
bgtail <id>— condensed last-40-lines (ANSI stripped, repeats collapsed, ~8KB cap). The wake message itself already carries the exit code and the log's last line, so many turns need no follow-up read at all. - Whole-log analysis:
ctx_execute_fileon the job's log path to extract only failure lines. NevercatorReada full bgrun log.
The jobs dir (default ~/.pi-bgrun/jobs, overridable via jobsDir / PI_BGRUN_DIR)
is shared by every pi session on the machine — that sharing is what enables
cross-session job lookup, session-restart reconstruction, and machine-wide
cleanup. By default each session only tracks its own jobs: the widget and
bgstatus listings show this session's running jobs, and finished jobs are
hidden (ask for them explicitly with bgstatus includeDone: true). Jobs
started by other sessions can still be inspected by id, but they don't clutter
your widget.
Configuration is layered (later wins): defaults ← user config file ← project config file (trusted projects only) ← environment variables.
- User:
~/.pi/agent/pi-bgrun.json - Project:
<project>/.pi/pi-bgrun.json
{
"adoptForeignJobs": false,
"showCompletedJobs": false,
"cleanupDays": 7,
"globalAutoClean": true,
"jobsDir": "/some/other/dir"
}Environment variables (same knobs, handy for one-off overrides):
| Variable | Default | Description |
|---|---|---|
PI_BGRUN_DIR |
~/.pi-bgrun/jobs |
Override where job logs are stored. |
PI_BGRUN_FOREIGN_JOBS |
false |
Adopt other sessions' running jobs into this session's widget and job list. Adopted jobs are polled so they leave the widget when they finish. |
PI_BGRUN_SHOW_COMPLETED |
false |
Include finished jobs in bgstatus listings by default. |
PI_BGRUN_CLEANUP_DAYS |
7 |
Log retention for cleanup sweeps and the bgclean default. |
PI_BGRUN_GLOBAL_AUTO_CLEAN |
true |
Set 0/false to disable the automatic global orphan sweep (see below). |
Cleanup follows the same principle as everything else: one session should not delete another session's artifacts.
- Session-scoped auto-sweep (default) runs at
session_startandsession_shutdownand removes only this session's finished logs older thancleanupDays. Cheap and unthrottled. - Global orphan sweep (default on; opt out with
globalAutoClean: false/PI_BGRUN_GLOBAL_AUTO_CLEAN=0) — also sweeps the whole shared jobs dir at session boundaries, removing finished logs (exit marker, or dead pid) older thancleanupDays. This is what keeps orphans from sessions that crashed or will never be resumed from accumulating: a week-old finished log is garbage under the same retention its owning session would apply itself. Throttled to once percleanupDaysvia a.last-cleanmarker so restart-heavy workflows don't re-sweep on every launch. Running jobs are pid-protected, so live sessions are never affected. - Manual:
bgcleancleans this session's old logs;bgcleanwithall: truesweeps every session's logs immediately (and refreshes the marker). - Running jobs are never swept while their pid is alive.
Early / pre-release. See .pi/wip/pi-port-plan.md in the source tree for the design.