Tandem is a local, provider-neutral harness for pairing one conversational agent with one or more execution workers. It includes a terminal CLI and an early macOS desktop app.
The initial profile uses:
- Outer: Codex CLI — conversation, research, planning, task decomposition, and review.
- Worker: Claude CLI — bounded implementation inside an isolated Git worktree.
- Fallback: Freebuff CLI — an optional interactive recovery path when a primary subscription is exhausted or temporarily unavailable.
- Runtime: cmux when Tandem is launched inside cmux, then tmux when installed, then a detached local process.
- State: SQLite under
~/.tandem/.
The multiplexer is a visibility and attachment layer. It is not Tandem's source of truth.
The release installer runs on macOS, Linux, and WSL. It installs a standalone CLI and, on macOS, the signed and notarized desktop app. No source checkout or local build is required.
| Requirement | Version | Why it is needed |
|---|---|---|
| Git | Current | Isolated worker branches and worktrees |
| Node.js | 22.13 or newer | Tandem CLI and built-in SQLite ledger |
| Codex CLI | Current, authenticated | Default conversation, planning, and review provider |
| Claude Code | Current, authenticated | Default implementation worker |
Tandem uses the subscriptions already authenticated in the provider CLIs. It does not ask for OpenAI or Anthropic API keys.
Install and authenticate the providers before running Tandem:
# Codex: run the official installer, then sign in on first launch.
curl -fsSL https://chatgpt.com/codex/install.sh | sh
codex
# Claude Code: do not use sudo with this npm install.
npm install -g @anthropic-ai/claude-code
claudeIf the provider CLIs are already installed and authenticated, skip these commands.
curl -fsSL https://github.com/jcast90/tandem/releases/latest/download/install.sh | bashThe installer verifies release checksums, upgrades an existing Tandem CLI in
place, and adds ~/.local/bin to zsh's login profile for a new installation.
Future releases use one command:
tandem updatetandem setup
tandem doctorThen start Tandem inside a clean Git repository:
cd ~/projects/my-app
tandem chatFor contributor builds, release operations, uninstalling, and platform notes, see Installation. For common first-run problems, see Troubleshooting.
This is a working 0.1.0 vertical slice, not a production-ready autonomous
agent platform. The CLI orchestration path and desktop conversation path both
work. It intentionally starts with two subscription-backed CLI adapters while
keeping roles, providers, transports, models, and session runtimes separate in
the domain model.
The Tandem desktop app is a calm project-and-chat interface over the same local orchestration core:
- Codex owns the outer conversation through the authenticated Codex CLI
app-server. - Claude remains the bounded execution worker through the authenticated Claude CLI.
- Projects contain chats in the sidebar.
- New chats can start in any saved project or a newly selected directory. Project rows also expose a one-click new-chat action, and chats can be archived or permanently deleted from their inline menu.
- Codex delegation calls bind the resulting Claude task to the active chat.
- Substantive turns create a durable outer goal. Claude-routed work also creates a nested worker goal, carries that exact goal through delegation, and keeps both lifecycle states synchronized with completion, blocking, and canceling.
- Claude progress, blocked questions, final reports, verification, and isolated commit metadata appear inline while the outer turn continues.
- Work is grouped into small chronological, collapsible segments between the assistant updates that produced it instead of one turn-wide activity dump.
- Codex child agents and Claude execution workers share one provider-neutral Agents view, grouped into active and completed work. Compact agent chips appear beside the relevant conversation work and open the agent's real assignment, progress, messages, and file activity on demand.
- Assistant responses render Markdown, and local file links open a lightweight in-app preview with external-editor and Terminal shortcuts.
- The active Codex turn and Claude workers can be stopped independently or together. New composer messages steer the active Codex turn, while each worker card can send guidance to Claude's live streaming session.
- Changed files and worker steps are expandable and clickable.
- The composer discovers enabled Codex skills and attaches selected skills as native turn input. Files and folders can be attached as local context, while installed plugins remain available through their owning CLI session.
- The composer can leave routing on Auto, keep a request with Codex, or require delegation to Claude. Auto keeps discussion, research, planning, review, and small bounded changes with Codex while routing substantive implementation, debugging, verification, and long-running execution to Claude. Short follow-ups use recent conversation context, and each turn shows the selected provider and reason. Codex models and reasoning effort are discovered from the authenticated local app-server; Claude model aliases and permission overrides flow into each delegated work order.
- Settings → Routing maps conversation, quick changes, research, architecture, implementation, and verification to a provider profile, model, effort, and safe parallelism ceiling. Auto applies that shared policy, while an explicit composer selection wins for the current message.
- Settings → Benchmarks compares matched work across Codex alone, Claude alone, both subscriptions used manually, and Tandem Auto. Linked execution runs contribute timing, task outcomes, tests, evidence, and CLI-reported usage; acceptance, quality, human attention, revisions, and quota deltas remain explicit operator scores.
- Ask, auto-approve, and full-access permission modes map to the corresponding Codex approval/sandbox policy and Claude CLI permission mode.
- Worker activity stays behind an on-demand panel rather than turning the app into an IDE or monitoring dashboard.
- Dependency-aware execution runs appear as compact, collapsible task groups in that panel, including concurrency, task states, and integration readiness.
- Connections settings show each CLI's resolved path, version, and subscription authentication state, with retry, login, and diagnostic-log actions.
- Tandem health-checks its local Codex service, restarts it with bounded backoff after a dropped or silent connection, and cleans up the full Codex/MCP process tree when reconnecting or quitting.
- Finder-launched builds discover CLIs installed by NVM, asdf, Homebrew, Bun,
pnpm, and common user-local paths without relying on an inherited terminal
PATH. - No OpenAI or Anthropic API key is requested, and the app does not use an API billing path.
The Codex app-server protocol is currently experimental. Tandem keeps its
protocol client isolated under apps/desktop/src/lib/codex.ts so it can be
updated without changing the UI or orchestration ledger.
Run the desktop app from source:
pnpm install
pnpm build
pnpm desktopBuild a local macOS application:
pnpm desktop:buildThe .app is produced under:
apps/desktop/src-tauri/target/release/bundle/macos/Tandem.app
The bundle includes Tandem's MCP and worker runners. Codex CLI, Claude CLI, and Node.js still come from the user's authenticated local installation.
- cmux or tmux for visible, attachable worker sessions
- Freebuff CLI as an authenticated interactive fallback
- Ponytail for execution policy optimization
Tandem finds the cmux control binary on PATH or inside
/Applications/cmux.app. cmux only authorizes its socket from processes
started inside a cmux terminal, so start tandem chat inside cmux to get visible
worker workspaces.
tandem setup lets you pin model IDs or aliases. Leaving a model blank means
the corresponding CLI's configured default remains authoritative.
Start in a clean Git repository:
cd ~/projects/my-app
tandem chatYou can also seed the outer conversation:
tandem chat "Research the auth flow, propose a plan, and delegate implementation."Before Codex opens, press Shift+Tab (or Tab) to cycle Ask, Auto, and Full
permissions, then press Enter. macOS reserves Command+Tab for switching apps,
so terminal programs cannot bind it. The selected mode is a session policy:
Codex, delegated Claude tasks, scheduler workers, and their child agents inherit
it. Scripts can bypass the selector with --permissions.
Reference additional repositories or directories in Auto mode with repeatable roots:
tandem chat --add-dir ../shared-lib --add-dir ~/Documents/referenceTandem supports Ponytail as an
execution-optimization policy. Install its Codex and Claude plugins once, then
select off, lite, full, or ultra globally or per chat:
tandem ponytail install
tandem ponytail mode full
tandem chat --ponytail ultraTandem launches Codex with a one-invocation MCP configuration. It does not rewrite your global Codex configuration. The MCP server gives the outer agent these tools:
tandem_goal_createtandem_goal_updatetandem_goal_listtandem_run_createtandem_run_gettandem_run_waittandem_run_canceltandem_run_checkpointtandem_run_integratetandem_room_createtandem_room_gettandem_room_waittandem_room_contributetandem_room_resumetandem_room_canceltandem_delegatetandem_task_gettandem_task_listtandem_task_waittandem_task_cancel
Meeting rooms use bounded deliberation instead of an open-ended model chat. A room can run one to five substantive rounds, followed by a separate chair synthesis. The complete five-round sequence is independent proposals, adversarial critique, reframing and cross-pollination, falsification, and final revision. Each round runs participants concurrently; later rounds receive the persisted room history under stable anonymous coworker aliases such as Member A and Member B. Participants respond directly to one another, steelman and challenge specific ideas, ask questions for the next round, propose alternatives, and explicitly explain what changed their positions. The configured chair then weighs claims by evidence and how well they survived criticism, showing how the seed idea evolved while preserving dissent and hypotheses that still need validation.
Choose a purpose-specific preset when the desired output is known. These rooms
lock to the complete five-round sequence; general remains configurable from
one to five rounds.
| Preset | Use it to |
|---|---|
general |
Explore an ambiguous idea and evolve the framing |
problem-discovery |
Find evidence-backed pains and rank validation opportunities |
decision |
Compare options and make a reversible, testable choice |
architecture |
Resolve technical design and produce an ADR |
red-team |
Attack a proposal and define go or no-go conditions |
research |
Reconcile evidence, contradictions, and uncertainty |
execution-planning |
Build dependencies, parallel work, checkpoints, and rollback |
From a normal tandem chat, ask naturally—for example, “Start an architecture
room for this design.” Tandem selects the matching preset and returns the chair
synthesis to the same conversation.
If Ollama is installed locally, Tandem also exposes the default room participant
local-muse-glimmer using muse-glimmer:latest. Include it like any other
profile when asking Tandem to convene a room; it runs locally and reports no
subscription usage telemetry.
Preview a room definition before spending provider usage:
tandem room plan --file test/fixtures/deliberation-room.json --rounds 5Start the durable runner and follow it until synthesis:
tandem room start --file test/fixtures/deliberation-room.json --rounds 5 --cd ~/projects/my-app
tandem room watch <room-id>The --rounds option overrides the JSON definition for that invocation. You can
also persist the choice as "rounds": 5 in the room file. More rounds multiply
provider usage, so room definitions should set a deliberate
maxEstimatedTokens budget for expensive discussions.
Codex, Claude, and Ollama turns execute concurrently inside each round. Each prompt, response, provider session ID, reported usage object, lifecycle event, and final synthesis is persisted in SQLite. Completed turns are skipped when a room resumes after an app or process restart. The first round is blind; later prompts use anonymized contribution labels rather than provider names, and the chair returns one standalone Markdown synthesis.
The current Freebuff CLI exposes an interactive terminal UI but no supported structured one-shot output mode. Tandem therefore records it as an interactive-only participant/fallback and will not scrape its terminal UI or pretend an unattended run completed. The room pauses at a durable checkpoint and prints the exact saved prompt. After asking Freebuff, save its response and continue with:
tandem room contribute <room-id> --profile fallback-freebuff --file response.md
tandem room watch <room-id>The same create, wait, contribute, and cancel lifecycle is available to the outer conversation through Tandem's MCP tools, so the completed chair synthesis can appear as a single response in the chat.
Tandem assigns its own conversation ID when the desktop starts or opens an outer
thread. The record lives in ~/.tandem/tandem.sqlite and maps the Tandem
conversation to its project and provider thread; provider transcripts remain in
their native stores rather than being copied.
tandem conversation list
tandem resume <conversation-id>
tandem resume <conversation-id> "Continue with the Problem Discovery Room"Short unique ID prefixes are accepted. Resuming restores the original outer thread while reattaching Tandem's MCP server, so its rooms, goals, and delegated work remain available.
For product research, set preset to problem-discovery. Tandem fixes the room
at five rounds and ends with ranked problem cards instead of a product pitch.
Codex, Claude, Freebuff, and Muse Glimmer each contribute and cast one locked
ballot. The chair's separate synthesis call reports a fixed 3-2-1 tally and
cannot add a vote, reorder the result, or break a tie. The protocol treats
competition, workarounds, existing spend, and buyer reachability as primary
evidence. Google Trends comparisons are recorded as a supporting signal with
their query, geography, time range, direction, baseline, and caveats; relative
search interest never counts as market size or willingness to pay.
{
"question": "Find a painful, reachable problem worth solving.",
"preset": "problem-discovery",
"participants": [
{ "profileId": "outer-primary" },
{ "profileId": "worker-primary" },
{ "profileId": "fallback-freebuff" },
{ "profileId": "local-muse-glimmer" }
],
"chairProfileId": "outer-primary",
"maxEstimatedTokens": 120000
}Routing fallback chains are ordered and never loop:
tandem routing set implementation --fallback fallback-freebuff
tandem routing listOnly quota exhaustion and temporary provider outages activate automatic fallback. Authentication, invalid model, malformed request, test, and ordinary implementation failures remain visible as the original failure.
When Codex delegates, Tandem:
- Refuses to proceed if the repository is dirty.
- Creates an isolated worktree and
tandem/<task-key>branch fromHEAD. - Opens a cmux workspace, tmux session/window, or detached process.
- Runs Claude with a bounded objective, acceptance criteria, and structured report schema.
- Records lifecycle events and usage reported by the CLI in SQLite.
- Commits completed worker changes on the isolated branch.
- Returns the commit to Codex for review with
git show.
Applying the worker result to your current branch is deliberately explicit:
tandem task show <task-id>
git show <commit-sha>
tandem apply <task-id>A run is a bounded dependency plan for multiple modifying workers. Each task selects a provider profile/model, declares its dependencies and intended write scope, and starts from the run's immutable source commit. Unknown or overlapping write scopes are serialized automatically.
Create a plan such as:
{
"objective": "Add the feature and verify it",
"policy": {
"maxConcurrency": 2,
"maxTasks": 6,
"maxEstimatedTokens": 120000,
"maxWallTimeMs": 3600000,
"failureMode": "fail-fast",
"autoIntegrate": true
},
"tasks": [
{
"key": "implementation",
"objective": "Implement the feature",
"acceptanceCriteria": ["Focused tests pass"],
"estimatedTokens": 40000,
"writeScope": ["src/feature"]
},
{
"key": "docs",
"objective": "Document the feature",
"estimatedTokens": 15000,
"writeScope": ["README.md", "docs"]
},
{
"key": "integration-tests",
"objective": "Add integration coverage",
"dependsOn": ["implementation"],
"estimatedTokens": 25000,
"writeScope": ["test/integration"]
}
]
}Then supervise and apply it:
tandem run start --file plan.json --cd ~/projects/my-app
tandem run watch <run-id>
tandem run checkpoint <run-id> "implementation complete"
tandem run show <run-id>
tandem run apply <run-id>The scheduler enforces task-count, estimated-token, wall-time, and concurrency budgets. Completed worker trees are normalized into one commit per task; Tandem then composes those commits in dependency order inside a separate integration worktree. Actual changed-path overlap between parallel tasks blocks integration. Applying the final commit is staged in another disposable worktree, rechecks the target branch, and only then fast-forwards the clean user checkout.
Tandem's benchmark ledger tests the product claim instead of assuming it: can the same combined subscription budget produce more accepted, high-quality work through Tandem than through either tool alone or both tools used manually?
Create one benchmark set, then record the same representative task under all four modes:
tandem benchmark create "July implementation set" --budget 200
tandem benchmark add <benchmark-id> --variant codex-only \
--label "Implement export history" --class implementation --difficulty 4
tandem benchmark add <benchmark-id> --variant claude-only \
--label "Implement export history" --class implementation --difficulty 4
tandem benchmark add <benchmark-id> --variant manual-dual \
--label "Implement export history" --class implementation --difficulty 4
tandem run start --file plan.json --cd ~/projects/my-app \
--benchmark <benchmark-id> --variant tandem-auto \
--label "Implement export history" --difficulty 4Score an outcome after reviewing the result:
tandem benchmark score <trial-id> --accepted yes --quality 92 \
--wall-minutes 31 --human-minutes 7 --revisions 0 \
--codex-usage 2 --claude-usage 3
tandem benchmark show <benchmark-id>The north-star value is quality-adjusted accepted work: accepted results earn
difficulty × quality / 100 points. Tandem also reports acceptance, wall time,
human attention, revisions, provider-reported tokens, and subscription quota
deltas separately. Missing provider telemetry stays unknown. A useful pilot
uses at least 8–12 real tasks spanning research, architecture, implementation,
and verification; one favorable task is not evidence of savings.
tandem setup
tandem doctor
tandem chat [--cd <repo>] [--add-dir <path>] [--model <model>]
[--permissions <ask|auto|full>] [--ponytail <off|lite|full|ultra>] [initial prompt]
tandem permissions [ask|auto|full]
tandem ponytail status|install|mode <off|lite|full|ultra>
tandem status
tandem goal list
tandem goal create [--parent <goal-id>] <objective>
tandem goal update <goal-id> <active|complete|blocked|canceled>
tandem task list [--status <status>]
tandem task show <task-id>
tandem task watch <task-id> [--once]
tandem task steer <task-id> <guidance>
tandem task cancel <task-id>
tandem apply <completed-task-id>
tandem run list
tandem run start --file <plan.json> [--cd <repo>]
tandem run show <run-id>
tandem run watch <run-id> [--once]
tandem run cancel <run-id> [reason]
tandem run checkpoint <run-id> <label>
tandem run integrate <run-id>
tandem run apply <run-id>
tandem benchmark list
tandem benchmark create <name> [--budget <dollars>]
tandem benchmark show <benchmark-id>
tandem benchmark add <benchmark-id> --variant <variant> --label <task>
tandem benchmark score <trial-id> [outcome options]
tandem benchmark export [benchmark-id]
Task IDs can be supplied by a unique prefix.
Configuration lives at ~/.tandem/config.json:
{
"version": 1,
"runtime": "auto",
"policy": {
"permissionMode": "auto",
"ponytailMode": "full"
},
"profiles": [
{
"id": "outer-primary",
"role": "outer",
"provider": "openai",
"transport": "codex-cli",
"command": "codex",
"model": null,
"settings": {
"search": true,
"permissionMode": "auto"
}
},
{
"id": "worker-primary",
"role": "worker",
"provider": "anthropic",
"transport": "claude-cli",
"command": "claude",
"model": null,
"settings": {
"permissionMode": "auto",
"effort": "high"
}
}
],
"routing": {
"outer": "outer-primary",
"worker": "worker-primary",
"reviewer": null,
"taskRules": [
{
"taskClass": "research",
"profileId": "outer-primary",
"model": null,
"effort": "high",
"maxConcurrency": 3
},
{
"taskClass": "implementation",
"profileId": "worker-primary",
"model": null,
"effort": "high",
"maxConcurrency": 3
}
]
}
}The example abbreviates taskRules; Tandem fills all six categories when an
older version-one configuration does not contain them. Inspect or change the
same policy without opening the desktop app:
tandem routing list
tandem routing set research --profile worker-primary --model sonnet --effort medium --concurrency 2
tandem routing resetUse default for --model and auto for --effort to defer to the selected
profile or authenticated CLI.
Set TANDEM_HOME to use a different state directory.
With "runtime": "auto":
authorized cmux terminal
→ cmux new-workspace --cwd <worktree> --command <worker>
→ otherwise tmux, when installed
→ otherwise detached process with logs in ~/.tandem/logs
The runtime used for every task is persisted in SQLite. Closing a cmux/tmux surface does not erase task history or the Git worktree.
- A Git worktree protects the current checkout from worker edits, but it is not an operating-system security sandbox.
- The top-level
ask,auto, orfullpermission mode is inherited by Codex, Claude workers, scheduler tasks, and child agents. Tandem translates it into each provider's native flags.fullremoves the Codex sandbox and bypasses Claude permission checks; use it only when you understand the filesystem and network authority it grants. - Freebuff 0.0.142 exposes neither permission flags nor a non-interactive worker protocol. Tandem can set its working directory but cannot suppress prompts in that upstream interactive UI.
- Claude workers receive a conservative environment containing normal shell, locale, Tandem, and multiplexer variables. Ambient API tokens and unrelated project secrets are not forwarded by default.
- Tandem refuses to delegate from a dirty repository so a worker never silently misses uncommitted work.
- Worker commits are never automatically cherry-picked into the user's current branch.
- Run integration and apply happen in disposable worktrees. A conflicting worker result or an advanced target branch leaves the user checkout untouched.
- Missing write scopes are treated conservatively and serialized. Parallel task results that actually touch overlapping paths are blocked before integration.
- Canceling or failing a task preserves its worktree for inspection and recovery.
The orchestration layer operates on role profiles:
role → provider → transport → model
Adapters expose capability probes and execution/session operations. New adapters can therefore add API transports, other providers, or local models without changing goals, tasks, events, worktrees, or runtime selection.
Planned follow-ups include richer approval prompts, resumable blocked-worker questions, revision rounds in the same worker session, provider-reported usage reconciliation, evaluation suites, and safe worktree cleanup.
pnpm check
bash -n scripts/*.shTests use temporary state and Git repositories. They validate the SQLite ledger, worktree/commit/apply boundary, and the MCP tool surface without calling either provider model.
See Contributing for architecture rules, desktop development, and pull request expectations. Please report vulnerabilities through the private process in Security, not a public issue. Participation is governed by the Code of Conduct.
Tandem is available under the MIT License.