pi-wakeup

schedule_wakeup / cancel_wakeup for the pi coding agent: session-local self-pacing, the pi analog of Claude Code's ScheduleWakeup dynamic mode.

Packages

Package details

extension

Install pi-wakeup from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:pi-wakeup
Package
pi-wakeup
Version
0.3.0
Published
Nov 6, 2026
Downloads
338/mo · 34/wk
Author
schuettc
License
MIT
Types
extension
Size
56.2 KB
Dependencies
0 dependencies · 0 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

pi-wakeup

A pi extension giving a running session two tools to pace itself — the pi analog of Claude Code's ScheduleWakeup in dynamic mode.

Tools

  • schedule_wakeup(delay_seconds, prompt, reason?) — ask to be woken after a delay. When the timer fires, prompt is delivered back into the session: immediately as a steered turn if the session is idle, or queued as a followUp if a turn is already running. delay_seconds is clamped to [60, 3600]. Returns a wakeup id.
  • cancel_wakeup(id?) — cancel one pending wakeup by id, or every pending wakeup when the id is omitted.

Use it to re-check something without a human poking the session — a build that takes a few minutes, a remote queue, a rate-limit window — rather than busy-waiting.

Countdown

While a wakeup is pending, the session shows a live countdown to the soonest one: 17:34 · 12m · <reason> (local fire time, whole minutes left, a truncated reason). Under a minute reads <1m; a missing reason drops that segment; more than one pending appends · +N [6m, 11m], the minutes left on the next two others (… when there are more). It re-renders every 30 s (an unref'd interval) and immediately on schedule / fire / cancel / delivery failure, and clears when nothing is pending or on shutdown.

Where it shows is decided once at session start:

  • tmux bar — if this pane is inside tmux ($TMUX + $TMUX_PANE) and the server's global status-left / status-right already renders @wakeup, the countdown is written to the pane-session user option @wakeup (muster's @muster_inbox pattern) and nothing is drawn in the pi pane. Add this to your tmux status-right to render it:

    set -g status-right '#{?@wakeup,⏰ #{@wakeup}  ,}'
  • above the editor — otherwise (no tmux, or a config that doesn't render @wakeup), one line via ctx.ui.setWidget("wakeup", …) above the pi editor.

Every tmux / UI call is best-effort: a failure in a timer callback is caught and logged, never thrown.

Design

  • Session-local. A wakeup is an in-process setTimeout, not a cron job. It lives and dies with the session, and survives /reload: the pending wakeups are handed to the reloaded extension, which re-arms each one for the time it had left. This is deliberate: subagents and pacing in pi are in-process (the muster-teammate shape is shelved), and cross-session scheduling is a different product (cron + pi -p), not this tool.
  • Busy-gated delivery. agent_start / agent_settled track whether a turn is running; delivery picks steer (idle) or followUp (busy) accordingly, so a wakeup never interrupts a turn in flight.
  • Best-effort. Every handler and the timer callback swallow-and-log on error — a missed wakeup must never take the session process down. The pure decisions (clamp, delivery mode, message shape) live in src/schedule.ts and are unit-tested with zero mocking; src/index.ts is the thin pi wiring, in the same split the guardrails package uses.
  • Persistence. Each schedule / fire / cancel is written with pi.appendEntry so the session transcript records the wakeup lifecycle.
  • Visible to other programs. The pending wakeups are also written to $XDG_STATE_HOME/pi-wakeup/<session id>.json (~/.local/state/pi-wakeup by default): {"session":"<id>","wakeups":[{"fire_at":"2026-11-06T18:10:00Z","reason":"CI"}]}, soonest first, fire times in UTC. It is rewritten atomically on every schedule, fire and cancel and removed when none are pending or the session ends, so a workspace's idle stop can keep the machine up for a pending wakeup and start it again before one.

Known follow-up

Re-arming a pending wakeup across a session resume is not yet implemented. The extension API exposes no entry read-back, and reconstructing pending wakeups by parsing the transcript blind is exactly the kind of vacuous code this project avoids. Within a live session, wakeups fire; a session closed with a pending wakeup drops it on resume. When pi exposes a custom-entry reader (or a resume hook carrying prior entries), SessionStartEvent.reason === "resume" is the place to re-arm from the persisted wakeup_scheduled / wakeup_fired / wakeup_cancelled records.

Tests

node --test src/*.test.ts — pure clamp/delivery/message logic plus the wired tool behavior (registration, idle-vs-busy delivery, clamp, cancel-one, cancel-all, a throwing sendMessage contained, shutdown cleanup) against a fake pi with an injectable timer.

Install

pi install npm:pi-wakeup