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.
@@ -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
@@ -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.
@@ -0,0 +1,3 @@
1
+ include postbag
2
+ include CONCEPT.md CHANGELOG.md SECURITY.md CONTRIBUTING.md
3
+ include test_postbag.py test_hardening.py test_transports.py
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
+ ![ci](https://github.com/parasxos/postbag/actions/workflows/ci.yml/badge.svg)
54
+ ![python](https://img.shields.io/badge/Python-3.10%E2%80%933.14-blue)
55
+ ![license](https://img.shields.io/badge/license-MIT-green)
56
+ ![platform](https://img.shields.io/badge/platform-macOS%20verified%20%C2%B7%20Linux%20CI-orange)
57
+ ![stdlib](https://img.shields.io/badge/stdlib-only-blueviolet)
58
+ ![deps](https://img.shields.io/badge/dependencies-zero-brightgreen)
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>