A tiny file-based protocol for two AI agents that review each other's work — enforced turns, an append-only audit log, and a human who can always see everything.
Project description
debate
A tiny file-based protocol for two AI agents that review each other's work — with enforced turns, an append-only audit log, and a human who can always see everything.
Zero dependencies. Two files. One rule: nobody posts out of turn.
The problem
Multi-agent frameworks assume one orchestrator owns all the agents in one runtime. Reality is messier: you have Claude Code in a terminal and some other agent on another harness — different vendors, different processes, maybe different machines — and you want one to build and the other to review without you being the copy-paste courier between them.
debate is the smallest thing that solves this: the agents exchange messages through two files in a shared directory (a git repo works beautifully — the audit log becomes history you can diff), and a dumb watcher wakes whichever agent's turn it is. No framework, no server, no queue, no API keys. If your agent can be invoked from a shell and can read files, it can hold up its end of a review.
How it works
CHANNEL.md— the mailbox. Append-only, human-readable, git-diffable. Every message has a sequence number, sender, type (review-request,verdict,fix-report,question,info,close), a thread slug, and refs (e.g.branch@commit).signal.json— the doorbell. Five fields (seq,turn,thread,last_entry,updated_at). Watchers poll this, never the mailbox.debate post— the only writer, and the place the protocol is enforced rather than requested: out-of-turn posts are refused, one thread open at a time, thread caps stop runaway loops, and the mailbox append always lands before the doorbell bump (so a watcher firing onseqnever reads a half-written entry).debate watch-once— one tick of a deliberately dumb watcher. Run it from cron every few minutes: it mirrors new entries to wherever you already look, and if it's an agent's turn on an open thread, invokes that agent's pinned command. No LLM runs when nothing changed.
Quickstart
pip install debate # or just vendor the two modules; they're stdlib-only
debate init --root ./collab --parties claude,glm --supervisor owner
# The builder opens a review thread:
debate post --root ./collab --from claude --type review-request \
--thread feature-x --refs feature-x@abc123 --body "Please review commit abc123: ..."
# The reviewer replies (their harness invokes this after reading the thread):
debate post --root ./collab --from glm --type verdict \
--thread feature-x --refs feature-x@abc123 --body "APPROVE — verified: 27 tests pass at abc123."
# Whoever acted last closes:
debate post --root ./collab --from claude --type close --thread feature-x --body "Merged. Closing."
Wire the watcher to a scheduler with a config that pins each agent's invocation:
{
"state_path": "/somewhere/outside/the/channel/watcher-state.json",
"commands": { "claude": ["claude", "-p", "{prompt}"] },
"prompts": { "claude": "It is your turn on the review channel at ./collab. Read the open thread, act, post via debate, then stop." },
"debounce_seconds": { "claude": 600 },
"retry_seconds": 1800
}
debate watch-once --root ./collab --config watcher.json # cron this every ~3 minutes
A party without a commands entry is never invoked — that's how a human-driven side works: the
human's live session answers the doorbell itself, and the watcher only covers for it when it
doesn't (see debounce_seconds).
The trust model — read this before running agents unattended
Be precise about what is enforced and what is merely requested:
- Enforced (hard): turn order, one-open-thread, thread caps, entry format, write-then-signal
ordering. These live in
post; an agent that misbehaves gets refused, not warned. - Advisory (soft): everything an agent does outside the mailbox. "Don't push to main", "don't touch config" — if you put those in an agent's pinned prompt, you are trusting the model to comply. In our production use this failed exactly once and exactly as theory predicts: an unattended agent made a true-at-some-point but stale claim about repository state because it inferred state from channel history instead of checking. The protocol can force when an agent speaks; it cannot force what the agent says to be correct.
Mitigations that earn their keep: pin prompts in config (never compose them at runtime), require agents to cite fresh evidence (commit SHA + test count) in verdicts, gate merges on the human reading the mirrored verdict, and give unattended sessions an isolated worktree instead of your live checkout. The case study walks through the real incident.
Design rules (each one paid for in production)
- Gate on an open thread, not the turn field. After a
close,turnmeans nothing. debate clears both on close; the watcher checks both anyway. - Once per seq. An invocation that produced no reply gets one timed retry, then a supervisor escalation. Two agents in a refusal loop burn money forever; the cap is the brake.
- Debounce before invoking. A human-driven session may be about to answer; the fallback should be a fallback.
- The watcher's memory lives outside the channel. Its state file is not part of the shared record and never collides with a fresh clone.
- Supervisor posts don't take a turn. The human can interject at any point without breaking the agents' alternation.
- The mailbox is the record. If it didn't happen in
CHANNEL.md, it didn't happen — and corrections are new entries (aclose-typed post with a fresh slug drops a correction into the record without opening a thread), never edits.
Why not just…
- GitHub PRs + a review bot? Works great if both agents live where your forge is. debate is forge-independent, works fully offline/local, and round-trips in seconds on a cron tick without webhooks or API tokens. (If you have GitHub and one vendor's bot, use them.)
- AutoGen / LangGraph / CrewAI? Those orchestrate agents they own, in-process. debate coordinates agents that nobody jointly owns — different vendors, different harnesses, different lifetimes — and leaves a human-auditable trail as a first-class artifact.
- A message queue? You'd be trading two greppable files and
git logfor a broker. The audit log is the point.
Limitations, honestly
- Two parties. Turn alternation between exactly two named agents (plus a supervisor) is a feature, not a to-do: a review needs a builder and a reviewer. N-party consensus is a different protocol.
- Polling. The doorbell is designed to be cheap to poll on a minutes-scale cron. If you need sub-second latency, this is not your transport.
- A tolerated race. Two agents may open different new threads near-simultaneously when none is open. With minutes-scale polling the window is tiny, and the supervisor untangles the rare collision; single-writer locking would cost more than it buys.
- Reference implementation. Extracted from a working production setup, generalized, and tested — but young. Read the code; it's ~600 lines including the CLI.
Field notes
The production predecessor ran real code-review cycles between Claude Code (the builder, in a live terminal) and a GLM-based reviewer hosted on Hermes, Nous Research's open-source agent harness — whose cron scheduler also ran the watcher and mirrored every entry to the supervisor's phone through its Telegram gateway. That's the intended shape: debate doesn't run your agents; whatever harnesses you already have, do. A typical review round-tripped in about five minutes of wall clock, most of it the reviewer independently re-running the test suite. The one night it went sideways is the case study.
The name
Parliamentary, not adversarial: structured turns, one motion on the floor at a time, and
everything said is on the record — CHANNEL.md is the hansard. (If you arrived from the
"AI safety via debate" literature: this is not the formal debate game with opposing advocates
before a judge — it's review correspondence with teeth.)
License
MIT
Project details
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 debate-0.1.0.tar.gz.
File metadata
- Download URL: debate-0.1.0.tar.gz
- Upload date:
- Size: 21.5 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
639a197a1f96e350ebc1539c40c7aef6ca7d484e602367477265bc8d35039378
|
|
| MD5 |
ea5a324e9474a829f950cafda00b91eb
|
|
| BLAKE2b-256 |
27b975ee56fbc1fc525332097131b2b258a2a7e0b206f8dc9843c4d8c2f109b6
|
Provenance
The following attestation bundles were made for debate-0.1.0.tar.gz:
Publisher:
release.yml on zolcal/debate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
debate-0.1.0.tar.gz -
Subject digest:
639a197a1f96e350ebc1539c40c7aef6ca7d484e602367477265bc8d35039378 - Sigstore transparency entry: 2085176507
- Sigstore integration time:
-
Permalink:
zolcal/debate@ac0b7841928763130500b2ce6a972ec4138d819c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/zolcal
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ac0b7841928763130500b2ce6a972ec4138d819c -
Trigger Event:
push
-
Statement type:
File details
Details for the file debate-0.1.0-py3-none-any.whl.
File metadata
- Download URL: debate-0.1.0-py3-none-any.whl
- Upload date:
- Size: 15.8 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2b4850e185f5a8b1b73f6a6fd1b94decc578bfa9ab3f8662eba16e0683e19b4b
|
|
| MD5 |
a2b55ba4cee29a70512649d3936ee7df
|
|
| BLAKE2b-256 |
42513c29b53bfe977ef0ef07dc0a91a448f67f604f47d18897af5af91acbd5c0
|
Provenance
The following attestation bundles were made for debate-0.1.0-py3-none-any.whl:
Publisher:
release.yml on zolcal/debate
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
debate-0.1.0-py3-none-any.whl -
Subject digest:
2b4850e185f5a8b1b73f6a6fd1b94decc578bfa9ab3f8662eba16e0683e19b4b - Sigstore transparency entry: 2085176539
- Sigstore integration time:
-
Permalink:
zolcal/debate@ac0b7841928763130500b2ce6a972ec4138d819c -
Branch / Tag:
refs/tags/v0.1.0 - Owner: https://github.com/zolcal
-
Access:
private
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
release.yml@ac0b7841928763130500b2ce6a972ec4138d819c -
Trigger Event:
push
-
Statement type: