joust
Parallel prototype orchestrator for coding agents.
Splits one vague feature request into N variants, implements each in an isolated git worktree, lets an agent judge them against a rubric, and gates the final pick on a human.
Joust is in the same family as proctor
and magellan: a tool whose
--help text is the agent's onboarding. Joust never calls an LLM itself. It
manages state, worktrees, and prompts; an agent runner (Claude Code, Codex,
or a shell script) does the thinking.
Install
As a Claude Code plugin (recommended)
/plugin marketplace add nclandrei/joust
/plugin install joust@joust
The plugin puts joust on PATH automatically at session start. See
docs/plugin.md for the full guide.
As a standalone CLI
uv tool install joust # or: pipx install joust
Or from a clone for local development:
git clone https://github.com/nclandrei/joust.git
cd joust
uv venv
uv pip install -e .
Then joust --version and joust --help to get started.
Configuration
Joust state lives under $JOUST_HOME (default: ~/.joust/). Set the
env var to relocate state — useful in CI or when running multiple joust
instances:
export JOUST_HOME=/tmp/joust-ephemeral
joust init demo --goal "..."
There is no other configuration file: every other knob (rubric, runner,
model, base ref) is set per-run via joust init/joust runner set.
Shell completion (after install):
joust completion bash >> ~/.bashrc
joust completion zsh >> ~/.zshrc
joust completion fish > ~/.config/fish/completions/joust.fish
Platform support
macOS and Linux. Joust uses POSIX file locking (fcntl) so Windows
is not supported — try WSL.
The workflow
joust init <slug> --goal "what you are exploring"
joust scenarios suggest --count 3 # prints a brainstorming playbook
joust scenarios add minimal -d "..."
joust scenarios add power -d "..."
joust scenarios add opinionated -d "..."
joust runner suggest # asks agent to ask user which model
joust runner set --runner claude-code --model opus
joust scenarios approve --yes # HUMAN gate: approve the scenario set
joust lock # materializes N git worktrees
# --- dispatch one subagent per worktree ---
joust record variant-01 --status built --summary "..."
joust record variant-02 --status built --summary "..."
joust record variant-03 --status failed --summary "..."
joust judge # prints a judging playbook
joust score variant-01 --correctness 4 --simplicity 5 --fit 5 --extensibility 2 --rationale "..."
joust score variant-02 ...
joust score variant-03 ...
joust compare # comparison table
joust diff variant-01 # files/lines changed in this variant
joust pick variant-01 --yes # human-only gate
joust merge --yes # merge picked variant into the base branch
joust archive --prune # clean up losing branches
At any point, joust log prints the append-only event timeline for the
active run (joust log --json for machine-readable output,
joust log --last 10 for the most recent events).
For a full walkthrough with realistic output at every step, see
docs/examples/first-run.md — three
rate-limit variants on a Flask endpoint with a custom rubric.
Design principles (borrowed from proctor/magellan)
-
--helpis the onboarding. A fresh agent runsjoust --helpandjoust <cmd> --helpand can complete a run without reading source. -
No LLM calls. Joust emits static playbooks for brainstorming and judging; the calling agent reads them and acts. That keeps joust deterministic, testable, and model-agnostic.
-
Contract-first, append-only state. Every run has a
manifest.jsonplus anevents.jsonllog. Nothing is mutated in place. -
Two human gates.
joust scenarios approve --yessits between brainstorming and worktree creation (so the agent cannot silently lock a run with scenarios it made up).joust pick --yessits between judging and merging (so the agent cannot silently declare a winner). Both refuse to run without--yes. -
Runner is recorded, not assumed.
joust runner suggestprints a playbook telling the agent to ask the user which model to dispatch subagents as (e.g. opus / sonnet / haiku). The answer is recorded withjoust runner setand shows up in every variant'sPROMPT.md. Joust itself never spawns a subagent — the driver does, using the recorded model.
State layout
$JOUST_HOME/ # default: ~/.joust
active.json # repo_path -> {slug, run_id}
runs/
<slug>/
<run-id>/
manifest.json
events.jsonl
worktrees/
variant-01/ # git worktree + PROMPT.md
variant-02/
variant-03/
artifacts/
variant-01/ # files copied by `joust record --artifact`
Each variant lives on a branch named joust/<run-id>/<variant-id>, rooted at
the SHA of the base ref captured at joust init time.
How to drive it from Claude Code
The simplest usage is a one-paragraph skill:
Read
joust --help, thenjoust scenarios suggest --help. Propose scenarios and register them. Runjoust lock. Then spawn one subagent per worktree (use the Agent tool withisolation: "worktree"pointing at each worktree path). Each subagent's prompt is the worktree'sPROMPT.md. When they finish, they calljoust record. Then calljoust judge, fill in scores withjoust score, and runjoust compare. Stop and ask the human to runjoust pick.
Joust stays out of the model layer entirely — it just gives the agent a structured, inspectable place to put its work.
Composition with proctor
If a variant is a UI or end-to-end feature, run proctor inside its
worktree to capture manual-test evidence. joust owns "which variant wins";
proctor owns "does this variant actually work". The two are orthogonal.
Housekeeping
joust diff <variant>— shows a short-stat diff for the variant's branch against the base, plus the list of changed files. Useful during judging to see at a glance which variant touched what.joust log— prints the append-only event timeline for the active run (init, scenario_add, approve, lock, record, score, pick, merge, archive). Supports--jsonand--last N.joust merge --yes— merges the picked variant's branch back into the base branch. Takes no positional argument; the winner is read frommanifest.picked(set byjoust pick). Refuses unless a variant has been picked,--yesis passed, and the base branch hasn't moved past the SHA recorded atjoust init(use--forceto override the drift check).joust doctor— diagnoses drift between joust state, git's view of worktrees/branches, and the filesystem. Detects stale active pointers (when the run dir has beenrm -rfd), missing variant worktrees, and orphanjoust/<run-id>/variant-*branches left over from aborted runs. Runjoust doctor --fixto clean them up.joust runs— lists every run for the current repo.joust use <slug> <run-id>— switches which run is active in the current repo.joust report— prints filesystem paths for the active run's manifest, events log, and worktrees.joust --version/-V— prints the installed joust version and exits.
Testing
uv pip install -e ".[dev]"
pytest tests/
The smoke test exercises the full pipeline end-to-end against a throwaway git repo and asserts final manifest + events state.
Status
v1.0 — stable, public, installable three ways: Claude Code plugin, PyPI, or from a clone. 44 tests pass across Python 3.11/3.12/3.13 on macOS and Linux in CI.
Milestones reached:
- v0.2.0 — repo public, PyPI release, CI on push/PR. ✅
- v0.3.0 — customizable rubric (
joust init --rubric ...), richerscenarios suggestplaybook that surfaces prior runs, and a full walkthrough atdocs/examples/first-run.md. ✅ - v1.0.0 — Claude Code plugin under
plugin/that putsjouston PATH at session start. Seedocs/plugin.md. ✅
See docs/superpowers/specs/ for the design.
Metadata
Release files for joust 1.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | |
|---|---|---|---|
| joust-1.1.0.tar.gz | 69.5 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| joust-1.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 104.0 kB
Release files / joust-1.1.0.tar.gz
| Download URL | joust-1.1.0.tar.gz |
|---|---|
| Size | 69.5 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
48b4850c1575085999ec8743939d5aa3dfaaa37c93a51d1e861fa8e1744d296e
|
|
BLAKE2b-256 checksum How to use checksums |
2b24bd8df40687d8c2619724345a2d8dcd6d4198adfa098537b3cc0c8032d454
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 25, 2026.
Transparency logRelease files / joust-1.1.0-py3-none-any.whl
| Download URL | joust-1.1.0-py3-none-any.whl |
|---|---|
| Size | 34.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f6313c4f924ac904ab9f4083e1a939e5303ded1fdb850159efdd662a327702ac
|
|
BLAKE2b-256 checksum How to use checksums |
b7cb4eb4bffcdb50226d9590292b5b2619e8ee9744b83ce7335d031aa7ea862d
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/6.1.0 CPython/3.13.12
|
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Apr 25, 2026.
Transparency log