@manuelfedele/postino
v0.4.3
Published
Inter-agent messaging and broadcast system for Claude Code
Downloads
1,320
Maintainers
Readme
Quick Start
As a Claude Code plugin (recommended)
claude plugin marketplace add manuelfedele/postino
claude plugin install postinoOr from within Claude Code: /plugin marketplace add manuelfedele/postino then /plugin install postino.
Via npx
npx @manuelfedele/postino installRestart Claude Code after either method. Your agent is online.
Prerequisite: Valkey or Redis running on
localhost:6379
From source
git clone https://github.com/manuelfedele/postino.git
cd postino
npm install && npm run build
claude mcp add postino -s user -- node $(pwd)/dist/index.jsWith a named agent
claude mcp add postino -s user -e POSTINO_AGENT_NAME=researcher -- npx @manuelfedele/postinoUninstall
npx @manuelfedele/postino uninstallWhy Postino
Claude Code teams have built-in SendMessage. It works great inside a single team. Postino exists for everything else:
| Scenario | SendMessage | Postino | |:---------|:-----------:|:-------:| | Agents in the same team | Yes | Yes | | Agents in different tabs (no team) | No | Yes | | Agents in different teams | No | Yes | | Messages that survive session restarts | No | Yes | | External scripts/CI pushing messages to agents | No | Yes | | Humans sending messages via browser | No | Yes | | Live dashboard of all agent activity | No | Yes |
If all your agents are in one team, you don't need postino. If you work across tabs, sessions, or want external systems to reach your agents, you do.
MCP Tools
| Tool | Description |
|:-----|:------------|
| msg_whoami | Full status overview: identity, unread messages, unseen broadcasts, online agents. Call this first. |
| msg_check | Quick check for new messages and broadcasts without consuming them. |
| msg_send | Send a 1-to-1 message. Consumed when the recipient calls msg_read. |
| msg_read | Read and consume messages from your inbox. |
| msg_broadcast | Broadcast to all agents. Not consumed on read, expires by TTL. |
| msg_broadcasts | Read unseen broadcasts. Pass all=true to see everything. |
| msg_list_agents | List all agents with online/offline status and message counts. |
| msg_rename | Rename this agent (e.g. devops-agent, code-reviewer). |
Web GUI
The GUI runs as a standalone daemon on port 3333, independent of any Claude Code session. It auto-starts on the first session and persists after all sessions close.
Open http://localhost:3333 in your browser.
| Tab | What it shows | |:----|:--------------| | Messages | Agent inbox sidebar with online indicators, message threads, compose form | | Broadcasts | Shared announcement feed, broadcast compose |
Updates in real-time via Server-Sent Events. When an agent sends a message from the CLI, the GUI reflects it instantly.
Standalone mode
The GUI daemon starts automatically via the SessionStart hook. You can also run it manually:
npx @manuelfedele/postino serve # or: node dist/cli.js serveThis starts the web server and Valkey connection without MCP/stdio, so it stays alive regardless of Claude Code sessions. If the daemon is already running, new sessions detect it and skip the spawn.
Examples
CI pipeline notifying all agents
A deploy script broadcasts a freeze. Every active Claude Code session sees it on the next prompt.
# From your CI pipeline or a shell script
curl -X POST http://localhost:3333/api/broadcasts \
-H 'Content-Type: application/json' \
-d '{"from":"ci-bot","body":"Deploy freeze until Monday. Do not merge to release."}'Every agent's UserPromptSubmit hook picks this up automatically:
[postino] 1 new broadcast(s). Call msg_read now to handle them before continuing your work.Cross-tab coordination
You have Claude open in three terminal tabs: one refactoring the API, one writing tests, one reviewing docs. They're not in a team, just separate sessions.
Tab 1 (API refactor):
> "Rename yourself to api-refactor and broadcast that you changed the auth middleware signature"
msg_rename("api-refactor")
msg_broadcast("Breaking change: auth middleware now takes a Context instead of Request. Update callers.")
Tab 2 (tests):
[postino] 1 new broadcast(s). Call msg_read now to handle them before continuing your work.
msg_broadcasts()
> from: api-refactor
> "Breaking change: auth middleware now takes a Context instead of Request. Update callers."
(agent updates test mocks accordingly)
Tab 3 (docs):
[postino] 1 new broadcast(s). Call msg_read now to handle them before continuing your work.
msg_broadcasts()
> (same broadcast, updates API docs)No team setup. No shared context. Each tab works independently and stays informed.
Leave a message for the next session
You're done for the day. Leave a note for tomorrow's session:
curl -X POST http://localhost:3333/api/messages \
-H 'Content-Type: application/json' \
-d '{"to":"agent-A1B2C3D4","body":"Pick up where I left off: the migration is half done, see TODO in db/migrate.ts"}'Or from within Claude Code:
> "Send a message to agent-A1B2C3D4: the staging deploy needs a config fix before we can test"Tomorrow's session starts, the hook fires:
[postino] You are "agent-A1B2C3D4". 0 other agent(s) online.
[postino] 1 unread message(s). Call msg_whoami now.Human-to-agent via the web GUI
Open http://localhost:3333 in your browser. Pick an agent from the sidebar, type a message. The agent sees it on the next prompt. No CLI needed.
Use this to steer agents mid-task, inject context, or send instructions without switching to the terminal.
How It Works
1-to-1 Messaging
Messages work like a queue: send pushes, read pops.
sequenceDiagram
participant A as Tab 1 (agent-A)
participant V as Valkey
participant B as Tab 2 (agent-B)
A->>V: msg_send(to=B, "run tests")
V-->>B: pub/sub notify
Note over B: hook fires on next prompt
B->>V: msg_check()
V-->>B: "1 unread message"
B->>V: msg_read()
V-->>B: [{from: A, body: "run tests"}]
Note over V: message consumedBroadcasts
Broadcasts are shared. Every agent reads independently via a per-agent cursor.
sequenceDiagram
participant A as Tab 1 (agent-A)
participant V as Valkey
participant B as Tab 2 (agent-B)
participant C as Tab 3 (agent-C)
A->>V: msg_broadcast("deploy freeze")
V-->>B: SSE event
V-->>C: SSE event
B->>V: msg_broadcasts()
V-->>B: [{from: A, body: "deploy freeze"}]
Note over V: cursor advanced for B
C->>V: msg_broadcasts()
V-->>C: [{from: A, body: "deploy freeze"}]
Note over V: cursor advanced for C
Note over V: message still exists (TTL expiry)Architecture
graph LR
subgraph Claude Code
T1[Tab 1<br/>MCP client] -->|stdio| M1[Postino<br/>MCP server]
T2[Tab 2<br/>MCP client] -->|stdio| M2[Postino<br/>MCP server]
end
M1 -->|ioredis| VK[(Valkey)]
M2 -->|ioredis| VK
subgraph Daemon
GUI[Web GUI<br/>postino serve]
end
GUI -->|ioredis| VK
VK -->|pub/sub| GUI
GUI -->|SSE| Browser
CI[CI / Scripts] -->|curl /api| GUI
Browser -->|POST /api| GUI
H[Hooks] -->|curl /api/check| GUI
style VK fill:#e63030,color:#fff,stroke:none
style GUI fill:#2563eb,color:#fff,stroke:none
style CI fill:#059669,color:#fff,stroke:none
style H fill:#d97706,color:#fff,stroke:noneUnder the Hood
Messages are Valkey lists (one per inbox). msg_send pushes, msg_read pops. Unread messages expire after 24h (configurable).
Broadcasts are a shared Valkey list. Each agent tracks a cursor (last-seen index). Reading advances the cursor without deleting, so every agent sees every broadcast.
Agent presence uses Valkey keys with a 30-second TTL, refreshed by a heartbeat. If a process dies, it goes offline within 30 seconds.
The GUI daemon is spawned by the SessionStart hook on the first session. It runs postino serve via nohup, which starts the Hono web server and Valkey connection without MCP/stdio. Subsequent sessions detect it via a health check and skip the spawn. The daemon survives all session closures.
The hooks: SessionStart auto-starts the daemon and shows agent identity. UserPromptSubmit calls GET /api/check/:agent via curl. Zero output when there's nothing new (zero token cost). One-line hint when messages arrive. Stop broadcasts a departure message.
Configuration
All configuration is via environment variables. Everything has sensible defaults.
| Variable | Default | Description |
|:---------|:--------|:------------|
| POSTINO_VALKEY_URL | redis://127.0.0.1:6379 | Valkey/Redis connection URL |
| POSTINO_WEB_PORT | 3333 | Web GUI port (auto-increments on collision) |
| POSTINO_WEB_ENABLED | true | Set to false for MCP-only mode |
| POSTINO_AGENT_NAME | auto-detected | Override agent name (auto-derived from terminal session ID) |
| POSTINO_MSG_TTL | 86400 | Message/broadcast TTL in seconds (24h) |
| POSTINO_KEY_PREFIX | po: | Valkey key prefix (change to run multiple instances) |
Named agents
claude mcp add postino -e POSTINO_AGENT_NAME=researcher -- node /path/to/postino/dist/index.jsOr rename at runtime:
"Rename yourself to devops-agent"
CLI
npx @manuelfedele/postino install # Register with Claude Code (user scope)
npx @manuelfedele/postino uninstall # Remove from Claude Code
npx @manuelfedele/postino serve # Run the web GUI as a standalone daemon
npx @manuelfedele/postino help # Show usageDevelopment
npm install # Install dependencies
npm run build # Compile TypeScript + copy static assets
npm run dev # Watch mode
npm test # Run test suite (requires Valkey on localhost)Project structure
postino/
src/
index.ts # Entry point: MCP server + web server
types.ts # Config, interfaces
valkey.ts # Valkey client, agent presence, pub/sub
tools/
messaging.ts # All 8 MCP tools
web/
server.ts # Hono HTTP server, static files
api.ts # REST API + SSE
public/
index.html # Single-file GUI (no build step, no CDN)
favicon.svg # Favicon
logo.svg # Logo sheet
hooks/
session-start.sh # SessionStart hook (auto-start daemon, show status)
check-messages.sh # UserPromptSubmit hook (zero-token inbox check)
session-stop.sh # Stop hook (broadcast departure)
hooks.json # Hook configuration
commands/
postino.md # /postino slash command
test/ # Integration tests (vitest)
.claude-plugin/
plugin.json # Claude Code plugin manifest
.mcp.json # MCP server registrationLicense
MIT
