@bramburn/pi-loop
A pi extension for cron/event-based agent re-wake loops and background process monitoring.
Package details
Install @bramburn/pi-loop from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@bramburn/pi-loop- Package
@bramburn/pi-loop- Version
2.0.0- Published
- Aug 9, 2026
- Downloads
- 618/mo · 327/wk
- Author
- bramburn
- License
- MIT
- Types
- extension
- Size
- 1.3 MB
- Dependencies
- 2 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./src/index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
Install
pi install npm:@bramburn/pi-loop
Quick start
LoopCreate trigger="5m" prompt="Check if the build passed"
LoopCreate trigger="tool_execution_start" prompt="Log the tool being used" triggerType="event"
LoopList
LoopDelete id="1"
Widget (v2.0)
pi-loop renders an above-editor widget showing every loop, monitor, and task at a glance:
pi-loop · 3 loops · 1 monitor · 3 tasks
├─ * #1 [active] check deploy status (cron: */5 * * * *) → firing (2s ago)
├─ * #2 [active] tail logs (event: tool_execution_start)
├─ * #3 [active] weekly report (cron: 0 9 * * 1 · auto-task)
├─ > #5 [running] npm test --watch (42 lines, 3m)
└─ 3 tasks: active: wire validator into tests
Render snapshots (text-based, generated from renderWidgetLines):
docs/screenshots/widget-default-80-width-80.txt— 80-column terminaldocs/screenshots/widget-wide-120-width-120.txt— 120-column terminaldocs/screenshots/widget-narrow-50-width-50.txt— width-safety net (clamped to 50 cols)
When a loop fires, the row shows → firing (Ns ago) for 5 seconds, refreshing every second. Press Ctrl+Shift+L for a scrollable loop list overlay. Press Escape during a long-running fire to skip or cancel.
Commands
/loop [interval] [prompt] — interactive loop creation.
/loop # menu
/loop 5m check the deploy # 5-minute cron loop
/loop-resume <id> — re-arm a stored loop by ID and re-add it to the trigger system. Use this after a session/process restart when a stored event/hybrid loop's trigger subscription was lost. Idempotent: re-arming an already-active loop just refreshes the trigger.
/loop-settings — open the unified settings TUI editor (loopScope, taskScope, debug, autoClear, sortOrder, hiddenAt, maxVisible, showAll, taskThreshold).
/loop-resume 5 # re-arm loop #5 by id
/loop-resume # open a single-select picker of all stored loops
/loop-resume (no args) — open a simple picker listing every stored loop as * #N [status] prompt (trigger). Pick a row to re-arm it, or < Back to exit without changing anything. Each terminal reads and writes its own .pi/loops/bindings-<sessionId>.json so parallel sessions do not interfere.
Tools
| Tool | What it does |
|---|---|
LoopCreate |
Schedule a prompt on a cron timer, a pi event, or both with debounce |
LoopUpdate |
Update progress for a dynamic goal loop (self-paced mode) |
LoopList |
Show active loops with IDs, triggers, and next-fire times |
LoopDelete |
Delete or pause a loop |
MonitorCreate |
(retired — see Retired tools) |
MonitorList |
(retired) |
MonitorStop |
(retired) |
TaskCreate |
(retired — see Retired tools) |
TaskList |
(retired) |
TaskUpdate |
(retired) |
TaskDelete |
(retired) |
Trigger types: cron (5m, 1h, 0 9 * * 1-5), event (any pi event source), or hybrid (both, debounced).
Tasks
With pi-tasks
Works with @tintinweb/pi-tasks. Pass autoTask: true on LoopCreate and each loop fire auto-creates a tracked task. Detection happens over pi's event bus — no manual wiring.
Without pi-tasks
If pi-tasks does not respond during startup detection, pi-loop registers a native fallback task system for the session:
- session- or project-scoped task files under
.pi/tasks/persettings.taskScope TaskCreate,TaskList,TaskUpdate,TaskDelete/tasksinteractive viewer- above-editor widget task tracking (replaces the v1.x status-line task summary)
This fallback is session-sticky: pi-loop decides once at startup whether pi-tasks or native tasks own task management for that session.
Status line
pi-loop keeps a compact persistent status line in the TUI.
When active work exists, it shows a single focus-friendly line such as:
1 loop · 1 monitor
2 tasks | active: Fix deploy polling
1 loop · 2 monitors · 3 tasks | next: Update README
When no loops, monitors, or native tasks are active, the status line clears completely.
Only task counts and the single active/next task are shown there so attention stays on what is currently happening. Use LoopList, MonitorList, and /tasks for detail.
Configuration
All configuration lives in .pi/pi-loop-settings.json — see userflow/settings-v2.md for the full schema and migration guide. The v2.0 release removes the v1.x PI_LOOP_* environment variables (see CHANGELOG.md for the clean break). To change loopScope, taskScope, debug logging, auto-clear behaviour, sort order, or backlog threshold, run /loop-settings (no environment variables needed).
| Sentry | Effect | Default |
|---|---|---|
SENTRY_DSN |
Enable anonymous crash + log reporting (Sentry). Set to your project DSN to opt in. | unset → telemetry disabled |
SENTRY_ENVIRONMENT |
Environment tag for events (e.g. production, development) |
development |
SENTRY_TRACES_SAMPLE_RATE |
Performance transaction sample rate (0.0–1.0) |
0.1 |
SENTRY_CAPTURE_LOGS |
Pipe debug() output into Sentry logs |
true |
SENTRY_DEBUG |
Verbose Sentry SDK debug logging to stderr | false |
In project scope (default), loop and task files are saved to .pi/loops/loops.json and .pi/tasks/tasks.json so they survive across chat sessions and process restarts in the same repository — mirroring pi-goal-x's .pi/goals/ pattern. In memory scope nothing persists to disk.
Recommended scope policy
loopScope: project is the default and best balance for normal use.
projectis the default: loops and tasks persist across sessions and process restarts in the same repo, so a 5m cron loop survives closing and reopening pi.sessionis best when you want each pi session isolated (e.g. concurrent worktrees, throwaway explorations). Loops disappear when the session ID changes.memoryis best for disposable scratch work, tests, or situations where you explicitly do not want any persisted loop/task state.
Re-arming loops after a restart
Cron loops re-arm themselves automatically only if they are bound to this session (see Per-Session Bindings below). Event/hybrid loops do not auto-re-arm their trigger subscriptions — use /loop-resume <id> (programmatic equivalent: LoopDelete({id, action: "resume"})) to re-bind them.
Per-session bindings (multi-terminal parallelism)
If you run two or three pi terminals in the same repo and want each one to fire a different subset of loops, use the bindings mechanism:
- Each terminal has its own
.pi/loops/bindings-<sessionId>.jsonfile listing the loop IDs it has chosen to arm. - A fresh session (no bindings file yet) starts with zero loops armed (strict isolation). Run
/loop-resume <id>to bind loops for this terminal. - Terminal A binding loop #5 does not cause Terminal B to fire #5, because each session reads only its own bindings file and its trigger subscriptions are process-local.
This is a deliberate behavior change from previous versions, where every session armed every active loop on start.
Crash analytics (opt-in)
pi-loop integrates with Sentry for anonymous crash analytics and structured log capture. Telemetry is strictly opt-in — leaving SENTRY_DSN unset (the default) makes every Sentry callsite a no-op.
To enable reporting:
- Apply for Sentry for Open Source and create a project for
@bramburn/pi-loop. Once approved, you'll receive a DSN likehttps://publickey@o1234567.ingest.us.sentry.io/1234567. - Set
SENTRY_DSNin your shell environment (or add it to.env— see.env.example):export SENTRY_DSN=https://publickey@o1234567.ingest.us.sentry.io/1234567 - Restart pi. The extension will initialize Sentry on load and start capturing:
- Unhandled exceptions and unhandled promise rejections
- Tool errors via the
wrapToolExecutewrapper around everypi.registerToolcall - Breadcrumbs on
session_switch,loop_fire, and tool entry - Optional
debug()log output (controlled bySENTRY_CAPTURE_LOGS)
All events are passed through a beforeSend hook that strips:
- Absolute filesystem paths (Windows + Unix user dirs)
process.env.*references- Sentry DSN literals
- Values under sensitive keys (
prompt,message,text,body,content,description) - Stack-frame
filename/abs_pathfields
The DSN itself is a public client identifier (it's shipped to browsers in Sentry's own SDK) — it only grants permission to send events, not to read them. Even so, the DSN is never committed to this repository. See .env.example for the full telemetry env-var matrix.
For the wider design rationale see docs/SENTRY.md.
Retired tools
The Loop family is now active (see Status and Quick start). The following tools and commands remain present in source but not registered in src/index.ts to keep the extension footprint minimal:
| File | What's in it |
|---|---|
src/tools/monitor-tools.ts |
MonitorCreate, MonitorList, MonitorStop, MonitorDelete |
src/tools/native-task-tools.ts |
TaskCreate, TaskList, TaskGet, TaskClaim, TaskHeartbeat, TaskUpdate, TaskDelete, TaskPrune |
src/tools/workflow-tools.ts |
Workflow step-execution tools |
src/commands/monitors-command.ts |
/monitors command |
src/commands/tasks-command.ts |
/tasks command |
The infrastructure that would back these tools is still in place: src/monitor-manager.ts, src/task-store.ts, src/runtime/task-*.ts coordinators. To re-enable any of them, add the matching register*() call to src/index.ts and provide the runtime stubs that the registered tools depend on.
Limits
25 active loops, 25 running monitors. Recurring loops expire after 7 days.
Development
npm run typecheck
npm run lint
npm test
npm run build
See CONTRIBUTING.md.
License
MIT — LICENSE