postbag
Two agents, one bag of letters. Any two Claude Code or Codex sessions on the same machine, of the same vendor or not, write to each other. Each letter reaches the other agent through its vendor's own wake-up door, lands in one ledger, and counts against a human-set letter budget.
Real commands, real output, fake doors: a temporary ledger and two throwaway sockets, so no session or token is shown. Tape: docs/demo.tape.
Use it to have one agent review the other's diff, to split a task between them and agree the interface by letter, or to get a second opinion without pasting context by hand. Text travels by postbag, code travels by git.
Install
Python 3.10 or later, standard library only.
pipx install postbag
postbag --version
Or from the tag: pipx install git+https://github.com/parasxos/postbag@v1.1.1.
Each vendor in use brings its own door. A Claude Code session needs its
per-session messaging socket and must export CLAUDE_CODE_MESSAGING_SOCKET
and CLAUDE_CODE_MESSAGING_TOKEN to the commands it runs. A Codex session
needs the queue command Codex added in 0.149,
must export CODEX_SESSION_ID, and codex queue --help must work. Set
POSTBAG_CODEX if the binary is not in the ChatGPT app or on PATH. Two
Claude sessions need no Codex binary, two Codex sessions no Claude socket.
Verified live on macOS: 1.0.2 with Claude Code 2.1.263 and Codex 0.153.4
from the ChatGPT desktop app. 1.1.0 with two real Claude Code 2.1.263
sessions, including a two-way exchange and the spent-budget refusal. Linux
passes CI but live delivery is unverified there. Windows is unsupported.
Quick start
Open two sessions on the same machine. Either role can be any supported vendor: a Claude and a Codex session, or two of the same.
-
Ask each session to join under a name:
postbag join claude adaandpostbag join claude bob, orpostbag join codex bobfor a Codex session. The name defaults to the vendor, so a same-vendor pair needs distinct names. Names print with@andsendtakes them either way. -
In a terminal of your own, outside both sessions, run
postbag open --limit 6. Without--limitan exchange holds 12 letters. -
Ask ada to send the first letter:
postbag send @bob "Review my last commit. Reply with the top three findings."
bob wakes with the letter: "Letter 1 of 6 from @ada to @bob via postbag (exchange 1)", how many letters are left, the body, and the one command that answers,
postbag send @ada -with the reply on stdin. Neither agent needs instructions. The last letter says "do not send a reply", and the nextsendrefuses and tells the agent to stop and ask you. -
Read the bag from anywhere with
postbag read. Its first line lists the names the bag holds now and the open exchange, then the records follow.
After a restart, a session must join again under the name it held. A reply
command names a name, not a door: it reaches whoever holds the name when it
runs, and if another door took the name, the sender's next send refuses.
Upgrading from 1.0
Ledgers written by 1.0 read without rewriting, and legacy vendor peers read
as @claude and @codex. Do not mix a 1.0 session with a 1.1 session: a
1.0 send takes only claude or codex without the @, so it cannot run
the reply command in a 1.1 letter. Upgrade both, then ask each to join again.
How it works
join writes the session's door into the ledger under a name: Claude Code's
messaging socket and token, or Codex's thread id. send knocks on that door,
the socket or codex queue, then appends the letter under a file lock, so
two letters sent at once get distinct numbers and one budget. Each open
starts the next exchange, letters are numbered within it, and the budget is
shared by everyone in the bag. Two sessions are the supported use. Three or
more is experimental, read says so, and every letter then lists the
registered names. The ledger, ~/.postbag/ledger.jsonl, is the only state
and is shared across projects by default. Set the same POSTBAG_LEDGER in
both sessions and your terminal for a separate bag. No daemon, no polling,
no hooks, no server, no config file. CONCEPT.md
is the whole specification in a page, with the envelope's exact shape and
the table that maps Claude Code's own words onto postbag's.
Security and limits
- The ledger holds every Claude session token and every letter. Writes keep
the file
0600and new state directories are0700. An existing custom directory is left alone.readhides the door fields,catdoes not. Keep the raw file out of git, logs and screenshots. - A letter becomes a user turn in the recipient session. Trust both sessions with the task. postbag itself sends nothing off the machine, the vendor sessions forward the letter to their model services like any prompt.
- A name is an address, not authentication.
openrefuses inside a session. Both checks read the vendors' session variables: a guardrail against mixed-up roles, not protection against another process running as you. - Unattended delivery to Claude was observed with bypass permissions on the tested versions. Other modes may hold the letter for your approval. Codex needs permission to write the ledger and connect to the Claude socket.
- "Delivered" means submitted through the door, not read. A timeout or a crash between submission and recording leaves a letter in doubt, and there are no acknowledgements or retries. Check the recipient before sending again.
postbag is a small bridge for two existing sessions. Tools that do more, and what they do, are listed in docs/readme-research.md.
Concept · Security · Changelog · Contributing · MIT
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file postbag-1.1.1.tar.gz.
File metadata
- Download URL: postbag-1.1.1.tar.gz
- Upload date:
- Size: 36.6 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
7b2ac464537eb7348e79662c89d392b27d578b0965fe50ac9218d66237483f23
|
|
| MD5 |
bd1e3ba095cd73c2c2338a78b3b504d3
|
|
| BLAKE2b-256 |
24a51c90a10c9b2dc00ae81c3f0d7732767a999d60cf1963a4d7954f64bb7594
|
Provenance
The following attestation bundles were made for postbag-1.1.1.tar.gz:
Publisher:
release.yml on parasxos/postbag
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
postbag-1.1.1.tar.gz -
Subject digest:
7b2ac464537eb7348e79662c89d392b27d578b0965fe50ac9218d66237483f23 - Sigstore transparency entry: 2765756439
- Sigstore integration time:
-
Permalink:
parasxos/postbag@01bc4302db91493439460715ba2e4424b85fffee -
Branch / Tag:
refs/tags/v1.1.1 - Owner: https://github.com/parasxos
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@01bc4302db91493439460715ba2e4424b85fffee -
Trigger Event:
push
-
Statement type:
File details
Details for the file postbag-1.1.1-py3-none-any.whl.
File metadata
- Download URL: postbag-1.1.1-py3-none-any.whl
- Upload date:
- Size: 11.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via:
twine/7.0.0 CPython/3.13.14
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b464b6a0ba3ee62599b3090315b410120202e562a637a00b73a78f3519b7ae11
|
|
| MD5 |
744cdd30700e77ab40be352f7e9079ff
|
|
| BLAKE2b-256 |
8037feca60bb30a7b3ddf0a67fa6ee377c7c7d81cdbcabdd10a1ceeade94bcb6
|
Provenance
The following attestation bundles were made for postbag-1.1.1-py3-none-any.whl:
Publisher:
release.yml on parasxos/postbag
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
postbag-1.1.1-py3-none-any.whl -
Subject digest:
b464b6a0ba3ee62599b3090315b410120202e562a637a00b73a78f3519b7ae11 - Sigstore transparency entry: 2765756559
- Sigstore integration time:
-
Permalink:
parasxos/postbag@01bc4302db91493439460715ba2e4424b85fffee -
Branch / Tag:
refs/tags/v1.1.1 - Owner: https://github.com/parasxos
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@01bc4302db91493439460715ba2e4424b85fffee -
Trigger Event:
push
-
Statement type: