Your coding agents talk to each other.
Claude Code and Codex work a problem out together: one brings the other in, it digs in and replies, and they go back and forth on the same thread, whichever of them starts. You watch every step. No app, no server: a command and a skill.
Claude Code and Codex today. Each agent plugs into parlo through one small adapter, so more agents can join.
npm install -g parlo-cliparlo connect claude · parlo connect codexWatch Claude Code and Codex work it out together
Transcript, word for word
Turn 1 · Claude Code asks Codex · thread 10 · 21:26
The file under review, billing.py:
def apply_discount(price_cents: int, percent: int) -> int:
"""Return the price after a percentage discount, rounded to the nearest cent."""
return price_cents - price_cents * percent // 100$ parlo ask codex - <<'PARLO' Review billing.py: does apply_discount round to the nearest cent, as its docstring says? Show one input where it does not. PARLO parlo: thread 10 — asking codex (read only) codex: I’ll inspect `billing.py` and verify the calculation against the docstring. codex: $ sed -n '1,240p' billing.py codex: No. It floors the discount amount, rather than rounding the final discounted price to the nearest cent. Example: `apply_discount(3, 25)` returns `3`, but 25% o… parlo: codex answered on thread 10. ----- reply -----
No. It floors the discount amount, rather than rounding the final discounted price to the nearest cent.
Example: apply_discount(3, 25) returns 3, but 25% off 3¢ is 2.25¢, which rounds to 2¢.
----- end ----- Continue with: parlo ask codex --thread 10 "<message>" [exit 0]
Turn 2 · the follow-up, same thread 10 · 21:26
$ parlo ask codex --thread 10 - <<'PARLO' Give the smallest fix as a unified diff. Do not apply it. PARLO parlo: thread 10 — asking codex (read only) codex: ```diff --- a/billing.py +++ b/billing.py @@ -1,5 +1,5 @@ def apply_discount(price_cents: int, percent: int) -> int: """Return the price after a percentage dis… parlo: codex answered on thread 10. ----- reply -----
----- end ----- Continue with: parlo ask codex --thread 10 "<message>" [exit 0]
Turn 3 · Codex asks Claude Code · thread 11 · 21:27–21:28
A person asked Codex to get Claude’s opinion on the fix. What Codex wrote and ran:
I’ll use the Parlo skill to have Claude review the rounding change, with the requested edge cases called out.
$ parlo ask --detach claude -- 'Please independently review this proposed replacement for the body of Python billing.py::apply_discount: return (price_cents * (100 - percent) + 50) // 100. Is it correct? Assess specifically percent=0, percent=100, and rounding an exact half-cent result such as 2.5 cents. State assumptions (e.g., permitted percent range, desired rounding rule), give a concise verdict, and flag any edge cases.' parlo: thread 11 started with claude. Get the reply with: parlo wait 11 $ parlo wait 11 parlo: thread 11 is still working. Run: parlo wait 11 [exit 4] $ parlo wait 11 parlo: claude answered on thread 11. ----- reply -----
Verdict: the formula is correct. It gives round-half-up to the nearest cent, provided price_cents >= 0 and percent is an int from 0 to 100. That is a real fix: the current body (billing.py:3) never rounds down, so 99¢ at 10% returns 90 where 89.1 should round to 89. The new formula returns 89.
Assumptions
price_centsis anintand not negative.percentis anintfrom 0 to 100.- Rounding rule: halves round up, matching
Decimal'sROUND_HALF_UP. This is not Python'sround(), which rounds halves to the nearest even number. - The docstring ("rounded to the nearest cent") doesn't say how to break ties, so treat this rule as an assumption.
Checks (run with exact Fraction arithmetic; verified)
- percent=0:
(100p+50)//100equalspfor everypfrom −5000 to 5000. - percent=100:
50//100is 0 for everyp. - Exact half (5¢ at 50% = 2.5¢): returns 3.
round(2.5)would return 2. - Wider sweep:
pfrom 0 to 20000 with every integerpercentfrom 0 to 100 gave 0 mismatches against half-up.
Edge cases to flag
- Negative prices (refunds): −2.5 becomes −2, while +2.5 becomes 3. Halves always move toward +∞, so a refund line doesn't mirror its sale. If refunds go through this function, take the sign out, apply the formula to
abs(price), then put the sign back. - percent outside 0–100: nothing checks it. 150% gives −50 and −10% gives 110. Add a check that raises
ValueErrorif negative results or surcharges aren't intended. - Fractional percent as a float: it silently returns a
float, which breaks the-> inttype hint. It can also round wrong:apply_discount(125, 64.4)returns44.0, but the exact value is 44.5, which should round to 45. Either requireint, or takeDecimal/Fraction, or use basis points (hundredths of a percent). total()rounding (outside this change):billing.py:8rounds each unit price before multiplying by quantity, so each line can be off by up toqty × 0.5¢. Check whether the business wants rounding per unit, per line, or per invoice.- Tests: the repo has none. Add at least: percent 0 and 100, the 2.5¢ half case, 99¢ at 10% returning 89 (the old code returns 90), and an exhaustive sweep against
Fraction.
----- end ----- Continue with: parlo ask claude --thread 11 "<message>"
From Codex’s own log. Its shell had no parlo on the PATH, so the first try failed; in its words: “The parlo shortcut isn’t on this shell’s PATH, so I’m using its configured launcher to send the same read-only review request to Claude.” The launcher is shown here as parlo, and Node’s warning lines are left out.
Claude’s verdict: the replacement is correct for non-negative integer price_cents and integer percent in 0..100, using round-half-up.
percent = 0: returns the original price.percent = 100: returns0.- Exact half-cent: e.g.
5cents at50%yields2.5, and the formula returns3.
Claude flagged that negative prices round toward zero at ties (e.g. -2.5 → -2), and fractional/float percentages should be rejected or handled with Decimal/basis points.
Two models, one problem
A second opinion from a different model, as a conversation: a cross-model code review without pasting answers between two terminals.
The colleague is read-only by default, so a second opinion can’t change your files. Codex runs with -s read-only, Claude Code in plan mode. Changing files needs --write or --worktree.
A real run: asked, on a read-only thread, to edit app.py.
I couldn’t modify app.py: this workspace is read-only.git status stayed clean. In turn 2 above, Codex proposed the diff and did not apply it.
Follow-ups keep the thread, so the colleague remembers. In a real run, asked “Remember the number 41. Reply with just: ok”, it replied ok; asked on --thread 3 “What number did I ask you to remember?”, it replied 41.
Either agent can start the conversation
Claude Code brings in Codex, or Codex brings in Claude Code. Most tools only go one way.
Codex starts its colleague with --detach, which returns at once, and collects the reply with parlo wait. It waits up to 25 s; exit 4 means still working, so Codex asks again.
$ parlo ask --detach claude -- 'What is 2 + 2? Reply with just the answer.' parlo: thread 9 started with claude. Get the reply with: parlo wait 9 $ parlo wait 9 parlo: claude answered on thread 9. ----- reply ----- 4 ----- end -----
A real run, thread 9.
Hard limits, so agents never loop
They talk as much as the work needs, and no more. parlo enforces the limits; agents can’t talk their way past.
| Limit | Value |
|---|---|
| Colleague of a colleague | 2 levels deep, no further |
| Colleagues working at once, per caller | 3 |
| Messages in one thread | 20 |
| One message | 256 KiB |
| Waiting for a reply | 30 minutes, then the colleague is stopped |
A colleague can only answer its caller. It can’t start a conversation with its caller, or with anyone above it.
exit 3parlo: you already have 3 colleagues working (limit 3). Wait for one to answer (parlo threads) before asking another. parlo: thread 12 reached 20 messages. Decide yourself or ask the person.
Access never widens down the tree: a colleague asked read-only can only start read-only colleagues. Stopping is total: stopping parlo ask (from /tasks, Ctrl+C, or closing Claude Code) stops its colleague and every command that colleague started.
Exit codes: 0 the colleague replied · 1 the colleague failed (the line says why) · 2 usage error · 3 refused by a limit · 4 still working (parlo wait) · 5 stopped
Watch the conversation as it happens
Each step the colleague takes is one short line. In Claude Code you read them in /tasks.
- Claude runs
parlo ask codex "…"in the background. - parlo starts Codex without its interface (
codex exec), read-only, and gives it the message. - Each step Codex takes is printed as one short line. In Claude Code you read them in
/tasks. - When Codex is done, parlo prints its reply last, between
----- reply -----and----- end -----, and Claude Code wakes Claude up with it. - A follow-up,
parlo ask codex --thread 1 "…", continues the same Codex session, so Codex remembers the conversation.
parlo: thread 1 — asking claude (read only) claude: Read /private/tmp/parlo-demo/app.py claude: `app.py` prints `hi parlo`. parlo: claude answered on thread 1. ----- reply ----- `app.py` prints `hi parlo`. ----- end ----- Continue with: parlo ask claude --thread 1 "<message>"
A real run: Claude Code asking a Claude colleague, as it reads in /tasks.
Stop it from /tasks and the colleague stops with it: parlo: thread 6 was stopped. parlo threads lists every thread in this agent tree:
#1 claude idle 2 messages 17:50 "What does app.py print? Answer in one short sentence."
Install in one line
One npm package. connect adds the skill to each agent; it shows the changes and asks first.
$ npm install -g parlo-cli $ parlo connect claude # installs the skill into Claude Code $ parlo connect codex # installs the skill into Codex
Then tell Claude: “ask Codex to review this function.”
- System
- macOS or Linux
- Node
^22.19.0 || >=24.11.0- Agents
- Claude Code and/or Codex, installed and logged in
- Tested
- codex-cli 0.166.0 and Claude Code 2.1.316
No app, no server, no MCP server, no hooks: a command and a skill. Data in one file, ~/.parlo/parlo.db (SQLite).
Uninstall
$ parlo uninstall $ npm uninstall -g parlo-cli
parlo uninstall stops every colleague and removes the skills and the Codex rule; --purge also deletes ~/.parlo after a yes in a terminal. npm uninstall alone leaves those files behind.
Frequently asked questions
Can Claude Code talk to Codex?
Yes. Claude Code sends Codex a message with parlo ask codex "…", Codex works on it and replies, and they can go back and forth on the same thread.
Can Codex start a conversation with Claude Code?
Yes. Codex runs parlo ask --detach claude -- '…', then parlo wait <thread> to collect the reply.
Does it only work with Claude Code and Codex?
Today, yes. Each agent plugs into parlo through one small adapter, so more agents can be added.
Will the other agent change my files?
Not unless you allow it. The colleague is read-only by default; pass --write or --worktree to let it change files.
Can two agents get stuck talking forever?
No. A colleague can only answer its caller, and parlo enforces hard limits: 20 messages per thread, 3 colleagues at once per caller, 2 levels deep, and 30 minutes to reply before the colleague is stopped.
Does it need a server or an MCP setup?
No. parlo is a command and a skill: no app, no server, no MCP server, no hooks. Its data lives in one file, ~/.parlo/parlo.db.
How do I remove it completely?
Run parlo uninstall, then npm uninstall -g parlo-cli. parlo uninstall --purge also deletes ~/.parlo after you confirm in a terminal; npm uninstall alone leaves those files behind.