llm-mux
The Makefile for LLMs.
Just as Make orchestrates shell commands, llm-mux orchestrates LLM calls. Write a TOML workflow, run it with one command, get results from any model - or all of them at once.
# .llm-mux/workflows/review.toml
= "review"
[[]]
= "diff"
= "shell"
= "git diff HEAD~1"
[[]]
= "analyze"
= "query"
= "analyzer" # runs Claude + Gemini in parallel
= "Review these changes:\n\n{{ steps.diff.output }}"
= ["diff"]
[[]]
= "synthesize"
= "query"
= "coder"
= """
Turn these reviews into one minimal patch.
Return only a unified diff or a JSON edits array:
{{ steps.analyze.output }}
"""
= ["analyze"]
[[]]
= "fix"
= "apply"
= "synthesize"
= "cargo test" # rolls back if tests fail
= true
= ["synthesize"]
No SDK. No Python. No boilerplate. A single Rust binary, a config file, done.
Built-in repository review
repository-review gathers a bounded Git diff, asks every backend in the
default role for an independent review, reconciles the findings with one
backend, and produces a structured patch.
Review and patch generation do not modify the repository. Applying is explicit:
Applied edits are transactional and must pass git diff --check; otherwise
they are rolled back. Use llm-mux runs show <id> to inspect every model’s
prompt, output, usage, and cost, or llm-mux runs resume <id> after fixing a
provider or configuration failure.
Why llm-mux
You have API keys for Claude, Gemini, and a local Ollama instance. Right now you're copy-pasting the same prompt into each one and manually merging the results. llm-mux removes that entirely.
- Route to multiple models at once - run a role across Claude + Gemini in parallel, get both responses
- Chain steps together - shell commands, LLM queries, file edits, and verification steps compose naturally
- Apply and verify - LLM suggests edits, llm-mux applies them, runs your test suite, rolls back on failure
- Dry-run first -
--dry-runrenders every step without commands, provider requests, or persistent writes - Inspect and resume runs - prompts, outputs, provider usage, and failures are kept in a durable SQLite ledger
- Works anywhere - single binary with direct HTTP support; CLI backends use their corresponding installed tools
llm-mux grew out of lok, an earlier take on the same idea. Lok is no longer developed; this is where the work continues.
Install
Or download a prebuilt binary from releases.
Setup
1. Configure backends
Create ~/.config/llm-mux/config.toml:
[]
= "claude"
= ["-p"]
[]
= "npx"
= ["@google/gemini-cli", "-m", "gemini-2.0-flash"]
[]
= "http://localhost:11434/v1"
= "llama3"
# Any OpenAI-compatible HTTP endpoint works
[]
= "https://api.openai.com/v1"
= "gpt-4o"
= "${OPENAI_API_KEY}"
2. Define roles
Roles map task types to one or more backends:
[]
= "General queries and built-in workflows"
= ["claude", "gemini"]
= "first"
[]
= "Code analysis"
= ["claude", "gemini"]
= "parallel" # first | parallel | fallback
[]
= "Fast local queries"
= ["ollama"]
= "first"
[]
= "Turn findings into one structured patch"
= ["claude"]
= "first"
llm-mux init --global puts every detected backend in the default role.
Ordinary queries use its first backend; workflow steps can opt into parallel
execution.
3. Create a workflow
Workflows live in .llm-mux/workflows/ (project) or ~/.config/llm-mux/workflows/ (global).
Project workflows can execute shell commands, so running or validating one
requires the explicit --allow-project-workflows trust flag:
= "review"
= "Review code changes"
[[]]
= "diff"
= "shell"
= "git diff HEAD~1"
[[]]
= "analyze"
= "query"
= "analyzer"
= """
Review these changes for bugs, security issues, and improvements:
{{ steps.diff.output }}
"""
= ["diff"]
4. Run
Workflow Steps
shell - run a command
[[]]
= "fetch"
= "shell"
= "gh pr diff {{ args.pr }}"
query - call LLM backend(s)
[[]]
= "analyze"
= "query"
= "analyzer"
= "Find bugs in:\n\n{{ steps.fetch.output }}"
= ["fetch"]
apply - apply LLM-suggested edits
[[]]
= "fix"
= "apply"
= "analyze"
= "cargo test"
= true
= ["analyze"]
store - persist findings to SQLite memory
[[]]
= "save"
= "store"
= "{{ steps.analyze.output }}"
= ["analyze"]
Template Variables
Inside prompts and shell commands:
{{ args.name }} workflow arguments
{{ steps.name.output }} previous step output
{{ env.VAR }} environment variables
{{ team }} auto-detected team
{{ ecosystem.name }} detected ecosystem
{{ ecosystem.knowledge }} stored ecosystem facts
{{ ecosystem.current_project }} current project info
Filters: shell_escape, json, join, lines, trim, truncate_chars,
default.
Configuration Reference
Backend options
[]
= "claude" # CLI command or HTTP base URL
= ["-p"] # CLI arguments
= "gpt-4o" # model name (HTTP backends)
= "${ENV_VAR}" # API key; ${VAR} expands from environment
= true
= 300 # seconds
= 3
= 2.50 # optional; enables cost estimates
= 10.00 # optional; enables cost estimates
Token usage is recorded when a provider reports it. Cost is shown as unknown unless pricing is configured explicitly; llm-mux does not assume model prices.
Run history and resume
Every workflow execution is assigned a run ID and stored in
~/.config/llm-mux/runs.db. The ledger includes resolved prompts or commands,
outputs, errors, duration, backend/model, token usage, and estimated cost.
Resume reloads the workflow from its original project directory, restores only successful prior step results, and executes the unfinished dependency tail as a new run. Existing run records are immutable.
Role execution modes
first- use first available backendparallel- run all backends, collect all resultsfallback- try each backend until one succeeds
Teams
Auto-detect project type and apply team-specific backend overrides:
[]
= ["Cargo.toml"]
= "cargo clippy && cargo test"
[]
= ["claude", "codex"]
Ecosystems
Track multi-project systems and seed context into prompts:
[]
= [
"API uses JWT tokens with 1 hour expiration",
"Redis cache invalidation happens via pub/sub",
]
[]
= "~/projects/myapp-api"
= "rust"
= ["database"]
CLI Reference
llm-mux run <workflow> [args...] Run a workflow
llm-mux run <workflow> --dry-run Preview without executing
llm-mux runs show <id> Inspect a recorded run
llm-mux runs resume <id> Resume unfinished steps as a new run
llm-mux validate <workflow> Validate workflow syntax
llm-mux doctor Check backend availability
llm-mux backends List configured backends
llm-mux teams List configured teams
llm-mux roles List configured roles
llm-mux ecosystems List configured ecosystems
llm-mux init --global Generate starter config
llm-mux init --project Generate project config
Global options:
--team <name> Override auto-detected team
--output <mode> console | json | quiet
--debug Enable debug output
--allow-project-backends Trust project backend execution/credential fields
--allow-project-workflows Trust project workflows to execute commands
Examples
Parallel bug hunt across models
= "bug-hunt"
[[]]
= "read"
= "shell"
= "cat {{ args.file }}"
[[]]
= "hunt"
= "query"
= "analyzer" # parallel across all backends in role
= "Find every bug in this file:\n\n{{ steps.read.output }}"
= ["read"]
Fix and verify
= "fix"
[[]]
= "identify"
= "query"
= "analyzer"
= "Identify the bug in {{ args.file }}"
[[]]
= "patch"
= "query"
= "coder"
= """
Fix this bug: {{ steps.identify.output }}
Return only JSON in this format:
{"edits":[{"path":"src/file.rs","old":"exact old text","new":"replacement text"}]}
"""
= ["identify"]
[[]]
= "apply"
= "apply"
= "patch"
= "cargo test"
= true
= ["patch"]
Iterate over files
[[]]
= "list"
= "shell"
= "git diff --name-only HEAD~1"
[[]]
= "review-each"
= "query"
= "analyzer"
= "steps.list.output | lines"
= "Review the changed file {{ item }}. Inspect it in the working tree."
= ["list"]
Contributing
License
MIT