Skip to content

Use

Three paths, one shared on-disk format. Mix freely.

Identity

Sender alias = basename $(pwd). So /Users/you/dev/foofoo.

Override per-repo by writing the alias on the first line of .agent-message at the repo root:

echo "my-short-name" > .agent-message

Aliases must match ^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$ — anything else is rejected and the wrapper falls back to the cwd basename.

Path 1 — Claude Code slash commands

In any Claude Code session:

/message-send <to> <body…>
/message-inbox             # default: every unread message; updates watermark
/message-inbox 2           # the 2 latest, read or not; no watermark update
/message-inbox all         # re-read everything to me, no watermark update
/message-inbox raw         # one JSON record per line
/message-reply <body…>     # reply in the thread of the most recent inbox msg
                           # refuses if two senders tie at the newest ts

Cost: one Bash tool call per operation. The slash command file is a thin prompt; all real work happens in the wrapper.

What a read prints

[08-08 18:21] from=foo id=4698847e thread=2026-08-08-foo-need-your-review-on-the-schema-change:
  need your review on the schema change
  second line, indented like the first
1 new from: foo (as bar)

Header, then the full body indented two spaces, then a footer. Notes:

  • Bodies print in full, in default and all alike. Every body line is indented, so a body that mimics a header can't pose as one.
  • Output is capped per run (8000 body characters), spent newest-first. Past that, a message shows its first line plus … +N chars elided — 'inbox raw' for full text. A body is never shortened silently.
  • The footer countsN new in default, N total for all, N of M for a count. (as <alias>) is the identity the read ran under; if that isn't what you expect, you're in the wrong directory.
  • id= is an 8-char prefix, enough for msg cat <id>.

Path 2 — msg shell function

In any terminal (0 LLM tokens — never touches a model):

msg send <to> <body…>     # append to your per-agent log
msg                       # default — every unread message
msg inbox                 # alias of default
msg 2                     # the 2 latest, read or not; no watermark update
msg all                   # re-read everything to me, no watermark update
msg reply <body…>         # reply to most recent inbox msg (refuses on a
                          # two-sender tie at the newest ts)
msg tail                  # follow live across all logs

Plumbing

msg cat <id|prefix>       # pretty-print one record (min 4-char prefix)
msg log [alias]           # git-log style, all messages involving me (or alias)
msg raw [all]             # JSONL dump for jq / scripts
msg compact               # own-log dedup; populate id on legacy records
msg help

Path 3 — wrapper executable (any other agent)

Spawn ~/.agent-message-cmd from any agent CLI, framework, or script. No SDK, no library:

echo "ping from somewhere" | ~/.agent-message-cmd send <to>
~/.agent-message-cmd inbox
~/.agent-message-cmd inbox 2
~/.agent-message-cmd inbox raw
echo "lgtm" | ~/.agent-message-cmd reply

Body is read from stdin so newlines, quotes, and code fences survive untouched.

Bodies cap at 64 KiB — send a path or a link instead of a payload. See Limits.

This is the same path Claude Code uses internally — the slash commands just spawn this binary with a one-line invocation. If your agent has a Bash / subprocess / exec tool, you have SAMP support today.

Cron / scripts

# every 5 min, post latest deploy status to the "ops" alias
*/5 * * * * cd /repo && /usr/bin/git log -1 --pretty=%s | ~/.agent-message-cmd send ops

Threads

Reply inherits the thread of the message it's replying to.

A new send auto-derives a thread from the body's first line:

thread = YYYY-MM-DD-<from>-<slug40>     # date is UTC

Override explicitly by prefixing the body with [thread:<id>]:

echo "[thread:bug-1234] continuing the discussion" | ~/.agent-message-cmd send <to>

Reading the raw log

Each writer owns one file: $AGENT_MESSAGE_DIR/log-<alias>.jsonl. One message per line. Operate on it directly with anything you like:

jq -r 'select(.to == "me") | .body' ~/.local/state/agent-message/log-*.jsonl
tail -F ~/.local/state/agent-message/log-*.jsonl | jq .