discourse runs a moderated panel discussion between LLMs. A human Moderator
directs the conversation turn by turn — introducing topics and granting the
floor — while two or more panelists (backed by resumable Claude Code, Codex,
Grok Build and xAI API sessions) reply into a single shared, append-only
transcript.
Note: this tool shares its name with the Discourse forum software but has no relation to it — it is a local single-user CLI utility.
$ discourse new socrates
discourse: ◉ Edit the roster for 'socrates'; save and quit to continue
discourse: ✓ Created discourse 'socrates' with: Claude, Codex, Build
$ discourse socrates
discourse: ◉ discourse socrates | Claude Codex | turn 0
> What is the nature of justice?
> /to Claude
discourse: ◉ Waiting for Claude…
discourse: ◉ Claude replied in 38s, $0.41 this call
[Claude] Justice, at minimum, requires that likes be treated alike...
> /to Codex
discourse: ◉ Waiting for Codex…
discourse: ◉ Codex replied in 71s
[Codex] I'd frame it more structurally: justice is what a system produces
when no participant can unilaterally rewrite the rules in their own favour...
> /status
discourse socrates | turn 3 | last speaker: Codex
Claude claude session:yes pending:1 cost:$0.41 (last $0.41)
Codex codex session:yes pending:0 tokens:in=18204/out=612
> /end
Closing statements from panelists? y/N y
[Claude] ...
[Codex] ...
> /quit
discourse new NAME creates NAME/ in the current directory with the
default roster (Claude, Codex, Build) and, when run from a terminal,
opens the roster in $EDITOR (falling back to vi) so it can be trimmed or
changed before anything starts; quitting the editor without saving keeps the
default. Pass a conf file instead to skip the editor:
discourse new socrates myroster.conf. discourse NAME opens (or reopens)
the REPL for an existing discourse.
◉ Reopening a discourse resumes every panelist's backend session and continues the turn count — nothing is replayed, and no tokens are spent re-sending earlier entries.
Anything not starting with / is a Moderator entry: appended to the
transcript and to every other panelist's inbox.
| Command | Effect |
|---|---|
/to NAME |
Give NAME the floor. Case-insensitive, unambiguous prefix allowed (e.g. /to cla). Refused if NAME has nothing new waiting. Prints how long the reply took and, for claude, what it cost. |
/show [n] |
Print the last n transcript entries (default 10). Display only. |
/status |
Turn count, roster, each panelist's session state, pending inbox size, cumulative and last-call cost (claude), and token totals (codex). |
/ed |
Open $EDITOR (falls back to vi) for a multi-line Moderator entry. The text goes into history as a single line (newlines become spaces), so Up brings it back for a quick edit and resend. |
/end |
Offer a closing round (each panelist is asked for closing statements in roster order), stamp the discourse as closed, and exit. Reopening afterwards is allowed. |
/quit or Ctrl-D |
Leave without ceremony. |
/help |
Command summary. |
The prompt has readline editing, and up-arrow history that persists per
discourse in NAME/history (everything typed at the prompt, commands
included, plus /ed entries flattened to one line), so a discussion
reopened days later still has its earlier entries a few keystrokes away. Ctrl-C at the prompt clears the
current line; Ctrl-C during a backend call kills the call, discards any
partial output, and returns to the prompt with the discourse's state
untouched — the same /to can simply be retried.
Write @path in a Moderator entry to point panelists at a file — a
manuscript, a dataset, an earlier review:
> Review this book. @../drafts/manuscript.epub
> /to Claude
Relative paths are resolved against the directory you launched discourse
from and rewritten to absolute paths before the entry is stored, so both
backends (whose working directory is pinned to the discourse folder) find the
file. An @path that does not exist is left as typed with a warning.
Something like me@example.com is not touched.
▲ claude, codex and grokbuild panelists are coding agents underneath.
Given an @path they will read it — Claude via its file tools, Codex and
Grok Build via shell commands in a read-only sandbox — and they can read
anything else reachable from the discourse directory,
including the transcript and neighbouring files. Keep a discourse in a clean
directory when independence matters (for instance, a review that should not
see earlier reviews). A grok panelist has no tools at all: paste the
material into the entry itself if it needs to see it.
The roster is a plain pipe-separated file. The default one:
# Name|type|model|extra-args
Claude|claude|claude-fable-5-1|--effort high
Codex|codex|gpt-5.6-sol|-c model_reasoning_effort=high
Build|grokbuild|grok-4.6|--effort high
# Grok|grok|grok-4.6|reasoning={"effort":"high"}
- Name — must match
[A-Za-z0-9_]+and be unique, case-insensitively (names become inbox filenames and state-file keys). - type —
claude,codex,grokbuild,grok, ormock. - model — optional; leave empty to use the backend's default model
(
claudereads its default from~/.claude/settings.json,codexfrom~/.codex/config.toml, where the reasoning effort also lives;grokbuildfrom~/.grok/config.toml;grokdefaults togrok-4.3). - extra-args — optional; appended verbatim to every call for that
panelist. Useful for effort levels:
Claude|claude|claude-fable-5-1|--effort high,Codex|codex|gpt-5.6-sol|-c model_reasoning_effort=mediumorBuild|grokbuild|grok-4.6|--effort high. Forgrokthey arekey=valuerequest fields instead, merged into the API call with JSON values kept typed:Grok|grok|grok-4.6|temperature=0.4 reasoning={"effort":"high"}. #starts a comment; blank lines are skipped.- At least two panelists are required.
The roster can be edited freely until the first /to: nothing is sent to
any backend before then, the conf is re-read every time the discourse is
opened, and opening creates the inbox for any panelist added by hand (a
panelist removed by hand simply drops out; its inbox file stays behind,
unused).
▲ Once a panelist has taken the floor its line is tied to a live backend
session. Changing that panelist's name or type afterwards abandons the
session and the next /to starts it from scratch, with no memory of the
discussion so far. Model and extra-args changes mid-discourse are not
tested.
| Type | Backend | Notes |
|---|---|---|
claude |
claude -p (resumed with --resume) |
Read-only file tools available; used when an entry asks for a file. Per-call cost is reported. |
codex |
codex exec (resumed with codex exec resume) |
Runs with sandbox_mode=read-only; may run read-only shell commands to open files. Token totals are reported. |
grokbuild |
grok headless (Grok Build; resumed with -r) |
Runs with --sandbox read-only: may read files and run read-only commands, cannot write or reach the network from child processes. Per-call cost is reported. Every call carries the harness's own system prompt (~18k tokens), so a one-line reply costs a few cents. |
grok |
xAI Responses API via curl (resumed with previous_response_id) |
No CLI, no tools: a pure conversational panelist that cannot read an @path. Responses are stored server-side by xAI; the session id is the id of the latest response. Per-call cost is reported. |
mock |
none | Deterministic canned replies, zero tokens. Use it to dry-run a discourse, exercise the REPL, or test a roster before spending real tokens. |
Two ways to seat Grok. grok talks to the API directly: no tools, cheapest
per turn, sessions stored by xAI. grokbuild runs xAI's coding-agent CLI:
it can read files like the other coding agents, its sessions live locally in
~/.grok/sessions, and each turn pays for the harness's system prompt on
top of the conversation. Pick grok for pure debate and grokbuild when
Grok needs to read something.
socrates/
discourse.conf # roster; edit until the first /to
transcript.md # append-only, attributed record — the actual artifact
state # bash-sourceable turn count, last speaker, session ids
history # readline history for the prompt, written when run from a terminal
inbox/<Name> # one unseen-entry buffer per panelist (the watermark)
raw/ # one raw backend call per turn (debugging, cost review)
lock/ # present only while a REPL has this discourse open
Every entry — Moderator or panelist — is appended to transcript.md and to
the inbox/ of every other panelist. Granting a panelist the floor sends
its inbox contents to the backend, appends the reply to the transcript, fans
it out to the other inboxes, and truncates the panelist's own inbox — a
transaction that only commits on success, so a failed or interrupted call
leaves everything exactly as it was.
Each panelist's session carries the whole discussion so far, so the cost of a turn grows with everything said before it — and with everything read. Two things observed in practice:
- ◉ Asking a panelist to read a book-length
@pathmakes every later turn of that panelist heavy (hundreds of thousands of tokens per reply), because the material stays in its session. Give large documents to the panelist that needs them, not to all of them. - ▲ Prompt caching only helps while turns follow each other closely. After a
gap of an hour or more, the next reply re-caches the entire session — a
single one-line reply can then cost as much as a long one. Batch your
turns; check
/statusfor the running total. - ◉
groksessions live on xAI's servers (their documentation says stored responses are kept for 30 days). A discourse left idle longer than that cannot resume itsgrokpanelists.
- Bash 5.2+
jqanduuidgen— required whenever the roster includes anything other thanmockpanelists.claudeCLI (Claude Code) onPATH, forclaudepanelists.codexCLI onPATH, forcodexpanelists. It installs vianpm install -g @openai/codex, which typically lands in a directory such as~/.npm-global/bin— make sure that directory is onPATH.grokCLI (Grok Build) onPATH, forgrokbuildpanelists. Install with the script from https://x.ai/cli/install.sh, which places it in~/.grok/binand links it into~/.local/bin. It authenticates with a browser login or falls back toXAI_API_KEY.curland anXAI_API_KEYin the environment, forgrokpanelists. The key is passed tocurlas a header on stdin, never on the command line.
✗ discourse checks for required backends and tools when a discourse is
opened and refuses to start with a clear error if one is missing. The
default roster seats all three coding agents; without Grok Build installed,
delete the Build line in the editor that discourse new opens.
GNU General Public License v3.0 — see LICENSE.