❯ tales / Docs

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.

i
What Tales is not: an autonomous agent that ships code on its own. The recommendation is a suggestion; the gate is yours.

Install & prerequisites

  • Rust toolchain (stable) to build the workspace.
  • Runs on macOS, Linux, and Windows — all three are covered by CI. tales open opens a new terminal window on macOS, Windows, and common Linux desktop terminals; plain tales works 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
i
Windows. Run .\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>
i
Replace <tool-a>/<tool-b> with any installed tool key (see Supported tools).

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 (Enter open/select · c use current · p or : type a path · Backspace parent), then a permission prompt approves writing .tales artifacts 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, or Ctrl-J insert a newline; plain Enter sends. Pasting a multi-line block lands as one edit instead of firing several submits.
  • Scroll back. PageUp/PageDown scroll 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-N shell · Ctrl-X/Ctrl-L/Ctrl-O open agent panes · Ctrl-S sends the handoff · Tab switches focus.
  • Observe / intervene. A focused agent pane receives your keystrokes directly. Ctrl-A sends 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, Enter or /confirm <n> launches the executor in its own live pane and sends it the plan.
i
Two execution paths. In the workspace, confirming hands the plan to a live, interactive CLI pane you watch and steer. The scriptable tales run and the classic planner run the executor headlessly through the core. Same plan, different driver.
!
Live panes share a directory. Worktree isolation is automatic only for 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
i
This project's 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; 0 disables the cap.
  • --no-project-context disables the cached repo map; --refresh-project-context rebuilds it before a run.
  • --project-context-max-files and --project-context-max-manifest-chars control how much repo shape is indexed.
  • --report-path and --report-json-path write the final local report and structured summary, including prompt telemetry and optimization hints.
  • --dry-run --json on run/discuss emits the resolved SessionConfig, prompt forecast, profile status, tool recommendations, run advice, tool readiness, and report destinations without spawning agents.
i
Reports include provider token/cost data only when adapters expose machine-readable usage. Local prompt estimates are deterministic heuristics and remain separate from provider billing.

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-model is only valid for a separate executor; pointing --execute at 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.
i
Live today in 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/:

ModelSizeEasy (18)Mixed-signal (11)Game-dev (13)Speed
keyword coordinator—100%63.6%—instant
prompted qwen3.5:9b6.6 GB100%90.9%—~5 s/task
TalesSML (fine-tuned)398 MB100%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 planEasy (18)Mixed-signal (11)Game-dev (13)Malformed
plan well-formed100%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
i
Caching. Routing runs at temperature 0, so a decision is deterministic — the 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.
i
Honest status. The engine acts on TalesSML's 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:

ConditionWall-clockCostQuality
Opus 4.8 — solo (plans + executes)355s$1.53100%
Tales — Opus+Codex plan → Haiku executes481s$1.74100%

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.

i
Honesty. Single trial per arm — the direction is structural; exact percentages wobble. Costs are each tool's own meter at list prices (Opus 4.8 $5/$25, Haiku 4.5 $1/$5 per M; Codex from its token ledger × gpt-5.5 rates). We have not benchmarked against Fable 5 — "frontier-class" is the target we're chasing, not a measured result.

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:

ToolKeyCapability notesInstall
Claude Codeclauderesumable, model/effort override, high strength/high pricenpm i -g @anthropic-ai/claude-code
Codexcodexstructured output, sandbox, model/effort overridenpm i -g @openai/codex
Open Codeopencoderesumable cwd session, model override, generic price tiernpm i -g opencode-ai
Gemini CLIgeminigeneric headless CLI, model overridenpm i -g @google/gemini-cli
GLMglmgeneric low-price CLI rowvendor CLI
Kimikimigeneric low-price CLI rowMoonshot 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.

!
Roster rule. A tool whose session continuity is keyed on the working directory (e.g. Open Code) can only be connected once per run — two would resume each other's session. Tales rejects this up front.

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-checks PATH per 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/--critic to run with a specific roster.
  • --worktree ignored. The cwd isn't a git repo; Tales prints a note and runs without isolation.
  • Browser workspace rejected. tales-web requires 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 --refresh or pass --refresh-project-context to rebuild before a run.
  • Merge left mid-merge (Conflict). The executor's branch conflicts with your base; resolve and commit, or git 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.