@gregjohnso/pi-monitor
Background shell command runner for the pi coding agent. Each stdout line becomes a live TUI event and enters the LLM's next turn as context.
Package details
Install @gregjohnso/pi-monitor from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@gregjohnso/pi-monitor- Package
@gregjohnso/pi-monitor- Version
0.1.0- Published
- Apr 24, 2026
- Downloads
- 71/mo · 71/wk
- Author
- gregjohnso
- License
- MIT
- Types
- extension
- Size
- 42.1 KB
- Dependencies
- 0 dependencies · 3 peers
Pi manifest JSON
{
"extensions": [
"./extensions/monitor"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
Monitor Extension
Spawn a background shell command from inside pi. Each stdout line becomes an event you see live in the TUI and that the LLM sees on your next turn. Silence costs zero tokens.
pi's default stance is "no background bash — use tmux" (README.md line 460).
This extension fills that gap on purpose: --no-monitor flag to disable
at startup, session-scoped lifetime, firehose auto-stop, max-concurrent
cap.
Monitor surfaces events. It does not drive turns. Autonomous
turn-driving on every event belongs in a future /loop extension. See
Event delivery model.
Install
# from npm
pi install npm:@gregjohnso/pi-monitor
# or directly from git
pi install git:github.com/gregjohnso/pi-monitor
Then /reload inside pi, or start a new session.
To develop locally, symlink this repo into your extensions directory:
ln -s /absolute/path/to/pi-monitor/extensions/monitor ~/.pi/agent/extensions/monitor
Tools exposed to the LLM
| Tool | Purpose |
|---|---|
monitor |
Start a background command; stdout lines become events |
monitor_stop |
Kill a running monitor by id |
monitor_list |
Enumerate running monitors with status + activity |
monitor_read_stderr |
Pull the stderr tail for a monitor on demand (no wake) |
monitor parameters
| Param | Type | Default | Meaning |
|---|---|---|---|
description |
string | — | Short label shown with every notification (max 80 chars) |
command |
string | — | Shell command whose stdout lines become events |
timeout_ms |
integer | 300 000 | Auto-kill after N ms. Max 3 600 000 (1 hr). Ignored when persistent. |
persistent |
boolean | false | If true, runs until session end. Stop with monitor_stop. |
Event semantics
- Each stdout line is one event.
- 200 ms coalesce window: lines arriving close together are grouped into a single event so multi-line output from one source stays together.
- Stderr is never streamed. It is piped to
~/.pi/agent/monitor/<id>.stderrand exposed viamonitor_read_stderr. - Per-event truncation at 400 chars with a
…(truncated)suffix. - Firehose auto-stop: a monitor emitting more than 50 lines/sec over 10s is automatically stopped with an explanatory final event so the model can retry with a better filter.
- Session-scoped lifetime: children die when pi exits.
Event delivery model
Events are delivered with deliverAs: "followUp", triggerTurn: false.
They render inline in the transcript (and enter the LLM's context) via the
agent-loop's followUp drain when the current turn ends. A transient toast
also fires immediately via ctx.ui.notify, so events are visible live
during mid-stream tool execution.
A status widget shows "N monitor" / "N monitors" while any are running.
Note: pi's agent-loop (agent-loop.js:127) drains the followUp queue
unconditionally and starts a fresh LLM call on what lands there. For a
noisy monitor this can cascade into extra turns. If that becomes a
problem, suppress emission when the transcript already ends with a
monitor-event. Autonomous per-event turn-driving is scoped out to a
future /loop extension.
Three rules
- Always add
grep --line-buffered(orstdbuf -oL) in pipes. Without it, pipe buffering silently delays events by minutes. - Append
|| trueafter network calls in poll loops so one transient failure does not kill the monitor. - Be selective with stdout. Monitors exceeding 50 lines/sec over 10s are auto-stopped — filter aggressively before piping into the stream.
Example monitors
# Stream filter: tail logs, surface only errors
tail -f /var/log/app.log | grep --line-buffered -E "ERROR|FATAL"
# Poll-and-if: watch a PR for new comments, robust to transient failures
while true; do
gh api "repos/owner/repo/pulls/123/comments" --jq '.[-1].body' || true
sleep 30
done
# Dev server: catch build errors as they appear
npm run dev 2>&1 | grep --line-buffered -E "error|Failed"
User commands
| Command | Effect |
|---|---|
/monitors |
List running monitors with status + age |
/monitor-stop <id|all> |
SIGTERM a monitor (SIGKILL fallback after 2s), or stop all |
/monitor-tail <id> [n] |
Dump last N stderr lines to the TUI (default 200) |
/monitor-clean |
Alias for /monitor-stop all |
Flags
| Flag | Effect |
|---|---|
--no-monitor |
Disable the extension (tools/commands not registered). |
Interop
- Plan-mode: while plan-mode is active, a
tool_callhook blocks themonitorstart tool.monitor_list/monitor_stop/monitor_read_stderrremain available. - Permissions: no per-spawn confirm. The LLM is trusted the same way
as for
bash. Safety rails are structural: session-scoped processes,MAX_CONCURRENT = 8, firehose auto-stop at 50 lines/s over 10s, and--no-monitorto disable the whole extension for a session.
Settings (module constants in index.ts)
BATCH_WINDOW_MS = 200 // coalesce window
PER_EVENT_MAX_CHARS = 400 // per-event truncation cap
DEFAULT_TIMEOUT_MS = 300_000 // 5 min
MAX_TIMEOUT_MS = 3_600_000 // 1 hr
MAX_CONCURRENT = 8
RATE_LIMIT_LINES_PER_SEC = 50
RATE_LIMIT_WINDOW_SEC = 10
STDERR_DIR = ~/.pi/agent/monitor
Running the unit tests
cd ~/.pi/agent/extensions/monitor
npx tsx --test utils.test.ts
vs. bash and tmux
bash has no background mode. For fire-and-forget tasks where you only
want one "done" notification, use bash in a tmux pane. For live event
streaming the LLM can react to on your next turn, use monitor.
Known limitations
- Children do not survive pi restart — by design.
- Does not inherit pi's bash allow/deny settings. Disable the whole
extension for a session with
--no-monitorif needed. - Windows untested; uses
process.env.SHELL || "/bin/sh". - Noisy monitors can trigger cascading short LLM turns via pi's followUp
drain; autonomous turn-driving is scoped to a future
/loopextension.
Inspired by the background-monitor tool in Claude Code.