Skip to content

Latest commit

 

History

30 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

discourse

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.

Quick start

$ 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.

REPL commands

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.

Referencing files

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.

discourse.conf

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).
  • typeclaude, codex, grokbuild, grok, or mock.
  • model — optional; leave empty to use the backend's default model (claude reads its default from ~/.claude/settings.json, codex from ~/.codex/config.toml, where the reasoning effort also lives; grokbuild from ~/.grok/config.toml; grok defaults to grok-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=medium or Build|grokbuild|grok-4.6|--effort high. For grok they are key=value request 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.

Panelist types

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.

Grok: API or Grok Build?

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.

Anatomy of a discourse directory

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.

Cost and context

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 @path makes 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 /status for the running total.
  • grok sessions live on xAI's servers (their documentation says stored responses are kept for 30 days). A discourse left idle longer than that cannot resume its grok panelists.

Requirements

  • Bash 5.2+
  • jq and uuidgen — required whenever the roster includes anything other than mock panelists.
  • claude CLI (Claude Code) on PATH, for claude panelists.
  • codex CLI on PATH, for codex panelists. It installs via npm install -g @openai/codex, which typically lands in a directory such as ~/.npm-global/bin — make sure that directory is on PATH.
  • grok CLI (Grok Build) on PATH, for grokbuild panelists. Install with the script from https://x.ai/cli/install.sh, which places it in ~/.grok/bin and links it into ~/.local/bin. It authenticates with a browser login or falls back to XAI_API_KEY.
  • curl and an XAI_API_KEY in the environment, for grok panelists. The key is passed to curl as 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.

License

GNU General Public License v3.0 — see LICENSE.

About

Moderated multi-LLM panel discussions from the terminal: a human directs turn-by-turn debate between resumable Claude Code and Codex CLI sessions

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages