pi-process-monitor2.0.9
Non-blocking background watcher for pi: start a process, SSH poll, or log tail and get pinged in-session on milestones, failure, or exit. Claude Code's Monitor tool, with conditional delivery.
Untested on omp
This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.
Reports 0
No reports yet.
Readme
pi-process-monitor
Non-blocking, crash-safe background observation for Pi. Start one owned process, poll an independently owned remote job, or tail a file. Matching milestones and failures ping the session without blocking or flooding context.

Notification contract
Notices use official Pi TUI components through a bundled shared module. They render without loading pi-fornace or modifying Pi. New notices use a package-owned entry type; historical Fornace notices remain readable. extensions/shared/fornace-messages.ts is generated from the source identified in SOURCE.json, never edited independently.
Safety model
- Observe, do not schedule: spawn mode owns a local workload; poll mode runs only fast read-only probes.
- Stable identity: a logical watcher keeps one UUID through restart and exposes a short handle for commands.
- One local owner: atomic leases prevent two Pi processes from running the same logical watcher.
- Conservative recovery: files and classified remote probes auto-resume; local shell polls require confirmation.
- Bounded execution: poll ticks never overlap and have timeout, output caps, exponential backoff, jitter, failure suspension, and owned process-group cleanup.
- Inspectable: status/inspect report owner epoch, PID/PGID, command hash, process start, exit, signals, and truncation receipts.
- Crash-loop fuse: repeated abnormal starts quarantine local recovery instead of replaying it.
Long-running retryable computation still belongs in Restate, Trigger.dev, CI, or another durable workflow engine. Monitor observes those jobs; it does not replace them.
Install
pi install npm:pi-process-monitor@2
# or project-local
pi install npm:pi-process-monitor@2 -l
This release is a major because local poll recovery now fails closed. See migration.
Modes
The monitor tool takes one required, discriminated source object. source.type
is authoritative, so strict providers can safely emit null for irrelevant
source fields without creating source conflicts.
| Mode | source.type |
Required source fields | Default recovery | Purpose |
|---|---|---|---|---|
| spawn | spawn |
command |
never |
One extension-owned local process tree |
| poll | poll |
command, optional intervalSeconds |
remote safe-auto; local confirm |
Read-only observation of independent work |
| file tail | tail |
path |
safe-auto |
Appended log lines |
| process | process |
processBy plus pidFile or match |
confirm |
Structured local process observation |
| file | file |
path, optional tailLines |
safe-auto |
Structured file observation |
| SSH | ssh |
host, command |
safe-auto |
Structured remote observation |
| HTTP | http |
url, optional method |
safe-auto |
Structured endpoint observation |
Source examples:
{ "source": { "type": "spawn", "command": "npm test" }, "options": null }
{ "source": { "type": "poll", "command": "gh run view 123", "intervalSeconds": 15 }, "options": null }
{ "source": { "type": "tail", "path": "/tmp/job.log" }, "options": null }
{ "source": { "type": "process", "processBy": "pidFile", "pidFile": "/tmp/job.pid", "intervalSeconds": 10 }, "options": null }
{ "source": { "type": "file", "path": "/tmp/job.log", "tailLines": 20 }, "options": null }
{ "source": { "type": "ssh", "host": "h100", "command": "tail -n5 train.log", "intervalSeconds": 30 }, "options": null }
{ "source": { "type": "http", "url": "https://ci.example/run/42", "method": "GET", "intervalSeconds": 30 }, "options": null }
Minimal objects are accepted and normalized before validation. OpenAI strict
function calling may materialize every declared source field; in that form,
set unrelated fields to null. The runtime always follows source.type and
reports any non-null unrelated fields it ignored.
Start or reuse a watcher
{
"source": {
"type": "ssh",
"host": "h100",
"command": "tail -n5 /root/train.log; echo ALIVE=$(pgrep -fc axolotl)",
"intervalSeconds": 30
},
"options": {
"label": "h100-qlora",
"notifyOn": ["adapter.*saved", "error|oom|killed|traceback", "ALIVE=0"],
"expiresAt": "2026-08-03T00:00:00Z",
"reuse": "return-existing"
}
}
The result explicitly says created, reused, replaced, or quarantined.
Important parameters
source.type: explicit mode discriminator. It prevents strict schema normalizers from turning optional alternatives into conflicting sources.source.intervalSeconds: cadence forpoll,process,ssh, andhttp. Aspawnsource always runs once; cadence fields generated for it are ignored.source.processBy: selectspidFileormatch, so a strict provider cannot fabricate both process identities.options.recoveryPolicy:never,confirm, orsafe-auto.options.reuse:return-existing(default),replace, orparallel.options.reuseKey: explicit logical purpose; required for intentional parallel local polls.options.expiresAt: absolute ISO-8601 lifetime.timeoutSecondsremains as compatibility input and converts once toexpiresAt.options.pollTimeoutSeconds: per-tick deadline, shorter than the interval.options.maxConsecutiveFailures,options.backoffMaxSeconds: bounded retry behavior.options.safetyClass:auto,observer, orunsafe-shell.observeris an explicit acknowledgement, not a sandbox.options.notifyOn,options.heartbeatMinutes,options.coalesceSeconds,options.maxLines, andoptions.cwdretain their previous meanings.
Raw local shell polls that contain workload executables/verbs, redirection, or backgrounding are quarantined. Prefer a structured probe. Never put training, conversion, build, test, package installation, or “start-if-missing” logic in a poll command.
Tools
| Tool | Purpose |
|---|---|
monitor |
create/reuse/replace/quarantine a watcher |
monitor_status |
logical lifecycle, owner, recovery, expiry, tick and failure summary |
monitor_inspect { id } |
full lease and process receipt |
monitor_kill { id } |
stop one watcher and only its owned process group |
monitor_recover |
list/approve/reject quarantined watchers |
monitor_gc |
dry-run/apply checkpoint and external lease cleanup |
monitor_kill_all { confirm: true } |
current-session owned groups only |
All seven tools register inside the fornace-monitor namespace as optional
tools. A session carries one family line and their names; the full declaration
and agent guidance appear once a load (native tool_search, codemode
searchTools(), or tool_load) names them. Explicit selection such as
--tools monitor declares the tool directly.
Commands
/monitor npm run dev
/monitor --poll --every 30 -- ssh h100 'tail -n5 train.log'
/monitor --file /tmp/train.log
/monitors
/monitor-kill <TAB>
/monitor-recover <id> approve|reject
/monitor-gc --apply
Recovery and persistence
Version 2 state is reduced only from Pi's active branch. Recovery retains the logical ID and appends a claim, never a synthetic creation. Extension reload transfers the same process-local resources to the fresh extension API for the exact native session manager. Child PIDs, leases, expiry and callbacks continue without replaying a spawn command. State and exit notifications produced during the reload gap are delivered through the new API. Quit and session replacement still stop owned resources and record the shutdown reason. A host restart cannot inherit live spawn resources: recoveryPolicy: never remains non-replayable. Checkpoints bound logical history; external GC archives corrupt/orphan lease receipts and removes empty state directories. It never kills an unverified PID or unrelated process.
Startup emits one summary rather than one notification per historical record:
monitor recovery: 2 resumed, 1 reused, 3 expired, 2 quarantined, 4 stale records compacted
Agent rules
- Call
monitor_statusor rely on exact reuse before creation. - Choose one explicit
source.type; never infer spawn versus poll from an optional field. - Use
spawnfor local work andpollonly for independent durable work. - Prefer PID files, workflow/run IDs, exact paths, remote job IDs, and structured source types.
- Give temporary observers an absolute
options.expiresAt. - A timeout means diagnose; do not launch an identical watcher or blocking retry.
- After abnormal restarts, inspect quarantined watchers before approval.
Development
npm install
npm run validate
Validation runs TypeScript checks, deterministic state/lease/process/poll/incident tests, extension load smoke, and package dry-run. Every source file is kept at or below 400 lines.
Grounding and migration receipts:
License
MIT © Francesco Frapporti