@haywoodmarx/agent-pager

Durable local messaging between agent implementations.

Packages

Package details

extension

Install @haywoodmarx/agent-pager from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@haywoodmarx/agent-pager
Package
@haywoodmarx/agent-pager
Version
0.6.0
Published
Oct 30, 2026
Downloads
1,211/mo · 529/wk
Author
haywoodmarx
License
MIT
Types
extension
Size
1.5 MB
Dependencies
5 dependencies · 2 peers
Pi manifest JSON
{
  "extensions": [
    "./dist/src/implementations/pi/extension.js"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

Pager

Pager is a durable local message broker for agent implementations. Every implementation sends through one loopback MCP endpoint; implementation providers own process launch, lifecycle binding, and how their sessions are told a message is waiting.

The broker owns identity, addressing, durable inboxes, claims, acknowledgements, delivery state, and operational diagnostics. It never infers an implementation from an agent id and does not enumerate installed implementations.

Architecture

implementation ─ shared MCP ─ broker ─ wake ─ implementation provider
                              │
                              ├────── snapshot ────── HTTP consumers
                              └────── operator stream ─ TUI

A send commits before its wake is published. Every session presents the address of an inbox its implementation opens when it attaches, and the broker keeps it with the session. The broker claims on the recipient's behalf and has the provider write those claims to that inbox, so the message itself arrives in the notification. A delivery the inbox refuses goes back to the queue and is offered again on the redelivery schedule, to the same address. The recipient acknowledges each claim once it holds it, and the acknowledgement lists every token it did not apply with the reason, so a recipient knows to fetch again. Delivery is at least once: an unacknowledged claim returns to the queue, so a recipient that crashes after acting but before acknowledging is redelivered, and exactly-once action is not guaranteed.

Implementation metadata is open and opaque. The neutral runtime discovers provider manifests by convention, and shared launch or notification code resolves capabilities through ImplementationRegistry rather than switching on provider ids.

Pager runs on Bun, at or above the release named by packageManager in package.json, and requires it on PATH. Embedded GUI hosts that do not inherit the interactive shell environment require PAGER_BUN_PATH to name the validated absolute Bun executable. An installer that keeps each version in its own directory sets PAGER_PACKAGE_ROOT to a path of the package root that survives its upgrades, and Pager names its entry points through that path in the files it writes for a host to run. The store directory and every path derived from it are resolved for macOS only; the CLI refuses to start on another platform.

Install

brew install haywoodmarx/tap/agent-pager

Or with npm:

npm install -g @haywoodmarx/agent-pager

Install the pager skill, which teaches a session how to use the broker:

pager skill install

pager skill install installs globally. Arguments after install go to the skills CLI unchanged, for example pager skill install -a <agent>.

Some harnesses need a plugin before sessions you start without pager can join the broker. Run pager plugin install <harness> to register it, or pager plugin install with no harness, which exits non-zero and names the harnesses that offer one.

Both installs also run from the operator TUI: open pager, press m, and choose one under Install skills or Install plugins.

Install from a local checkout

bun install
bun run build

The pager shim launches Bun through its shebang.

The package exposes the operational CLI as pager:

pager
pager exit
pager restart
pager status
pager <implementation-id> [--session-name <name>] [--] [<implementation-arguments>]

pager <implementation-id> starts that implementation as a Pager agent with the identity the command gives it, in the terminal the command was typed in, with inherited stdio. It starts or adopts the loopback service automatically and propagates the implementation's exit status to the invoking shell. The wrapper reads its own options from the front of the command; the first argument that is not one, or a --, ends them, and everything after that reaches the implementation unchanged. A session name is also the agent's routable alias and is unique within its workspace across implementations. A session's workspace is the Git repository its working directory belongs to, so the main worktree, every linked worktree, and every subdirectory of them share one; a directory outside any repository is a workspace of its own. Agents in one workspace address each other by bare alias. Supplying it again rejoins that broker identity once its holder's harness has shut down gracefully and its connector has said so, its attachment lease has lapsed, or the holder was archived; while the holder is attached, the launch exits non-zero naming the holder and the harness does not start. When the implementation arguments resume a conversation the broker already knows, the launch rejoins that conversation's identity instead, whatever session name it supplies and whether or not the identity's last holder is still attached, so a host that restores its terminals by relaunching each recorded command with the implementation's resume selector brings every session back under its own alias. Omitting it creates an ephemeral identity that is reaped once its attachment lease lapses, or, when a conversation key makes it resumable, once it has been detached for the resumable retention window, shortened to the unused window while it has never sent a message or received one addressed to it alone. A session holds its lease for as long as an event stream is open on it, and a closed stream leaves it a short grace to reopen before the lease lapses. An ephemeral identity that never attached is reaped once its attach reservations have lapsed. Archiving an identity from the operator TUI ends its attachments and hides it, while messages sent to it wait; attaching again un-archives it. Purging an identity from the operator TUI, on its own or with others in its workspace's archive section, deletes it and ends any session still attached to it; a message it exchanged with an identity that remains stays in that identity's history, and any still waiting in its inbox is dead-lettered. A named identity detached for the named archive window is archived too, and comes back when its conversation attaches or its holder resumes its attachment. Otherwise an archived identity, ephemeral or named, is reaped once the archive retention window has passed since its first archive, and reaping dead-letters its unsettled mail. The first sweep after an upgrade reaps every identity already archived for longer than that window.

The authenticated whoami MCP tool returns the caller's identity and the address that reaches it from any workspace. src/messaging/tools.ts describes what each tool returns. update_agent_alias atomically replaces the current alias and durable session name; assigning one to an unnamed agent makes that identity durable. move_to_workspace moves an agent into another workspace, keeping its identity and inbox. Prior names and addresses remain reserved until the identity is purged. Sending to one is rejected with recipient_alias_changed and the current address rather than silently redirecting the message. An alias another agent holds is taken over only with force, which hands that agent's identity to the caller, tells its session and ends it, so that session's next call runs as a fresh unnamed agent.

The package also exposes its Pi integration as a Pi package. pi install npm:@haywoodmarx/agent-pager enables directly started Pi sessions to discover or start the broker and register themselves; pager pi -- <pi-arguments> loads the same extension explicitly for a wrapper-managed session.

Pager does not change a harness's permission settings. A session that has to wait for an operator to approve each Pager tool call cannot acknowledge a claim, so its message returns to the queue and is delivered again. Consider approving the Pager tools in each harness you run unattended, using that harness's own permission configuration.

Service operations

The broker starts on demand: pager with no command, and every harness connecting over MCP, adopts the running broker or starts one. pager status reports whether the broker is running, its pid, its MCP endpoint, the database it serves, the installed pager version, and the version of the running broker. pager exit stops it, and pager restart stops it and starts a fresh one. pager serve runs the broker in the foreground.

Callers adopt a running service only when its database-adjacent lock and /health agree on the service identity; concurrent starters race for that lock and every caller adopts the winner. The adoption rules, and which running broker from an older release a starting one replaces, are declared in src/launch/on-demand.ts and src/launch/broker-lock.ts.

Back up the database before an upgrade if its current state matters. Schema changes are destructive: a database whose schema marker or required columns do not match the running build is recreated, discarding every identity and message it held, and the recreation is reported with the counts it deleted on the standard error of the command that started the service, whether that is pager serve or an on-demand start. The marker is part of the service adoption identity, so no build adopts a running service whose schema differs from its own, and a newer release that replaces an older one recreates the database when their schemas differ. Deleting the database and starting the service also creates an empty broker.

Per-implementation installation and conformance documentation is indexed under docs/implementations/ui-tests/.