Skip to main content

Agent Letter Bridge

PyPI version

Message the CLI agents on your machine from your phone — and every message becomes part of their memory.

Agent Letter Bridge connects a chat app to AI agents on your own machine. An incoming message becomes a durable file on disk before it is acknowledged and before any agent is notified. The file is the source of truth; the ring is how it gets read. Letter first, then ring — without a bell, mail lands in a dead drop and nobody is told.

Integrated mode requires a doorbell helper with a ring <to> <type> <id> command that prints one doorbell-outcome v=1 line. Letterbox 0.4.0 defines that format and its fixtures, but no public Letterbox edition ships a ring dispatcher yet — integrated deployment needs a helper that provides it. A helper that still prints prose will make every ring failing / unparseable. alb --doctor reports helper does not emit doorbell-outcome v=1 instead of waiting for a silent ring-health failure after cutover.

Most tools in this space deliver an external message as the agent's input — typed into its terminal, or handed to its session as a prompt. This one delivers it as a durable, deduplicated, enveloped letter, written to disk before the platform is even told the message was received. That buys you three things: a context window that stays clean, because the doorbell is one contentless line and the body enters only when the agent chooses to read it; messages that survive compaction at full fidelity, because a letter lives outside the session; and a growing archive of records — origin, receipt time, content, every origin allowlist-verified — that any memory system can take as ground truth. Delivery that behaves like memory, not like typing.

More memory than message.

Why this exists

CLI agents don't have phones. The most capable coding agents live in terminal panes on a machine, and the moment you stand up from the desk they are unreachable — while everything else in your life answers from the phone in your pocket. This bridge gives the agents on your machine a messaging app: text them from anywhere, and they can answer.

The added edge is what arrives. A letter is more than the message. It carries its origin — the chat it came from, verified against the allowlist — when it was received, its platform addressing and an exactly-once guarantee — a record, in plain Markdown, that everything downstream can trust: the agent reading it now, the memory system ingesting it later, the search that asks what was said last month. Replies are addressed to the letter, which knows its own way home. Since v0.2 the archive runs both ways: an outbound reply is written as its own letter before the platform is touched, and its delivery events are recorded as immutable files beside it.

Sending works the same way round. Your agent answers a letter it holds, and the destination comes from that letter rather than from anything the agent chose — so a reply goes where the message came from, checked against the allowlist again at the moment of sending. A letter can be answered only once: the claim is an exclusive file create, so a retried or duplicated invocation cannot put a second message on the wire. That bounds sending, not delivery — where the outcome is uncertain the letter is dead-lettered with the uncertainty recorded, rather than retried into a possible duplicate.

Reply by default; initiating only by explicit, revocable grant. Every reply is tied to a stored incoming letter and goes back to that letter's chat, checked against the allowlist again at the moment of sending. Starting a new conversation is a separate, narrower path: the bridge can open one only to a destination the operator has authorised with a grant it can revoke at any time, and even then only within a small daily and hourly budget with a cap on messages in flight. Without a grant the bridge still cannot originate contact with anyone; with one, it cannot originate contact anywhere the operator has not named, nor more often than the budget allows.

