@haywoodmarx/agent-pager0.2.0
Durable local messaging between agent implementations.
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
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 registers the address of an inbox its implementation opens, and 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. 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
Some harnesses need a plugin before sessions you start without pager can join the broker. To install one, open pager, press m, and choose it under 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>
The bare implementation form starts or adopts the loopback service automatically. A command already running in a hosted terminal keeps the implementation in that terminal with inherited stdio. Otherwise, it prefers an available discovered terminal provider and falls back to inherited stdio; implementation exit status is propagated to the invoking shell. 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 heartbeat 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. Omitting it creates an ephemeral identity that is reaped once its attachment heartbeat 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. 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. 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. 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/acceptance/.