1. Conduid
  2. Data
  3. Agent Mail Rust
MCP server · Data

Agent Mail Rust

Rust MCP server for multi-agent coordination: 34 tools, Git-backed archive, SQLite indexing, advisory file locks, and an interactive TUI console

Unclaimed Custom license dataai-agentsmcpcoordinationtuirust
64Good

Scored 9 hours ago · breakdown

About Agent Mail Rust

Agent Mail Rust is an MCP server published by Dicklesworthstone in the Data category: rust MCP server for multi-agent coordination: 34 tools, Git-backed archive, SQLite indexing, advisory file locks, and an interactive TUI console.

The repository has 204 stars and 40 forks, with the last commit 9 hours ago. Recent commit activity is the strongest signal that reported issues will get attention.

Install

From source
git clone https://github.com/Dicklesworthstone/mcp_agent_mail_rust

This server has no ConduID identity, so agent calls to it are not receipted. Pin the version you install and review the source before granting it credentials.

Ask AI

✦

Ask AI about Agent Mail Rust

Powered by Claude · Grounded in docs

I know everything about Agent Mail Rust. Ask me about installation, configuration, usage, or troubleshooting.

Security checks

Static checks derived from the repository 9 hours ago. Heuristics on the source, not an audit.

  • ✓README presentThe repository ships documentation.
  • ✓License declaredUsable under a stated license.
  • ✓Tests presentA test suite exists in the repository.
  • ✓Dependencies pinnedA lockfile pins dependency versions.
  • !No dynamic code executionSource scan found eval or shell execution. Review how tool inputs reach it before granting credentials.
  • !Scoped permissionsDoesn't declare a permission scope. Assume it can do anything its process can.

Releases

