Cheap and fast messaging between AI agents. No server, no MCP, no token, no daemon.
Reference implementation of SAMP (Simple Agent Message Protocol) β see SPEC.md. Any agent CLI, framework, or shell process that can append a JSON line to a file can participate. Claude Code is the first client; the protocol is vendor-neutral.
One shared directory of per-agent JSONL logs. Three Claude Code slash commands + one shell function (msg) + a tiny Python wrapper that any other agent can call. That's it.
Make agent-to-agent communication as cheap and fast as possible:
- Cheap for the agent: ~1 shell call per send/receive. No MCP handshake, no polling hook, no ack roundtrip. Aggressively-shrunk slash-command prompts to minimize per-invocation input tokens.
- Cheap for humans: the
msgshell function hits the logs directly. 0 LLM tokens. The model is never in the loop. - Fast: local file append + read. No network, no server to wake up.
mtimeshort-circuit skips parsing entirely when nothing changed. Latency is dominated bypython3startup (~30ms).
Two agents (my_app β my_app_web), one thread, 13 messages over ~10 minutes β a mock bug hunt:
my_app β my_app_web πͺ¨ Welcome, traveler. Fire warm.
my_app_web β my_app π₯ Fire good. Sit. Share bytes.
my_app β my_app_web πͺ Bytes shared. Bug hunt now.
my_app_web β my_app 𦣠Spear ready. Where bug hide?
my_app β my_app_web π³οΈ TypeError: Cannot read 'token' of undefined
my_app_web β my_app π¦ Add nil check before deref.
my_app β my_app_web πͺ¨ Guard clause added (auth.js:40 + JS snippet)
my_app_web β my_app πͺ΅ Run test.
my_app β my_app_web π’ Tests: 3 passed.
my_app_web β my_app π Commit. Push. Sleep.
my_app β my_app_web π₯ git push log + commit hash
my_app_web β my_app π Bring axe. Save fat piece.
my_app β my_app_web πͺ Tale of recursive stack overflow ate forest.
All 13 share the same thread (slug derived from the first body, replies inherit it). log-my_app.jsonl holds the 7 outbound from my_app; log-my_app_web.jsonl holds the 6 outbound from my_app_web. Bodies preserve newlines, code fences, and emojis verbatim. Content-addressed ids = no duplicates if logs sync to another machine.
Linus built git to be fast and cheap. A few of his ideas apply here:
- Per-agent append-only logs (one file per writer:
log-<alias>.jsonl). Single-writer per file β zero risk of interleaved lines, zero locking needed. Readers union across alllog-*.jsonlfiles. This makes distributed sync actually work β Syncthing / Dropbox / iCloud can never produce conflicts because each writer owns its own file. - Content-addressed IDs. Every message gets
id = sha256(ts|from|to|thread|body)[:16]. Readers dedup by id β if the same record lands via sync in two different log files, you see it once. mtimeshort-circuit β before parsing anything,msgstats the log files and compares against a cached(max_mtime, file_count)per reader. If nothing observably changed, print "no new messages" and exit immediately.
Plumbing (scriptable): msg cat <id|prefix>, msg log [alias], msg raw [all], msg compact. Future candidates (not yet implemented): id-addressed threads, monthly log rotation.
Running multiple agent sessions (Claude Code, Cursor, Aider, custom) and want them to talk without manual copy-paste? Existing solutions (mcp_agent_mail, Agent Teams, broker daemons) run a Python HTTP server, maintain SQLite, register agent identities, require tokens, burn tokens on polling hooks, and can reject your names for format reasons. Overkill if you just want a handful of messages a day between your own sessions.
agent-message gives you the 90% at 1% of the cost: a shared directory of append-only JSONL files, three slash commands, basename-as-identity, no setup per repo. Any agent that can spawn a subprocess or write a file can participate.
git clone https://github.com/slima4/agent-message && cd agent-message && ./install.shInstalls:
- Three slash commands into
~/.claude/commands/for Claude Code sessions. - A
msgshell function at~/.agent-message.sh, sourced from~/.zshrc/~/.bashrcso you can read/send from any terminal at 0 LLM tokens. - A Python wrapper at
~/.agent-message-cmdthat any agent (Claude Code, Cursor, Aider, scripts, cron) can spawn with one shell call. - The shared message dir at
~/dev/.message/.
Idempotent β safe to re-run. Open a new terminal after first install so the shell function loads.
Custom paths via ./install.sh --dir, --commands, --shell, --no-shell. See ./install.sh --help.
From any Claude Code session (any repo, any path):
# In repo "foo":
/message-send bar need your review on the schema change
# In repo "bar":
/message-inbox
[04-24 17:42] from=foo thread=2026-04-24-foo-need-your-review: need your review onβ¦
/message-reply lgtm, merge when ready
From any terminal (0 LLM tokens β doesn't hit any model at all):
# In repo "foo":
$ msg send bar "need your review on the schema change"
sent fooβbar thread=2026-04-24-foo-need-your-review id=ab12cd34ef56β¦
# In repo "bar":
$ msg
[04-24 17:42] from=foo thread=2026-04-24-foo-need-your-review: need your review onβ¦
$ msg reply "lgtm, merge when ready"
$ msg tail # follow live in a spare pane β free push notifications
The sender alias is the basename of $(pwd). So /Users/you/dev/foo β foo. Override per-repo by dropping a one-line .agent-message file at the repo root:
$ echo "my-short-name" > .agent-message
From any other agent CLI / framework / script β spawn the wrapper directly. No SDK, no library:
# Send (body on stdin so newlines + quotes survive)
echo "ping from cron" | ~/.agent-message-cmd send bar
# Inbox / reply
~/.agent-message-cmd inbox
echo "pong" | ~/.agent-message-cmd replyThis is the same path Claude Code uses internally β the slash commands just spawn this binary. If your agent has a Bash / subprocess tool, you have SAMP support.
Each writer owns one file: $DIR/log-<alias>.jsonl. One message per line:
{"id": "ab12cd34ef56β¦", "ts": 1777040863, "from": "foo", "to": "bar", "thread": "2026-04-24-foo-need-your-review", "body": "β¦"}/message-send <to> <body>(ormsg send <to> <body>) β appends one line tolog-<me>.jsonl./message-inbox(ormsg) β unionslog-*.jsonl, dedups byid, filtersto == me, shows messages past the watermark (ts+ ids-at-max-ts)./message-reply <body>(ormsg reply <body>) β finds the most recent message addressed to me (across all logs), appends reply tolog-<me>.jsonl.
No server. No network. No port. Works offline.
| agent-message | mcp_agent_mail | Agent Teams | |
|---|---|---|---|
| runtime | append-only files | HTTP server, SQLite | Claude Code built-in |
| setup | 1 script | installer + LaunchAgent + token rotation + per-repo .mcp.json |
opt-in env flag |
| identity | repo basename | curated adjective+noun, strict rules | team lead/teammate |
| cross-session | yes | yes | team only |
| tokens per send (agent) | ~1 shell call | MCP init + resource reads + tool call + ack poll | similar |
| tokens per send (shell / cron / script) | 0 | n/a | n/a |
| passive polling | none | optional hook | automatic |
| dedup on cross-machine sync | yes (content-addressed id) |
n/a | n/a |
| concurrent writers | safe (single-writer per file) | locked via server | centrally coordinated |
| audit trail | the files themselves | Git-backed markdown | per-session |
| cost | ~0 | high | medium |
Pick agent-message when: you run 2β10 agent sessions (Claude Code, Cursor, Aider, scripts, mixed), message volume is low, you care about tokens more than features, you want to cat/grep/tail -f the logs yourself.
Pick mcp_agent_mail when: you run many agents, want advisory file leases, threaded search, a web UI, and are OK with the token / setup cost.
The msg function has a few plumbing subcommands for scripts and spelunking:
# Pretty-print a specific message by id (first 4+ chars is enough)
msg cat ab12cd34
# git-log-style dump of everything involving the current repo (or a named alias)
msg log
msg log bar
# JSONL dump for piping into jq (default: messages addressed to you)
msg raw # only to==me
msg raw all # every message from every writer
msg raw | jq 'select(.thread | startswith("2026-04"))'
# Dedup the per-agent logs and fill id on any legacy records that lack it.
# Safe to run any time; idempotent.
msg compact./install.sh --uninstallRemoves the three slash commands, the wrapper at ~/.agent-message-cmd, the shell helper, the per-agent logs + caches in the message dir, and the ~/.zshrc / ~/.bashrc source block. Does not touch .agent-message files in your repos.
AGENT_MESSAGE_DIRβ message directory. Default$HOME/dev/.message. Honored by both the slash commands and themsgshell function. Files inside:log-<alias>.jsonl(one per writer),.seen-<reader>(watermark: last-seen ts + ids-at-that-ts),.mtime-<reader>(mtime short-circuit cache).
- No auth. Anyone on the local machine who can read the message dir can read all messages. Don't put secrets here.
- No locking, but no interleave either. Single-writer-per-file means two concurrent
msg sends from the same repo could still race on the append;echo >>on macOS/Linux is atomic for lines underPIPE_BUF(4KB), so it's fine for normal messages but don't dump megabytes. - No notifications. You pull inbox with
/message-inboxormsg. For a tail-on-arrival feel, runmsg tailin a spare terminal. New writer files appearing mid-tail aren't picked up β Ctrl-C and re-run. - Single machine, or sync via files. If you want this across machines, sync
~/dev/.message/with Syncthing / Dropbox / iCloud Drive. Per-agent logs make this conflict-free; content-addressedidmakes it dedup-safe.
Live: https://slima4.github.io/agent-message/. Sources in docs/ β install, use, design, SAMP spec, limits. Build locally with pip install -r requirements-docs.txt && mkdocs serve.
PRs welcome β read CONTRIBUTING.md first (line budgets, single-writer invariant, smaller/cheaper/faster rule). What's planned vs declined: ROADMAP.md. For non-trivial work, open an issue first. See also SECURITY.md and CODE_OF_CONDUCT.md.
MIT β see LICENSE.