@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

extension

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>.stderr and exposed via monitor_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

  1. Always add grep --line-buffered (or stdbuf -oL) in pipes. Without it, pipe buffering silently delays events by minutes.
  2. Append || true after network calls in poll loops so one transient failure does not kill the monitor.
  3. 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_call hook blocks the monitor start tool. monitor_list / monitor_stop / monitor_read_stderr remain 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-monitor to 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-monitor if 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 /loop extension.

Inspired by the background-monitor tool in Claude Code.