If the agent already has a messenger of its own (a gateway bot, a chat plugin — for example Hermes Agent's gateway), the bridge runs beside it on its own bot; the two never share a token at the same time — see the install guide.

What that does not claim: an agent still chooses which stored letter to answer and what to write in it, so this is not immunity to being persuaded into sending something. It is a guarantee about where a message can go, not about what it says — and it covers this send path, not everything else your agent can reach. This describes the current release.

That is what makes this a front door rather than a pipe. Whatever you build behind it — today's agent, tomorrow's memory system — inherits records instead of scrollback. The input side of your setup is settled once.

The context economics follow from the same split. The body never enters the agent's context window until the agent chooses to read it — the doorbell is one contentless line — and it can be re-read at full fidelity after a compaction, because a letter is storage outside the window. What a session forgets, the inbox still knows.

Memory systems get the same service. Underneath every one of them — vault-based, RAG, graph, whatever comes next — sits the same need: durable, addressable, re-readable records of what was actually said. A letter already is one. Point any memory system at the inbox and it has its ground truth: no scraper, no export, no plugin. We are deliberately only the storage half — the records are ours, the librarian can be anyone's.

Durability is the supporting property, not the pitch: your phone keeps your copy, and the letter is your agent's — still on disk after a crash, a restart or a compaction, which is how a resurrected agent gets its context back.

Design

Four roles with deliberately unequal privilege. The separation is the product — and it is enforced at module boundaries and proved by tests (the poller code path cannot ring; the watchdog reads only mirrored state), not by OS process isolation: the resident bridge holds the token and the notifier in one process, and docs/operations.md states that limit plainly rather than letting this table imply more.

Role Trust May do May never do
Poller untrusted fetch, write letter, then ack ring, notify, or touch a terminal
Notifier in-session ring after a letter exists carry message content in the ring
Send helper bounded reply to an origin; initiate only on a live grant, within budget send with no grant; exceed the budget; send on allowlist miss; auto-retry
Watchdog independent read mirrored health, report restart anything; depend on what it monitors

Order is the invariant. Letter to disk → then platform ack. A crash between fetch and write causes redelivery, never loss.

Read docs/invariants.md before trusting this with a token. The invariants are the product; the code is how they are kept.

Install

Start here: INSTALL.md — one numbered path from nothing to a working bridge, about 15 minutes, with the checkpoints that catch the failures that otherwise look like something else.

Installing it with a CLI agent rather than by hand? Give the agent docs/agent-install.md — the same install written as a brief, with the boundaries an agent needs and a human infers.

The short version, once you know the shape:

pipx install agent-letter-bridge                # or: uv tool install agent-letter-bridge
alb --init --root ~/.alb                        # creates the files, asks for the rest

For pip, first create and activate a dedicated virtual environment, then run python -m pip install agent-letter-bridge. For development from a checkout, use pipx install . instead. Installation does not configure or start a relay.

--init may offer to start the bridge for you, and what comes next depends on your answer. Only one process may hold a root at a time, so these are two routes, not two steps:

# (a) you DECLINED the offer, or it was not made — run one cycle by hand:
alb --config ~/.alb/bridge.env --root ~/.alb --once

# (b) init STARTED it — do not run --once, it will exit 4 against a held
#     root, which is the lock working. Message the bot and watch instead:
alb --status --root ~/.alb

--init creates the state directory, a mode-600 config and a deny-all allowlist, then asks you for the things no program can derive: your bot token, whether your agent already has a mailbox, and your chat id — which it will either read for you, or print the command for you to run, your choice. It never invents an allowlist entry, never overwrites a file, and never touches the network unless you ask it to.

It will deliver nothing until a chat id is in that allowlist. That is deliberate and it is the step people skip.

If nothing arrives, run alb --doctor --root ~/.alb. A fail-closed allowlist is indistinguishable from a dead bot, so the doctor tells you which you have. Each cycle also reports itself — fetched 2 · published 1 · denied 1 (allowlist) — so a working deny is visible to you without being visible to the sender.

It refuses to start on a missing, world-readable or incomplete config. That is deliberate: a bridge that starts wrong is harder to diagnose at 3am than one that will not start at all.

The ring is what makes the bridge live, and it needs a multiplexer — cmux or tmux, selected with ALB_NOTIFIER. Mail without a bell is a dead drop: delivered, safe, and unread until someone thinks to look. What the design guarantees is that the ring may fail without costing a letter — never that you would want to run without one. If you must (no multiplexer, an agent that sweeps on its own schedule), the bridge still delivers and alb --status says the ring is disabled rather than leaving you guessing. Adapters are small files behind a written contract (docs/adapter-contract.md); a Herdr adapter is planned, and will ship when there is a live workspace to prove the doorbell against — untested transports do not ship here.

Reference and failure modes: docs/operations.md. Waking an agent that already handles other mail: docs/agent-setup.md.

Scope, plainly

It carries your own messages to your own agents. Your token stays on your machine and is used only against your chosen platform's API, from your own machine — there is no service in between. Messages travel over that platform, as they must.

It hands the agent a letter, not the message as a command. The platform message body is written to disk and never copied into the composer — the terminal input an agent's session would run — so it cannot arrive as text that is already running. The allowlist decides which chats may create letters; it does not make their contents trusted. Once an agent chooses to read a letter, its body is still untrusted text, and this tool does not make it safe.

The comparisons are checked, not asserted. Every claim of difference from neighbouring tools was verified against their current code and docs before it was written down: docs/COMPARE.md.

It pairs with Agent Letterbox. Letterbox is where mail rests between agents on one machine. Letter Bridge is how mail crosses in from outside.

Status

v0.4.1. Across releases, inbound delivery, ringing and bounded replies have been exercised live against real bots on macOS and Linux, cmux and tmux. v0.2 adds durable outbound letters, correspondent identity and threading, and read-only retrieval (--list, --show, --search, --thread, --export); those are covered by the suite and reviewed, but have not yet had the same live mileage as the inbound path. Automated coverage still uses fakes — the suite proves the invariants, the live runs prove the transports, and those are different claims. Available on PyPI, but not formally audited; see docs/threat-model.md for what is and is not claimed.

New inbound letter IDs use UTC. New inbound letters and outbound reply records also carry an explicit UTC sent: field: local publication/composition time, not proof of platform delivery or the original message's authoring time. Delivery events remain separate records beside the reply. Reply IDs remain source-derived so retries keep the same claim identity; their inherited ID stamp need not equal the reply's composition time. Existing letters are neither rewritten nor assigned a timezone they did not declare.

Upgrading from v0.2.1 (legacy installations). Three things change that you would notice. A rate limit or a gateway error is now waited out and retried rather than ending the bridge, so the relay survives the platform asking it to slow down. alb --check gives an agent a verdict to act on when it wakes: nothing to do, restart it and here is how, or something a restart will not fix. And alb --stop asks the process holding the lock to stand down, rather than signalling a pid that may by then belong to something else. Upgrading is pipx upgrade agent-letter-bridge for a PyPI installation, or pipx install --force . from a reviewed checkout. Plain pipx install . sees the existing install, prints Not modifying existing installation and stops. Stop the running bridge by whatever route the version you are ON supports: alb --stop is new here, and 0.2.1 will not honour it. Configs, allowlist and state carry forward untouched; check with alb --version before restarting. The full list is in CHANGELOG.md.

If multiple environments or editable checkouts exist, verify command -v alb and alb --version, and check that the resident launch command uses the intended environment's executable. Upgrading one environment does not update another or reload a running bridge.

What you need

  • Python 3.11+. Standard library only, zero third-party runtime dependencies.
  • A terminal multiplexer — cmux or tmux — selected with ALB_NOTIFIER. The ring types a line into a pane, so a pane must exist to type into. Expect to want this: without it mail lands durably and nobody is told. The bridge runs regardless, and alb --status reports the ring as disabled rather than leaving you guessing.
  • A CLI agent sitting in that pane.
  • A bot on your chat platform.

You do not need Agent Letterbox. They are separate products that share a metaphor and nothing else — no shared code, no shared files, no shared config. Letterbox carries mail between agents on one machine; this carries mail in from outside. Install either, both, or neither.

Requirements

Python 3.11+, standard library only. Zero third-party runtime dependencies — by design, and enforced in CI. macOS (launchd) and Linux (systemd user units). Windows is a declared gap, not a promise.

Licence

MIT — see LICENSE.

Release files for agent-letter-bridge 0.4.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for agent-letter-bridge 0.4.1
File Size Uploaded
agent_letter_bridge-0.4.1.tar.gz 300.7 kB Details

Built distribution (wheel)

Table of built distributions (wheels) for agent-letter-bridge 0.4.1
File Interpreter ABI Platform
agent_letter_bridge-0.4.1-py3-none-any.whl Python 3 none any Details

Total release size: 425.6 kB

Release files / agent_letter_bridge-0.4.1.tar.gz

Download URL agent_letter_bridge-0.4.1.tar.gz
Size 300.7 kB
Tags Source
SHA-256 checksum
How to use checksums
d4f78f6d82c85f6a0bddfd3f2e11f76b9da4f1f020e6e700c697ca68cb2ee81d
BLAKE2b-256 checksum
How to use checksums
a299a9ad705eac225d5d1f29902821529d9ca4d98c02dc9e7db02e1193a71033
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release files / agent_letter_bridge-0.4.1-py3-none-any.whl

Download URL agent_letter_bridge-0.4.1-py3-none-any.whl
Size 124.9 kB
Tags Python 3
SHA-256 checksum
How to use checksums
337f540e50c87473e00d9fe6896090a21802820ef3a27d01acdf8a9a54c3db8a
BLAKE2b-256 checksum
How to use checksums
bc37353e14b99af703f3ca2995196a553be8a438acdfedeea8d8e87bc1ca079e
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Sep 24, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.4.1 This release

2 release files

0.4.0

2 release files

0.3.2

2 release files

0.3.1

2 release files

0.3.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page