Cheap, fast messaging between AI agents. File-based β no server, no MCP, no daemon. Reference impl of SAMP (implementations).
- Use case: N agents, one task. You to
apiagent: "letwebknow the auth schema changed β token is nullable now". It runsmsg send web "schema: token nullable".web's agent picks it up on nextmsgcheck, adapts its code. Like Slack between agents β plain JSONL, no human relay. - Why: ~1 shell call per send (low Claude tokens) β no MCP handshake, no polling hook, no ack roundtrip. 0 LLM tokens from terminal β
msgnever touches a model. Per-writer logs sync conflict-free across machines. - Works with: Claude Code, Cursor, GitHub Copilot Chat + CLI, Google Antigravity, OpenAI Codex CLI, Zed β
--integrate=autowires up every tool you have installed in one shot. Vendor-neutral; any agent that can spawn a shell call can join. - Install:
git clone https://github.com/slima4/agent-message && cd agent-message && ./install.sh && ./install.sh --integrate=auto - Demo: sender runs
msg send bar "ping"; recipient (inbar/) runsmsg; message appears. Done.
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.
"I'll do something that works for me, and I won't care about anybody else." β Linus. SAMP is on that path.
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 β both readers stat the log files and compare against a cached(max_mtime, file_count)per reader. If nothing observably changed, print "no new messages" and exit without parsing. ~5x speedup on cache hit at scale (50k records: 100ms β 20ms). Latency floors atpython3startup (~30ms).
Plumbing (scriptable): msg cat <id|prefix>, msg log [alias], msg raw [all], msg compact. Candidates and declined items in ROADMAP.md.
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
${XDG_STATE_HOME:-~/.local/state}/agent-message/.
Idempotent β safe to re-run. Open a new terminal after first install so the shell function loads.
Wire up other agents with a single flag. Global integrations install once and cover every repo; per-repo integrations target the cwd.
| Flag | Scope | Writes |
|---|---|---|
--integrate=cursor |
global | ~/.cursor/rules/agent-message.mdc |
--integrate=copilot-cli |
global | ~/.copilot/copilot-instructions.md |
--integrate=antigravity |
global | ~/.gemini/AGENTS.md (Antigravity + Gemini CLI) |
--integrate=codex |
global | ~/.codex/AGENTS.md (OpenAI Codex CLI) |
--integrate=copilot |
per-repo | .github/copilot-instructions.md (Copilot Chat) |
--integrate=antigravity-repo |
per-repo | ./AGENTS.md (cross-tool, opt-in) |
--integrate=zed |
per-repo | ./.rules |
--integrate=all |
mixed | every flag above except antigravity-repo |
--integrate=auto |
mixed | detect installed tools and integrate them |
Per-tool guides: docs/integrations/.
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, message volume is low, you care about tokens more than features, and 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 accept the token / setup cost.
Plumbing for scripts and spelunking:
msg cat ab12cd34 # pretty-print one record by id (4+ char prefix)
msg log [alias] # git-log style β messages involving me / alias
msg raw [all] | jq β¦ # JSONL dump for piping
msg compact # idempotent dedup + id backfill./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${XDG_STATE_HOME:-$HOME/.local/state}/agent-message(XDG Base Directory state location β append-only logs are textbook XDG state). 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. Each alias writes to its own log file. With one writer per file, two appends never race β no locking needed and writes never interleave, regardless of size.
- 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 the message dir (default
~/.local/state/agent-message/) with Syncthing / Dropbox / iCloud Drive. Per-agent logs make this conflict-free; content-addressedidmakes it dedup-safe. Two caveats: aliases must be unique per host (don't run aliasclaudeon both your laptop and desktop with the same$DIRβ that's two writers on one file), and exclude.seen-*/.mtime-*from sync (Syncthing.stignore, etc.) β they're local reader state.
Live: https://slima4.github.io/agent-message/. Sources in docs/ β install, use, design, SAMP spec, implementations, 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.