postbag 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- postbag-1.0.0/CHANGELOG.md +47 -0
- postbag-1.0.0/CONCEPT.md +60 -0
- postbag-1.0.0/CONTRIBUTING.md +42 -0
- postbag-1.0.0/LICENSE +21 -0
- postbag-1.0.0/MANIFEST.in +3 -0
- postbag-1.0.0/PKG-INFO +386 -0
- postbag-1.0.0/README.md +353 -0
- postbag-1.0.0/SECURITY.md +43 -0
- postbag-1.0.0/postbag +9 -0
- postbag-1.0.0/postbag.egg-info/PKG-INFO +386 -0
- postbag-1.0.0/postbag.egg-info/SOURCES.txt +19 -0
- postbag-1.0.0/postbag.egg-info/dependency_links.txt +1 -0
- postbag-1.0.0/postbag.egg-info/entry_points.txt +2 -0
- postbag-1.0.0/postbag.egg-info/requires.txt +5 -0
- postbag-1.0.0/postbag.egg-info/top_level.txt +1 -0
- postbag-1.0.0/postbag.py +290 -0
- postbag-1.0.0/pyproject.toml +51 -0
- postbag-1.0.0/setup.cfg +4 -0
- postbag-1.0.0/test_hardening.py +318 -0
- postbag-1.0.0/test_postbag.py +291 -0
- postbag-1.0.0/test_transports.py +109 -0
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to postbag are recorded here. The format follows
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/) and the project
|
|
5
|
+
uses [Semantic Versioning](https://semver.org/).
|
|
6
|
+
|
|
7
|
+
## [1.0.0] - 2026-09-08
|
|
8
|
+
|
|
9
|
+
First public release. Two agents on one machine, a Claude Code session and
|
|
10
|
+
a Codex session, correspond by letters through each vendor's own door.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
- `postbag.py` as an installable module with a `postbag` console command.
|
|
14
|
+
`pipx install git+https://github.com/parasxos/postbag@v1.0.0`.
|
|
15
|
+
- `postbag --version`.
|
|
16
|
+
- Ledger records are validated on every read: kind, sequence number, peers,
|
|
17
|
+
door fields, budget. A bad line is refused by number.
|
|
18
|
+
- The ledger directory is created `0700` and the file kept `0600`, since the
|
|
19
|
+
Claude session token lives in it. Appends are flushed and fsynced.
|
|
20
|
+
- Reads take a shared lock, writes an exclusive one, so concurrent sends
|
|
21
|
+
get distinct numbers and one budget.
|
|
22
|
+
- Timestamps carry a UTC offset.
|
|
23
|
+
- The Codex binary is found through `POSTBAG_CODEX`, then the ChatGPT app
|
|
24
|
+
bundle on macOS, then `codex` on `PATH`.
|
|
25
|
+
- MIT license, changelog, security policy, contributing guide, CI on
|
|
26
|
+
macOS and Linux for Python 3.10 to 3.14, release workflow on `v*` tags.
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- The reply instruction inside every letter now uses the heredoc delimiter
|
|
30
|
+
`POSTBAG` and tells the reader to pick one that does not occur in the reply.
|
|
31
|
+
- A refusal because a door did not answer names the `join` command the
|
|
32
|
+
recipient must run again.
|
|
33
|
+
- The checked-in `postbag` executable is a thin wrapper around `postbag.py`,
|
|
34
|
+
so a symlink to a checkout keeps working.
|
|
35
|
+
|
|
36
|
+
### Unchanged by design
|
|
37
|
+
- Two peers, four verbs, one ledger, no daemon, no polling, no hooks, no
|
|
38
|
+
configuration file. See [CONCEPT.md](CONCEPT.md).
|
|
39
|
+
- Legacy ledgers written before 1.0.0 still read.
|
|
40
|
+
|
|
41
|
+
## [0.x] - 2026-09-08
|
|
42
|
+
|
|
43
|
+
Four commits from "bridge" to "postbag" on the day the idea was born:
|
|
44
|
+
the ledger became the only state, `open` became human-only, and every
|
|
45
|
+
refusal learned to say stop.
|
|
46
|
+
|
|
47
|
+
[1.0.0]: https://github.com/parasxos/postbag/releases/tag/v1.0.0
|
postbag-1.0.0/CONCEPT.md
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
# postbag: two agents correspond by letters
|
|
2
|
+
|
|
3
|
+
## The idea
|
|
4
|
+
|
|
5
|
+
Two agents on one machine talk the way two people in adjacent offices do.
|
|
6
|
+
One writes a letter and slides it under the other's door. The door is
|
|
7
|
+
whatever wakes that agent natively. Every letter goes into one bag.
|
|
8
|
+
Nothing else exists.
|
|
9
|
+
|
|
10
|
+
## Five nouns
|
|
11
|
+
|
|
12
|
+
| Noun | Definition |
|
|
13
|
+
|---|---|
|
|
14
|
+
| **peer** | One of exactly two agents: `claude` or `codex`. |
|
|
15
|
+
| **door** | The native way to reach a peer. Claude: its inbox socket and token. Codex: its thread id, reached with `codex queue`. |
|
|
16
|
+
| **letter** | Text from one peer to the other. Numbered, timestamped, delivered, then recorded. |
|
|
17
|
+
| **exchange** | A budget of letters, opened by a human. |
|
|
18
|
+
| **ledger** | One append-only file, the bag. The whole history, the only state. |
|
|
19
|
+
|
|
20
|
+
## Four verbs
|
|
21
|
+
|
|
22
|
+
| Verb | Who | Effect |
|
|
23
|
+
|---|---|---|
|
|
24
|
+
| `join` | a peer, from inside its own session | records its door |
|
|
25
|
+
| `open` | a human, outside both sessions | starts an exchange with a budget of letters |
|
|
26
|
+
| `send` | a peer, from inside its own session | knocks on the recipient's door, then records the letter |
|
|
27
|
+
| `read` | anyone | prints the ledger |
|
|
28
|
+
|
|
29
|
+
## Principles
|
|
30
|
+
|
|
31
|
+
1. **Two peers, so addressing one names the other.** `send codex` is
|
|
32
|
+
from claude. There is no `--from`.
|
|
33
|
+
2. **The door is native.** No daemon, no polling, no hooks. Delivery
|
|
34
|
+
uses the mechanism each vendor built to reach its own agent.
|
|
35
|
+
3. **The letter teaches its reader how to answer.** Each delivered
|
|
36
|
+
letter begins with its number, its sender, and either the one command
|
|
37
|
+
that replies or the words "do not reply". Neither agent needs prior
|
|
38
|
+
instruction.
|
|
39
|
+
4. **The ledger is the truth.** The ledger records completed sends: a
|
|
40
|
+
letter is in it iff it was delivered and then recorded. Delivered means
|
|
41
|
+
submitted through the door, the socket write returned or `codex queue`
|
|
42
|
+
exited 0. Neither door acknowledges, and neither proves the agent read
|
|
43
|
+
it. Submission and recording are two steps, not one; a crash between
|
|
44
|
+
them leaves a submitted letter unrecorded. Doors and budgets are read
|
|
45
|
+
from the ledger, never from anywhere else. History is a file you can
|
|
46
|
+
`cat`.
|
|
47
|
+
5. **The human bounds the conversation.** An exchange holds the letters
|
|
48
|
+
its opener granted. When they are spent, `send` refuses and tells the
|
|
49
|
+
agent to stop. Every refusal an agent can meet tells it to stop and
|
|
50
|
+
ask the human. Who may run each verb is decided by the session
|
|
51
|
+
variables the vendors themselves export, the same signal for `join`,
|
|
52
|
+
`open` and `send`.
|
|
53
|
+
6. **Everything the agents share is the repository.** The bridge moves
|
|
54
|
+
text, never files. Work products travel through git.
|
|
55
|
+
|
|
56
|
+
## What is deliberately absent
|
|
57
|
+
|
|
58
|
+
Roles, topics, threads, acknowledgements, retries, a server, a
|
|
59
|
+
configuration file, a protocol document for the agents, a third peer. Each was
|
|
60
|
+
considered and found to add a noun without adding a capability.
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# Contributing
|
|
2
|
+
|
|
3
|
+
Read [CONCEPT.md](CONCEPT.md) first. It is short and it is the
|
|
4
|
+
specification. Two peers, four verbs, one ledger, native doors, a human
|
|
5
|
+
sets the budget. A change that adds a noun has to name the capability the
|
|
6
|
+
existing nouns cannot provide.
|
|
7
|
+
|
|
8
|
+
## Develop
|
|
9
|
+
|
|
10
|
+
```sh
|
|
11
|
+
git clone https://github.com/parasxos/postbag.git
|
|
12
|
+
cd postbag
|
|
13
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
14
|
+
python -m pip install -e ".[dev]"
|
|
15
|
+
python -m pytest -q
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
Tests use a temporary `POSTBAG_LEDGER` and fake doors. They must never
|
|
19
|
+
reach a real session. Keep them fast and stdlib-only.
|
|
20
|
+
|
|
21
|
+
## Change
|
|
22
|
+
|
|
23
|
+
- Every refusal goes through `fail()` and ends with "stop and ask the
|
|
24
|
+
human". Tests match on that wording.
|
|
25
|
+
- `send` does budget, knock, then append, all under the ledger lock. A
|
|
26
|
+
letter exists iff it was delivered and then recorded.
|
|
27
|
+
- The envelope text in `envelope()` is the protocol the agents follow.
|
|
28
|
+
Change it and its tests together.
|
|
29
|
+
- Never print a door field other than the peer name.
|
|
30
|
+
- Legacy ledgers must keep reading.
|
|
31
|
+
|
|
32
|
+
Commit subjects are short and imperative.
|
|
33
|
+
|
|
34
|
+
## Release
|
|
35
|
+
|
|
36
|
+
1. Bump `__version__` in `postbag.py` and add a `CHANGELOG.md` entry.
|
|
37
|
+
2. CI green on `main`.
|
|
38
|
+
3. `git tag v<version> && git push origin v<version>`. The release
|
|
39
|
+
workflow builds the wheel and sdist, checks them, and publishes a GitHub
|
|
40
|
+
release with checksums.
|
|
41
|
+
4. Install from the tag in a clean venv and run one two-way exchange with
|
|
42
|
+
real sessions, including the spent-budget refusal.
|
postbag-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Paris Moschovakos
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
postbag-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,386 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: postbag
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: Claude Code and Codex exchange letters, one shared bag. No daemon, no polling, no hooks.
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Project-URL: Homepage, https://github.com/parasxos/postbag
|
|
7
|
+
Project-URL: Documentation, https://github.com/parasxos/postbag#readme
|
|
8
|
+
Project-URL: Issues, https://github.com/parasxos/postbag/issues
|
|
9
|
+
Project-URL: Repository, https://github.com/parasxos/postbag
|
|
10
|
+
Project-URL: Changelog, https://github.com/parasxos/postbag/blob/main/CHANGELOG.md
|
|
11
|
+
Keywords: claude-code,codex,agents,multi-agent,cli
|
|
12
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
13
|
+
Classifier: Environment :: Console
|
|
14
|
+
Classifier: Intended Audience :: Developers
|
|
15
|
+
Classifier: Operating System :: MacOS
|
|
16
|
+
Classifier: Operating System :: POSIX :: Linux
|
|
17
|
+
Classifier: Programming Language :: Python :: 3
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
19
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
23
|
+
Classifier: Topic :: Software Development :: Libraries
|
|
24
|
+
Classifier: Topic :: Utilities
|
|
25
|
+
Requires-Python: >=3.10
|
|
26
|
+
Description-Content-Type: text/markdown
|
|
27
|
+
License-File: LICENSE
|
|
28
|
+
Provides-Extra: dev
|
|
29
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
30
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
31
|
+
Requires-Dist: twine>=6; extra == "dev"
|
|
32
|
+
Dynamic: license-file
|
|
33
|
+
|
|
34
|
+
<div align="center">
|
|
35
|
+
|
|
36
|
+
# 📮 postbag
|
|
37
|
+
|
|
38
|
+
### Two agents, one bag of letters.
|
|
39
|
+
|
|
40
|
+
**Claude Code and Codex, on one machine, correspond by letter.** Each letter
|
|
41
|
+
is delivered through the vendor's own wake-up mechanism, so the idle agent
|
|
42
|
+
wakes and answers. Every letter lands in one append-only ledger. A human
|
|
43
|
+
opens each exchange with a budget of letters. When it is spent, sending
|
|
44
|
+
refuses and tells the agent to stop.
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
pipx install git+https://github.com/parasxos/postbag@v1.0.0
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
One Python module, standard library only. No daemon, no polling, no
|
|
51
|
+
hooks, no server, no config file.
|
|
52
|
+
|
|
53
|
+

|
|
54
|
+

|
|
55
|
+

|
|
56
|
+

|
|
57
|
+

|
|
58
|
+

|
|
59
|
+
|
|
60
|
+
</div>
|
|
61
|
+
|
|
62
|
+
---
|
|
63
|
+
|
|
64
|
+
## ✨ What you can do
|
|
65
|
+
|
|
66
|
+
🔍 **Cross-review.** Claude writes the parser, Codex reads it cold and sends
|
|
67
|
+
back what it would change. Then swap. Each agent sees the other's work as a
|
|
68
|
+
letter in its own session, with the one command that answers it.
|
|
69
|
+
|
|
70
|
+
✂️ **Split a task.** One agent takes the backend, the other the tests. They
|
|
71
|
+
agree on the interface by letter, then work in the same repository. Text
|
|
72
|
+
travels by postbag, code travels by git.
|
|
73
|
+
|
|
74
|
+
🧠 **Second opinion.** Stuck on a design choice? Ask the other agent in one
|
|
75
|
+
letter and get an answer without leaving your session or pasting context by
|
|
76
|
+
hand.
|
|
77
|
+
|
|
78
|
+
🪞 **Mirrored implementation.** Both agents implement the same change from
|
|
79
|
+
the same brief. Compare the two, keep the better one, and let them argue
|
|
80
|
+
about the diff.
|
|
81
|
+
|
|
82
|
+
🤝 **Handoff.** Finish your part, write one letter that says what is done and
|
|
83
|
+
what is next, and the other agent picks it up on its next turn. The ledger is
|
|
84
|
+
the handoff document.
|
|
85
|
+
|
|
86
|
+
**Built with itself.** This release was made by a Claude Code session and
|
|
87
|
+
a Codex session corresponding over postbag: mirrored plans, mirrored
|
|
88
|
+
implementations, cross-review of every merge. The pull request is the
|
|
89
|
+
record.
|
|
90
|
+
|
|
91
|
+
## ⚡ How it is built
|
|
92
|
+
|
|
93
|
+
postbag adds nothing that has to be running. Delivery uses the mechanism
|
|
94
|
+
each vendor built to reach its own agent: Claude Code has a per-session
|
|
95
|
+
messaging socket, Codex has `codex queue`. postbag knocks on the right one,
|
|
96
|
+
then records the letter in one JSONL file. There is no background process,
|
|
97
|
+
no broker, no server to register, no polling loop.
|
|
98
|
+
|
|
99
|
+
What it leaves out is deliberate too: no acknowledgements, no retries, no
|
|
100
|
+
threads, no roles, no third peer. [CONCEPT.md](CONCEPT.md) lists each
|
|
101
|
+
omission and why it adds a noun without adding a capability.
|
|
102
|
+
|
|
103
|
+
## 🛡️ Built to be trusted
|
|
104
|
+
|
|
105
|
+
- 🧾 **The ledger is the truth.** A letter exists if and only if it was
|
|
106
|
+
delivered and then recorded. Doors and budgets are read from the ledger and
|
|
107
|
+
nowhere else. History is one file you can `cat`.
|
|
108
|
+
- 🛑 **The budget is the brake.** A delivered letter becomes a user turn in
|
|
109
|
+
the recipient session. Two agents will keep answering each other. The
|
|
110
|
+
exchange holds exactly the letters its opener granted. When they are spent,
|
|
111
|
+
`send` refuses and tells the agent to stop and ask the human.
|
|
112
|
+
- 🙋 **`open` is the human's verb.** `open` refuses to run inside either
|
|
113
|
+
agent's session, so neither agent extends its own budget. The check reads
|
|
114
|
+
the session variables the vendors export: a guardrail against mixed-up
|
|
115
|
+
roles, not authentication.
|
|
116
|
+
- 🚪 **Every operational refusal says stop.** Each refusal an agent can meet
|
|
117
|
+
while joining, opening or sending ends with "stop and ask the human", so
|
|
118
|
+
a failed send never turns into a retry loop.
|
|
119
|
+
- 🔒 **Private by construction.** The ledger is created with mode 0600 in a
|
|
120
|
+
0700 directory, opened without following symlinks, validated on every
|
|
121
|
+
read, and written under an exclusive lock. Two letters sent at once get
|
|
122
|
+
distinct numbers and share one budget.
|
|
123
|
+
- 🧱 **Nothing to run, nothing to configure.** No daemon, no polling, no
|
|
124
|
+
hooks, no server, no config file. Two environment variables are the only
|
|
125
|
+
knobs, and both have working defaults.
|
|
126
|
+
- 📜 **A short spec.** [CONCEPT.md](CONCEPT.md) is the specification: five
|
|
127
|
+
nouns, four verbs, six principles. The code follows it line by line.
|
|
128
|
+
|
|
129
|
+
## ✅ Prerequisites
|
|
130
|
+
|
|
131
|
+
- Python 3.10 or later. No third-party packages.
|
|
132
|
+
- A Claude Code session that exports `CLAUDE_CODE_MESSAGING_SOCKET` and
|
|
133
|
+
`CLAUDE_CODE_MESSAGING_TOKEN` to the commands it runs, and a Codex session
|
|
134
|
+
that exports `CODEX_SESSION_ID`, with a `codex` binary that has `queue`
|
|
135
|
+
(0.149 or later; `codex queue --help` must work).
|
|
136
|
+
- **macOS** is where the two-agent exchange is verified end to end, with
|
|
137
|
+
Claude Code 2.1.263 and Codex 0.153 from the ChatGPT desktop app.
|
|
138
|
+
- **Linux**: the module runs and the test suite passes in CI. The live
|
|
139
|
+
two-agent exchange is not verified there. `codex` must be on `PATH` or
|
|
140
|
+
named by `POSTBAG_CODEX`, and both sessions must export their variables.
|
|
141
|
+
- Windows is not supported. postbag uses Unix sockets and file locks.
|
|
142
|
+
|
|
143
|
+
## 🚀 Quick start
|
|
144
|
+
|
|
145
|
+
1. **Install** with [pipx](https://pipx.pypa.io/):
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
pipx install git+https://github.com/parasxos/postbag@v1.0.0
|
|
149
|
+
postbag --version
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
Or run it from a clone with nothing installed. The checked-in `postbag`
|
|
153
|
+
executable is a thin wrapper around the module:
|
|
154
|
+
|
|
155
|
+
```bash
|
|
156
|
+
git clone https://github.com/parasxos/postbag.git
|
|
157
|
+
mkdir -p ~/.local/bin && ln -sf "$PWD/postbag/postbag" ~/.local/bin/postbag
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
`~/.local/bin` has to be on your `PATH`.
|
|
161
|
+
|
|
162
|
+
2. **Each agent joins from inside its own session.** Ask Claude Code to run
|
|
163
|
+
the first, and Codex to run the second:
|
|
164
|
+
|
|
165
|
+
```bash
|
|
166
|
+
postbag join claude
|
|
167
|
+
postbag join codex
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
3. **You open an exchange** from a terminal of your own, outside both
|
|
171
|
+
sessions:
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
postbag open --limit 6
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
4. **Either agent writes.** Ask Claude to send the first letter:
|
|
178
|
+
|
|
179
|
+
```bash
|
|
180
|
+
postbag send codex "Review src/parser.py for unhandled input. Reply with the top three findings."
|
|
181
|
+
```
|
|
182
|
+
|
|
183
|
+
Codex wakes with the letter, answers with the command the letter carries,
|
|
184
|
+
and Claude wakes in turn. When the budget is spent, the last letter says
|
|
185
|
+
"do not reply" and the next `send` refuses.
|
|
186
|
+
|
|
187
|
+
5. **Read the bag** at any time, from anywhere. The transcript below is
|
|
188
|
+
illustrative: the timestamps, findings and commit id are invented for
|
|
189
|
+
the example, the format is exact.
|
|
190
|
+
|
|
191
|
+
```bash
|
|
192
|
+
postbag read
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
```
|
|
196
|
+
1 2026-09-08T10:02:11+02:00 join claude
|
|
197
|
+
2 2026-09-08T10:02:40+02:00 join codex
|
|
198
|
+
3 2026-09-08T10:03:05+02:00 open 6 letters
|
|
199
|
+
4 2026-09-08T10:03:30+02:00 letter claude -> codex
|
|
200
|
+
Review src/parser.py for unhandled input. Reply with the top three findings.
|
|
201
|
+
5 2026-09-08T10:05:12+02:00 letter codex -> claude
|
|
202
|
+
1. parse_line accepts an empty string and returns None without logging.
|
|
203
|
+
2. The date branch swallows ValueError and falls through to the default.
|
|
204
|
+
3. No upper bound on field count, a long line allocates unbounded memory.
|
|
205
|
+
6 2026-09-08T10:07:48+02:00 letter claude -> codex
|
|
206
|
+
Fixed all three in 4f2a9c1. Please re-review the date branch only.
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
What the recipient actually sees is the letter wrapped in a short envelope:
|
|
210
|
+
|
|
211
|
+
```
|
|
212
|
+
Letter 4 from claude via postbag. If it needs an answer, reply with:
|
|
213
|
+
postbag send claude - <<'POSTBAG'
|
|
214
|
+
...
|
|
215
|
+
POSTBAG
|
|
216
|
+
Choose a delimiter that does not occur in your reply. Otherwise do nothing.
|
|
217
|
+
|
|
218
|
+
Review src/parser.py for unhandled input. Reply with the top three findings.
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
The envelope is the whole protocol. Neither agent needs prior instruction.
|
|
222
|
+
|
|
223
|
+
> 💡 The Codex binary ships inside the ChatGPT desktop app on macOS and is
|
|
224
|
+
> found automatically. Elsewhere, `codex` must be on `PATH`, or set
|
|
225
|
+
> `POSTBAG_CODEX=/path/to/codex`. `codex queue` needs Codex 0.149 or later.
|
|
226
|
+
|
|
227
|
+
## 🔁 A workflow for paired work
|
|
228
|
+
|
|
229
|
+
Give both agents the same goal, then ask them to repeat this at each stage.
|
|
230
|
+
It is how this release was made.
|
|
231
|
+
|
|
232
|
+
| Stage | Each agent, independently | Agree by letter before moving on |
|
|
233
|
+
|---|---|---|
|
|
234
|
+
| Plan | Inspect the problem and propose a small solution. | Scope, acceptance checks, who owns which files. |
|
|
235
|
+
| Implement | Build a candidate on its own branch or worktree. | Compare diffs and combine the strongest parts. |
|
|
236
|
+
| Test | Run the checks and read the other candidate. | Fix failures, test the combined result. |
|
|
237
|
+
| Ship | Review the release diff and notes. | One agent performs the release, the other verifies it. |
|
|
238
|
+
|
|
239
|
+
Letters carry disagreements, commit ids and evidence. Work products move
|
|
240
|
+
through git. Never let both agents edit the same file at once.
|
|
241
|
+
|
|
242
|
+
## 🚪 How the doors work
|
|
243
|
+
|
|
244
|
+
A door is the native way to reach a peer. `join` records it in the ledger.
|
|
245
|
+
`send` knocks on it, then records the letter.
|
|
246
|
+
|
|
247
|
+
**claude.** Claude Code binds a per-session inbox socket and exports
|
|
248
|
+
`CLAUDE_CODE_MESSAGING_SOCKET` and `CLAUDE_CODE_MESSAGING_TOKEN` to the
|
|
249
|
+
commands it runs. `join claude` records both. A letter is one auth line and
|
|
250
|
+
one user-message line written to that socket. Claude Code reads the message
|
|
251
|
+
between tool calls during a turn, or starts a new turn with it when idle.
|
|
252
|
+
Because the letter carries the session's own token, a session running with
|
|
253
|
+
bypass permissions delivers it instead of holding it for approval. Verified
|
|
254
|
+
on macOS with Claude Code 2.1.263.
|
|
255
|
+
|
|
256
|
+
**codex.** `join codex` records the thread id from `CODEX_SESSION_ID`. A
|
|
257
|
+
letter is `codex queue --thread ID --message TEXT`. Codex stores it and
|
|
258
|
+
submits it when the thread's current turn ends, at once if the thread is
|
|
259
|
+
idle, or on resume if no Codex process has the thread open. Verified on
|
|
260
|
+
macOS with Codex 0.153.
|
|
261
|
+
|
|
262
|
+
**Who may run what.** The same session variables decide it. `join` and
|
|
263
|
+
`send` need the peer's own variables, so `send codex` can only come from a
|
|
264
|
+
Claude session and `send claude` only from a Codex session. `open` needs
|
|
265
|
+
none, and refuses if either set is present.
|
|
266
|
+
|
|
267
|
+
**State.** `~/.postbag/ledger.jsonl`, and nothing else. Override with
|
|
268
|
+
`POSTBAG_LEDGER`, using the same value in both sessions and your terminal.
|
|
269
|
+
Two different ledgers are two independent pairs with two budgets.
|
|
270
|
+
|
|
271
|
+
## 🔐 Security
|
|
272
|
+
|
|
273
|
+
Plain facts, so you can decide whether this fits your machine. The longer
|
|
274
|
+
version is [SECURITY.md](SECURITY.md).
|
|
275
|
+
|
|
276
|
+
- **The Claude session token is stored in the ledger.** `join claude` writes
|
|
277
|
+
the socket path and the token as a record. The file is mode 0600 in a
|
|
278
|
+
0700 directory. Anyone who can read it can write a user turn into that
|
|
279
|
+
Claude session. Treat the ledger like a credential file, because it is one.
|
|
280
|
+
`postbag read` never prints it. `cat` does.
|
|
281
|
+
- **A letter is a user turn.** The recipient treats the body as if you had
|
|
282
|
+
typed it. This is the feature, and it is also the risk. The other agent can
|
|
283
|
+
ask yours to do anything you could ask it. The human's letter budget is the
|
|
284
|
+
brake, and it is the only brake.
|
|
285
|
+
- **Unattended delivery was verified with bypass permissions.** In other
|
|
286
|
+
permission modes Claude Code may hold the incoming letter for your
|
|
287
|
+
approval; approve it and the turn proceeds. Choose the mode you would
|
|
288
|
+
choose for the task anyway. postbag does not need more.
|
|
289
|
+
- **The Codex sandbox must be opened a little.** Codex has to write the
|
|
290
|
+
ledger and connect to the Claude socket. Approve the escalation it asks
|
|
291
|
+
for, or run it with a sandbox profile that allows both.
|
|
292
|
+
- **Delivered means submitted.** `send` reports success when the letter
|
|
293
|
+
went through the door: the socket write returned, or `codex queue` exited
|
|
294
|
+
0. Neither door acknowledges. That is not proof the agent read it or acted
|
|
295
|
+
on it. A crash between the knock and
|
|
296
|
+
the append can leave a delivered letter unrecorded. There is no
|
|
297
|
+
acknowledgement, retry or exactly-once guarantee. When in doubt, read the
|
|
298
|
+
ledger and the recipient session before sending again.
|
|
299
|
+
- **postbag itself sends nothing off the machine.** It talks to a local Unix
|
|
300
|
+
socket and a local `codex` process; there is no network code in it. The
|
|
301
|
+
vendor sessions do what they always do: a letter becomes part of the
|
|
302
|
+
recipient's conversation and reaches that vendor's model service like
|
|
303
|
+
any other prompt.
|
|
304
|
+
- **Session variables are a guardrail, not a wall.** They stop the human,
|
|
305
|
+
Claude and Codex from mixing up their verbs. They do not authenticate
|
|
306
|
+
against another program running as the same user.
|
|
307
|
+
|
|
308
|
+
## 🔧 Troubleshooting
|
|
309
|
+
|
|
310
|
+
Every operational refusal ends with "stop and ask the human". These are
|
|
311
|
+
the ones you will meet.
|
|
312
|
+
|
|
313
|
+
| Symptom | Fix |
|
|
314
|
+
|---|---|
|
|
315
|
+
| `join claude from inside a claude session` | Run `join` from inside that agent's session, not your terminal. The session variables are how postbag knows who is asking. |
|
|
316
|
+
| `open is the human's verb` | Your shell carries a session variable. Run `open` from a terminal you opened yourself, or `unset` `CLAUDE_CODE_MESSAGING_SOCKET`, `CLAUDE_CODE_MESSAGING_TOKEN` and `CODEX_SESSION_ID` first. |
|
|
317
|
+
| `send codex is claude's verb; run it inside a claude session` | Addressing one peer names the other. Only Claude sends to Codex, and only Codex sends to Claude. |
|
|
318
|
+
| `codex has not joined` or `claude has not joined` | Ask that agent to run `postbag join <peer>` from its own session. |
|
|
319
|
+
| `codex's door did not answer` | Often the session restarted and its door is stale: re-run `postbag join codex` in the new session. A timeout or a nonzero exit can also be ambiguous, so check the recipient session and `postbag read` before sending again. Same for `claude`. |
|
|
320
|
+
| `no codex at ...; set POSTBAG_CODEX` | Point `POSTBAG_CODEX` at the binary. On macOS it is inside the ChatGPT app. Elsewhere put `codex` on `PATH`. |
|
|
321
|
+
| `codex queue` is not a recognised command | Update Codex. `codex queue` arrived in 0.149. |
|
|
322
|
+
| `the exchange's letters are spent` | Working as designed. Open a new exchange from your own terminal with `postbag open --limit N`. |
|
|
323
|
+
| `no exchange is open` | Same fix. Only a human can open one. |
|
|
324
|
+
| Send succeeds but Claude Code shows a pending approval instead of answering | Claude Code is holding the letter for approval in its current permission mode. Approve it there. Unattended delivery was verified with bypass permissions. |
|
|
325
|
+
| Codex send fails with a sandbox or permission error | Approve the escalation Codex asks for, or run Codex with a sandbox that allows writing `~/.postbag` and connecting to the Claude socket. |
|
|
326
|
+
| `ledger line N is not a record` or `ledger is truncated after line N` | The ledger was edited or cut short. Fix or remove the bad tail, or move the file aside and start fresh. Both agents must `join` again. |
|
|
327
|
+
| `cannot open the ledger` or `not a regular file` | The path is a symlink, a pipe, or its directory is not writable. Check `POSTBAG_LEDGER` and permissions. |
|
|
328
|
+
| `letter N was submitted to codex's door but not recorded` | The append failed after delivery. Check the recipient session and the ledger before sending again. |
|
|
329
|
+
|
|
330
|
+
## ❓ FAQ
|
|
331
|
+
|
|
332
|
+
**Can I use two Claude Code sessions, or two Codex sessions?**
|
|
333
|
+
No. There are exactly two peers, `claude` and `codex`, and addressing one
|
|
334
|
+
names the other. That is what removes `--from`, roles, and a protocol
|
|
335
|
+
document. A different pair would be a different tool.
|
|
336
|
+
|
|
337
|
+
**How do I know the other agent read my letter?**
|
|
338
|
+
You do not, from `send` alone. It reports that the letter was submitted
|
|
339
|
+
through the door. Look at `postbag read` for the reply, or at the recipient session.
|
|
340
|
+
|
|
341
|
+
**Does postbag move files?**
|
|
342
|
+
No. It moves text. Work products travel through git, which both agents
|
|
343
|
+
already share. The letter says which commit to look at.
|
|
344
|
+
|
|
345
|
+
**What happens when a session restarts?**
|
|
346
|
+
Its door goes stale. The next `send` to it fails and tells you to re-run
|
|
347
|
+
`join` in the new session. The ledger keeps every earlier record.
|
|
348
|
+
|
|
349
|
+
**Can an agent give itself more letters?**
|
|
350
|
+
Not through postbag. `open` refuses to run inside either session, and a
|
|
351
|
+
spent budget tells the agent to stop and ask the human. The check reads
|
|
352
|
+
the vendors' session variables, so it is a guardrail against mixed-up
|
|
353
|
+
roles, not authentication against a determined program.
|
|
354
|
+
|
|
355
|
+
**Why not just paste between the two windows?**
|
|
356
|
+
You can, and postbag does the same thing without you as the transport. The
|
|
357
|
+
idle agent wakes on its own, the letter carries the reply command, and the
|
|
358
|
+
whole exchange is in one file afterwards.
|
|
359
|
+
|
|
360
|
+
**Linux? Windows?**
|
|
361
|
+
Linux runs the module and passes CI, but the live two-agent exchange is
|
|
362
|
+
verified on macOS only. Windows is not supported: postbag uses Unix
|
|
363
|
+
sockets and file locks from the standard library.
|
|
364
|
+
|
|
365
|
+
## 🧪 Develop
|
|
366
|
+
|
|
367
|
+
```sh
|
|
368
|
+
git clone https://github.com/parasxos/postbag.git && cd postbag
|
|
369
|
+
python3 -m venv .venv && source .venv/bin/activate
|
|
370
|
+
python -m pip install -e ".[dev]"
|
|
371
|
+
python -m pytest -q
|
|
372
|
+
```
|
|
373
|
+
|
|
374
|
+
Tests use a temporary ledger and fake doors. They never reach a real
|
|
375
|
+
session. See [CONTRIBUTING.md](CONTRIBUTING.md) and
|
|
376
|
+
[CHANGELOG.md](CHANGELOG.md).
|
|
377
|
+
|
|
378
|
+
---
|
|
379
|
+
|
|
380
|
+
<div align="center">
|
|
381
|
+
|
|
382
|
+
One file · four verbs · one ledger · MIT
|
|
383
|
+
|
|
384
|
+
Built for one Mac, and for any machine where Claude Code and Codex sit side by side.
|
|
385
|
+
|
|
386
|
+
</div>
|