@maheidem/pi-loop
Pi extension: /loop — a generic recurring-prompt loop (Claude Code /loop style) delivered as in-session steer custom messages. Skills (e.g. shepherd) are consumers of /loop, not part of it.
Package details
Install @maheidem/pi-loop from npm and Pi will load the resources declared by the package manifest.
$ pi install npm:@maheidem/pi-loop- Package
@maheidem/pi-loop- Version
0.7.1- Published
- Sep 10, 2026
- Downloads
- 251/mo · 251/wk
- Author
- marcos-heidemann
- License
- MIT
- Types
- extension
- Size
- 49 KB
- Dependencies
- 0 dependencies · 2 peers
Pi manifest JSON
{
"extensions": [
"./index.ts"
]
}Security note
Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.
README
loop — /loop for pi
Run any prompt on a schedule inside the current session. Modeled on
Claude Code's /loop (CronCreate): the interval and the prompt are yours;
the loop just keeps injecting the prompt into this conversation every X.
The loop knows nothing about what the prompt says. It is a generic
mechanism. Specializations — like the bundled shepherd skill
(skills/shepherd/) — are consumers of /loop, not part of it.
Usage
/loop # interactive dashboard
/loop 5m check the deploy and tell me what happened
/loop 30s run the test suite and fix failures # fast loop (min 15s)
/loop 2h <prompt> # slow loop
/loop status # show active loop
/loop stop # cancel
The bare command opens a responsive dashboard in TUI mode. It shows the current schedule, live countdown, delivery mode, prompt preview, and safe start/edit/stop actions. In print/RPC/JSON modes, the bare command reports status instead. The nested forms remain the stable scripting interface and are taught by argument autocomplete.
Interval formats: 30s, 5m, 2h, 1d (minimum 15s). Omit the interval
for the default 10m. A trailing every 5m clause also works:
/loop check CI every 5m.
Each tick arrives as a loop-tick custom message — the prompt verbatim,
with a one-line provenance header so the model knows it is a scheduled
message, not a fresh user request — rendered in the TUI as a collapsible
card (↻ loop tick #7 · check the deploy…):
[loop tick #7 · scheduled by /loop, not typed by the user]
check the deploy and tell me what happened
How it works
One mechanism: an in-session timer (index.ts). Each tick is a single
pi.sendMessage({ customType: "loop-tick", content, display: true, details }, { deliverAs: "steer", triggerTurn: true }) call — steer
injects into a busy agent and wakes an idle one in the same call, so there
is no idle check to race. Catch-up: on agent_settled, if a tick's fire
time passed while the agent was busy, it fires immediately (at most one
catch-up tick, no backlog). The timer dies with the pi process, the same
lifetime a Claude Code in-session cron has. (An earlier version also
spawned an external RPC runner; it was removed — old state entries
carrying a runnerPid are still read, the field is ignored.)
State & resume
Loop state persists as a loop custom entry in the session JSONL
(pi.appendEntry). On /resume, session_start re-arms the timer — CC's
"restored on --resume" semantics. /loop stop appends a stopped: true
entry. (Entries written by earlier versions under the old entry type, or
carrying a legacy runnerPid, are still read.)
Safety & limits
- One active loop per session (
/loopwhile armed → stop first). - Session-scoped by lifetime (delivery is a single in-process call — no child processes, nothing to orphan). No wall-clock expiry.
The shepherd skill (a consumer of /loop)
The bundled skills/shepherd/ skill (installed at
~/.pi/agent/skills/shepherd) is the pi port of the Claude Code shepherd
plugin: it pins /loop to a fixed 10-minute cadence with the
shepherd-role prompt (watch, act, unblock, understand problems). It's a
separate thing from the loop — the loop is generic; the skill just builds
a specific prompt and hands it to /loop.
/skill:shepherd <goal to shepherd>
Differences vs Claude Code /loop
| Claude Code | pi /loop | |
|---|---|---|
| Scheduler | built-in CronCreate (in-process, session-scoped) |
in-session timer, single steer delivery |
| Survives model stall | yes (cron fires between turns) | yes (steer injects into a busy turn) |
| Survives restart | no (restored on --resume) |
no (re-armed on /resume via session entry) |
| Interval | cron granularity, 1m–7d | any ≥15s, no expiry |
| Dynamic interval | yes (model picks delay) | no — fixed interval only |
| Jitter | yes (deterministic offset) | no |
Development
cd custom-extensions/loop
npm install
npm run typecheck
npm test # domain + dashboard contracts + real pi E2E (~30s)
Install as a pi package (~/.pi/agent/settings.json → packages):
"/Users/maheidem/Documents/dev/pi-coder-management/custom-extensions/loop"
then /reload in pi. The skill is in skills/shepherd/SKILL.md (source of
truth) and installed to ~/.pi/agent/skills/shepherd/ (copy after edits).
Files
index.ts— the extension:/loopcommands, timer, steer delivery, catch-up, tick rendererstate.ts— pure helpers: interval parsing, tick provenance line, stateui/loop-panel.ts— Pi-free dashboard view modelui/settings-panel.ts— vendored workbench panel interaction primitiveskills/shepherd/SKILL.md— the shepherd skill (a consumer of /loop)