Documentation
Orchestrate your AI coding tools
Tales runs the AI coding CLIs you already use as collaborators on one task: they debate a plan in a live terminal, recommend who executes, and you confirm before anything runs.
Overview
Tales is a lightweight Rust orchestrator that drives AI coding CLIs as subprocesses so they collaborate on the same project. They discuss a plan, each emits a recommendation for who should implement it, and no editing or execution hand-off starts until you confirm.
It isn't a coding model or an API wrapper — it drives the tools you already have installed and authenticated, normalizes their very different I/O into one event stream, and keeps a human at the decision gate. What it does ship is a model of its own: TalesSML, a small local conductor that reads a task and decides how to coordinate the other models (see The conductor). Tales is not just a coordination harness around other models — it ships a model whose job is to coordinate and orchestrate them.
Two strong models beat one
A single model commits to its first framing and rarely escapes it. Tales puts two (or more) models with different priors in the same room: one drafts, the other critiques, and the disagreement surfaces assumptions a solo run buries. The analogy to OpenRouter's model fusion and AWS Kiro is deliberate: combine the strengths of more than one model instead of betting on one.
It's model- and tool-agnostic by design — any AI coding CLI joins behind one small adapter — and the room doesn't have to be uniform. You can tier it: strong, expensive models plan; a cheaper, faster model implements. Spend on judgment, not on typing.
Install & prerequisites
- Rust toolchain (stable) to build the workspace.
- Runs on macOS, Linux, and Windows — all three are covered by CI.
tales openopens a new terminal window on macOS, Windows, and common Linux desktop terminals; plaintalesworks inside any terminal you already have open. - At least one AI coding CLI on your
PATH, installed and authenticated. The default discussion pairs two, so for a single tool you pass explicit flags (see Troubleshooting).
Tales detects which tools are on PATH and shows the connectable ones — you only need the ones you intend to use. See Supported tools for the adapters that ship today.
# build → target/release: tales, tales-tui, tales-web cargo build --release # put target/release on your PATH, then open the terminal: tales
Try the whole flow with no API calls:
tales-tui --demo tales-web --demo
.\target\release\tales.exe, or add target\release to PATH. Release checks live at scripts/release-check.sh (macOS/Linux) and .\scripts\release-check.ps1 (Windows).Quickstart
Start with a local preflight, then open the interactive terminal. Connect your tools, type a task, watch them plan, then pick who executes. Or go straight in, scriptable:
tales doctor --all tales context tales
# two tools plan + argue, you confirm who executes tales run "add a /health endpoint" \ --drafter <tool-a> --critic <tool-b> --execute <tool-a>
The interactive terminal
Type tales to open a multi-pane terminal workspace: a Tales orchestrator pane plus sibling shell and agent-CLI panes.
- Onboard. On launch, a folder browser picks the workspace (
Enteropen/select ·cuse current ·por:type a path ·Backspaceparent), then a permission prompt approves writing.talesartifacts and launching CLIs from that folder before any agent spawns. - Discuss. In the Tales pane, type your task and press
Enter; the drafter and critic discuss in place, streamed token-by-token. Discussion is the default — a formal plan is only requested when both agents say one is needed. - Write long prompts. The prompt is a full multi-line editor: insert and edit anywhere with
Left/Right,Home/End, word motion (Alt+Left/Right), and word/line kills (Ctrl-W,Ctrl-U,Ctrl-K).Alt+Enter,Shift+Enter, orCtrl-Jinsert a newline; plainEntersends. Pasting a multi-line block lands as one edit instead of firing several submits. - Scroll back.
PageUp/PageDownscroll the conversation once it grows past the viewport, with a right-aligned "↑ scrolled" indicator while scrolled up; sending a message or the gate opening snaps the view back to the live tail. - Discover commands. Press
/to turn the footer into a live, filtered list of the slash commands with one-line descriptions; it narrows as you type (/ha→/handoff) and disappears for normal messages or an unknown token. - Open panes as needed.
Ctrl-Nshell ·Ctrl-X/Ctrl-L/Ctrl-Oopen agent panes ·Ctrl-Ssends the handoff ·Tabswitches focus. - Observe / intervene. A focused agent pane receives your keystrokes directly.
Ctrl-Asends an explicit approval, but only when that pane is waiting on one. - Confirm & hand off. After the discussion, agents emit recommendations and Tales surfaces the aggregated pick. At the gate,
Enteror/confirm <n>launches the executor in its own live pane and sends it the plan.
tales run and the classic planner run the executor headlessly through the core. Same plan, different driver.tales run --worktree. Live executor panes start in the same working dir, so if you open several to run in parallel, isolate them yourself (separate worktrees/branches).Prefer one focused view? tales-tui --classic is the original connect → prompt → plan → confirm screen. Skip setup with pre-connect / pre-fill:
tales-tui --connect <tool-a> --connect <tool-b> tales-tui --connect <tool-a> --prefill "Design a rate limiter"
The browser UI
tales-web runs a local Axum server and streams the same bus events into a minimal browser terminal. Launch it with no task to pick a workspace and task in the UI; launch it with a task to auto-start the legacy flow.
# taskless start: choose workspace + task in the browser tales-web --demo # direct start: old positional task flow still works tales-web "review this repo" --demo --port 7878
- Workspace selector. The browser sends an absolute folder path to
/api/session/start; the server canonicalizes it and rejects non-directories before any agents spawn. - Pre-session preview. The header shows tool readiness, prompt forecast, context budget, cache status, report paths, and run advice before the WebSocket starts.
- Same gate. The browser can approve, override, or reject the recommended executor, and nothing executes until that gate releases.
Scriptable commands
The non-interactive counterparts — for scripts, CI, and dogfooding.
tales doctor — local readiness, no model calls
tales doctor tales doctor --all --json tales doctor --agent claude --agent codex --refresh-project-context
Checks known tools, PATH readiness, roster safety, adapter capabilities, and project-context cache metadata. --all inventories every registry row without failing on missing optional tools.
tales context — inspect the cached project map
tales context tales context --refresh --json --output .tales/context.txt
The cache is deterministic and local: file map plus selected manifest excerpts, stored outside the repo and injected into first planning prompts so agents spend fewer tokens rediscovering shape.
tales profile — local workspace memory, no source snippets
tales profile refresh tales profile show --json tales profile clear
Profiles live under the Tales cache tree and store metadata only: detected commands, conventions, preferred installed tools, warnings, approved report paths, and run summaries.
tales eval — compare collaboration shapes without model calls
tales eval run "fix lint errors" --scenario debate_parallel --json tales eval compare "fix lint errors" tales eval report .tales/evals/compare.json
The eval harness records deterministic prompt forecasts, recommendation chips, and pass/fail checks for solo, debate, and tiered shapes. It does not spawn agents unless a future real-adapter eval mode is explicitly added.
tales solo — one agent, one turn
tales solo --model <model> "Reply with exactly: hello from tales" tales solo --agent <tool> --image diagram.png "Explain this architecture"
--agent · --model · --cwd · --sandbox · --image (repeatable). Image/PDF support varies by tool — see Supported tools.
tales discuss — a live drafter/critic discussion, no execution
tales discuss --drafter <tool-a> --critic <tool-b> --turns 2 \ --sandbox read-only "Design a minimal rate limiter. 5 bullets."
tales run — the full pipeline: discuss → recommend → execute
tales run "Build X" --drafter <tool-a> --critic <tool-b> --execute <tool-a>
The recommendation is computed and printed, but --execute decides who runs. It can be one of the planners, or a different tool entirely (see Tiered execution). Add --worktree to run that executor in its own git worktree and merge back:
tales run "Add a /health endpoint" --execute <tool> --worktree
landing/ page and these docs were drafted and reviewed with Tales via tales discuss, then finalized against the source.tales coordinator — the orchestration model
tales coordinator predict "migrate all cron jobs to the queue" tales coordinator export-dataset --out conductor-dataset.jsonl # add --plans for v2 tales coordinator train # retrain the embedded MLP from traces
The pure-Rust keyword/MLP coordinator that ships in the binary, plus the dataset exporter that feeds TalesSML (see The conductor). predict shows the shape/difficulty for a task; export-dataset --plans emits the richer orchestration-plan training target; the fine-tune itself runs in the Python training/ sidecar.
Preflight & context controls
Planning defaults are optimized for fewer repeated tokens: parallel rounds, resumable adapter deltas, cached project context, and compact local-change handoffs. You can inspect and tune those controls without spending model calls.
--context-budget-chars <n>caps transcript/plan context pasted into stateless prompts;0disables the cap.--no-project-contextdisables the cached repo map;--refresh-project-contextrebuilds it before a run.--project-context-max-filesand--project-context-max-manifest-charscontrol how much repo shape is indexed.--report-pathand--report-json-pathwrite the final local report and structured summary, including prompt telemetry and optimization hints.--dry-run --jsononrun/discussemits the resolvedSessionConfig, prompt forecast, profile status, tool recommendations, run advice, tool readiness, and report destinations without spawning agents.
Tiered execution new
The room doesn't have to be uniform. When --execute names a tool that isn't a planner, Tales enrolls it as a separate executor that sits out the debate and implements the agreed plan with its own model via --execute-model. Strong models plan; a cheap, fast model runs.
# two strong models plan + argue, a cheap/fast one implements tales run "add OAuth login" \ --drafter <tool-a> --drafter-model <strong> \ --critic <tool-b> --critic-model <strong> \ --execute <tool-c> --execute-model <cheap-fast>
--execute-modelis only valid for a separate executor; pointing--executeat one of the planners (same-tool tiering) is rejected for now.- The executor sits out the discussion and the vote, but it's still a valid recommendation candidate.
tales run. Bigger rooms (N planners) and same-tool tiering are on the roadmap; the core is already model- and tool-agnostic.The conductor — TalesSML new
Tiering (above) lets you assemble the room with flags. TalesSML is the small model that does it for you — Tales' own orchestration brain. It's not a coding model: it's a tiny open base (Qwen2.5, 0.5–3B), LoRA-fine-tuned on Tales-generated coordination data and served locally — no cloud, no per-token cost, ~0.18 s/decision. The same approach Sakana built Fugu's Conductor on: a coordination policy on top of existing models, not a frontier model trained from scratch.
Given one task, TalesSML reasons over the work and emits a full orchestration plan:
# shape · difficulty · roster · coordination · verify · escalate { "shape": "debate", // solo | debate | tiered "difficulty": 0.74, // correctness risk, 0–1 "roster": [ {"role":"drafter", "agent":"claude","model":"opus", "why":"strongest reasoning"}, {"role":"critic", "agent":"codex", "model":"gpt-5.x","why":"best reviewer"}, {"role":"executor","agent":"claude","model":"haiku", "why":"cheap/fast typing"} ], "coordination": {"order":["drafter","critic","executor"], "parallelizable":true}, "verify": {"required":true, "method":"tests", "agent":"codex"}, "escalate": {"if":["failed_verification"], "to":{"agent":"claude","model":"opus"}} }
Why a model and not just keywords? A keyword counter sees "migrate all the cron jobs" and routes mechanical-tiered at 99% confidence — it can't tell that's a design decision. A model that reasons over the task can. Measured on held-out corpora in training/:
| Model | Size | Easy (18) | Mixed-signal (11) | Game-dev (13) | Speed |
|---|---|---|---|---|---|
| keyword coordinator | — | 100% | 63.6% | — | instant |
| prompted qwen3.5:9b | 6.6 GB | 100% | 90.9% | — | ~5 s/task |
| TalesSML (fine-tuned) | 398 MB | 100% | 100% | 92.3% | ~0.18 s/task |
That table scores the routing decision. The v2 plan model (a 1.5B fine-tune, ~986 MB Q4) emits the whole orchestration plan, graded by eval_plan.py against the deterministic policy (shape + roster + coordination + verify + escalate). A train→eval→refine flywheel — clean the numeric target, teach JSON discipline via the system prompt, then close the tiered/debate boundary with adversarial data — took it from 61/55/54% plan-valid (with 12/42 malformed) to:
| TalesSML v2 — full plan | Easy (18) | Mixed-signal (11) | Game-dev (13) | Malformed |
|---|---|---|---|---|
| plan well-formed | 100% | 100% | 92% | 0 |
Point Tales at a locally served TalesSML with --conductor llm (opt-in llm-conductor build feature; HTTP stays out of the lean default):
cargo build --release --features llm-conductor tales run "decide whether to migrate the cron jobs to the queue" \ --conductor llm --conductor-url http://localhost:8080/v1
LlmConductor memoizes each (model, prompt, task) to .tales/conductor-cache.json (the key folds in the prompt, so it self-invalidates when either changes). A repeated task is then instant and free: the local analogue of provider prompt caching, but with zero token cost on a hit.shape+difficulty today (the LlmConductor parses them; extra plan fields are forward-compatible), with a hard fallback to the pure-Rust tales coordinator when the server is unreachable. The full plan (roster/coordination/verify/escalate) is what v2 emits and is graded above; wiring it into adaptive turn-taking is next. Train your own from tales coordinator export-dataset --plans; see training/.The bet & benchmark
A single frontier model is the bar. The bet behind Tales is that an orchestrated team of cheaper models can reach for it — two strong-but-not-top models argue out the plan, a cheap fast model does the typing, a human holds the trigger. The aim is frontier-class results at a lower blended cost, approaching a model like Claude Fable 5 — not a claim to beat it (Fable 5 wasn't available to benchmark against). Here is the honest evidence so far.
A fair, end-to-end test
Implement a regex engine (fullmatch(pattern, text) over literals, ., * + ?, |, groups, character classes, escapes), scored against 400 hidden cases with Python's re as the oracle (no import re). Two conditions, each producing a real solution.py that gets scored:
| Condition | Wall-clock | Cost | Quality |
|---|---|---|---|
| Opus 4.8 — solo (plans + executes) | 355s | $1.53 | 100% |
| Tales — Opus+Codex plan → Haiku executes | 481s | $1.74 | 100% |
On this task the single model won on every axis — 36% faster, 13% cheaper, same quality. The reason is the point: a regex engine's difficulty lives in the implementation, not in separable boilerplate, so the cheap executor either shipped a systematic bug (43.8% blind) or iterated against tests until green (100%, but 47 rounds and ~4.6M cached tokens) — the iteration ate its per-token cost advantage. A frontier model just wrote it correctly in one pass.
Where Tales beats a single model — and where it doesn't
The deciding factor is the shape of the work, not its size:
- ✅ Mechanically voluminous execution — boilerplate, repetitive endpoints, broad migrations. The two-planner cost is roughly fixed and amortizes; the cheap executor applies its cheap rate to a lot of volume it doesn't need to be smart about. Bigger helps Tales here.
- ✅ Ambiguous, architecture-defining work with no known-correct answer — two strong models arguing, plus a human gate, catch a bad design before it becomes a costly diff. A quality/risk play more than a cost/speed one.
- ❌ Algorithmically hard, self-contained tasks where correctness is the implementation. The cheap executor's capability gap and the lossy plan handoff compound — bigger makes it worse, and one frontier model wins.
Parallel planning
Planning used to run the two models sequentially (latency = the sum of their turns) and re-paste the whole transcript each turn. Both are fixed: planners now draft concurrently (round 1 independent, then one synthesizes a merged plan while the other cross-reviews), and resumable adapters get only the unseen tail each turn. Live A/B on the same planning task: 250.8s sequential → 176.5s parallel — a 30% speedup (the synthesis step adds some Opus output, so parallel runs ~20% pricier — a speed-for-cost trade). Default for tales run/discuss; --sequential opts out.
Supported tools
Tales is tool-agnostic — each tool is one small AgentAdapter + a registry row. These ship today and are valid values for --agent / --drafter / --critic / --execute and the connect screen:
| Tool | Key | Capability notes | Install |
|---|---|---|---|
| Claude Code | claude | resumable, model/effort override, high strength/high price | npm i -g @anthropic-ai/claude-code |
| Codex | codex | structured output, sandbox, model/effort override | npm i -g @openai/codex |
| Open Code | opencode | resumable cwd session, model override, generic price tier | npm i -g opencode-ai |
| Gemini CLI | gemini | generic headless CLI, model override | npm i -g @google/gemini-cli |
| GLM | glm | generic low-price CLI row | vendor CLI |
| Kimi | kimi | generic low-price CLI row | Moonshot Kimi CLI |
Adding another is a small change: implement one bespoke AgentAdapter or add one generic KNOWN_TOOLS row with run_args, model_flag, and prompt_flag — the picker, CLI, and orchestrator all read that registry.
Architecture
A Cargo workspace with a strictly UI-agnostic core. Frontends exchange runtime events and commands with tales-core only through the bus — which is why the web frontend dropped in with zero engine changes.
AgentAdapter
Each CLI implements one trait: spawn() launches the process and pumps normalized AgentEvents (TokenDelta, MessageFinal, ToolCall, TurnComplete, Error) into a channel, and returns a command sender driven with AgentCommands. The engine and frontends see only these enums — never the concrete tool. Differences between tools (e.g. bidirectional stream-json vs turn-based exec/resume) live only in AgentCaps flags, never an if claude {…}.
The bus
EventBus owns a lossy broadcast channel for events (a slow frontend lags and recovers; it never blocks the engine) and an mpsc channel for commands (many interchangeable producers, one engine consumer).
Project context & telemetry
project_context builds a compact cached repo snapshot from filenames and important manifest excerpts. prompt_forecast estimates local prompt payloads before model calls, while session reports record prompt counts, prompt chars, provider token/cost data when available, local-change handoffs, and optimization hints.
Session config, profile, and eval
session defines the shared SessionConfig, agent seat, prompt budget, report path, and approval-policy contract used by CLI and web preflight. workspace_profile stores local metadata memory under the Tales cache tree, and eval_harness compares collaboration shapes without model calls.
Worktree isolation
WorktreeManager can give each agent its own git worktree on its own branch. Today tales run --worktree uses it for the single selected executor: create one worktree, run there, and on success merge back — Clean, NoChanges, or Conflict { files }. The worktree is pruned on every exit path.
Recommendation & gate
After discussion, each planner emits a JSON recommendation (recommended_executor + confidence + rationale, tolerantly parsed). aggregate tallies confidence-weighted scores into a Recommendation with a confident flag. The recommendation is advisory; execution begins only when you confirm.
Troubleshooting
- Tool not connectable / "not found". The CLI isn't on
PATH. Install it, reopen; Tales re-checksPATHper run. - Auth failures. Tales doesn't manage credentials — authenticate the underlying CLI directly once, then retry.
- Only one tool installed. The default discussion needs two participants; pass
--connect <tool>(TUI) or explicit--drafter/--criticto run with a specific roster. --worktreeignored. The cwd isn't a git repo; Tales prints a note and runs without isolation.- Browser workspace rejected.
tales-webrequires an absolute path to an existing directory; choose the launch cwd shortcut or paste a canonical folder path. - Context cache feels stale. Use
tales context --refreshor pass--refresh-project-contextto rebuild before a run. - Merge left mid-merge (
Conflict). The executor's branch conflicts with your base; resolve and commit, orgit merge --abort. - Can't connect a tool twice. Expected for cwd-session tools — see the roster rule under Supported tools.
These docs were drafted by one AI coder and reviewed by another through tales discuss, then finalized against the source. If a detail drifts from the code, the code wins — please open an issue.