@gregjohnso/pi-monitor

Background shell command runner for pi. Each stdout line becomes a live TUI event that wakes the agent.

Packages

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.1
Published
Aug 19, 2026
Downloads
225/mo · 34/wk
Author
gregjohnso
License
MIT
Types
extension
Size
49.4 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

Make pi event-driven. Catch errors in real time, watch logs and deploys, poll PRs — and save tokens by reacting instead of polling.

Reverse-engineered port of Claude Code's Monitor tool (announced by Anthropic on 2026-04-09). Same model, pi-native plumbing.

Problem

Background work in pi was blind. You ran something with bash, the turn ended, and you saw a single "done" message later — no visibility into what happened along the way. The alternative was polling: waking the agent every N seconds with a fresh prompt to ask "did anything happen yet?". Five-minute test run, 30-second poll = ten full LLM calls, nine of them wasted.

Quick Win

Tell pi to monitor your dev server. One sentence:

"Monitor npm run dev for errors and tell me the moment one shows up."

pi launches the server, attaches a background filter, and only wakes when something breaks. Zero tokens spent waiting.

The Shift

Time-driven → event-driven. Before monitor, pi checked things at intervals. With monitor, pi watches things and reacts when they happen. Same architectural difference as polling a database every five seconds vs. subscribing to a change stream. One wastes cycles. The other responds instantly.

Monitor works by launching a shell command whose stdout becomes an event stream. Each line of output is a notification that wakes the session. If the command is silent, the agent spends nothing. The moment something matches your filter it pushes into the conversation and pi starts reacting — while the underlying process keeps running.

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 What it controls
description Short label shown in every notification ("errors in deploy.log").
command Shell script whose stdout is the event stream.
timeout_ms Auto-kill after N ms. Default 300 000 (5 min), max 3 600 000 (1 hr).
persistent If true, lives for the whole session. Stop manually with monitor_stop.

The command is where the real work happens. Each line it prints to stdout becomes one notification. Lines arriving within 200 ms of each other batch into a single notification, so multi-line output from one event groups naturally. Stderr goes to a file you can read later but does not trigger events.

Set persistent: true for things that should live as long as your session — dev server watchers, log tailers, PR monitors. For bounded tasks (a test run, a deploy window), let timeout_ms auto-kill the monitor when the window closes.

Two filter shapes

Stream filter — watch continuous output, surface matching lines

# tail logs, surface only errors
tail -f /var/log/app.log | grep --line-buffered -E "ERROR|FATAL"

# dev server: catch build errors as they appear
npm run dev 2>&1 | grep --line-buffered -E "error|Failed"

Poll-and-if — check a source on an interval, emit when something changes

# 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

# print a line only when test results change
last=""
while true; do
  cur="$(make -s test 2>&1 | tail -1)" || true
  if [ "$cur" != "$last" ]; then echo "$cur"; last="$cur"; fi
  sleep 5
done

Both shapes follow the same rule: stdout lines are events; silence means nothing to report. pi keeps working on other things while the monitor runs quietly in the background.

Three rules

  1. Always make pipes line-buffered. grep --line-buffered, awk -W interactive, or wrap with stdbuf -oL. Without it, pipe buffering can delay events by minutes. This is the single most common mistake.

  2. Handle transient failures in poll loops. Append || true after each API call so one network timeout does not kill the monitor. Pair it with a sleep N between iterations so the loop can't trip the firehose auto-stop.

  3. Be selective with stdout. Every line becomes a conversation message and counts against context. Monitors emitting >50 lines/sec over 10 s are auto-stopped. Filter raw logs through a specific grep pattern. Never stream unfiltered output.

Poll-interval tip: use ≥30 s for remote APIs (rate limits apply), 0.5–1 s for local checks.

Token economics

A /loop checking your test suite every 2 minutes over a 10-minute run costs 5 full LLM calls. Each loads context, processes the prompt, and returns a response. Five calls, five charges, four with no useful work to do.

Monitor inverts this. pi watches the test runner's output through a filter. When test #23 fails at minute 4, that failure line pushes directly into the session. pi starts diagnosing the error while tests 24–47 are still running. No wasted calls. No delayed discovery. The savings compound in long workflows — deploy pipelines, overnight builds, multi-hour CI runs.

Use cases

  • Dev server error catching. Monitor your Next.js / Vite / FastAPI dev server and get notified the moment a build error or crash loop appears.
  • Test suite triage. Surface failing tests the instant they fail. pi starts writing fixes while the rest of the suite finishes.
  • Deploy pipeline watching. Follow CI/CD output and get pinged on failures, warnings, or specific deployment stages completing.
  • PR review polling. Watch for new comments, review requests, or status checks on GitHub pull requests.
  • Log monitoring. Tail production or staging logs with a filter for specific patterns. Each matching line becomes an event pi can act on immediately.

monitor vs. fire-and-forget bash

This distinction trips people up. Both run things in the background. The difference is the feedback model.

bash (in tmux, say) monitor
Notifications One — when it's done One per matching stdout line
Visibility during run None Live stream, filtered
Best for "run X and ping me" "tell me the moment X breaks"
Cost when nothing happens Constant (still alive) Zero (no events = no tokens)

bash is fire-and-forget. Good for "run this build and tell me when it's done." Monitor is a live stream. Good for "run this build and tell me the moment something goes wrong." Monitor keeps pi reactive during the process, not just after it.

Three automation layers

Trigger Best for
Hooks Tool events Validation before/after tool calls
Schedulers Time Recurring work on a fixed cadence
Monitor Events Reacting to real-time output

Hooks fire on pi's own actions (before a file edit, after a commit). Schedulers fire on a clock. Monitors fire on external events. The strongest setups combine all three: hooks enforce guardrails, schedulers handle periodic maintenance, monitors provide real-time observability on everything else.

Event semantics

  • Each stdout line is one event.
  • 200 ms coalesce window: lines arriving close together group 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. Keep your filter patterns specific enough that 400 chars is enough signal.
  • Firehose auto-stop: a monitor emitting >50 lines/sec over 10 s is automatically stopped with an explanatory final event so the model can retry with a tighter filter.
  • Session-scoped lifetime: children die when pi exits. By design.

Event delivery model

Events use deliverAs: "followUp", triggerTurn: true. Each event wakes pi. If a turn is active, followUp queues the event until the current work finishes. Otherwise, pi starts a new turn immediately. The event appears in the transcript and enters the LLM's context.

A transient ctx.ui.notify toast displays the event during active tool execution. A status widget shows "N monitor" / "N monitors" while any are running.

User commands

Command Effect
/monitors List running monitors with status + age
/monitor-stop <id|all> SIGTERM a monitor (SIGKILL fallback after 2 s), 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).

Plan-mode interop

While plan-mode is active, a tool_call hook blocks the monitor start tool. monitor_list / monitor_stop / monitor_read_stderr remain available — you can still triage what's already running.

Permissions

There is no per-spawn confirm. The LLM is trusted the same way as for bash. Safety rails are structural:

  • session-scoped processes (children die with pi)
  • MAX_CONCURRENT = 8
  • firehose auto-stop at 50 lines/s over 10 s
  • --no-monitor to disable the entire 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 /path/to/pi-monitor
npm test

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".

Inspired by — and behavior-compatible with — the Monitor tool in Claude Code.

Share your own pi extension by publishing it to npm with the pi-package keyword — pi's package gallery auto-indexes it.