v0.3.39v0.3.39: small-TMPDIR startup fix (GH #347), integrity guard on never-written pages (GH #291), project-addressed mailboxes · 13 Oct 2026This release fixes two failures that took servers out of service, and stops one healthy mailbox class from blocking backups: Startup on a small or full temp directory (GH#347). On v0.3.38 a small tmpfs `TMPDIR` killed the server with…
v0.3.38v0.3.38: server descriptor-leak fix (GH #333) and FrankenSQLite 0.4.10 · 8 Oct 2026This release carries the fix for the server descriptor leak (GH #333). On v0.3.36, and on macOS also v0.3.37, a long-running `am` server accumulated descriptors on `storage.sqlite3` until it reached `RLIMIT_NOFILE`. From then on every tool…
v0.3.37v0.3.37: safe mailbox upgrades after the v30 short-record incident · 6 Oct 2026This release fixes the v30 mailbox upgrade incident (br-2hpuk): after `ALTER TABLE messages ADD COLUMN archive_metadata_json`, existing message rows written by v0.3.36 were misread shifted by one column (empty inboxes, no search hits), and…
v0.3.36v0.3.36 · 16 Sep 2026This release fixes mailbox recovery, contended writes, search pagination, and CLI credential handling, and updates the pinned dependencies used by the release build. Windows recovery flushes database and directory handles with the required…
v0.3.35v0.3.35: lifecycle tools require the registration token over HTTP · 9 Sep 2026v0.3.35 This patch closes the remaining authorization gap behind PR #310: the agent lifecycle tools now require the registration token when called over HTTP. FrankenSQLite remains pinned to 0.3.18 with SQLModel 0.4.0, Asupersync 0.4.9, and…

README

MCP Agent Mail (Rust)

"It's like Gmail for your coding agents!"

A mail-like coordination layer for AI coding agents, exposed as an MCP server with 45 tools and 25 resources, Git-backed archive, SQLite indexing, an interactive 16-screen TUI, a server-rendered web UI, and an agent-first robot CLI. The Rust rewrite of the original Python project (1,700+ stars).

Supported agents: Claude Code, Codex CLI, Gemini CLI, Oh My Pi (OMP), GitHub Copilot CLI, and any MCP-compatible client.

Watch the 23-minute walkthrough to see seven AI coding agents send over 1,000 messages to each other while implementing a development plan over two days.

curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/mcp_agent_mail_rust/main/install.sh?$(date +%s)" | bash

Table of Contents


TL;DR

The Problem: Modern projects often run multiple coding agents at once (backend, frontend, scripts, infra). Without a shared coordination fabric, agents overwrite each other's edits, miss critical context from parallel workstreams, and require humans to relay messages across tools and teams.

The Solution: Agent Mail gives every coding agent a persistent identity (e.g., GreenCastle), an inbox/outbox, searchable threaded conversations, and advisory file reservations (leases) to signal editing intent. Everything is backed by Git for human-auditable artifacts and SQLite for fast indexing and search.

Why Use Agent Mail?

Feature What It Does
Advisory File Reservations Agents declare exclusive or shared leases on file globs before editing, preventing conflicts with a pre-commit guard
Asynchronous Messaging Threaded inbox/outbox with subjects, CC/BCC, acknowledgments, and importance levels
Token-Efficient Messages stored in a per-project archive, not in agent context windows
25 MCP Resources Read-only inbox, thread, reservation, tooling, identity, and attention views for cheap lookups
45 MCP Tools Infrastructure, identity, messaging, contacts, reservations, search, macros, product bus, and build slots
16-Screen TUI Live operator cockpit for messages, threads, agents, search, reservations, metrics, health, analytics, attachments, archive browsing, and ATC
Web UI Server-rendered /mail/ routes for human oversight, unified inbox review, search, attachments, and overseer messaging
Robot Mode 19 agent-optimized CLI subcommands with toon/json output (md for thread and message views) for non-interactive workflows
Git-Backed Archive Every message, reservation, and agent profile stored as files in per-project Git repos
Hybrid Search Search V3 via frankensearch. The lexical tier ships by default; semantic and hybrid routing need a build with the hybrid feature (feature = "hybrid") and the runtime opt-in AM_SEARCH_SEMANTIC_ENABLED=true. Without both, every query runs lexical.
Pre-Commit Guard Git hook that blocks commits touching files reserved by other agents
Dual-Mode Interface MCP server (mcp-agent-mail) and operator CLI (am) share tools but enforce strict surface separation

Quick Example

# Start the server (the installer already configured your detected coding agents;
# `am setup run` re-runs that setup at any time)
am

# That's it. Server starts on 127.0.0.1:8765 with the interactive TUI.

# Agents coordinate through MCP tools:
#   ensure_project(human_key="/abs/path")
#   register_agent(project_key="/abs/path", program="claude-code", model="opus-4.6")
#   file_reservation_paths(project_key="/abs/path", agent_name="BlueLake", paths=["src/**"], ttl_seconds=3600, exclusive=true)
#   send_message(project_key="/abs/path", sender_name="BlueLake", to=["GreenCastle"], subject="Starting refactor", body_md="Taking src/**", thread_id="FEAT-123")
#   fetch_inbox(project_key="/abs/path", agent_name="BlueLake")

# Or use the robot CLI for non-interactive agent workflows:
am robot status --project /abs/path --agent BlueLake
am robot inbox --project /abs/path --agent BlueLake --urgent --format json
am robot reservations --project /abs/path --agent BlueLake --conflicts

What Agent Conversations Look Like

Example exchange between two agents coordinating a refactor:

┌──────────────────────────────────────────────────────────────────────────────┐
│ Thread: FEAT-123 - Auth module refactor                                      │
├──────────────────────────────────────────────────────────────────────────────┤
│                                                                              │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ GreenCastle -> BlueLake                                 2026-02-16 10:03 │ │
│ │ Subject: Starting auth refactor                                          │ │
│ ├──────────────────────────────────────────────────────────────────────────┤ │
│ │ I'm reserving src/auth/** for the next hour. Can you focus on the API    │ │
│ │ tests in tests/api/** instead?                                           │ │
│ │ [ack_required: true]                                                     │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ BlueLake -> GreenCastle                                 2026-02-16 10:04 │ │
│ │ Subject: Re: Starting auth refactor                                      │ │
│ ├──────────────────────────────────────────────────────────────────────────┤ │
│ │ Confirmed. Releasing my reservation on src/auth/** and taking            │ │
│ │ tests/api/** exclusively. Will sync when I hit the auth middleware       │ │
│ │ boundary.                                                                │ │
│ │ [ack: OK]                                                                │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ BlueLake -> GreenCastle                                 2026-02-16 10:31 │ │
│ │ Subject: Re: Starting auth refactor                                      │ │
│ ├──────────────────────────────────────────────────────────────────────────┤ │
│ │ Found a broken assertion in tests/api/auth_test.rs:142 -- the expected   │ │
│ │ token format changed. Heads up if you're touching the JWT issuer.        │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
│ ┌──────────────────────────────────────────────────────────────────────────┐ │
│ │ GreenCastle -> BlueLake                                 2026-02-16 10:33 │ │
│ │ Subject: Re: Starting auth refactor                                      │ │
│ ├──────────────────────────────────────────────────────────────────────────┤ │
│ │ Good catch. I just changed the claims struct. Updated the test fixture   │ │
│ │ in my commit. Releasing src/auth/** now -- all yours if you need it.     │ │
│ └──────────────────────────────────────────────────────────────────────────┘ │
│                                                                              │
└──────────────────────────────────────────────────────────────────────────────┘

No human relay needed. Agents negotiate file ownership, flag breaking changes in real time, and hand off work through structured, threaded messages stored in Git.


Why This Exists

Modern projects often run multiple coding agents at once (backend, frontend, scripts, infra). Without a shared coordination fabric, agents overwrite each other's edits, miss critical context from parallel workstreams, and require humans to relay messages across tools and teams.

Agent Mail has been available since October 2025 and was designed around real multi-agent coding workloads across providers such as Claude Code, Codex CLI, and Gemini CLI. The adjacent Beads and bv tools make it more useful as a full coordination stack: Beads tracks work, bv helps pick the right next work, and Agent Mail carries the coordination traffic.

The Footguns Agent Mail Avoids

No "broadcast to all" mode. Given the option, many agents will overuse broadcast-style messaging. That is the equivalent of default reply-all in email: lots of irrelevant noise and wasted context.

Carefully refined API ergonomics. Bad MCP documentation and poor agent ergonomics quietly wreck reliability. Agent Mail's 45 tool definitions have gone through repeated real-world iteration so they work predictably without wasting tokens.

No git worktrees. Worktrees can slow development velocity and create reconciliation debt when agents diverge. Agent Mail takes the opposite approach: keep agents in one shared space, surface conflicts quickly, and give them tools to coordinate through them.

Advisory file reservations instead of hard locks. For this problem, advisory reservations fit better than hard locks. Agents can temporarily claim files while they work, reservations expire automatically, and stale claims can be reclaimed. That makes the system robust to crashed or reset agents; hard locks would not.

Semi-persistent identity. An identity that can last for the duration of a discrete task (for the purpose of coordination), but one that can also vanish without a trace and not break things. You don't want ringleader agents whose death takes down the whole system. Agent Mail identities are memorable (e.g., GreenCastle), but ephemeral by design.

Graph-aware task selection. If you have 200-500 tasks, you don't want agents randomly choosing them or wasting context communicating about what to do. There's usually a "right answer" for what each agent should work on, and that right answer comes from the dependency structure of the tasks. That's what bv computes using graph theory, like a compass that tells each agent which direction will unlock the most work overall.

What Agent Mail Gives You

  • Prevents conflicts: Explicit file reservations (leases) for files/globs prevent agents from overwriting each other
  • Reduces human relay work: Agents send messages directly to each other with threaded conversations, acknowledgments, and priority levels
  • Keeps communication off the token budget: Messages stored in per-project Git archive, not consuming agent context windows
  • Offers quick reads: resource://inbox/{Agent}?project=<abs-path>, resource://thread/{id}?project=<abs-path>, and 23 other MCP resources
  • Provides full audit trails: Every instruction, lease, message, and attachment is in Git for human review
  • Scales across repos: Frontend and backend agents in different repos coordinate through the product bus and contact system

Typical Use Cases

  • Multiple agents splitting a large refactor across services while staying in sync
  • Frontend and backend agent teams coordinating thread-by-thread across repositories
  • Protecting critical migrations with exclusive file reservations and pre-commit guards
  • Searching and summarizing long technical discussions as threads evolve
  • Running agent swarms with Beads task tracking for dependency-aware work selection

Productivity Math

Parallel agent work changes the economics of supervision. One human operator can spend an hour steering several agents while those agents produce many hours of implementation work in parallel. The exact multiplier depends on the task and on how disciplined the workflow is, but the point is straightforward: coordination overhead matters, and Agent Mail is built to keep that overhead low.


What People Are Saying

"Agent Mail and Beads feel like the first 'agent-native' tooling." — @jefftangx

"Agent mail is a truly brain-melting experience the first time. Thanks for building it." — @quastora

"Between Claude Code, Codex CLI, and Gemini; Beads; and Agent Mail — basically already 80% the way to the autonomous corporation. It blows my mind this all works now!" — @curious_vii

"Use it with agent mail == holy grail." — @metapog

"The only correct answer to this is mcp agent mail." — @skillcreatorai

"GPT 5.2 suggesting beads + agent mail for agent co-ordination (of course, I am already using them)." — @jjpcodes


Design Philosophy

Mail metaphor, not chat. Agents send discrete messages with subjects, recipients, and thread IDs. Work coordination is structured communication with clear intent, not a firehose. Imagine if your email system at work defaulted to reply-all every time; that's what chat-based coordination does, and it burns context fast.

SQLite for live state, Git for the durable ledger. The accepted write path mutates SQLite first so inboxes, resources, TUI, web, and robot views see one fresh operational state. The same write then emits human-readable message, profile, and reservation artifacts into the per-project Git archive for audit and recovery.

Advisory, not mandatory. File reservations are advisory leases, not hard locks. The pre-commit guard enforces them at commit time, but agents can override if needed. Acquiring a reservation reports conflicts instead of waiting for another agent to release its lease. Reservations expire on a TTL; they do not prevent deadlocks in the storage or runtime layers.

Resilient to agent death. Agents die all the time: context windows overflow, sessions crash, memory gets wiped. Any agent can vanish without breaking the system. No ringleader agents, no single points of failure. Semi-persistent identities exist for coordination but don't create hard dependencies.

Dual persistence. Human-readable Markdown in Git for auditability; SQLite for live indexing plus Search V3 for fast lexical/semantic retrieval. Both stay in sync through the write pipeline.

Structured concurrency, no Tokio. The entire async stack uses asupersync with Cx-threaded structured concurrency. No orphan tasks, cancel-correct channels, and deterministic testing with virtual time.


Rust vs. Python: Stress Test Results

The Python implementation had three recurring failure modes under real multi-agent workloads: Git lock file contention from concurrent writes, SQLite pool exhaustion under sustained load, and cascading failures when many agents hit the server simultaneously. The Rust rewrite addresses these through batching, bounded pools, and backpressure. The measurements below are retained results from the original stress campaign, not a passing verdict for the current release candidate or a guarantee for every workload.

The 10-Test Gauntlet

Test Result Key Metrics
30-agent message pipeline PASS 150/150 success, p99=6.8s, 0 errors
10-project concurrent ops PASS 150/150 success, 0 errors
Commit coalescer batching PASS 9.1x batching ratio (100 writes → 11 commits)
Stale git lock recovery PASS Lock detected, cleaned, writes resumed
Mixed reservations + messages PASS 80+80 ops, 0 errors
WBQ saturation PASS 2000/2000 enqueued, 0 errors, 0 fallbacks
Pool exhaustion (60 threads, pool=15) PASS 600/600 success, 0 timeouts, 24 ops/sec
Sustained 30s mixed workload PASS 1494 ops, ~49 RPS, p99=2.6s, 0 errors
Thundering herd (50 threads, 1 agent) PASS All 50 got same ID, 0 errors
Inbox reads during message storm PASS 150 sends + 300 reads, 0 errors

Python Problem → Rust Fix

Python Failure Mode What the Rust Tests Exercise Result
Git lock file contention Commit coalescer batching (100 concurrent writes → 11 commits, 9.1x reduction), stale lock recovery, multi-project isolation 0 lock errors
SQLite pool exhaustion 60 threads on pool of 15, sustained 50 RPS for 30s, thundering herd (50 threads → 1 agent) 0 timeouts, 0 DB errors
Overloading with many agents 30 agents × 5 messages, 10 projects × 5 agents, 2000 WBQ operations, mixed reservation+message workload 0 errors across all

What Makes the Difference

  • Fewer Git lock acquisitions. The commit coalescer batches rapid-fire writes into fewer git commits (9.1x reduction in the recorded run). Git plumbing commits avoid index.lock in the normal archive path; other Git operations can still contend.
  • Pool exhaustion handled gracefully. Even with 4x more threads than pool connections (60 vs 15), all 600 operations succeeded with 0 timeouts. WAL mode + a bounded 20s busy_timeout lets writers queue rather than fail (and give up before the 30s client deadline).
  • Stale lock recovery works. Crashed-process lock files are detected via PID checking and cleaned up automatically, so a dead agent never holds the archive hostage.
  • Write-behind queue backpressure is clean. 2000 rapid-fire enqueues from 20 threads — all accepted with 0 fallbacks or errors.
  • Read/write concurrency is solid. Concurrent inbox reads and message writes produce 0 errors. WAL mode allows unlimited readers alongside writers.

The stress tests live in crates/mcp-agent-mail-storage/tests/stress_pipeline.rs (Rust unit tests targeting the DB+Git pipeline) and tests/e2e/test_stress_load.sh (HTTP E2E tests hammering a live server through the full network→server→DB→git pipeline).


Installation

One-Liner (recommended)

curl -fsSL "https://raw.githubusercontent.com/Dicklesworthstone/mcp_agent_mail_rust/main/install.sh?$(date +%s)" | bash

Prerequisites: the installer verifies the release with minisign and refuses to install without it, and minimal Linux images also lack xz. macOS: brew install minisign. Debian 12+ and Ubuntu 24.04+: apt install minisign xz-utils. Ubuntu 22.04 and Debian 10 have no minisign package: use the static binary from the minisign releases.

Downloads the right binary for your platform, installs to ~/.local/bin, optionally updates your PATH, and auto-configures detected Codex CLI configs for HTTP MCP URL mode. Downloaded release archives are verified before extraction by default. The requested version is normalized to the release tag vX.Y.Z (or vX.Y.Z-prerelease), and the trust model depends on the release generation:

  • Releases v0.3.31 and later (the current model): releases are built and published by the maintainer's own release infrastructure rather than GitHub Actions, and every release ships a SHA256SUMS manifest plus a detached minisign signature (SHA256SUMS.minisig) made with the maintainer-held release key (id 1BBD79B28BF718D0; the public key is pinned inside the installer). The installer verifies the signature over the exact manifest bytes with minisign, then verifies the archive's SHA-256 against the authenticated manifest. A missing minisign executable, manifest, signature, or checksum entry aborts the install; cosign is not required or consulted for these releases. This replaced the earlier keyless GitHub-Actions Sigstore requirement, which had become unsatisfiable once releases stopped being built by Actions — the trust anchor is now a key the maintainer controls (the same model as the maintainer's other released tools), and verification remains fail-closed. am self-update applies the same model with a built-in minisign verifier (the release public key is embedded in the binary; no minisign executable is needed) and refuses unsigned or tampered manifests. See SECURITY.md.
  • Releases before v0.3.31 (legacy): the installer requires a SHA-256 witness plus a non-empty modern Sigstore bundle that cosign validates against the literal workflow identity https://github.com/Dicklesworthstone/mcp_agent_mail_rust/.github/workflows/dist.yml@refs/tags/<requested-tag> and OIDC issuer https://token.actions.githubusercontent.com, so a valid archive and bundle from any other tag are rejected. A stable cosign v3.1.3 or newer in the v3 line must be on PATH; older versions are rejected because of GHSA-fx35-mq7g-6g98, and v4 is not accepted until its verifier contract is reviewed. Verification forces the modern bundle parser and ignores caller-supplied SIGSTORE_ROOT_FILE, SIGSTORE_REKOR_PUBLIC_KEY, and SIGSTORE_CT_LOG_PUBLIC_KEY_FILE overrides.

In both models, a missing checksum, SHA-256 implementation, signature witness, or required verifier executable—and any malformed or mismatched signature evidence—aborts the install.

Options: --version vX.Y.Z, --dest DIR, --system (installs to /usr/local/bin), --from-source, --verify (run an additional post-install self-test), --no-verify (unsafe explicit escape that skips the checksum and signature checks), --easy-mode (auto-update PATH), --force, --no-service (never install/modify/restart the background service; also implied automatically by a non-default --dest), --uninstall, --yes, --purge. --no-verify allows unauthenticated downloaded binaries to execute during mandatory version probes before installation; malicious archive bytes can therefore run arbitrary code as the installer user. Archive verification applies to downloaded release archives, not source builds. Every downloaded archive must still contain exactly the two expected flat, non-empty regular-file members, and both staged binaries and both installed binaries must report the exact requested version. Those member/version checks are mandatory even with --no-verify; the flag also does not disable the optional broader --verify post-install self-test. A failed archive download never silently changes into a source build. Explicit --from-source checks out the exact requested tag, obtains immutable sibling revisions from that tag's release workflow, builds with cargo --locked, and subjects the staged and installed pair to the same exact-version and byte-preserving transactional replacement gates.

A successful source install prints the path of a mode-private source-receipt retained inside its committed transaction-history directory. The hash-witnessed receipt binds the normalized release tag, the exact Agent Mail/frankensearch/fast_cmaes/beads_rust commits, and both installed binary SHA-256 digests. On current source, the Beads commit records an additional installer checkout; the compiled beads_rust package comes from the registry version and checksum in Cargo.lock. Release tags that predate the immutable dependency-pin record fail closed rather than building mutable sibling branches. Interrupted or rolled-back transactions retain their evidence under an explicitly non-committed history name and are never reported as installed source provenance.

Windows One-Liner (PowerShell)

iwr -useb "https://raw.githubusercontent.com/Dicklesworthstone/mcp_agent_mail_rust/main/install.ps1?$(Get-Random)" | iex

PowerShell enforces the same per-release trust contract before Expand-Archive: for releases v0.3.31 and later it verifies the minisign-signed SHA256SUMS manifest with the same pinned maintainer key (a minisign executable on PATH is required; cosign is not consulted), and for older releases it requires the same stable cosign v3.1.3-or-newer-in-v3 verifier, forces modern-bundle parsing, and isolates verification from the three custom Sigstore trust environment variables named above. The ZIP must contain exactly flat am.exe and mcp-agent-mail.exe, and their staged and post-install version lines must exactly match the requested release. Both executables are replaced under one per-destination installer mutex and rollback domain; backups remain until byte-for-byte installed-digest and version checks pass. Options: -Version vX.Y.Z, -Dest PATH, -Force, -Verify (explicitly request the already-default cryptographic checks), and -NoVerify (the unsafe escape that skips only those checksum and signature checks). Archive-member and exact-version checks still run with -NoVerify, which means unauthenticated downloaded executables run during version probes and may execute arbitrary code as the installer user.

Container (Docker or Podman)

Each release also publishes a linux/amd64 + linux/arm64 image to GHCR, tagged vX.Y.Z, X.Y.Z, 0.3 and latest:

docker run -d --name agent-mail -p 127.0.0.1:8765:8765 \
  -e HTTP_BEARER_TOKEN="$(openssl rand -hex 32)" \
  -v agent-mail-data:/data/mailbox \
  ghcr.io/dicklesworthstone/mcp_agent_mail_rust:latest

The image runs mcp-agent-mail serve --no-tui as the non-root appuser, serving MCP on port 8765 at /mcp/, with the whole mailbox (Git archive and SQLite) under /data/mailbox. Mount a volume there so mail survives replacing the container. Every request needs the token (Authorization: Bearer <token>); clients connect to http://127.0.0.1:8765/mcp/ with it. Keep the published port on loopback unless remote agents should reach it. Docker's healthcheck polls /health/liveness. The image packages the release's Linux glibc binaries unchanged (Dockerfile.release).

From Source

git clone https://github.com/Dicklesworthstone/mcp_agent_mail_rust
cd mcp_agent_mail_rust
./install-local.sh          # builds release, installs to ~/.local/bin
# DEST=/usr/local/bin ./install-local.sh   # custom destination

The script resolves the correct Cargo target directory via cargo metadata, so the installed binary always matches the freshly-built artifact regardless of CARGO_TARGET_DIR overrides or workspace settings. Do not manually copy from target/release/am -- if CARGO_TARGET_DIR is set, that path may be stale.

Requires Rust nightly (see rust-toolchain.toml). On current main, Cargo fetches FrankenSearch 0.6.1 at revision 616c9a7a6bdada97d81f760808996911ba3be294, recorded in Cargo.toml and Cargo.lock and aligned with FRANKENSEARCH_COMMIT in .github/workflows/dist.yml. This revision shares FastMCP's rustix 1.1.5 requirement; the enabled search features retain local Model2Vec and native reranking without FastEmbed or Tokio. A manual build needs no FrankenSearch, fast_cmaes, or Beads sibling checkout. Use cargo build --locked to retain the checked-in dependency graph. Earlier release tags may still require the gated ../frankensearch-rel-0332 and ../fast_cmaes checkouts; install.sh --from-source provisions their recorded revisions. The installer and container recipes retain those additional checkouts for provenance compatibility, while current Cargo builds consume the pinned git source. The mailbox uses registry FrankenSQLite =0.4.10 through SQLModel 0.5.0 on Asupersync 0.5.0. FastMCP 0.10.0 remains pinned to an immutable revision preserving protocol negotiation. Registry beads_rust =0.6.0 retains its separate FrankenSQLite 0.3.18 engine, patched to an immutable Git revision, with default features disabled. See UPGRADE_LOG.md for engine qualification results.

Platforms

Platform Architecture Binary
Linux x86_64 (static; the installer's default) mcp-agent-mail-x86_64-unknown-linux-musl
Linux x86_64 (glibc 2.28+) mcp-agent-mail-x86_64-unknown-linux-gnu
Linux aarch64 mcp-agent-mail-aarch64-unknown-linux-gnu
macOS x86_64 mcp-agent-mail-x86_64-apple-darwin
macOS Apple Silicon mcp-agent-mail-aarch64-apple-darwin
Windows x86_64 mcp-agent-mail-x86_64-pc-windows-msvc.zip

Quick Start

1. Start the server

am

Starts the HTTP server on 127.0.0.1:8765 with the interactive TUI. It leaves existing MCP client configuration untouched: the curl installer configures detected coding agents (Claude Code, Codex CLI, Gemini CLI, OMP, etc.), and am setup run (or am serve-http --setup) configures them explicitly afterwards.

2. Agents register and coordinate

Once the server is running, agents use MCP tools to coordinate:

Terminology note: ensure_project takes a human_key, which must be the absolute repo path. Most follow-on tools take project_key, which can be that same absolute path or the project's computed slug.

# Register identity
ensure_project(human_key="/abs/path/to/repo")
register_agent(project_key="/abs/path/to/repo", program="claude-code", model="opus-4.6")

# Reserve files before editing
file_reservation_paths(project_key="/abs/path/to/repo", agent_name="GreenCastle", paths=["src/**"], ttl_seconds=3600, exclusive=true)

# Send a message
send_message(project_key="/abs/path/to/repo", sender_name="GreenCastle", to=["BlueLake"],
             subject="Starting auth refactor", body_md="Taking src/auth/**",
             thread_id="FEAT-123", ack_required=true)

# Check inbox
fetch_inbox(project_key="/abs/path/to/repo", agent_name="BlueLake")
acknowledge_message(project_key="/abs/path/to/repo", agent_name="BlueLake", message_id=123)

3. Use macros for common flows

# Boot a full session (ensure project + register agent + reserve files + fetch inbox)
macro_start_session(human_key="/abs/path/to/repo", program="claude-code", model="opus-4.6")

# Prepare for a thread (fetch context + recent messages)
macro_prepare_thread(project_key="/abs/path/to/repo", thread_id="FEAT-123",
                     program="claude-code", model="opus-4.6")

# Reserve, work, release cycle
macro_file_reservation_cycle(project_key="/abs/path/to/repo", agent_name="GreenCastle",
                             paths=["src/auth/**"], ttl_seconds=3600, auto_release=true)

# Contact handshake between agents in different projects: approves the link and
# delivers the welcome into the target's project (without auto_accept the link
# stays pending, no welcome is sent, and the response says why; auto_accept never
# overrides a link the target has blocked)
macro_contact_handshake(project_key="/abs/path/to/repo", requester="GreenCastle",
                        target="BlueLake", to_project="/abs/path/to/other/repo",
                        auto_accept=true, welcome_subject="Coordination channel",
                        welcome_body="Use thread FEAT-123 for the cutover")

Agent Configuration

The installer and am command auto-detect installed agents and write their configs in HTTP URL mode. The examples below are the manual fallback.

Claude Code

Add to your project's .mcp.json or ~/.claude/settings.json:

{
  "mcpServers": {
    "agent-mail": {
      "command": "mcp-agent-mail",
      "args": []
    }
  }
}

Or for HTTP transport (when the server is already running):

{
  "mcpServers": {
    "agent-mail": {
      "type": "url",
      "url": "http://127.0.0.1:8765/mcp/"
    }
  }
}

Codex CLI

The curl installer writes this automatically for detected Codex CLI installs. For source installs, manual setup, or custom endpoint overrides, add this to ~/.codex/config.toml:

[mcp_servers.mcp_agent_mail]
url = "http://127.0.0.1:8765/mcp/"
# Add this when HTTP bearer auth is enabled:
http_headers = { Authorization = "Bearer <HTTP_BEARER_TOKEN>" }

Gemini CLI

Add to ~/.gemini/settings.json:

{
  "mcpServers": {
    "agent-mail": {
      "command": "mcp-agent-mail",
      "args": []
    }
  }
}

For a project-local Gemini setup plus Agent Mail identity registration:

scripts/register_gemini.sh /abs/path/to/repo
AGENT_NAME=BlueLake AGENT_MODEL=gemini-2.5-pro scripts/register_gemini.sh /abs/path/to/repo

Oh My Pi (OMP)

am setup run --agent omp writes OMP's native project config at .omp/mcp.json and the active profile's user config. The default profile uses ~/.omp/agent/mcp.json; named profiles selected through OMP_PROFILE (or the legacy PI_PROFILE) use ~/.omp/profiles/<name>/agent/mcp.json. Profile names must use OMP's lowercase [a-z0-9][a-z0-9._-]{0,63} syntax; an invalid explicit profile fails closed instead of redirecting setup to the default. Setup also honors OMP's PI_CONFIG_DIR and default-profile PI_CODING_AGENT_DIR overrides, with a stricter path-safety contract: neither may contain .. components. PI_CONFIG_DIR is rooted beneath the user's home directory; a relative PI_CODING_AGENT_DIR is resolved against the working directory, and an absolute path is accepted. For example, use agent instead of foo/../agent. Invalid overrides cause setup to fail before writing config files; setup also refuses symlink traversal when accessing those files. The project config is profile-independent and applies under every named OMP profile.

am setup run --agent omp --no-user-config leaves active-user bytes untouched, but setup and status still read the authorities that decide whether the project entry can run. They check the active canonical mcp.json disabledServers list against all three accepted Agent Mail aliases (mcp-agent-mail, mcp_agent_mail, and agent-mail). OMP also discovers the read-only .mcp.json sibling after mcp.json in both project and active-user scopes. An exact repeated server name is shadowed by the earlier source, while a different Agent Mail alias remains a separate MCP key; setup status reports and fingerprints that conflict without mirror-writing the secondary file. Status also evaluates mcp.enableProjectConfig with OMP's persistent settings precedence: the active profile's config.yml (falling back to config.yaml only when it is absent). When both main YAML names are absent, an existing legacy settings.json or agent.db remains a migration authority and setup fails closed until OMP has migrated it or an operator has inspected it. Project settings providers then merge in runtime order: .omp/settings.json, .omp/config.yml, .claude/settings.json, .codex/config.toml, .gemini/settings.json, project-root opencode.json and opencode.jsonc, .opencode/opencode.json and .opencode/opencode.jsonc, and .cursor/settings.json. Ordered ambient PI_CONFIG_FILES overlays apply last. Foreign-provider parse failures are skipped as OMP skips them. Unsafe or unreadable paths still fail closed: OMP may follow a symlink or otherwise read bytes that Agent Mail deliberately refuses to trust, so treating that authority as absent could produce a false-green verdict. The active-profile YAML, native .omp/config.yml, and explicit overlays are also strict authorities. An effective false, an unsupported authority, or a missing explicit overlay is reported as runtime-relevant drift instead of success. OpenCode {env:...} and {file:...} substitutions are also reported unsupported because their external dependencies cannot be proven by a cache of the config bytes alone. Remediation deliberately omits --no-user-config, because a project-only write cannot overcome an authority that excludes every project MCP source.

This check is bound to the persistent files and the PI_CONFIG_FILES value in the am process environment. A one-shot OMP --config argument or a programmatic runtime settings override is launch-specific and is outside this static setup/status boundary; run am setup status under the same ambient overlay environment used to launch OMP.

To configure it manually:

{
  "mcpServers": {
    "mcp-agent-mail": {
      "type": "http",
      "url": "http://127.0.0.1:8765/mcp/"
    }
  }
}

When HTTP bearer authentication is enabled, add an Authorization entry under headers; am setup run does this automatically and adds .omp/mcp.json to the project .gitignore so the credential is not committed.

Any MCP-Compatible Client

Agent Mail supports both stdio and HTTP transports:

  • stdio: Run mcp-agent-mail as a subprocess (the default for most MCP clients)
  • HTTP: Connect to http://127.0.0.1:8765/mcp/ when the server is running via am or mcp-agent-mail serve

One server process owns a mailbox at a time. While am (or mcp-agent-mail serve) is serving a storage root, a stdio mcp-agent-mail started for the same root refuses to start ("another Agent Mail server is already serving this storage root") instead of contending for it, so configure clients with the HTTP URL whenever am runs; the installer and am setup run write HTTP URL entries.

  • Examples: Token-free client templates live under docs/examples/mcp/

Server Modes

MCP Server (default)

mcp-agent-mail                          # stdio transport (for MCP client integration)
mcp-agent-mail serve                    # HTTP server with TUI (default 127.0.0.1:8765)
mcp-agent-mail serve --no-tui           # Headless server (CI/daemon mode)
mcp-agent-mail serve --reuse-running    # Reuse existing server on same port

CLI Operator Tool

am                                      # Start server + TUI (non-interactive: robot status)
am serve-http --port 9000               # Different port
am serve-http --setup                   # Explicitly update detected MCP clients before serving
am serve-http --host 0.0.0.0            # Bind to all interfaces
am serve-http --no-auth                 # Skip authentication (local dev)
am serve-http --path api                # Use /api/ transport instead of /mcp/
am --help                               # Full operator CLI

Server startup preserves existing MCP client configuration by default, including project and user-level URLs. Use am setup run to configure clients separately, or am serve-http --setup to update detected clients to that launch's endpoint. The setup flag can rewrite existing entries; omit it for temporary test servers.

For CLI deployments that require an explicitly supplied sending credential, set AGENT_MAIL_REQUIRE_EXPLICIT_SENDER_TOKEN=1. am mail send then requires --sender-token, --sender-token-file, or AGENT_MAIL_SENDER_TOKEN; it refuses to borrow a token from persisted agent identity state. The same resolver applies to queued-send replay and contact handshakes (use the environment variable where the command has no token flag). The default retains automatic token reuse. This controls CLI token selection; it does not establish MCP session ownership or isolate hostile processes sharing the same OS account. Server-side verified send enforcement remains MESSAGING_FAIL_CLOSED_SEND_PROFILE=true.

Session-bound agent identity (opt-in)

Available since v0.3.39 (see CHANGELOG.md). Set MESSAGING_SESSION_IDENTITY=true on the server to let an MCP session act as the identities it established without a registration token in the model-visible transcript:

  • The session. Over stdio, the session is the client's server process. Over Streamable HTTP, a successful initialize returns an Mcp-Session-Id header; requests that send it back (over any connection) share the session, and DELETE on the MCP endpoint with the header ends it. A session is tied to the Authorization it was created under: another credential, an unknown id or an expired id (24 h idle) gets a stateless request. Sessions live in memory, so a restart ends them. Bearer/JWT authorization is still checked on every request: this is identity context inside an authorized connection, not authentication.
  • Binding. create_agent_identity (use return_registration_token: false to keep the token out of the transcript) binds the new agent, and so does a register_agent that creates its agent. Naming an existing agent never binds it.
  • Acting as a bound agent. send_message / reply_message from the session as a bound agent are verified without sender_token ("verified_sender": true, "sender_verification": "session"), including under MESSAGING_FAIL_CLOSED_SEND_PROFILE.
  • No borrowed names. A session that holds an identity in a project cannot, in that project, send/reply, read an inbox (fetch_inbox, fetch_inbox_events, or that project's view in fetch_inbox_product), mark read or acknowledge, reserve/renew/release files, manage build slots, request a forced reservation release, or re-register (rewriting the profile and rotating the token) as any other agent: those calls fail with SESSION_IDENTITY_MISMATCH (a send or reply that carries the other agent's own sender_token is still accepted). A session holding no identity in the project keeps the trusted-local behavior.
  • Database outages. ACKs and reservation releases can be queued durably after the acting identity is authorized. If an outage prevents that check and the session holds a potentially relevant binding, the call returns the database error without queuing an intent, even for its own supplied name. Retry after database access recovers. Already authorized intents remain replayable when another session triggers recovery.
  • Lifecycle. retire_agent / deregister_agent drop the identity from the calling session; lifecycle authorization itself is unchanged (registration token, or a bound tmux pane over stdio).

This does not authenticate the model, provider or OS process, and it cannot isolate hostile clients that share one bearer credential and can read each other's session ids.

When an interactive am finds a healthy Agent Mail service already serving the configured endpoint, it attaches a read-only terminal view to that service's /mail/ws-state snapshot. It does not stop or restart the service, acquire a mutation lock, or run service-control commands. --takeover remains the explicit opt-in for a local replacement.

Dual-Mode Interface

This project keeps MCP server and CLI command surfaces separate:

Use case Entry point Notes
MCP server (default) mcp-agent-mail Default: MCP stdio transport. HTTP: serve.
CLI (operator + agent-first) am Recommended CLI entry point.
CLI via single binary AM_INTERFACE_MODE=cli mcp-agent-mail Same CLI surface, one binary.

Running CLI-only commands via the MCP binary produces a deterministic denial on stderr with exit code 2, and vice versa, preventing accidental mode confusion in automated workflows.


Operator CLI Surface

am is more than a launcher. It is the operator surface for runtime control, diagnostics, migration, exports, benchmarking, and agent-facing non-interactive workflows. In non-interactive contexts, bare am automatically falls back to robot output instead of trying to launch a blocking TUI.

Command Families

Surface Subcommands / form What it is for
Runtime serve-http, serve-stdio, `service install status
Quality gates ci, verify, lint, typecheck, bench Run the native quality pipeline, build-slot-protected verification lanes, and CLI/perf baselines
E2E and determinism `e2e list run
Share and deploy `share export update
Archive and recovery `archive save list
Coordination data agents ..., mail ..., contacts ..., macros ..., file_reservations ..., acks ..., list-acks Operate directly on the same concepts the MCP tools expose
Project and product routing projects ..., products ..., list-projects, beads ... Manage project identity, cross-project product groupings, and task-tracker views
Platform and setup `setup run status, config set-port
Migration and lifecycle `legacy detect import
Break-glass admin clear-and-reset-everything Fully reset local state after optional archival. A running server holding the mailbox is stopped for the wipe: a systemd user service or LaunchAgent through its manager (and started again afterwards), any other Agent Mail process with SIGTERM, then SIGKILL after a grace period. Use sparingly.

Setup Drift Reports

am setup status is read-only. It inventories supported MCP clients, reports the current redacted server entry beside the expected entry, and labels drift such as missing_file, legacy_stdio, stale_http_path, wrong_bearer_header, wrong_startup_timeout, duplicate_server_entries, and unsupported_config.

Use the reported remediation in two steps:

am setup status --format json
am setup run --dry-run --project-dir "$PWD" --format toon
am setup run --yes --project-dir "$PWD" --format toon

For bearer-header checks, pass --token to am setup status or expose the expected token through HTTP_BEARER_TOKEN / config.env; status output redacts token values.

Family Detail

Family Current subcommands / modes
share export, update, preview, verify, decrypt, wizard, static-export, deploy validate, deploy tooling, deploy verify, deploy verify-live
archive save, list, restore
guard install, uninstall, status, check
file_reservations list, active, soon, reserve, renew, release, conflicts
acks pending, remind, overdue
projects mark-identity, discovery-init, adopt
mail status, send, reply, inbox, read, ack, search, summarize-thread, replay-queued, discard-queued
products ensure, link, status, search, inbox, summarize-thread
doctor (28 verbs) check, health, triage, locks, drain, fix, undo, ls, explain, fixers, capabilities, robot-docs, artifacts, reclaim, selftest, mcp-selftest, write-selftest, support-bundle, repair, reconstruct, backups, restore, archive-scan, archive-verify, archive-normalize, fix-orphan-refs, pack-archive, vacuum
agents register, create, list, show, detect, reap, resolve-pane
tooling directory, schemas, metrics, metrics-core, diagnostics, locks, decommission-fts
macros start-session, prepare-thread, file-reservation-cycle, contact-handshake
contacts request, respond, list, policy
beads ready, list, show, status
setup run, status
golden capture, verify, list
flake-triage scan, reproduce, detect
robot status, inbox, timeline, overview, thread, search, message, navigate, reservations, metrics, health, analytics, agents, contacts, projects, attachments, atc, handoff, tui-dump
verify cargo-fmt, cargo-check, cargo-clippy, cargo-test, e2e-list, e2e-stdio, bench-quick, reliability-coverage
legacy detect, import, status
service install, uninstall, status, logs, restart

Durable Inbox Event Cursors

am inbox-events is the CLI for restart-safe inbox monitors. It reads append-only, body-free delivery events oldest first; its --after value is a delivery cursor, never a message ID. Persist next_cursor only after the corresponding events have been processed.

# Establish a durable starting position without consuming events.
am inbox-events --project /abs/path --agent BlueLake --position-now --format json

# Resume from the last processed delivery cursor.
am inbox-events --project /abs/path --agent BlueLake --after 42 --limit 100 --format json

Each page includes events, next_cursor, has_more, oldest_available_cursor, and tail_cursor. A cursor below retained history fails with code cursor_expired (the fetch_inbox_events tool reports CURSOR_EXPIRED); a cursor beyond the tail fails with cursor_ahead (CURSOR_AHEAD). Restart from the error's oldest_available_cursor or tail_cursor.


The 45 MCP Tools

9 Clusters

Cluster Count Tools
Infrastructure 4 health_check, ensure_project, install_precommit_guard, uninstall_precommit_guard
Identity 9 register_agent, create_agent_identity, retire_agent, unretire_agent, deregister_agent, whois, resolve_pane_identity, cleanup_pane_identities, list_agents
Messaging 9 send_message, reply_message, fetch_inbox, fetch_topic, fetch_inbox_events, get_message_delivery_receipt, acknowledge_message, mark_message_read, mark_all_read
Contacts 4 request_contact, respond_contact, list_contacts, set_contact_policy
File Reservations 5 check_file_reservation_conflicts, file_reservation_paths, renew_file_reservations, release_file_reservations, force_release_file_reservation
Search 2 search_messages, summarize_thread
Macros 4 macro_start_session, macro_prepare_thread, macro_contact_handshake, macro_file_reservation_cycle
Product Bus 5 ensure_product, products_link, search_messages_product, fetch_inbox_product, summarize_thread_product
Build Slots 3 acquire_build_slot, renew_build_slot, release_build_slot

fetch_inbox_events is the MCP equivalent of am inbox-events: it is a durable, recipient-scoped delivery feed for monitors, not a replacement for message-body inbox reads.

file_reservation_paths accepts an optional idempotency_key for an exact retry after a timeout. The same key and normalized request replay the original grant without a second lease or archive write and mark the response idempotent_replay: true. Reusing a key with different arguments returns IDEMPOTENCY_KEY_CONFLICT. Keys are scoped to (project, tool), expire after the configured retention window, and are optional; this is not a blanket idempotency claim for every mutating tool.

On replay, granted contains the original IDs and expiries, even after release or expiry; it does not reacquire those paths. conflicts is a current snapshot. A partial or empty original grant stays partial or empty; use a new key for a new acquisition attempt. Fresh reacquisition of an active lease reuses its ID and applies the requested exclusive/shared mode and reason after checking peers.

Keys recorded before the normalized path-set fingerprint update can return IDEMPOTENCY_KEY_CONFLICT after an upgrade, even within their retention window. The original grants remain unchanged; inspect them before making a new attempt.

25 MCP Resources

Read-only resources span environment/config inspection, project and agent discovery, inbox and thread views, reservation views, and tooling diagnostics. They are there so agents can fetch state cheaply without mutating anything.

resource://inbox/{Agent}?project=<abs-path>&limit=20
resource://thread/{id}?project=<abs-path>&include_bodies=true
resource://mailbox/{Agent}?project=<abs-path>
resource://views/ack-overdue/{Agent}?project=<abs-path>
resource://agents/<project-slug>
resource://file_reservations/<project-slug>?active_only=true
resource://tooling/metrics
resource://config/environment

resource://agents/... and resource://file_reservations/... take the project in the path segment. Inbox, mailbox, thread, and message resources put the agent or thread in the path and the project in the query string.

Macros vs. Granular Tools

  • Prefer macros when you want speed or are on a smaller model: macro_start_session, macro_prepare_thread, macro_file_reservation_cycle, macro_contact_handshake
  • Use granular tools when you need control: register_agent, file_reservation_paths, send_message, fetch_inbox, acknowledge_message

TUI Operations Console

The interactive TUI has 16 screens. Jump directly with 1-9, 0 (screen 10), and shifted digits !, @, #, $, %, ^ (screens 11-16). Use Tab/Shift+Tab to cycle in order.

# Screen Shows
1 Dashboard Real-time operational overview with event stream and anomaly rail
2 Messages Message browser with detail pane, presets, and compose/reply flows
3 Threads Thread explorer and conversation drill-down
4 Agents Agent roster with activity, state, and quick actions
5 Search Unified multi-scope search with facets and preview
6 Reservations File reservation status, conflicts, and create/release actions
7 Tool Metrics Per-tool call counts, latency distributions, and failures
8 System Health Probe/circuit/disk/memory diagnostics plus ATC health widget
9 Timeline Events/Commits/Combined/Log timeline views with inspector
10 Projects Project inventory and routing helpers
11 Contacts Contact links, policy view, and graph/mermaid modes
12 Explorer Unified inbox/outbox explorer with direction and ack filters
13 Analytics Anomaly insight feed with confidence and deep links
14 Attachments Attachment inventory with preview and provenance
15 Archive Browser Two-pane Git archive browser with tree + file preview
16 ATC Snapshot-driven ATC control surface with decision drill-in and retention report

Global keys: ? help, Ctrl+P/: command palette, / the screen's own filter (global search focus on screens without one), . contextual action menu, Ctrl+N compose overlay, Ctrl+Y toast-focus mode, Ctrl+T/Shift+T cycle theme, m toggle MCP/API transport, q quit. ATC keys: d decision detail, r retention report, i toggle detail pane, Tab switch agents/decisions.

Screen-specific highlights: Messages uses g for Local/Global inbox; Threads uses e/c for expand/collapse-all in conversation view; Timeline uses V for Events/Commits/Combined/Log and v for visual selection; Search uses f + facet rail navigation for scope/sort/field controls; Contacts uses n to cycle Table/Graph/Matrix (sender × recipient heatmap); batch-capable screens share Space/v/A/C; preset-enabled screens use Ctrl+S/Ctrl+L.

Command palette: Press Ctrl+P (or : outside text-entry) to open a searchable action launcher that includes screen navigation, transport/layout controls, and dynamic entities (agents/projects/threads/tools/reservations).

Themes: 42 named palettes are in the Ctrl+T/Shift+T cycle (Cyberpunk Aurora, Darcula, Lumen Light, Nordic Frost, High Contrast, Dracula, Monokai, Nord, and more; see NAMED_THEMES in crates/mcp-agent-mail-server/src/tui_theme.rs). The five config-level themes are selectable through TUI_THEME. Accessibility support includes high-contrast mode and reduced motion. Archive Browser note: use Enter to expand/preview, Tab to switch tree vs preview pane, / to filter filenames, and Ctrl+D/U for preview paging.

If an interactive am attaches to an already running service, this terminal is read-only and detaches with Ctrl-C; the service keeps serving MCP/API traffic. Use an explicit --takeover only when replacement is intended.


Robot Mode (am robot)

Non-interactive, agent-first CLI surface for TUI-equivalent situational awareness. Use it when you need structured snapshots quickly, especially in automated loops and when tokens matter.

19 Subcommands

Command Purpose Key flags
am robot status Dashboard synthesis --format, --project, --agent
am robot inbox Actionable inbox with urgency/ack synthesis --urgent, --ack-overdue, --unread, --all, --limit, --include-bodies
am robot timeline Project event stream (last 24h unless --since) --since, --kind, --source
am robot overview Cross-project summary --format, --counts
am robot thread <id> Full thread rendering --limit, --since, --format
am robot search <query> Full-text search with facets/relevance --kind, --importance, --since, --format
am robot message <id> Single-message deep view --format, --project
am robot navigate <resource://...> Resolve resources into robot-formatted output --format, --project, --agent
am robot reservations Reservation view with conflict/expiry awareness; JSON entries carry absolute granted_ts/expires_ts (RFC 3339 UTC) and integer remaining_seconds/granted_age_seconds next to the humanized remaining --all, --conflicts, --expiring, --agent
am robot metrics Tool call rates, failures, latency percentiles (server-wide) --format
am robot health Runtime/system diagnostics (server-wide) --format, --include-host
am robot analytics Anomaly and remediation summary --format, --project, --agent
am robot agents Agent roster and activity overview --active, --sort
am robot contacts Contact graph (incoming cross-project requests included) and policy surface --format, --project, --agent (links on either side)
am robot projects Per-project aggregate stats (every project) --format
am robot attachments Attachment inventory --format, --project
am robot atc Live ATC snapshot with local DB fallback when the server is unavailable --since, --stratum, --summary-only, --limit
am robot handoff Read-only stale bead ownership and handoff dashboard --stale-minutes, --active-minutes, --fresh-comment-minutes, --include-fresh, --dry-run
am robot tui-dump TUI freeze escape hatch (alias of am tui-dump): the situational snapshot the TUI renders, fetched live or from local SQLite --format

Output Formats

  • toon (default at TTY): Token-efficient, compact, optimized for agent parsing
  • json (default when piped): Machine-readable envelope with _meta, _alerts, _actions
  • md (thread/message-focused): Human-readable narrative for deep context

am robot overview summarizes the whole mailbox; the global --project and --agent flags do not scope this command. Message counts use one grouped query, and active reservations use one candidate scan with release-ledger filtering. --counts returns totals from the same aggregation. The snapshot cache is process-local, so separate CLI invocations each read the database.

am robot atc reads the live ATC snapshot over /mail/ws-state when the local server is running and falls back to a local SQLite rollup/liveness view when that snapshot is unavailable. Use --since to trim recent decisions/executions, --stratum to focus open-stratum counts, and --summary-only for the compact health view.

am robot handoff correlates in-progress beads with Agent Mail activity, active file reservations, thread mail, and recent comments. It is always read-only: reopen/takeover rows contain proposed br update ... --status open --json commands, but agents must inspect reservations, peer dirty work, and the related thread before running them.

Agent Workflow Recipes

# Startup triage
am robot status --project /abs/path --agent AgentName

# Immediate urgency pass
am robot inbox --project /abs/path --agent AgentName --urgent --format json

# Incremental monitoring loop
am robot timeline --project /abs/path --agent AgentName --since 2026-02-16T10:00:00Z

# Deep thread drill-down
am robot thread br-123 --project /abs/path --agent AgentName --format md

# Reservation safety check before edits
am robot reservations --project /abs/path --agent AgentName --conflicts --expiring 30

# Heavy verification recommendation with build-slot admission and rch
am robot status --project /abs/path --agent AgentName --format json \
  | jq '.recommendations[] | select(.category == "verification_lane")'

File Reservations for Multi-Agent Editing

Before editing, agents reserve file paths to avoid conflicts:

file_reservation_paths(project_key, agent_name, paths=["src/**"], ttl_seconds=3600, exclusive=true)

The pre-commit guard (mcp-agent-mail-guard) installs as a Git hook and blocks commits that touch files reserved by other agents. Reservations are advisory, TTL-based, and support glob patterns.

New reservation artifacts are named id-<reservation-id>-g<db-generation>.json and carry the same generation in their JSON. This prevents a re-created SQLite database from overwriting an older generation's row-id artifact. Readers still recognize legacy id-<reservation-id>.json artifacts; parity reports stamped artifacts from a different live generation as foreign-generation debris rather than current reservation drift.

The guard has narrow operational exemptions: .beads/** metadata is always exempt so shared task-state commits cannot be stranded, and an agent's own reservation does not conflict with its commit. Source files reserved by another agent remain protected. When no matching mailbox archive can be found (including a proven slug collision), the hook emits a warning and allows the commit rather than turning stale/missing mailbox state into a universal commit gate.

The pre-push hook lists the paths a push touches with one streamed git rev-list | git diff-tree --stdin pipeline per pushed ref, so its cost no longer grows with a git process per commit. The scan is bounded by AGENT_MAIL_GUARD_PUSH_MAX_COMMITS (default 2000 commits per ref, newest first), AGENT_MAIL_GUARD_PUSH_MAX_PATHS (default 100000 path records per ref) and AGENT_MAIL_GUARD_PUSH_TIMEOUT_SECS (default 120 seconds for the whole push); 0 removes a bound. Reaching a bound truncates the scan: the paths already read are still checked, and if none of them conflicts while another agent holds an active lease, the hook fails closed (exit 2) naming what was skipped and which variable to raise, the same way every other unfinished inspection does. AGENT_MAIL_GUARD_MODE=warn turns that into a warning.

Area Reserve glob
Core types/config crates/mcp-agent-mail-core/src/**
SQLite layer crates/mcp-agent-mail-db/src/**
Git archive crates/mcp-agent-mail-storage/src/**
Tool implementations crates/mcp-agent-mail-tools/src/**
TUI crates/mcp-agent-mail-server/src/tui_*.rs
CLI/launcher crates/mcp-agent-mail-cli/src/**

Multi-Agent Coordination Workflows

How Agents Interact (Protocol Flow)

sequenceDiagram
    participant A as Agent A (GreenCastle)
    participant S as Agent Mail Server
    participant B as Agent B (BlueLake)

    A->>S: ensure_project(human_key="/abs/path")
    A->>S: register_agent(project_key="/abs/path", program="claude-code")
    S-->>A: identity: GreenCastle

    B->>S: register_agent(project_key="/abs/path", program="codex-cli")
    S-->>B: identity: BlueLake

    A->>S: file_reservation_paths(project_key="/abs/path", agent_name="GreenCastle", paths=["src/auth/**"], exclusive=true)
    S-->>A: reserved ✓

    B->>S: file_reservation_paths(project_key="/abs/path", agent_name="BlueLake", paths=["src/auth/**"], exclusive=true)
    S-->>B: CONFLICT — reserved by GreenCastle

    B->>S: file_reservation_paths(project_key="/abs/path", agent_name="BlueLake", paths=["tests/api/**"], exclusive=true)
    S-->>B: reserved ✓

    A->>S: send_message(project_key="/abs/path", sender_name="GreenCastle", to=["BlueLake"], thread_id="FEAT-123", ack_required=true)
    S-->>B: new message in inbox

    B->>S: fetch_inbox(project_key="/abs/path", agent_name="BlueLake")
    S-->>B: [message from GreenCastle]

    B->>S: acknowledge_message(project_key="/abs/path", agent_name="BlueLake", message_id=1482)
    S-->>A: ack received ✓

    A->>S: release_file_reservations(project_key="/abs/path", agent_name="GreenCastle", paths=["src/auth/**"])
    S-->>A: released ✓

Same Repository

  1. Register identity: ensure_project(human_key=<abs-path>) + register_agent(project_key=<abs-path>, ...)

  2. Reserve files before editing: file_reservation_paths(project_key, agent_name, paths=["src/**"], ttl_seconds=3600, exclusive=true)

  3. Communicate with threads: send_message(..., thread_id="FEAT-123"), check with fetch_inbox, acknowledge with acknowledge_message

  4. Quick reads: resource://inbox/{Agent}?project=<abs-path>&limit=20

  5. Tell the whole project once: address the project's shared mailbox as project:<slug> (or project:<human_key>). Available since v0.3.39 (see CHANGELOG.md):

    send_message(project_key="/abs/path", sender_name="GreenCastle",
                 to=["project:abs-path"], subject="Schema freeze until 18:00",
                 body_md="Do not touch migrations/ until the release is cut.", ack_required=true)
    
    • The message is stored once, with one project delivery: no per-agent copies, rows, signals or archive inbox files. This is not broadcast (which stays rejected).
    • Every other non-retired agent of the project that was registered when the message was sent sees it in fetch_inbox (and resource://inbox/..., am robot inbox) with "via": "project". An agent that joins later is not handed the backlog; project history stays readable through threads, fetch_topic and search. An agent whose contact_policy is block_all opts out, and a direct to/cc/bcc delivery to the same agent takes precedence so nothing appears twice.
    • Read and acknowledgement state is per agent and recorded lazily: fetch_inbox read receipts, mark_message_read, acknowledge_message and mark_all_read record only the calling agent's state. get_message_delivery_receipt reports the delivery under project_mailbox with each visible agent's read/ack state.
    • It can be combined with named recipients in to/cc, cannot be bcc, and can only name the sender's own project. Replies may address it too.
    • Retention treats a project message as settled only after every agent that can see it has read it (and acknowledged it when required). Archive reconstruction restores the delivery from the canonical message's to/cc envelope, and salvage restores the per-agent receipts.
    • Restart-safe monitors see it too: a project delivery appends one event to the durable fetch_inbox_events / am inbox-events cursor ledger, and each visible agent's page includes it ("kind": "project") under the same single cursor.
    • am robot overview and am robot overview --counts include each visible agent's unread, urgent-unread, and overdue acknowledgement obligations. One shared message can therefore count for several agents, while a named recipient is counted only once; generating the overview never creates receipts or marks mail read.

Across Different Repos

  • Option A (single project bus): Register both repos under the same project_key. Keep reservation patterns specific (frontend/** vs backend/**).

  • Option B (separate projects): Each repo has its own project_key. Link two agents with a contact handshake, then address the other agent with a project-qualified name:

    macro_contact_handshake(project_key="/abs/path/frontend", requester="BlueLake",
                            target="GreenCastle", to_project="/abs/path/backend",
                            auto_accept=true)
    send_message(project_key="/abs/path/frontend", sender_name="BlueLake",
                 to=["GreenCastle@/abs/path/backend"], subject="API change",
                 body_md="Please bump /v2.", thread_id="br-124", ack_required=true)
    
    • Name@project takes the project's human key or its slug (as returned by ensure_project and listed by resource://projects), and so does the older project:<project>#Name form. The qualified name is resolved in that project only and is never auto-registered.
    • The message is delivered only while an approved, unexpired contact link exists between the two agents (in either direction). Without one the send fails with CONTACT_REQUIRED and a ready macro_contact_handshake call; a blocked link or a block_all recipient fails with CONTACT_BLOCKED. This holds even when the recipient's own contact policy is open.
    • The message is stored in the recipient's project, so the recipient reads it with its ordinary fetch_inbox, acknowledges it with acknowledge_message, and answers with reply_message in its own project. A default reply to a message from another project goes back to the original sender in that sender's project over the same link.
    • One message lives in one project: a send may address agents of one other project, or local agents, but not both (INVALID_ARGUMENT; send twice instead).
    • A plain name still addresses only the sender's project. Naming a contact from another project, or an agent registered in another project of the same product, is refused with CROSS_PROJECT_RECIPIENT (the error carries the qualified address to use); no same-name placeholder is created.

    The product bus (fetch_inbox_product, search_messages_product, summarize_thread_product) aggregates an agent's inboxes and threads across the linked projects.

External Git Coordination (opt-in)

mcp-agent-mail serializes its own in-process git shell-outs via per-repo flock on <repo>/.git/am.git-serialize.lock. External tools (wrapper scripts, editor integrations, CI) can honor the same sentinel by invoking git through scripts/git-with-amlock.sh:

# one-time: put the wrapper on PATH
install -m755 scripts/git-with-amlock.sh ~/.local/bin/git-with-amlock

# per-shell: resolve an "am-aware" git for automation
export GIT_BIN="$(command -v git-with-amlock || command -v git)"
alias gitam='"$GIT_BIN"'

# your automation invokes gitam instead of git:
gitam status
gitam commit -m 'synced'

Scope of the mitigation:

  • mcp-agent-mail's own in-process calls → always coordinated (automatic).
  • External tools that opt into the wrapper → coordinated.
  • Your IDE running git commit directly, with no wrapper → still races. The only complete mitigation for that case is to set AM_GIT_BINARY to a safe git binary, or upgrade/downgrade the system git. See the Known-bad git versions section.

The wrapper honors AM_GIT_BINARY for binary selection and AM_GIT_FLOCK_TIMEOUT_SECS (default 60s) for the bounded wait cap. Exit code 75 (EX_TEMPFAIL) signals a flock timeout.

With Beads Task Tracking

Agent Mail pairs with Beads (br) for dependency-aware task selection:

  1. Pick ready work: br ready --json (choose highest priority, no blockers)
  2. Reserve edit surface: file_reservation_paths(..., reason="br-123")
  3. Announce start: send_message(..., thread_id="br-123", subject="[br-123] Start: <title>", ack_required=true)
  4. Work and update: Reply in-thread with progress
  5. Complete and release: br close 123, release_file_reservations(...), final Mail reply

Use the Beads issue ID (br-123) as the Mail thread_id and prefix message subjects with [br-123] to keep everything linked.

Web UI

The server includes a lightweight, server-rendered web UI for humans at /mail/. Agents should continue using MCP tools and resources; the web UI is for human review and oversight. Use /mail/* for mailbox and task oversight. The authenticated live browser TUI mirror at /web-dashboard remains deferred and is not a supported surface.

The project website separately embeds the real production DashboardScreen through crates/mcp-agent-mail-dashboard-wasm/ and FrankenTUI's browser renderer. That surface is an interactive, read-only public replay: six SQLite aggregate counts are real, while all names, paths, messages, and replay events are synthetic. It does not connect to a visitor's mailbox or expose the deferred /mail/ws-input path. The deterministic opening frame carries 192 synthetic history events, 500 agent rows, 41 project rows, and 200 contact rows so the production dashboard opens at the same busy visual density as a real operator session without publishing mailbox content. See docs/SPEC-browser-parity-contract-deferred.md for the shipped replay boundary and the still-deferred live parity contract.

Routes

Route What You See
/mail/ and /mail/unified-inbox Unified inbox across all projects with importance filtering and related-project context
/mail/projects Project inventory page for browsing into one mailbox at a time
/mail/{project} Project overview with search entry point, agent roster, and quick links
/mail/{project}/inbox/{agent} Reverse-chronological inbox for one agent with pagination and mark-read actions
/mail/{project}/message/{id} Full message detail with metadata, recipients, thread preview, and attachments
/mail/{project}/thread/{thread_id} Full thread rendering with chronological conversation context
/mail/{project}/search?q=... Search V3-powered query route with field filters (subject:foo, body:"multi word")
/mail/{project}/file_reservations Active and historical file reservations
/mail/{project}/attachments Messages with attachments and provenance
/mail/{project}/overseer/compose Human compose form for pushing high-priority instructions to agents
/mail/api/unified-inbox JSON feed backing the unified inbox
/mail/api/projects/{project}/agents JSON roster for one project
/mail/archive/* Archive browser and time-travel routes

Auth: set HTTP_BEARER_TOKEN for production. For local dev, set HTTP_ALLOW_LOCALHOST_UNAUTHENTICATED=true to browse without headers.

Human Overseer

Sometimes you need to redirect agents mid-session. The Overseer compose form at /mail/{project}/overseer/compose lets humans send high-priority messages directly to any combination of agents.

Overseer messages:

  • Come from a special HumanOverseer agent (program: WebUI, model: Human)
  • Are always marked high importance so they stand out in agent inboxes
  • Bypass normal contact policies so you can always reach any agent
  • Include a preamble instructing agents to pause current work, handle the request, then resume

Agents see overseer messages in their normal inbox via fetch_inbox or resource://inbox/{name}?project=<abs-path>. They can reply in-thread like any other message. Everything is stored identically to agent-to-agent messages (Git + SQLite), fully auditable.


Deployment Validation

# Export a bundle
am share export -o /tmp/agent-mail-bundle --no-zip

# Verify a live deployment against the bundle
am share deploy verify-live https://example.github.io/agent-mail \
  --bundle /tmp/agent-mail-bundle \
  --json > /tmp/verify-live.json

# Inspect verdict
jq '.verdict, .summary, .config' /tmp/verify-live.json

Exit codes: 0 = pass, 1 = fail.


Configuration

All configuration via environment variables. The server reads them at startup via Config::from_env().

Variable Default Description
AM_INTERFACE_MODE (unset = MCP) mcp or cli
HTTP_HOST 127.0.0.1 Bind address
HTTP_PORT 8765 Bind port
HTTP_PATH /mcp/ MCP base path
HTTP_BEARER_TOKEN (from .env file) Auth token
HTTP_ALLOWED_HOSTS (none) Comma-separated extra Host: header values the HTTP listener accepts, in addition to the bind host, its loopback variant, and localhost. Set this (or pass repeatable serve-http --allowed-host <HOST>) to reach the /mail web UI via a hostname or reverse proxy instead of getting an HTTP 421. Empty by default (loopback-only).
DATABASE_URL <STORAGE_ROOT>/storage.sqlite3 SQLite connection URL. Unset, the database lives inside STORAGE_ROOT; an explicit relative path is resolved against the working directory
AM_CACHE_PROFILE balanced Cache budget preset: conservative, balanced, or high-memory
DATABASE_CACHE_BUDGET_KB profile-derived 524288 Total SQLite page-cache budget across pooled connections, clamped to 16 MiB..4 GiB
AM_READ_CACHE_ENTRIES_PER_CATEGORY profile-derived 16384 Per-category read-cache entry cap, clamped to 1,024..1,048,576
STORAGE_ROOT XDG-aware (see below) Archive root directory
ALLOW_EPHEMERAL_PROJECTS_IN_DEFAULT_STORAGE false Permit /tmp-style project roots in the default global mailbox archive. Prefer a per-run STORAGE_ROOT instead.
LOG_LEVEL info Minimum level for Agent Mail's own logs: trace, debug, info, warn, error or off (WARNING/CRITICAL accepted). Dependencies log at warn unless LOG_LEVEL is stricter. RUST_LOG, when set, replaces it for mcp-agent-mail (and for am with AM_ALLOW_DEBUG_STARTUP_LOGS=1)
TUI_ENABLED true Interactive TUI toggle
TUI_HIGH_CONTRAST false Accessibility mode
AM_TUI_TOAST_ENABLED true Enable toast notifications
AM_TUI_TOAST_SEVERITY info Minimum toast severity (info/warning/error/off)
AM_TUI_TOAST_POSITION top-right Toast stack position
AM_TUI_TOAST_MAX_VISIBLE 3 Max visible toasts at once
AM_TUI_TOAST_INFO_DISMISS_SECS 5 Info toast auto-dismiss timeout
AM_TUI_TOAST_WARN_DISMISS_SECS 8 Warning toast auto-dismiss timeout
AM_TUI_TOAST_ERROR_DISMISS_SECS 15 Error toast auto-dismiss timeout
AM_TUI_THREAD_PAGE_SIZE 20 Thread conversation page size
AM_TUI_THREAD_GUIDES rounded (theme default) Thread tree guide style (ascii/unicode/bold/double/rounded)
AM_TUI_COACH_HINTS_ENABLED true Enable contextual coach-hint notifications
AM_TUI_EFFECTS true Enable text/animation effects
AM_TUI_AMBIENT subtle Ambient mode (off/subtle/full)
AM_TUI_FULL_REDRAW_MAX_SECS 30 Wall-clock bound (seconds) for a guaranteed full TUI redraw that repairs terminal desync (e.g. after a mux pane swap). Each forced redraw resends the whole screen, so lower values cost terminal output; <= 0 disables the bound
AM_IDEMPOTENCY_RETENTION_SECS 86400 Retention window in seconds for file_reservation_paths idempotency keys; invalid or non-positive values fall back to 24 hours
WORKTREES_ENABLED false Build slots feature flag
INTEGRITY_CHECK_ON_STARTUP true Run the boot-time PRAGMA quick_check; false skips only this probe and leaves the background guard enabled
INTEGRITY_GUARD_ENABLED true Enable periodic integrity checks, automatic backups and database maintenance; false stops that worker
INTEGRITY_QUICK_CHECK_INTERVAL_SECONDS 300 Background quick-check cadence and initial delay when startup checking is skipped (minimum 1 second)
INTEGRITY_CHECK_INTERVAL_HOURS 1 Periodic full integrity-check cadence (0 disables scheduled full checks; quick checks remain enabled)
STARTUP_READINESS_BIND_TIMEOUT_SECS 20 Max seconds to wait for DB readiness before binding the listener anyway (/healthz stays up while the DB warms)
DB_MAINTENANCE_ENABLED true Enable checkpoint/ANALYZE/VACUUM/journal-size maintenance within the integrity guard; false leaves checks and backups enabled
DB_CHECKPOINT_INTERVAL_SECS 300 Passive WAL checkpoint cadence (0 disables that op)
DB_ANALYZE_INTERVAL_SECS 21600 ANALYZE planner-stats refresh cadence (0 disables)
DB_VACUUM_INTERVAL_SECS 86400 VACUUM reclaim/defragment cadence (0 disables)
DB_JOURNAL_SIZE_LIMIT_BYTES 16777216 journal_size_limit WAL truncation cap (16 MiB); an automatic checkpoint over the cap truncates the WAL
AM_GIT_BINARY (resolver) Override the git binary for all in-process shell-outs (mitigates the git 2.51.0 index race)
AM_GIT_FLOCK_TIMEOUT_SECS 60 Bounded wait for the per-repo am.git-serialize.lock before a git shell-out fails EX_TEMPFAIL (75)
AGENT_MAIL_GUARD_PUSH_MAX_COMMITS 2000 Most commits the pre-push guard inspects per pushed ref (newest first); past it the scan is truncated and fails closed. 0 removes the bound
AGENT_MAIL_GUARD_PUSH_MAX_PATHS 100000 Most --name-status path records the pre-push guard reads per pushed ref before truncating. 0 removes the bound
AGENT_MAIL_GUARD_PUSH_TIMEOUT_SECS 120 Wall-clock budget for the whole pre-push path scan; git is killed at the deadline and the scan is truncated. 0 removes the bound

For the full list of 100+ env vars, see crates/mcp-agent-mail-core/src/config.rs. The feature-flag and tuning-knob registry is inspectable at runtime with am flags list (--subsystem atc, --set for non-defaults, --json) and am flags explain <VAR>; see docs/FLAGS_REGISTRY.md.

Air Traffic Control (ATC) configuration

ATC is the built-in operator that reviews agent liveness, detects reservation deadlocks, and can send probe/advisory mail or reclaim reservations. It is on by default, but since v0.3.31 its executor defaults to shadow, so a fresh install observes and decides without writing any mail. The whole surface is the AM_ATC_* family below; am config atc (alias of am flags list --subsystem atc) prints each variable's effective value and whether it came from the process environment, the persisted config file, a project .env, or the compiled default. Restart the server after changing any of them except the two canary-report paths.

Variable Default Meaning
AM_ATC_ENABLED true Master switch. false stops the operator loop and makes the engine ignore every hook: no probes, advisories, reservation releases, population hydration, or experience rows. Liveness surfaces then fall back to the database last_active_ts written by ordinary tool calls ("passive liveness only"), and reservations held by crashed agents are reclaimed only by TTL expiry. Also forced false by ATC_LEARNING_DISABLED.
AM_ATC_EXECUTOR_MODE shadow How decisions become side effects: shadow (observe/decide, emit nothing durable), dry_run (same, suppressed effects shown in the operator snapshot), canary (send activity-check / acknowledgment-request mail, never force-release reservations), live (everything, including releases). Unknown values fall back to shadow.
AM_ATC_WRITE_MODE off Experience-ledger persistence: off, shadow (trace-log only), live (append rows to the ATC sidecar). Durable rows additionally need canary/live executor mode and a file-backed DB. Forced off by ATC_LEARNING_DISABLED.
AM_ATC_PROBE_INTERVAL_SECS 120 Operator tick interval and per-agent liveness-probe cadence (floor 5; ticks never run faster than 250 ms).
AM_ATC_ADVISORY_COOLDOWN_SECS 300 Minimum seconds between advisories to the same agent (floor 10).
AM_ATC_SUMMARY_INTERVAL_SECS 300 Seconds between ATC summary lines in the console/log (floor 10).
AM_ATC_SAFE_MODE_RECOVERY_COUNT 20 Consecutive correct liveness predictions required to leave safe mode (floor 1).
AM_ATC_EPROCESS_THRESHOLD 20 E-process calibration alert threshold; crossing it enters safe mode (20 ≈ 5% significance). Finite, > 0.
AM_ATC_CUSUM_THRESHOLD 5 CUSUM change-point threshold on prediction error. Finite, > 0.
AM_ATC_CUSUM_DELTA 0.1 Minimum shift the CUSUM detector is tuned to catch. Finite, > 0.
AM_ATC_LEDGER_CAPACITY 1000 In-memory evidence-ledger ring buffer size (entries) behind transparency cards (floor 10).
AM_ATC_SUSPICION_K 3 Rhythm-based liveness factor: an agent is suspect once its silence exceeds its expected gap by k standard deviations; lower probes sooner. Finite, > 0.
AM_ATC_EXPERIENCE_MAX_ROWS 50000 Ceiling on raw atc_experiences rows in the sidecar; the retention sweep rolls up/evicts above it. 0 disables.
AM_ATC_RETENTION_SWEEP_INTERVAL_SECS 900 Cadence of the sweep enforcing the ceiling above. 0 disables the sweep.
AM_ATC_POPULATION_RECENCY_SECS 604800 Only agents active within this window (7 days) are hydrated into ATC on cold start / periodic sync. Lower it on mailboxes with many recently-active identities to bound cold-start effect bursts. 0 hydrates nobody.
AM_ATC_POPULATION_LIMIT 4096 Maximum agents materialized per population sync, most recently active first (clamped to 1..=65536).
AM_ATC_POLICY_BUNDLE_PATH (unset) Path to a liveness policy bundle JSON to load instead of the compiled-in baseline.
AM_ATC_CANARY_REPORT_PATH (unset) Exact canary perf-gate report JSON for am robot atc / the TUI; default <STORAGE_ROOT>/atc_perf_gate/latest_canary_report.json. Read per render.
AM_ATC_CANARY_REPORT_DIR (unset) Directory holding latest_canary_report.json; AM_ATC_CANARY_REPORT_PATH wins if both are set. Read per render.
ATC_LEARNING_DISABLED false Hard kill switch (no AM_ prefix, kept for compatibility): forces AM_ATC_ENABLED=false and AM_ATC_WRITE_MODE=off regardless of the values above. Dynamic: am flags on ATC_LEARNING_DISABLED.

The file <STORAGE_ROOT>/.atc_kill_switch additionally stops durable ATC writes without a restart (checked every operator tick). To keep ATC's observation but stop it from writing mail, leave the default AM_ATC_EXECUTOR_MODE=shadow; to switch ATC off entirely use AM_ATC_ENABLED=false.

For operations guidance and troubleshooting, see docs/OPERATOR_RUNBOOK.md. For copy-paste operator recipes, see docs/OPERATOR_COOKBOOK.md.

Registration Proof Gate (optional, off by default)

By default Agent Mail uses a self-asserted identity model: any caller may register_agent under any well-formed name. That is the right trade-off for local, trusted, single-operator multi-agent coordination and remains the default — nothing changes unless you opt in.

Deployments where agents come from different operators or less-trusted sources can require a cryptographic proof before any identity is persisted. When enabled, register_agent (and every other path that can implicitly create an agent row, including send_message / request_contact auto-registration and the CLI) demands a registration_proof argument: a JSON bundle binding the final agent name, project key, program, model, capability scope, an issued_at/expires_at window, and a single-use nonce, signed (Ed25519) by one of your configured trust anchors. Verification is fail-closed: a missing, malformed, untrusted, expired, replayed, or mismatched proof rejects the registration and writes nothing. Consumed nonces are persisted in the database, so replay protection survives server restarts.

Variable Default Description
AM_REGISTRATION_PROOF_GATE_ENABLED false Master switch. Off = self-asserted identity (unchanged default).
AM_REGISTRATION_PROOF_TRUSTED_KEYS (empty) Comma-separated base64 Ed25519 public keys (32 raw bytes each). Enabled with no keys = every registration fails closed.
AM_REGISTRATION_PROOF_MAX_LIFETIME_SECONDS 300 Maximum allowed expires_at - issued_at window.
AM_REGISTRATION_PROOF_CLOCK_SKEW_SECONDS 60 Clock-skew tolerance for the validity window.
AM_REGISTRATION_PROOF_REQUIRE_NONCE true Require and consume a single-use nonce per proof (replay protection).

The canonical signing bytes are explicit (domain-separation tag + newline-delimited field=value lines; capabilities sorted and de-duplicated), so issuers can be written in any language. Verification is expressed through a pluggable ProofVerifier trait (crates/mcp-agent-mail-tools/src/proof_gate.rs); the shipped implementation is the Ed25519 trust-anchor verifier, and external verifier backends can be added without touching registration call sites.


Architecture

Cargo workspace with strict dependency layering:

MCP Client / Operator / Browser
   │           │           │
   ├─ stdio ───┤           │
   ├─ HTTP  ───┼───────────┤
   ▼           ▼           ▼
             mcp-agent-mail-server
                     │
        ┌────────────┼────────────┬─────────────┐
        ▼            ▼            ▼             ▼
   45 MCP Tools  25 Resources   TUI         Web UI
        │            │            │             │
        └────────────┴──────┬─────┴─────────────┘
                            ▼
                    mcp-agent-mail-tools
                            │
                 ┌──────────┼──────────┬──────────────┐
                 ▼          ▼          ▼              ▼
          mcp-agent-mail-db storage  search-core   share/export
           (SQLite index)  (Git)     (query path)  (bundles/static)
                 │
           mcp-agent-mail-core
      (config, models, errors, metrics)

Workspace Structure

mcp_agent_mail_rust/
├── Cargo.toml                              # Workspace root (12 member crates; dashboard-wasm is excluded and built standalone)
├── crates/
│   ├── mcp-agent-mail-core/                # Zero-dep: config, models, errors, metrics
│   ├── mcp-agent-mail-db/                  # SQLite schema, queries, pool, cache, Search V3 integration
│   ├── mcp-agent-mail-storage/             # Git archive, commit coalescer, notification signals
│   ├── mcp-agent-mail-search-core/         # Pluggable search traits
│   ├── mcp-agent-mail-guard/               # Pre-commit guard, reservation enforcement
│   ├── mcp-agent-mail-share/               # Snapshot, scrub, bundle, crypto, export
│   ├── mcp-agent-mail-tools/               # 45 MCP tool implementations (9 clusters)
│   ├── mcp-agent-mail-server/              # HTTP/MCP runtime, dispatch, TUI (16 screens)
│   ├── mcp-agent-mail/                     # Server binary (mcp-agent-mail)
│   ├── mcp-agent-mail-cli/                 # CLI binary (am) with robot mode
│   ├── mcp-agent-mail-test-helpers/        # Shared test scaffolding
│   ├── mcp-agent-mail-dashboard-wasm/      # Standalone public DashboardScreen WASM replay + safe exporter (excluded from the workspace)
│   └── mcp-agent-mail-conformance/         # Python parity tests
├── experimental/
│   └── mcp-agent-mail-wasm/                # Parked standalone WASM/browser prototype
├── tests/e2e/                              # End-to-end test scripts
├── scripts/                                # CLI integration tests, utilities
├── docs/                                   # ADRs, specs, runbooks, migration guides
├── install.sh                              # Unix installer
├── install.ps1                             # Windows PowerShell installer
└── rust-toolchain.toml                     # Nightly toolchain requirement

Canonical File Layout

The runtime uses several paths whose defaults have evolved across versions. This table resolves the three-way ambiguity:

Path Purpose Notes
~/.mcp_agent_mail_git_mailbox_repo/ Legacy archive root (pre-v1) No longer created; honored if it already exists on disk
$XDG_DATA_HOME/mcp-agent-mail/git_mailbox_repo/ Current archive root (STORAGE_ROOT default) ~/.local/share/mcp-agent-mail/git_mailbox_repo/ on most Linux systems
$XDG_CONFIG_HOME/mcp-agent-mail/config.env Canonical env file (installer writes here) ~/.config/mcp-agent-mail/config.env on most Linux systems
$XDG_CONFIG_HOME/mcp-agent-mail/.env Compatibility env file Checked when config.env is absent
~/.mcp_agent_mail/.env Legacy env file Checked after XDG paths
<STORAGE_ROOT>/storage.sqlite3 Runtime database (DATABASE_URL default) Inside the archive root unless DATABASE_URL is set

The STORAGE_ROOT resolution logic: if the legacy path ~/.mcp_agent_mail_git_mailbox_repo/ exists on disk, it is used for backward compatibility. Otherwise the XDG data directory is preferred. Override with STORAGE_ROOT=/your/path in the environment.

Storage Layout (inside STORAGE_ROOT)

$STORAGE_ROOT/                              # e.g. ~/.local/share/mcp-agent-mail/git_mailbox_repo/
├── projects/
│   └── {project_slug}/
│       ├── .git/                           # Per-project git repository
│       ├── messages/
│       │   └── {YYYY}/{MM}/               # Date-partitioned canonical messages
│       ├── agents/
│       │   └── {agent_name}/
│       │       ├── inbox/                  # Agent inbox copies
│       │       └── outbox/                 # Agent outbox copies
│       ├── build_slots/                    # Build slot leases (JSON)
│       └── file_reservations/              # Reservation artifacts
└── .archive.lock                           # Global advisory lock

Key Design Decisions

  • Git-backed archive for human auditability; SQLite as fast index
  • WAL mode with PRAGMA tuning and connection pooling for concurrent access
  • Write-behind cache with dual-indexed ReadCache and deferred touch batching (30s flush)
  • Async git commit coalescer (write-behind queue) to avoid commit storms
  • i64 microseconds for all timestamps (no chrono::NaiveDateTime in storage layer)
  • Search V3 via frankensearch: lexical tier ships by default in the supported search stack; semantic and hybrid fusion are compiled through the feature = "hybrid" gate and run only with AM_SEARCH_SEMANTIC_ENABLED=true; portable/no-default builds and the default runtime keep the deterministic lexical path.
  • Compatibility testing against intentionally retained Python wire contracts, plus Rust-native durability and behavior fixtures. The Rust implementation is authoritative; legacy parity never overrides reliability, security, bounded work, or structured-concurrency invariants.
  • Advisory file reservations with symmetric fnmatch, archive reading, and rename handling
  • #![forbid(unsafe_code)] across all crates
  • Query-only read lane — direct mailbox reads use an existing live SQLite pool and do not wait for archive reconstruction or write-behind coalescing
  • Evidence-bearing timeouts — timeout responses and health output expose the measured stage and latency evidence instead of assigning generic database blame
  • asupersync exclusively for all async operations (no Tokio, reqwest, hyper, or axum)

ATC Learning Implementation Map

Validated ATC learning loop — rollout remains gated by write mode.

ATC now has a durable live-learning path: hot-path atc_note_* hooks append experience rows, real runtime signals resolve outcomes, rollups/retention/replay are implemented, and robot/TUI surfaces consume the shared live operator snapshot. AM_ATC_WRITE_MODE still gates rollout (off default, shadow for observation, live for durable writes), so broader promotion remains operator-controlled rather than always-on. Treat the map below as the current ownership contract and rollout/hardening guide, not as a future-only blueprint.

The br-0qt6e ATC learning work is intentionally cross-cutting, but it should not be cross-owned. The codebase already has the right seams; future implementation should deepen those seams instead of spreading ATC state across tools, UI layers, or ad-hoc SQL in random crates.

Ownership by crate and module

Area Current modules Owns Must not own
Core contract crates/mcp-agent-mail-core/src/experience.rs, evidence_ledger.rs, atc_baseline.rs, config.rs Canonical ExperienceRow shape, lifecycle transitions, feature vector/extension schema, decision/evidence identifiers, frozen pre-learning baseline, env/config surface SQL persistence, archive writes, UI-specific formatting
SQLite persistence crates/mcp-agent-mail-db/src/schema.rs (v16 atc_experiences, atc_experience_rollups) Durable raw experience rows, open-resolution lookup indexes, stratum rollups, retention/compaction mechanics, future insert/resolve/query APIs Decision policy, ATC tick logic, web/TUI rendering
Git/archive boundary crates/mcp-agent-mail-storage/src/lib.rs Human-auditable artifact writes, WBQ/commit coalescing, selected policy/audit artifacts when explicitly promoted Raw ATC experience exhaust; the current WriteOp surface intentionally handles message, reservation, profile, and notification artifacts, not learning-row mirroring
ATC runtime crates/mcp-agent-mail-server/src/atc.rs Decision engine, liveness/conflict/routing/calibration math, policy bundle loading, global ATC singletons, tick loop, outcome feedback, summary snapshots Direct SQLite schema ownership, long-lived CLI state, alternate UI-specific models
Runtime wiring crates/mcp-agent-mail-server/src/lib.rs Bootstrapping via init_global_atc() and start_atc_operator_runtime(), tool-dispatch liveness hooks via atc_register_agent_with_project() and atc_observe_activity_with_project(), shared ATC operator snapshot publication Per-tool bespoke learning persistence; tool handlers should emit domain facts, not become the learning store
Agent/operator surfaces crates/mcp-agent-mail-cli/src/robot.rs, crates/mcp-agent-mail-server/src/tui_screens/system_health.rs, tui_web_dashboard.rs, /mail/ws-state Rendering the shared ATC/operator snapshot for robot and live TUI consumers; reserved browser-mirror modules stay read-only/non-owning and fall back to local heuristics only when live server state is unavailable Independent ATC truth, separate persistence, or duplicate learning logic
Tool/resource layer crates/mcp-agent-mail-tools/src/*, crates/mcp-agent-mail-tools/src/resources.rs Normal tool/resource contracts for mailbox coordination; ATC-adjacent work should surface through existing runtime hooks and shared snapshots Owning ATC learning state machines or experience-row lifecycle rules

Append and resolution hooks

  • Session/bootstrap and liveness hooks already land at the server dispatch boundary: successful register_agent and macro_start_session calls register agents with ATC, and tool execution updates ATC activity timestamps.
  • The durable append path now belongs in that same server/runtime seam, not inside individual UIs. Message and reservation learning hooks flow from the tool-result/event boundary in mcp-agent-mail-server/src/lib.rs into the ATC-facing note functions in mcp-agent-mail-server/src/atc.rs: atc_note_message_sent(), atc_note_message_received(), atc_note_reservation_granted(), atc_note_reservation_released(), and atc_note_reservation_conflicts().
  • Outcome resolution now flows through the server ATC runtime: message outcomes are resolved from the tool-result boundary (record_atc_message_outcome_from_tool_payload_with_pool in mcp-agent-mail-server/src/lib.rs) plus the overdue/retention sweeps, with the DB crate owning the actual row mutation and rollup updates. (atc_record_outcome() in atc.rs is the in-memory engine hook and currently has no production caller.)
  • /mail/ws-state polling, am robot atc, the ATC TUI screen, and the System Health screen stay snapshot-driven consumers of atc_operator_snapshot() / atc_summary(), not alternate sources of learning state. The deferred /web-dashboard/* browser mirror is intentionally outside the supported live ATC surface today.

Hot path vs. cold path boundaries

  • Hot path: mcp-agent-mail-server/src/atc.rs, the tool-dispatch hook in mcp-agent-mail-server/src/lib.rs, and the DB append/resolve calls behind AM_ATC_WRITE_MODE. This path must stay append-friendly, bounded, and free of Git write amplification.
  • Warm path: ATC operator snapshots, /mail/ws-state polling, am robot atc, and the TUI/System Health surfaces. These consumers should read already-computed ATC state and only fall back to local heuristics when the live server snapshot is unavailable. The deferred /web-dashboard/* mirror is intentionally excluded from the live warm path until a future resurrection effort lands.
  • Cold path: promoted policy bundles, transparency cards, replay artifacts, and operator-facing audit bundles. These may be written through mcp-agent-mail-storage, but only after they are intentionally compacted and selected.
  • Explicit non-owner boundary: mcp-agent-mail-search-core, share/export, and the WASM/browser mirror are consumers or transport layers. They should not become the canonical home of ATC learning policy, persistence, or attribution logic.

Verification ownership

  • crates/mcp-agent-mail-core/src/experience.rs: unit/property coverage for lifecycle validity, feature-vector stability, serialization, and idempotent resolution transitions.
  • crates/mcp-agent-mail-db/src/schema.rs plus the ATC DB query modules: migration coverage, insert/resolve semantics, rollup correctness, retention/compaction tests, and replay/reconstruct safety.
  • crates/mcp-agent-mail-server/src/atc.rs: decision math, policy-bundle loading, safe-mode transitions, tick budgeting, and feedback/calibration tests.
  • crates/mcp-agent-mail-server/src/lib.rs, tui_ws_state.rs, tui_screens/system_health.rs, and crates/mcp-agent-mail-cli/src/robot.rs: snapshot publication, route contracts, robot fallback behavior, and operator-surface rendering tests.
  • tests/e2e/ and performance harnesses: live-server E2E plus soak/perf coverage validate that ATC learning stays low-write-amplification and does not regress request/tick budgets.

Rollout and hardening priorities

  1. Keep mcp-agent-mail-core as the schema-and-policy contract so experience/evidence/config changes stay centralized and reviewable.
  2. Preserve mcp-agent-mail-db as the only durable ATC mutation/query surface for append, resolve, rollup, retention, and replay behavior.
  3. Continue routing real runtime events through atc_note_* and the server-side outcome-resolution path instead of duplicating learning logic in tools, UIs, or ad-hoc SQL paths.
  4. Keep robot, TUI, and browser/dashboard surfaces snapshot-driven consumers of atc_operator_snapshot() rather than alternate sources of ATC truth.
  5. Land remaining rollout/default-flip, data-minimization, and operator-hardening work on top of the existing live path rather than reopening seam ownership questions.

Core Data Model

Type Role
Project Stable identity for one codebase, keyed by absolute path and slug
Product Cross-project grouping for search, inbox aggregation, and routing
Agent Registered worker identity with name, program, model, policy, and task metadata
Message Canonical envelope for subject, body, importance, thread, attachments, and recipients
Recipient Delivery metadata for to/cc/bcc, read state, and acknowledgement state
Thread Logical conversation identifier spanning replies and summaries
FileReservation Advisory lease over file paths/globs with TTL, exclusivity, and reason
BuildSlot Named lease used to serialize build-heavy work on a shared checkout
ToolMetrics Per-tool latency, error, and call-count telemetry used by TUI, CLI, and diagnostics

The important boundary is this: Git stores the durable human-auditable artifacts, while SQLite stores the queryable operational state. Every surface in the project is built around that split.


How the System Works

Write Path

  1. An MCP tool call, CLI command, web overseer action, or guard hook enters the runtime.
  2. Inputs are normalized and validated against project identity, agent names, and policy constraints.
  3. The SQLite layer performs the indexed mutation: insert/update rows, enforce invariants, and emit IDs/timestamps.
  4. The storage layer writes canonical Markdown/JSON artifacts into the per-project archive.
  5. The commit coalescer batches archive updates so bursts of activity do not become commit storms.
  6. Metrics and event streams are updated so TUI, robot, and web surfaces all observe the same fresh state.

Read Path

  1. A resource read, robot command, TUI view, or web request asks for mailbox or tooling state.
  2. The server resolves scope: project, agent, product, thread, search query, or tooling view.
  3. For direct state, an existing live SQLite mailbox answers through a query-only lane; it does not wait for archive reconstruction or the write-behind coalescer, and a read request does not create a missing project. fetch_inbox performs its optional read-receipt update separately after the bounded read. For search, Search V3 plans the query and executes the appropriate lexical route, or the semantic/hybrid route when the feature = "hybrid" build path is compiled in and AM_SEARCH_SEMANTIC_ENABLED=true.
  4. The result is rendered as MCP JSON, robot toon/json/md, TUI widgets, or HTML under /mail/.

That consistency comes from a shared DB + archive + metrics pipeline. The TUI, web UI, robot CLI, and MCP resources are separate renderers over the same underlying state.


Search Architecture

Search V3 is not an afterthought bolted onto mailbox rows. It is a dedicated query path with shared planning and diagnostics:

  • Queries are normalized, classified, and routed through a unified search service.
  • Lexical mode provides the baseline contract and diagnostics surface; semantic and hybrid modes extend that contract in a hybrid-feature build with AM_SEARCH_SEMANTIC_ENABLED=true. Otherwise a semantic, hybrid, or auto request runs lexical.
  • Candidate budgeting and fusion keep broad natural-language queries from exploding while preserving exact-match strength for identifiers and short phrases.
  • The same search path serves MCP tools, am mail search, am robot search, TUI search, and web UI search routes.
  • Empty or non-searchable queries route through a deterministic SQL plan before Search V3 candidate retrieval. Legacy SQLite FTS artifacts still exist for migration hygiene and cleanup, but the current search architecture is Search V3 plus that deterministic SQL plan, not a hidden FTS fallback.

The dedicated mcp-agent-mail-search-core crate exists specifically so search planning and backends can evolve without entangling the rest of the mailbox stack.


Coordination Algorithms and Safety Invariants

  • Explicit recipients only. send_message keeps a broadcast field for schema compatibility, but broadcast=true is intentionally rejected so agents cannot spam every other agent by default.
  • Project isolation first. Agent names are only meaningful inside a project unless a contact link or product bus path explicitly bridges them.
  • Symmetric reservation matching. File reservation conflicts use symmetric glob overlap checks, so src/** conflicts with src/lib.rs in either direction.
  • TTL instead of forever-locks. Reservations and build slots expire, which makes the system robust to crashed or vanished agents.
  • Wrong-surface denial. MCP-only and CLI-only commands fail deterministically when invoked through the wrong entrypoint, reducing automation footguns.
  • Idempotent session bootstrapping. ensure_project, register_agent, and the session macros are designed so agents can safely retry startup without creating duplicate identity state.
  • Durable monitor cursors. Inbox-event consumers advance a recipient delivery cursor only after handling an oldest-first page; a message ID is never a safe cursor substitute.
  • Contact approval as policy, not convention. Cross-project messaging is gated by explicit contact workflows and per-agent contact policies.

These are the design rules that let many agents share one checkout without immediately degenerating into chaos.


Consistency and Recovery Model

  • SQLite is the live operational state. MCP tools, resources, the TUI, the web UI, and robot/CLI reads use SQLite for current inboxes, message IDs, read/ack state, reservations, agents, products, and search planning.
  • Git is the durable artifact ledger. Canonical messages, inbox/outbox copies, reservation files, and agent profiles are written as files under per-project archives so the mailbox remains human-auditable and recoverable.
  • Concurrent writes are queued, not hand-merged. Parallel agents can send to the same thread at the same time; the server/DB layer serializes the indexed mutations, and the archive write-behind queue plus commit coalescer batches the corresponding Git artifact writes. There is no workflow where two agents create a Git merge conflict and a human resolver has to pick the winning message.
  • Reservation artifacts have a database generation. A new physical SQLite database gets a new generation token, so its reservation row IDs cannot overwrite a prior generation's archive artifacts. Foreign stamped artifacts remain inspectable without being treated as live parity drift.
  • Repair is not reconstruct. am doctor repair is the in-place hygiene path. am doctor reconstruct is the archive-first rebuild path when SQLite is no longer trustworthy.
  • Backups are first-class. archive save, doctor backups, and restore flows all exist because operational recovery is part of the product, not a manual afterthought.
  • Stale lock cleanup is expected. Guard hooks, archive lock management, and doctor checks assume agents crash and processes die unexpectedly.

If the live layer gets sick, the durable ledger can rebuild it.


Comparison vs. Alternatives

Feature MCP Agent Mail Shared Files / Lockfiles Custom MCP Tools Chat-Based Coordination
Agent identity & discovery Persistent names, profiles, directory queries None Manual Ephemeral
Threaded messaging Subjects, CC/BCC, ack, importance, search N/A Build yourself Linear chat, no structure
File reservation / advisory locks Glob patterns, TTL, exclusive/shared, pre-commit guard Lockfiles (no TTL, no glob) Build yourself None
Audit trail Full Git history of all communication File timestamps Depends Chat logs (if saved)
Cross-repo coordination Product bus, contact system Manual Build yourself Manual
Search Search V3 (lexical + semantic + hybrid) grep Build yourself Limited
Operational visibility 16-screen TUI, robot CLI, metrics None None None
Token efficiency Messages stored externally, not in context Files in context Varies All in context

Development

# Quality gates
cargo fmt --check
cargo clippy --workspace --all-targets -- -D warnings
cargo check --workspace --all-targets
cargo test --workspace

# Compatibility and Rust-native conformance tests
cargo test -p mcp-agent-mail-conformance

# Run specific crate tests
cargo test -p mcp-agent-mail-db
cargo test -p mcp-agent-mail-tools
cargo test -p mcp-agent-mail-server

# E2E tests
tests/e2e/test_stdio.sh         # MCP stdio transport
tests/e2e/test_http.sh           # HTTP transport
tests/e2e/test_guard.sh          # Pre-commit guard
tests/e2e/test_macros.sh         # Macro tools
tests/e2e/test_share.sh          # Share/export
tests/e2e/test_dual_mode.sh      # Mode switching
scripts/e2e_cli.sh               # CLI integration (99 assertions)

# Native E2E runner (authoritative path)
am e2e list
am e2e run --project .                 # all suites
am e2e run --project . stdio http      # selected suites
am e2e run --project . --include tui_  # pattern include
am e2e run --project . tui_full_traversal  # traversal + flash + soak regression gate
am e2e run --project . --tag reliability --release-scorecard  # reliability release gate
#   ^ runs every @tags: reliability suite (incl. the incident_corpus harness) and writes
#     tests/artifacts/release_scorecard/<ts>/release_scorecard.json (per-suite rows +
#     per-incident-class rows with anchors + a combined release_ready verdict)

# Legacy compatibility shim (deprecated primary path)
./scripts/e2e_test.sh stdio
# rollback guard: AM_E2E_FORCE_LEGACY=1 ./scripts/e2e_test.sh stdio

# Benchmarks
cargo bench -p mcp-agent-mail

# Multi-agent builds: offload heavy cargo work through rch
rch exec -- cargo check --workspace --all-targets

# Shared-checkout verification lanes: acquire a build slot, run through rch, preserve logs
am verify cargo-check --path . --agent AgentName --block-on-conflicts
am verify cargo-clippy --path . --agent AgentName --block-on-conflicts
am verify cargo-test --path . --agent AgentName --block-on-conflicts
am verify e2e-stdio --path . --agent AgentName --block-on-conflicts
am verify bench-quick --path . --agent AgentName --block-on-conflicts

am verify is a thin am-run lane mapper. It always shows the exact command, uses a verify-* build slot even when WORKTREES_ENABLED is not set in the shell, routes cargo-heavy work through rch exec -- ..., and writes command.json, stdout.log, stderr.log, exit_code.txt, and result.json under STORAGE_ROOT/artifacts/verify/<timestamp>-<lane>/ unless --artifact-dir is supplied.

result.json includes rch_proof.status, child_exit_code, and the final proof exit_code. For rch exec lanes, a zero-exit child is only green when the captured output includes a positive remote-execution marker. Local fallback or missing remote proof fails closed, writes rch_proof_failure.txt, and best-effort captures rch_status_workers.json plus rch_queue.json so operators can distinguish a remote test failure from transport/sync/fleet degradation.

Release evidence for robot/doctor fields must prove the installed am binary, not only the source-built test binary. For parity-sensitive fields such as forensic_timeline, search-index health, recovery status, artifact links, next actions, and redaction, run the installed-binary parity gate against the release candidate installed on the same host or remote worker:

AM_INSTALLED_BINARY_PARITY_BIN=/path/to/installed/am \
  rch exec -- cargo test -p mcp-agent-mail-cli --test it \
  integration_runs::installed_binary_parity_probe_compares_source_and_installed_am -- --ignored --nocapture

The gate writes tests/artifacts/installed_binary_parity/<run>/parity_report.json with one pass/fail row per required JSON path, redacted source/installed values, value-mismatch status, and redacted command metadata. If the candidate lacks fields that source tests rely on, or returns different required values, the report is red and the release is not closed. A local direct am doctor check --json or am robot ... --format json probe is useful for quick inspection, but it is not sufficient release evidence unless paired with this parity report and an rch exec -- ... cargo proof.

Use --dry-run to inspect the lane without running it. Use --no-block-on-conflicts only when the slot conflict is understood and you intentionally want advisory behavior.

E2E Entrypoint Policy (T9.9)

  • Native am e2e is authoritative for operator and CI invocation.
  • scripts/e2e_test.sh remains compatibility-only and emits deprecation guidance.
  • Rollback condition: set AM_E2E_FORCE_LEGACY=1 only if native runner regression is confirmed.

Verification checklist:

  • am e2e list and am e2e run --project . <suite> succeed for targeted suites.
  • Artifact repro commands reference am e2e run (not script-first commands).
  • CI/release docs and runbooks do not present scripts/e2e_test.sh as the primary command.

Verification Realism Policy

The canonical realism and closure policy lives in docs/VERIFICATION_COVERAGE_LEDGER.md.

  • R0 and R1 are the lanes that carry closure weight.
  • R2 and R3 substitutes are second-best evidence and must name their compensating real-path coverage.
  • Stub-only lanes do not close transport, persistence, install/update, search-quality, LLM-quality, crypto/share, or operator-surface claims by themselves.

Key Dependencies

Crate Purpose
asupersync Structured async runtime (channels, sync, regions, HTTP, testing)
fastmcp-rust / fastmcp MCP protocol implementation (JSON-RPC, stdio, HTTP transport)
sqlmodel_rust / sqlmodel SQLite ORM (schema, queries, migrations, pool)
frankentui TUI rendering (widgets, themes, accessibility, markdown, syntax highlighting)
frankensearch Hybrid search engine (lexical + semantic, two-tier fusion, reranking)
beads_rust Issue tracking integration
toon Token-efficient compact encoding for robot mode output

Performance and Benchmarking

The repository carries both stress tests and reproducible benchmark baselines. The two serve different purposes:

  • Stress suites answer “does the system stay correct under ugly concurrency?”
  • Bench suites answer “how fast is the operator and mailbox path right now?”

Native am bench

The native CLI benchmark runner is built around four categories in crates/mcp-agent-mail-cli/src/bench.rs:

Category Built-in cases What it measures
Startup help Pure CLI startup and argument parsing
Analysis lint, typecheck Cost of native quality commands
Stub encoder stub_encode_1k, stub_encode_10k, stub_encode_100k Compact encoding subprocess path
Operational mail_inbox, mail_send, mail_send_no_atc, mail_send_atc_shadow, mail_send_atc_live, mail_search, robot_status, doctor_check, message_count, agents_list Real mailbox/operator workflows over a seeded DB (am bench --list is authoritative)

Checked-In Baselines

These numbers come from benches/BUDGETS.md, which records dated benchmark baselines and budgets.

CLI Operational Baselines (am bench --quick, 2026-02-09)

Command Baseline mean Budget
am --help 4.2ms < 10ms
am mail inbox 11.5ms < 25ms
am mail inbox --include-bodies 11.7ms < 25ms
am mail send 27.1ms < 50ms
am doctor check 5.8ms < 15ms
am list-projects 6.2ms < 15ms
am lint 457ms < 1000ms
am typecheck 399ms < 800ms

Search V3 frankensearch lexical-tier baselines (2026-02-18)

Tantivy is the lexical backend inside frankensearch; semantic and hybrid fusion need the hybrid feature and AM_SEARCH_SEMANTIC_ENABLED=true.

Corpus size Baseline p50 Baseline p95 Baseline p99 Budget p95
1,000 messages ~382µs ~531µs ~706µs < 1.5ms
5,000 messages ~622µs ~800µs ~1.1ms < 5ms
15,000 messages ~679µs ~805µs ~1.0ms < 15ms

Index build throughput baseline from the same file is 7.5K docs/sec at 1K, 36K docs/sec at 5K, and 90K docs/sec at 15K, with roughly 89-107 bytes/doc of disk overhead.

Historical Archive Write Baselines (2026-02-08)

Operation Baseline p50 Baseline p95 Budget p95 Notes
Single message, no attachments ~17.2ms ~21.3ms < 25ms Healthy write path
Single message, inline attachment ~22.0ms ~26.0ms < 25ms Slightly over budget
Single message, file attachment ~20.4ms ~25.2ms < 25ms Marginal
Batch 100 messages ~930ms ~1076ms < 250ms Historical pre-fix baseline; superseded below

These numbers are preserved as the pre-fix baseline for comparison.

Archive Write Baselines (2026-04-18)

Operation Baseline p50 Baseline p95 Budget p95 Notes
Single message, no attachments ~11.7ms ~12.4ms < 25ms Healthy warm-path write
Single message, inline attachment ~22.0ms ~26.0ms < 25ms Historical inline-attachment overrun remains tracked separately
Single message, file attachment ~20.6ms ~22.0ms < 25ms Healthy
Batch 100 messages ~213.5ms ~238.1ms < 250ms Fixed via storage-native batch bundle writes replacing the old per-message expansion loop

The archive batch-write path is now under budget on the warm steady-state benchmark path. See benches/BUDGETS.md and the tests/artifacts/perf/archive_batch_* artifacts for the detailed p99/span breakdown.

Reproduction Commands

# Native CLI benchmark catalog
am bench --list
am bench --quick

# Archive write path
cargo bench -p mcp-agent-mail --bench benchmarks -- archive_write

# Search V3 lexical path
cargo bench -p mcp-agent-mail-db --bench search_v3_bench

# Load / soak validation
cargo test -p mcp-agent-mail-db --test it sustained_load:: -- --ignored --nocapture

Hardware, kernel, and build profile matter. Treat these as checked-in reference baselines, not universal promises.


Mailbox Diagnostics (am doctor)

Over time, mailbox state can drift: stale locks from crashed processes, unresponsive local servers, MCP config skew, archive/SQLite divergence, orphaned rows, legacy FTS/search-side schema drift, or outright database corruption. The doctor command group detects these issues, explains what it found, and can now automatically apply the common fixes.

# Run diagnostics (fast, non-destructive)
am doctor check
am doctor check --verbose
am doctor check --json          # Machine-readable output

# Audit/archive hygiene without mutating the mailbox archive
am doctor archive-scan
am doctor archive-scan --json
am doctor archive-normalize --dry-run
am doctor archive-normalize --yes

# Repair (creates backup first, prompts before data changes)
am doctor repair --dry-run      # Preview what would change
am doctor repair                # Apply safe fixes, prompt for data fixes
am doctor repair --yes          # Auto-confirm everything (CI/automation)

# Archive-first recovery
am doctor reconstruct           # Rebuild SQLite from the Git archive (+ salvage what it can)
am doctor reconstruct --dry-run --json  # Build and validate a private preview candidate

# Full auto-remediation
am doctor fix --dry-run         # Preview all safe/automatic fixes
am doctor fix --yes             # Apply them without prompting

# Sanitized incident bundle for maintainer support
am doctor support-bundle --json
am doctor support-bundle --stdout-log /tmp/am.stdout --stderr-log /tmp/am.stderr --redact-subjects

# Backup management
am doctor backups               # List available backups
am doctor restore /path/to/backup.sqlite3

am doctor reconstruct --dry-run builds a candidate in temporary scratch space using the same archive and salvage merge as reconstruction. It checks full integrity and the promotion receipt's stable-key continuity rules without changing the mailbox or publishing a recovery receipt. Budget temporary disk space and runtime for a full rebuild. With --json, candidate_validation reports the accepted candidate's counts or the refusal detail; a refused preview exits nonzero. With --reseed-receipt-chain, it checks that the broken chain can be quarantined and validates the candidate against the resulting fresh lineage without moving the live receipts. A successful preview does not reserve promotion authority: the real operation rechecks the source and candidate under its own locks.

am doctor locks --json is the read-only owner report. Repair and reconstruct refuse a live, wedged, or unsafe-to-touch owner by default. A separate reclaimable class covers only a zombie or an unresponsive deleted-executable owner that has exceeded the idle threshold. For that class alone, am doctor repair --take-ownership (or reconstruct --take-ownership) wins a per-mailbox election, rechecks the evidence, moves stale activity-lock files into a witnessed quarantine, and then repairs. It never signals or kills the owner; all other owner classes still require the supervised drain path.

What check inspects:

Check Detects
Local runtime Port ownership, /health, JSON-RPC health_check, and runaway mcp-agent-mail CPU usage
MCP config Legacy stdio/Python launcher entries, wrong HTTP URLs, missing Codex timeout settings
Database file sanity Missing/zero-byte files, malformed relative paths, corruption signatures, reopen probe failures
Archive vs DB parity Cases where Git-backed canonical mail artifacts are ahead of SQLite and a reconstruct is safer than repair
Archive hygiene Missing/invalid project.json, duplicate canonical message ids, malformed canonical message files, and suspicious temp-project archive roots
Database integrity PRAGMA integrity_check, foreign-key violations, orphaned recipient rows, missing core tables
Search/index state Legacy FTS artifact presence, rebuildability, and search-side schema hygiene
Storage/runtime hygiene Stale archive locks, WAL mode, expired reservations, writable storage root

am doctor archive-scan is the non-mutating hygiene report for the Git archive itself. am doctor archive-normalize is the non-destructive remediation path for safe archive debt: it only rewrites project.json when the canonical absolute human_key is already known, and it quarantines duplicate canonical message files instead of deleting them. am doctor repair is the in-place SQLite hygiene path: it creates a backup, captures a forensic bundle, cleans orphaned rows, rebuilds legacy FTS artifacts if they still exist, and runs VACUUM/ANALYZE. am doctor reconstruct is the archive-first disaster-recovery path: it captures a forensic bundle, quarantines the bad database, rebuilds a fresh SQLite index from the Git archive, and merges any salvageable rows recovered from the old file while writing oversized warning sets to a report artifact instead of flooding the terminal. am doctor support-bundle creates a separate sanitized support artifact under STORAGE_ROOT/doctor/support-bundles/: it includes the current repair/reconstruct decision, schema/version shape, sidecar metadata, replay commands, redacted stdout/stderr when supplied, and sanitized copies of recent doctor reports. It deliberately omits raw SQLite files, canonical message files, message bodies, and attachments; pass --redact-subjects when subjects are sensitive. Review manifest.json before sharing because it lists every included file, redaction mode, source path class, and omitted evidence class. am doctor fix sits above both: it runs the full diagnostic pass, repairs MCP config and shell integration issues, removes stale archive lockfiles, enables WAL when needed, stops unhealthy local Agent Mail processes when the runtime health probes fail, and chooses between repair vs reconstruction based on what the probes found.


Troubleshooting

Problem Fix
"sender or recipients not registered" Register the sender and verify all recipient names are registered in the target project_key
"FILE_RESERVATION_CONFLICT" Adjust patterns, wait for TTL expiry, or use non-exclusive reservation
Auth errors with JWT Include bearer token with matching kid in the request header
Port 8765 already in use am serve-http --port 9000 or stop the existing server
TUI not rendering Check TUI_ENABLED=true and that your terminal supports 256 colors
TUI appears frozen (render/input stuck, but the process is still serving MCP/API) Do not kill the process. Run the non-interactive freeze escape hatch am tui-dump --format json: it returns the same situational snapshot the TUI renders, fetched live from /mail/ws-state (including a per-loop liveness verdict that names the stalled loop) and falling back to a local SQLite read if the whole process is wedged. Always exits 0. am robot health --format json also classifies the stall and points at the same read-out. If the freeze persists, restart headless: mcp-agent-mail serve --no-tui.
TUI shows garbled / stale cells (render corruption that clears on resize) A guaranteed full redraw is bounded by wall clock: AM_TUI_FULL_REDRAW_MAX_SECS (default 30s). Lower it (e.g. 2) to repair desync faster at the cost of resending the whole screen that often, or set <= 0 to disable the bound. Ensure you are on the latest build — confirm with am --version and reinstall via ./install-local.sh if stale, since render fixes ship in the binary, not the running session.
TUI becomes mostly blank after running for a while Upgrade to v0.3.21 or later and confirm with am --version. Older builds could mistake a healthy 100 ms frame for a missed 16.6 ms budget and then replace most content with an EssentialOnly frame. Current builds keep the console at full visual fidelity under load; am e2e run --project . tui_full_traversal verifies this against an emulated terminal screen.
Resource is temporarily busy The message includes the activity-lock owner PID, age, mode, subject, and executable when metadata is available. Run the read-only am doctor locks --json; do not remove lock files. Stale advisory metadata is reaped only after an exclusive-lock probe proves no kernel holder and records PID/executable liveness evidence. am clear-and-reset-everything and am archive restore do not stop here: they stop the running server themselves and start its service again afterwards.
Interactive am while a service is running It attaches a read-only TUI snapshot and leaves the service running. Restart coordination uses a single mailbox mutex with bounded jittered backoff, so concurrent starts wait for a live peer instead of restart-fighting. Use --takeover only for an intentional replacement.
CURSOR_EXPIRED from am inbox-events The saved delivery cursor fell below retained history. Start from --position-now for a new tail position, or choose a recovery policy using oldest_available_cursor; do not substitute a message ID.
Empty inbox Verify recipient names match exactly and messages were sent to that agent
Search returns nothing Try simpler terms and fewer filters; inspect diagnostics in search_messages explain output
Pre-commit guard blocking Check am robot reservations --conflicts for active reservations
Tools time out / "Database corruption detected" under heavy load Inspect the timeout response or health_check timeout diagnostics: they report contended_path, whether that stage exceeded the client deadline, p99 pool-acquire/database-write/archive queue/archive-commit latency, and blocking-dispatch occupancy. An unattributed dispatch timeout is reported as such, not blamed on SQLite. Also run am robot health --include-host --format json; if host_pressure_likely is true (low disk/inodes, high load-per-CPU, low free memory) or the data dir is not writable, relieve host pressure before reconstructing.
am serve-http shows systemd active (running) but port 8765 is not reachable (high memory, single thread) A degraded/corrupt DB is making the startup recovery slow. The server binds the listener within STARTUP_READINESS_BIND_TIMEOUT_SECS (default 20s) regardless, so /healthz returns 200 while the DB recovers in the background and /health reports warming_up/unavailable. If it persists: stop the service and run am doctor check --json; or quarantine storage.sqlite3* (move, don't delete) and restart to rebuild from the Git archive. To skip only the boot-time integrity probe: INTEGRITY_CHECK_ON_STARTUP=false am serve-http --no-tui. Periodic checks, backups and maintenance remain enabled, with the first cycle after INTEGRITY_QUICK_CHECK_INTERVAL_SECONDS (default 300s). Integrity health reports the configured skip and scheduled time, staying yellow until a full check passes; a failed check stays red. INTEGRITY_GUARD_ENABLED=false disables the entire worker and its backups/maintenance; DB_MAINTENANCE_ENABLED=false disables only maintenance.
Kernel-log segfault ... ip 0x1db250 ... in git[...] System has git 2.51.0 which races .git/index under multi-agent load. See Known-bad git versions below.
fatal: bad object HEAD or orphan stashes that appear after sessions Same as above. Set AM_GIT_BINARY to a safer git, then run am doctor fix-orphan-refs --all (a dry run unless --apply).

Known-bad git versions

mcp-agent-mail invokes git in a handful of places (pre-commit guard, share export, reservation activity probes, project identity detection). Some git versions have multi-process concurrency bugs that corrupt .git/index under load:

Version Status Primary mitigation
2.51.0 KNOWN BAD — segfaults in cache_entry walk under concurrent index writes (IP 0x1db250). Ubuntu 25.10 "questing" is the main affected distro. export AM_GIT_BINARY=/path/to/git-2.50.x/bin/git and/or upgrade to 2.51.1+ when available. See docs/RECOVERY_RUNBOOK.md#git-2-51-0-index-race.

Run am doctor check to see whether your system git is flagged. The check emits a structured finding (code GIT_2_51_0_INDEX_RACE, exit code 3 in CI mode) so pipelines can gate on it.

AM_GIT_BINARY overrides the resolver used by all in-process git shell-outs (identity detection, reservation activity, share/hosting, etc.). libgit2 calls (vendored) are unaffected — they don't exhibit this bug. Operators who have already migrated a path to libgit2 don't need to do anything.


Limitations

  • Rust nightly required. Uses Rust 2024 edition features that require the nightly compiler.
  • Pinned Git dependencies. Current main fetches FrankenSearch, FastMCP, and patched FrankenSQLite from immutable Git revisions. Source builds require access to those repositories as well as crates.io; use cargo build --locked. Earlier release tags may require sibling checkouts; install.sh --from-source provisions their recorded revisions.
  • Single-machine coordination. Designed for agents running on the same machine or accessing the same filesystem. Not a distributed system.
  • Advisory, not enforced. File reservations are advisory. Agents can bypass the pre-commit guard with --no-verify.
  • No built-in authentication federation. JWT support exists, but there's no centralized auth service. Each server manages its own tokens.
  • Slow file reservations at swarm scale. The release smoke's 60-agent swarm phase runs 3 projects of mixed sends, reads, acks and reservations, with a server kill midway. In it, file_reservation_paths and release_file_reservations miss the 5 s p99 budget. On v0.3.38 they take about 13 s at the median and about 25 s at p99. v0.3.39 stages each reservation's archive file outside the process-wide archive lock, which brings the median to about 4.7 s; the p99 is still about 15 s, because the archive batch drain holds that lock through its fsyncs (#344, #345). In the same runs no acknowledged message was lost across the kill, and integrity checks passed. Receipts: docs/evidence/release_smoke/.

FAQ

Q: How is this different from the Python version? A: This is a ground-up Rust rewrite with the same conceptual model but significant improvements: a 16-screen interactive TUI, robot mode CLI, Search V3 (lexical tier in release binaries; the semantic/hybrid tier is compiled into default-feature source builds), build slots, the product bus for cross-project coordination, and substantially better performance. The conformance suite protects supported compatibility for 38 tools (37 through the captured sequential behavior fixture plus fetch_topic through its retained schema/description and topic-bearing router tests), covers 7 Rust-native tools, and exercises all 25 MCP resources (23 through the captured fixture; the two Rust-native tooling resources through unit tests). The Rust implementation remains authoritative.

Q: Do I need to run a separate server for each project? A: No. One server handles multiple projects. Each project is identified by its absolute filesystem path as the project_key.

Q: How do agents get their names? A: Agent Mail generates memorable adjective+noun names (e.g., GreenCastle, BlueLake, RedHarbor) when agents register. Agents can also specify a name explicitly.

Q: Can agents in different repos talk to each other? A: Yes. Either share one project_key for both repos (Option A under "Across Different Repos"), or keep separate projects, approve a contact link with macro_contact_handshake(..., to_project=...), and address the other agent as Name@project (Option B). The message is stored in the recipient's project, where it reads, acknowledges and replies as usual. A plain name only addresses the sender's own project.

Q: Does this work with Claude Code's Max subscription? A: Yes. You can use a Max account with Agent Mail. Each agent session connects to the same MCP server regardless of subscription tier.

Q: What happens if the server crashes? A: SQLite is the normal live state and the Git archive is the durable audit/recovery ledger. On a clean shutdown the commit coalescer flushes queued archive writes; after corruption or unrecoverable drift, am doctor reconstruct can rebuild SQLite from the archive.

Q: How does this integrate with Beads? A: Use the Beads issue ID (e.g., br-123) as the Mail thread_id. Beads owns task status/priority/dependencies; Agent Mail carries the conversations and audit trail. The installer can optionally set up Beads alongside Agent Mail.


Appendix: Protocol Transcript

This is what a realistic agent-to-agent coordination flow looks like on the wire. Agents do not need to memorize JSON-RPC; the value of this appendix is that it shows the explicit, auditable contract the system exposes.

1. Boot a session

{"jsonrpc":"2.0","id":"1","method":"tools/call","params":{"name":"macro_start_session","arguments":{
  "human_key":"/data/projects/acme-api",
  "program":"codex-cli",
  "model":"gpt-5",
  "agent_name":"BlueLake",
  "task_description":"Auth refactor",
  "file_reservation_paths":["src/auth/**"],
  "file_reservation_reason":"br-123",
  "inbox_limit":10
}}}

Representative response shape:

{
  "project":{"slug":"data-projects-acme-api","human_key":"/data/projects/acme-api"},
  "agent":{"name":"BlueLake","program":"codex-cli","model":"gpt-5"},
  "file_reservations":{"granted":[{"path_pattern":"src/auth/**","exclusive":true}]},
  "inbox":[]
}

2. Notify another agent

{"jsonrpc":"2.0","id":"2","method":"tools/call","params":{"name":"send_message","arguments":{
  "project_key":"/data/projects/acme-api",
  "sender_name":"BlueLake",
  "to":["GreenCastle"],
  "subject":"[br-123] Starting auth refactor",
  "body_md":"I have `src/auth/**` reserved. Please keep your edits in `src/http/**` for now.",
  "thread_id":"br-123",
  "ack_required":true,
  "importance":"high"
}}}

3. Peer fetches inbox and acknowledges

{"jsonrpc":"2.0","id":"3","method":"tools/call","params":{"name":"fetch_inbox","arguments":{
  "project_key":"/data/projects/acme-api",
  "agent_name":"GreenCastle",
  "limit":20,
  "include_bodies":true
}}}
{"jsonrpc":"2.0","id":"4","method":"tools/call","params":{"name":"acknowledge_message","arguments":{
  "project_key":"/data/projects/acme-api",
  "agent_name":"GreenCastle",
  "message_id":1482
}}}

4. Reply in-thread

{"jsonrpc":"2.0","id":"5","method":"tools/call","params":{"name":"reply_message","arguments":{
  "project_key":"/data/projects/acme-api",
  "message_id":1482,
  "sender_name":"GreenCastle",
  "body_md":"Understood. I am staying in `src/http/**`. Ping me before changing token parsing."
}}}

5. Release the reservation when done

{"jsonrpc":"2.0","id":"6","method":"tools/call","params":{"name":"release_file_reservations","arguments":{
  "project_key":"/data/projects/acme-api",
  "agent_name":"BlueLake",
  "paths":["src/auth/**"]
}}}

One deliberate non-feature: broadcast=true on send_message is intentionally rejected. The system wants explicit routing, not default spam.


Appendix: TUI vs. Web vs. Robot

The project exposes the same underlying state through three different surfaces. They are optimized for different operators, not different data.

Surface Best for Output style
TUI Live human operations and triage Dense interactive cockpit
Web UI Browser-based review and overseer intervention Server-rendered HTML routes under /mail/
Robot CLI Agent loops, hooks, and machine-readable snapshots toon, json, and md

Representative TUI Shape

┌ Dashboard ───────────────────────────────────────────────────────────────┐
│ unread: 12   urgent: 2   ack_overdue: 1   reservations: 5              │
│ top threads: br-123, br-140, infra-cutover                             │
│ anomalies: 2 acks overdue | 1 reservation expiring < 15m               │
├ Screens ────────────────────────────────────────────────────────────────┤
│ 1 Dashboard  2 Messages  3 Threads  4 Agents  5 Search  6 Reservations │
│ 7 Tool Metrics  8 System Health  9 Timeline  0 Projects                │
│ ! Contacts  @ Explorer  # Analytics  $ Attachments  % Archive Browser  │
└──────────────────────────────────────────────────────────────────────────┘

Representative Web Shape

/mail/
  Unified Inbox
  Filter: importance=high
  Project: acme-api

  [high][ack required] #1482  [br-123] Starting auth refactor
  From: BlueLake   To: GreenCastle   Thread: br-123
  Quick links: thread | attachments | project overview | reservations

Representative Robot Shape

{
  "_meta": {
    "command": "robot status",
    "format": "json",
    "project": "acme-api",
    "agent": "BlueLake",
    "version": "1.0"
  },
  "_alerts": [
    {
      "severity": "warn",
      "summary": "2 acks overdue",
      "action": "am robot inbox --ack-overdue"
    }
  ],
  "_actions": [
    "am robot reservations --conflicts"
  ],
  "health": { "status": "ok" },
  "inbox_summary": { "total": 12, "urgent": 2, "ack_overdue": 1 }
}

The reason the three surfaces coexist is pragmatic. Humans need a cockpit and a browser view. Agents need compact, deterministic snapshots. All three ride on the same mailbox state.


Ready-Made Blurb for Your AGENTS.md

Add this to your project's AGENTS.md or CLAUDE.md to help agents use Agent Mail effectively:

## MCP Agent Mail: coordination for multi-agent workflows

What it is
- A mail-like layer that lets coding agents coordinate asynchronously via MCP tools and resources.
- Provides identities, inbox/outbox, searchable threads, and advisory file reservations,
  with human-auditable artifacts in Git.

Why it's useful
- Prevents agents from stepping on each other with explicit file reservations (leases).
- Keeps communication out of your token budget by storing messages in a per-project archive.
- Offers quick reads (`resource://inbox/{Agent}?project=<abs-path>`, `resource://thread/{id}?project=<abs-path>`) and macros that bundle common flows.

How to use effectively
1) Register an identity: call ensure_project with this repo's absolute path as
   `human_key`, then call register_agent using that same absolute path as `project_key`.
2) Reserve files before you edit: file_reservation_paths(project_key, agent_name,
   paths=["src/**"], ttl_seconds=3600, exclusive=true)
3) Communicate with threads: use send_message(..., thread_id="FEAT-123"); check inbox
   with fetch_inbox and acknowledge with acknowledge_message.
4) Quick reads: resource://inbox/{Agent}?project=<abs-path>&limit=20

Macros vs granular tools
- Prefer macros for speed: macro_start_session, macro_prepare_thread,
  macro_file_reservation_cycle, macro_contact_handshake.
- Use granular tools for control: register_agent, file_reservation_paths, send_message,
  fetch_inbox, acknowledge_message.

Common pitfalls
- "sender or recipients not registered": register the sender first and verify recipient names in the correct project_key.
- "FILE_RESERVATION_CONFLICT": adjust patterns, wait for expiry, or use non-exclusive.

Documentation

Document Purpose
INDEX.md Navigation map for the docs tree
OPERATOR_COOKBOOK.md Canonical copy-paste operator recipes for setup, triage, reservations, messaging, share/export, and diagnostics
OPERATOR_RUNBOOK.md Deployment, troubleshooting, diagnostics
DEVELOPER_GUIDE.md Dev setup, debugging, testing
MIGRATION_GUIDE.md Python to Rust migration
DUAL_MODE_ROLLOUT_PLAYBOOK.md Operational rollout notes for the dual-mode interface
RUNBOOK_LEGACY_PYTHON_TO_RUST_IMPORT.md am legacy / am upgrade migration operations
RELEASE_CHECKLIST.md Pre-release validation
ROLLOUT_PLAYBOOK.md Staged rollout strategy
SPEC-browser-parity-contract-deferred.md Shipped public WASM replay boundary and deferred live browser-parity contract
SPEC-threat-model.md Consolidated threat model covering assets, adversaries, attack surfaces, and mitigations
SPEC-search-v3-query-contract.md Query grammar, filters, and Search V3 contract
SPEC-web-ui-parity-contract.md Web UI parity and route contract
SPEC-verify-live-contract.md Static bundle live-verification rules
TUI_V2_CONTRACT.md TUI surface contract and parity expectations
TEMPLATE_OLD_REPO_RUST_CUTOVER_PR_CHECKLIST.md Canonical old-repo cutover PR checklist
TEMPLATE_OLD_REPO_RUST_CUTOVER_RELEASE_NOTES.md Release-notes template for Rust cutover
ADR-001 Dual-mode interface design
ADR-002 Single binary with mode switch
ADR-003 Search v3 implementation

About Contributions

Please don't take this the wrong way, but I do not accept outside contributions for any of my projects. I simply don't have the mental bandwidth to review anything, and it's my name on the thing, so I'm responsible for any problems it causes; thus, the risk-reward is highly asymmetric from my perspective. I'd also have to worry about other "stakeholders," which seems unwise for tools I mostly make for myself for free. Feel free to submit issues, and even PRs if you want to illustrate a proposed fix, but know I won't merge them directly. Instead, I'll have Claude or Codex review submissions via gh and independently decide whether and how to address them. Bug reports in particular are welcome. Sorry if this offends, but I want to avoid wasted time and hurt feelings. I understand this isn't in sync with the prevailing open-source ethos that seeks community contributions, but it's the only way I can move at this velocity and keep my sanity.


License

MIT License (with OpenAI/Anthropic Rider). See LICENSE.

README fetched from the repository 9 hours ago. The original is authoritative.

Questions

About Agent Mail Rust

How do I install Agent Mail Rust?

No package has been published for this server, so install it from source: clone the repository and follow its README.

Is Agent Mail Rust safe to use with an AI agent?

Its trust score is 64 out of 100 (good). It passes 4 of the 6 static security checks run so far; the failures are listed above. It has no ConduID identity yet, so agent calls to it are not receipted.

Is Agent Mail Rust still maintained?

Yes — the latest release is v0.3.39 (13 Oct 2026), and the last commit was 9 hours ago. The repository has 204 stars and 30 open issues.