pi-bg-shell
Background shell tasks for the Pi coding agent: start non-blocking commands with bash_bg and get woken with the output when they exit, Claude Code style.
Package details
Install pi-bg-shell from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:pi-bg-shell- Package
pi-bg-shell- Version
0.3.0- Published
- Oct 8, 2026
- Downloads
- 453/mo · 14/wk
- Author
- no_world
- License
- MIT
- Types
- extension
- Size
- 141 KB
- Dependencies
- 0 dependencies · 1 peer
Pi manifest JSON
{
"extensions": [
"./extensions/bg-shell/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
pi-bg-shell
English | 简体中文
Background shell tasks for the Pi coding agent — Claude Code style: start a command, keep working, get the output delivered automatically when it exits.
You: run the test suite, and meanwhile draft the changelog
Agent: bash_bg → Started background task #1 (pid 4242): npm test
Agent: (works on the changelog, answers you, reads files …)
Notify: Background task #1 completed (exit 0, 42.1s): npm test
--- stdout tail (1234/5678 bytes) ---
…
Why
Pi's built-in bash tool blocks the whole agent turn until the command finishes. A two-minute test suite means two minutes of a frozen session. pi-bg-shell gives the agent three tools to fire long-running commands (builds, test suites, dev servers, watches) in the background and get woken up with the output the moment they exit — no polling, no babysitting.
Tools
| Tool | What it does |
|---|---|
bash_bg |
Runs a command with bash -c in the background and returns immediately with a task id. Bounded tail buffers (1 MiB per stream) capture output; anything larger spills the full history to a file. |
bg_status |
Lists all tasks with one-line summaries, or fetches one task's full status and output tails by id. The listing also carries a Global pool section: detached tasks owned by other sessions, visible but not adopted here. |
bg_kill |
Terminates a task by id (default SIGTERM). Kill rights on a pool task belong to its creator or any subscriber — i.e. anyone who can see it in their own registry. |
bg_adopt |
Subscribes this session to a foreign pool task by manifest path (from bg_status's Global pool section): completion notices, pattern wakes, and kill rights follow (ADR-0007). |
Completion is delivered as a bg-shell-notify message via pi.sendMessage({ triggerTurn: true }) — the same wake primitive the official file-trigger example and pi-subagents' completion path use. Completions inside a 100 ms window merge into a single message, so a fan-out of tasks cannot stampede the session. Default wall-clock limit is 10 minutes per task (timeout_sec overrides; 0 disables).
Wakes while a task is still running
Exit is not the only delivery moment anymore (ADR-0005):
on_pattern— a literal substring watched line-by-line (grep semantics). The first matching line wakes the agent with the matched line plus the output tail; the task keeps running.on_pattern_allwakes on every match (rate-limited),on_pattern_stopstops the task right after delivering the match.bg_statusshowsrunning·matched×Nas a sub-state while it runs. For alerts that can recur (watches), preferon_pattern_allon a long-lived task (timeout_sec: 0) over re-spawning after each hit, and echo the marker on state transitions only — a persisting condition would otherwise re-wake at the 10 s floor.report_every_sec— while the task runs, a progress report (elapsed time + output tail) arrives every N seconds (clamped to ≥ 5) without interrupting it.
Agent: bash_bg {command: "./day_runner.sh", timeout_sec: 0,
on_pattern: "ROOTED", report_every_sec: 600}
Notify: Background task #4 still running (600.2s elapsed, report every 600s): ./day_runner.sh …
Notify: Background task #4 pattern match (on_pattern "ROOTED", match #1, running 1412.8s): ./day_runner.sh
--- matched line (stdout) ---
ROOTED device 3 ready
…
Both are delivery-timing controls, not orchestration: what to do next stays with the woken agent in the conversation.
Detached tasks (survive quitting pi)
bash_bg { detach: true } runs the command in its own session (setsid) with output going straight to files — quitting pi does not kill it (rebooting the machine still does). The real exit code is recorded by a wrapper; a manifest under tmpdir()/pi-bg-shell/ turns the root into a machine-wide task pool (ADR-0007): a session resumed with the same session id (pi -c / --resume) re-subscribes its survivors automatically; any other session sees them in bg_status's Global pool section and can adopt with bg_adopt. Deaths that happened while nobody watched are backfilled once by whichever session scans next — merged into one message and annotated unattended — finished while pi was away — so a "run and report back" promise survives any restart. The default timeout is off for detached tasks — pass timeout_sec explicitly if you want one.
You: start the 24h device soak, detached, wake me on ROOTED
Agent: bash_bg {command: "./soak.sh", detach: true, on_pattern: "ROOTED"}
… pi quits, reopened next morning …
Notify: Background task #1 pattern match (on_pattern "ROOTED", match #1, running 9h12m): ./soak.sh
bg_kill signals the detached task's whole process group.
On Windows, bash is typically the WSL relay and inherited Windows file handles cannot cross that boundary — detached tasks there switch to shell-side redirection (( cmd ) >> OUT 2>> ERR with /mnt/<drive>/…-translated paths), so output files and exit codes still land exactly where bg_status and pool scans expect them. The wrapper itself ships as a host-side script file (bash <file>, never -c argv) so the recorded exit code survives the relay's argument re-quoting. On Windows the detached relay is started through a GUI-subsystem wscript launcher — detached + windowsHide alone still pops a console — so spawns are popup-free and the task even survives the hosting terminal being closed. bg_kill on Windows terminates the whole relay tree via taskkill /t /f (there are no POSIX signals across the boundary) and writes a kill-attribution marker so other subscribers' notices say who killed the task. on_pattern watches an owned detached task's output from byte zero — early startup milestones wake the session just like adopted ones (which replay history). The pool is a bulletin board, not private property (ADR-0007): every session sees every manifest, subscription is a per-pid marker file (exclusive-create, so concurrent sessions never race), and artifacts are collected only once a task is terminal, reported, unsubscribed, and its creating process is gone.
User surface
While tasks run, a card-style status widget sits above the input editor and repaints every 500 ms — elapsed times tick, spinners rotate, and each card shows the original command plus the task's current output and volume:
bg · background
⠸ #1 chatty-ticker · running · 1m 12s / 10m
cmd: for i in $(seq 150); do echo …
⎿ ↓ 18.0k · [16:52:31] chatty heartbeat #93 …
⠋ #2 quiet-soak · running · 4m 2s / 10m
cmd: sleep 300
2 running · /bg panel
The header and task labels render plain; chrome (· running ·, cmd:, ⎿) is dim, stats are muted, and commands plus live output lines use the toolOutput color. Every line is truncated to the terminal width. More than six running tasks collapse into … +N more; the widget (and its ticker) removes itself when the last task ends. Running rows append the timeout budget after the elapsed time (1m 12s / 10m) whenever one is armed — timeout_sec: 0 and detached tasks (no default timeout) show none.
All information views open an overlay panel — never dump into the transcript:
| Command | What opens |
|---|---|
/bg |
Overlay panel: task list, ↑↓ select, Enter detail, K kill, r refresh, q/esc close |
/bg <id> |
The panel focused on one task's detail (status, exit code, command, running timeout with countdown, output tails, spill path) |
/bg tail <id> [bytes] |
Same detail view with a larger output tail (default 4096 bytes) |
/bg log <id> |
Same detail view; truncated tasks show their full-output spill path |
/bg kill <id> [signal] |
Terminates a task (default SIGTERM) with a toast ack |
Tab-completion offers subcommands and task ids. Headless modes (RPC/JSON/print) degrade to notify summaries.
Install
pi install git:github.com/No-World/pi-bg-shell
Or for local development:
git clone https://github.com/No-World/pi-bg-shell
pi --extension /path/to/pi-bg-shell/extensions/bg-shell/index.ts
Reloads (/reload) and session switches keep running tasks alive; quitting pi SIGTERMs+SIGKILLs every child and removes spill files.
Configuration
| Env var | Effect |
|---|---|
PI_BG_SHELL_TIMEOUT_SEC |
Default wall-clock limit per task when timeout_sec is omitted. Defaults to 600; set 0 to disable the default. Read once at pi startup — restarting pi applies a new value. |
An explicit timeout_sec on a bash_bg call always wins over the environment default.
How the wake works
bash_bgspawns the child; the tool result returns instantly.- On exit, the registry emits the final snapshot through its
onExithook. - A debounce window (100 ms) batches concurrent completions into one message.
- The notifier calls
pi.sendMessage(..., { triggerTurn: true })— an idle session starts a new agent turn; a busy session gets the message queued by pi. - The agent reads the output tail (and the spill-file path for full output) and continues.
The task registry lives on globalThis (a Symbol.for key), so it survives extension reloads; the entry point rebinds the exit hook on every load so post-reload completions still reach the agent. Design trade-offs are recorded in docs/adrs/0003-background-shell-wake-mechanism.md.
Development
npm install
npm test # node:test suite + mechanical doc gates
npm run typecheck # strict TypeScript check
npm run check:docs # doc gates alone
pi --extension . # exercise interactively
The repository keeps the full engineering harness: ADRs with a mechanical index gate (docs/adrs/README.md), a pitfalls handbook (docs/PITFALLS.md), postmortem loop templates (docs/postmortems/README.md), a ubiquitous-language glossary (CONTEXT.md), and a CI lane that mirrors the local gate on every PR (.github/workflows/pr-checks.yml). Contribution rules live in AGENTS.md.