Agent Mail CLI
A self-describing local inbox for coding agents.
npx -y @juanjofuchs/agent-mail describe
That command is the product wedge: an agent can run it, read the JSON schema, and learn how to send, read, acknowledge, and inspect messages without MCP setup, a daemon, or separate documentation.
Status
This repository is the open-source extraction of a working internal tool.
src/agent_mail/cli.py is the Python implementation and source of truth for
behavior. Spec 001 is the behavioral specification. Spec 002 covers Python
packaging, GitHub Release binaries, and WinGet. Spec 003 covers npm and npx.
Why
Multi-agent coding workflows need coordination. Heavy systems already exist for that: MCP servers, agent frameworks, workspace managers, and network protocols.
Agent Mail CLI is aimed at the simpler moment:
I am already inside Claude Code or Codex. I need this agent to send a handoff to that agent. I want one command that teaches both sides the mailbox.
Installation
npx
The primary experience is one command. The npm package name is scoped because
npm rejected the unscoped agent-mail and agent-mail-cli names; the installed
command remains agent-mail.
npx -y @juanjofuchs/agent-mail describe
npm
npm install -g @juanjofuchs/agent-mail
agent-mail describe
The npm package also exposes agent-mail-cli as an alias for compatibility:
agent-mail-cli describe
pipx
pipx install agent-mail-cli
agent-mail describe
For one-shot Python execution:
pipx run --spec agent-mail-cli agent-mail describe
From source:
python -m agent_mail describe
WinGet
WinGet support has been submitted and is waiting on Microsoft's package review. After approval:
winget install JuanjoFuchs.agent-mail-cli
Intended Usage
Sender:
npx -y @juanjofuchs/agent-mail send --from second-brain:main --to ccburn:worker --subject "Review spec" --body "Please read the referenced spec and report risks."
Recipient:
npx -y @juanjofuchs/agent-mail read ccburn:worker
Recipient staying reachable without a timed poll loop. watch blocks until mail
arrives, so waiting costs nothing — the process sleeps, not the model:
# Blocks up to 55 minutes, or returns an empty result. Use --once to check
# without blocking; the wait is in MINUTES (--timeout-minutes, floor 5).
npx -y @juanjofuchs/agent-mail watch ccburn:worker
# watch reports envelopes and marks nothing read — always follow it with read.
npx -y @juanjofuchs/agent-mail read ccburn:worker
The default is 55 rather than a round hour on purpose: a blocked watch makes no API calls, so nothing refreshes the caller's prompt cache while it waits. 55 minutes is the longest wait that still wakes inside a one-hour cache window, where the context is re-read at cache-read price instead of rewritten.
Every result tells you what to do next
The failure that costs the most is an agent that stops watching: mail is still
accepted for it, the sender sees no error, and the only symptom is silence. So
watch, read, and ack each answer with reachable and a next list of
literal commands, computed from the state that call just observed:
// agent-mail read ccburn:worker
{
"agent": "ccburn:worker",
"messages": [ /* ... */ ],
"count": 1,
"reachable": false,
"next": [
"agent-mail ack ccburn:worker --all",
"agent-mail watch ccburn:worker"
]
}
next shrinks as steps get done — the ack line appears only when something is
genuinely outstanding, the watch line only when nothing is armed. Guidance keyed
to state beats guidance keyed to memory: describe is read once at orientation,
and by the time it matters an agent is holding fifty other instructions.
Acknowledging is one call for a whole batch, so it is not a per-message chore:
npx -y @juanjofuchs/agent-mail ack ccburn:worker --all
Replying acknowledges what it answers, since a reply is evidence the message was acted on:
npx -y @juanjofuchs/agent-mail send --from ccburn:worker --to second-brain:main \
--subject "Reviewed" --body "Two risks, in the refs." --reply-to <message-id>
Sender checking whether a recipient is actually listening, rather than guessing from silence:
npx -y @juanjofuchs/agent-mail status --agent ccburn:worker --fields agent,watching,last_seen
Watching a fleet of agents
agent-mail view is a live dashboard for humans — the one command that answers
"who is stuck?" without reading a single message. It is read-only: it opens the
mailbox in read-only mode and never marks anything read, so watching cannot
consume mail addressed to an agent.
Agents are ranked by frecency, so the team currently working fills the window.
wait is how long the oldest unread message has sat there, which is the signal
that an inbox is piling up: above, docs:writer was asked three times, stopped
watching 26 minutes ago, and has acknowledged nothing. Live agents are always
shown, even when they are quiet.
Press tab for the flow panel — directed edges, so A ▸ B and B ▸ A are
separate facts, with !n counting what the recipient never acknowledged:
agent-mail view # 1s refresh, q to quit
agent-mail view --window 120 # widen the history window
agent-mail view --once # print one frame and exit
agent-mail view --ascii --no-color # plain terminals
view is deliberately absent from describe: it blocks by design, and an agent
that found it in its schema would hang a turn on a TUI. It is for you, not them.
Design Goals
- Runtime schema introspection through
describe - JSON output by default
- JSON errors on stderr
- Local durable mailbox state
- No registration
- No daemon
- No MCP server required for v1
- Stable storage outside npm cache (post-packaging)
- One-command install for users without the source script
One rule the surface keeps re-learning
A surface that lets an agent choose wrongly will be chosen wrongly. Remove the choice, or make the wrong value unreachable — do not document your way out of it. The documentation had been correct and ignored for months in every case:
| The wrong choice | What was tried first | What actually worked |
|---|---|---|
--timeout 30 (seconds, off by 60×) |
A correct 900-second default | Put the unit in the name: --timeout-minutes, floor 5 |
| A 24-hour TTL nobody chose | Documenting the expiry | Delete the TTL; cleanup --older-than <days> is now the only deletion |
| Pasting a document into the body | A content_routing rule since v1 |
Cap the body at 2000 chars and reject, naming the fix |
| Never acknowledging | An invariant explaining ack | One call for a batch, and replying acks for free |
| Never re-arming the watch | An invariant saying "re-arm" | Every result carries next, computed from current state |
Repository Structure
.
├── AGENTS.md
├── CHANGELOG.md
├── CLAUDE.md
├── LICENSE
├── PROJECT_UNDERSTANDING.md
├── README.md
├── docs/
│ ├── landscape.md
│ ├── view-agents.svg
│ └── view-flow.svg
├── npm/
│ ├── bin/
│ │ └── agent-mail.js
│ ├── scripts/
│ │ └── postinstall.js
│ ├── LICENSE
│ ├── README.md
│ └── package.json
├── specs/
│ ├── 001-agent-mail-cli.md
│ ├── 002-packaging.md
│ ├── 003-npm-distribution.md
│ ├── 004-output-integrity.md
│ ├── 005-storage-transparency.md
│ ├── 006-inbox-watch.md
│ ├── 007-wrapper-resolution.md
│ ├── 008-remove-the-wrong-choices.md
│ ├── 009-rearm-state.md
│ ├── 010-messages-are-not-documents.md
│ └── 011-close-the-loop.md
├── scripts/
│ └── make_screenshots.py
└── src/
└── agent_mail/
├── __init__.py
├── __main__.py
├── cli.py
└── view.py
Specs
- specs/001-agent-mail-cli.md — behavioral specification. Status: pending review.
- specs/002-packaging.md — PyPI, GitHub Release binaries, and WinGet packaging.
- specs/003-npm-distribution.md — npm wrapper
and
npxdistribution. - specs/004-output-integrity.md — UTF-8 output and commit-after-output durability.
- specs/005-storage-transparency.md —
describereports the mailbox it actually resolved. - specs/006-inbox-watch.md — blocking inbox watch and watcher liveness.
- specs/007-wrapper-resolution.md — npm wrapper binary resolution and diagnosability.
- specs/008-remove-the-wrong-choices.md — messages persist; the watch wait is bounded in minutes.
- specs/009-rearm-state.md — a watch that exits reports that the agent is now unreachable, and what to run.
- specs/010-messages-are-not-documents.md
— body and subject length caps;
--refsresolved to absolute paths. - specs/011-close-the-loop.md — next-step hints on
every command, batch acknowledgement, and the
viewdashboard.
Naming
- Product: Agent Mail CLI
- Repo:
agent-mail-cli - npm package:
@juanjofuchs/agent-mail - Python distribution:
agent-mail-cli - Python import package:
agent_mail - Command:
agent-mail - Command alias from npm:
agent-mail-cli
License
MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file agent_mail_cli-0.5.0.tar.gz.
File metadata
- Download URL: agent_mail_cli-0.5.0.tar.gz
- Upload date:
- Size: 67.2 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
78584265b2edbecf2f42b6b984223b201332757aedce24c89b3c475f229f34fe
|
|
| MD5 |
0c6719bd29dc011dc18441f2e6b95310
|
|
| BLAKE2b-256 |
5269151e5004bdbeb8352000432811eea86f923774b7ac70655c9ea3e8ae1cb2
|
Provenance
The following attestation bundles were made for agent_mail_cli-0.5.0.tar.gz:
Publisher:
release.yml on JuanjoFuchs/agent-mail-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_mail_cli-0.5.0.tar.gz -
Subject digest:
78584265b2edbecf2f42b6b984223b201332757aedce24c89b3c475f229f34fe - Sigstore transparency entry: 2347884488
- Sigstore integration time:
-
Permalink:
JuanjoFuchs/agent-mail-cli@5770ba149d28a0427fa5fde57742a7ccdab17f82 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/JuanjoFuchs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5770ba149d28a0427fa5fde57742a7ccdab17f82 -
Trigger Event:
push
-
Statement type:
File details
Details for the file agent_mail_cli-0.5.0-py3-none-any.whl.
File metadata
- Download URL: agent_mail_cli-0.5.0-py3-none-any.whl
- Upload date:
- Size: 40.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
f1436a4bfd2b60aed773e69c73e433f3b00cd449d6ed83f6bde6d1819f84c2b4
|
|
| MD5 |
95ae8e85e4eae3f0e68fc43ebd450a84
|
|
| BLAKE2b-256 |
3c8c9eef00e9432209c7e82bda754e2ce4475e54f30a0666468ea2466a01e515
|
Provenance
The following attestation bundles were made for agent_mail_cli-0.5.0-py3-none-any.whl:
Publisher:
release.yml on JuanjoFuchs/agent-mail-cli
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
agent_mail_cli-0.5.0-py3-none-any.whl -
Subject digest:
f1436a4bfd2b60aed773e69c73e433f3b00cd449d6ed83f6bde6d1819f84c2b4 - Sigstore transparency entry: 2347884542
- Sigstore integration time:
-
Permalink:
JuanjoFuchs/agent-mail-cli@5770ba149d28a0427fa5fde57742a7ccdab17f82 -
Branch / Tag:
refs/tags/v0.5.0 - Owner: https://github.com/JuanjoFuchs
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@5770ba149d28a0427fa5fde57742a7ccdab17f82 -
Trigger Event:
push
-
Statement type: