Parallel prototype orchestrator for coding agents. Split one vague feature request into N variants, implement them in isolated git worktrees, judge, and pick a winner.
Project description
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.
Project details
Release history Release notifications | RSS feed
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 joust-1.1.0.tar.gz.
File metadata
- Download URL: joust-1.1.0.tar.gz
- Upload date:
- Size: 69.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 |
48b4850c1575085999ec8743939d5aa3dfaaa37c93a51d1e861fa8e1744d296e
|
|
| MD5 |
0baea84f014da7e112c0be5f5f64f320
|
|
| BLAKE2b-256 |
2b24bd8df40687d8c2619724345a2d8dcd6d4198adfa098537b3cc0c8032d454
|
Provenance
The following attestation bundles were made for joust-1.1.0.tar.gz:
Publisher:
publish.yml on nclandrei/joust
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
joust-1.1.0.tar.gz -
Subject digest:
48b4850c1575085999ec8743939d5aa3dfaaa37c93a51d1e861fa8e1744d296e - Sigstore transparency entry: 1381449119
- Sigstore integration time:
-
Permalink:
nclandrei/joust@8bef65a2a36aece7cb57a4e017e03c6b01afd395 -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/nclandrei
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8bef65a2a36aece7cb57a4e017e03c6b01afd395 -
Trigger Event:
release
-
Statement type:
File details
Details for the file joust-1.1.0-py3-none-any.whl.
File metadata
- Download URL: joust-1.1.0-py3-none-any.whl
- Upload date:
- Size: 34.5 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 |
f6313c4f924ac904ab9f4083e1a939e5303ded1fdb850159efdd662a327702ac
|
|
| MD5 |
1fb85c666a9a50e0f46a2d653c793ac6
|
|
| BLAKE2b-256 |
b7cb4eb4bffcdb50226d9590292b5b2619e8ee9744b83ce7335d031aa7ea862d
|
Provenance
The following attestation bundles were made for joust-1.1.0-py3-none-any.whl:
Publisher:
publish.yml on nclandrei/joust
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
joust-1.1.0-py3-none-any.whl -
Subject digest:
f6313c4f924ac904ab9f4083e1a939e5303ded1fdb850159efdd662a327702ac - Sigstore transparency entry: 1381449197
- Sigstore integration time:
-
Permalink:
nclandrei/joust@8bef65a2a36aece7cb57a4e017e03c6b01afd395 -
Branch / Tag:
refs/tags/v1.1.0 - Owner: https://github.com/nclandrei
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@8bef65a2a36aece7cb57a4e017e03c6b01afd395 -
Trigger Event:
release
-
Statement type: