Baya-cli 🕊️

Docs

Everything Baya does, and how

From install to a recovered run. This site is the place for the full picture; the wiki stays the technical source of truth for contributors.

Overview

Baya is a zero-config command-line orchestrator for the AI coding agents you already have installed and authenticated. Write the actions in plain text and run one command: Baya turns them into a dependency graph, routes each task to the provider and model that fit it, runs independent work in parallel, and carries the run through to a report.

There is no config format, no DSL, and no separate API key. It works with codex, claude, copilot, and opencode — use one default model, or name a different model or provider for a specific task.

Status: early. The walking skeleton, provider breadth, and most of concurrency and resilience have landed and are published to npm as baya-cli. Still open: --on-error stop, a parallel-aware status line, and the recovery prompt.

Install & first run

Requires Node 24+ and at least one supported CLI on your machine.

npm install -g baya-cli

Then check what Baya can see. baya doctor resolves every provider — path, version, and capabilities — and is worth running first on any new machine, since provider binaries are frequently off $PATH.

baya doctor

On the first real run, Baya asks once which provider and model to default to, stores the answer in ~/.config/baya/config.json, and never asks again. Change it later with baya config.

Run baya upgrade any time to update every installed provider CLI to its latest version; baya upgrade <provider> narrows to one.

baya upgrade

Quick start

Point Baya at any plain-text list of tasks. It plans, shows you the graph, and waits for your approval before running anything.

baya ./tasks.md

To see the plan without running it, use baya plan or --dry-run. To run unattended, review a captured manifest first, then execute it:

baya plan tasks.md --plan-out plan.json
baya run tasks.md --plan-in plan.json --yes

Writing task lists

Any UTF-8 text file works. Baya never parses these structurally — the planner reads every format for intent, so depends_on: and plain prose like “once the schema and UI are done” get you the same graph.

A task is as long as it needs to be. A single item can run for several sentences and wrap across indented lines, spelling out constraints and acceptance criteria — the planner treats the whole item as one task. This site’s own build list is written exactly that way, one paragraph-long task per bullet.

Markdown

A heading for the goal, one bullet per task — each bullet as long as the task needs. Wrapped continuation lines are indented under the bullet.

# Ship the orders endpoint

- Design the REST API for orders — list, get, create, and cancel.
  Define pagination, the error response shapes, and an idempotency
  key on create. Write it up as an OpenAPI document. Use Sonnet.
- Generate the Postgres schema and migrations from that design.
- Build the React table that consumes the list endpoint: sortable
  columns, a status filter, and empty and loading states. Run with codex.
- Once the schema and UI are done, write integration tests that
  exercise every endpoint against a throwaway database.

A bare TODO.txt

One task per line, numbered or not. The line itself can be as detailed as you like.

1 Design the REST API for orders (list, get, create, cancel) with pagination, error shapes, and an idempotency key on create. Use Sonnet.
2 Generate the Postgres schema and migrations from that design.
3 Build the sortable, filterable React table that consumes the list endpoint. Run with codex.
4 Once the schema and UI are done, write integration tests for every endpoint.

YAML

The same intent, with explicit depends_on if you think better that way. Use a | block scalar for a multi-line task:.

- id: design-api
  task: |
    Design the REST API for orders — list, get, create, cancel.
    Define pagination, error response shapes, and an idempotency
    key on create. Write it up as an OpenAPI document. Use Sonnet.
- id: gen-schema
  task: Generate the Postgres schema and migrations from that design.
  depends_on: [design-api]
- id: build-ui
  task: Build the React table that consumes the list endpoint. Run with codex.
  depends_on: [gen-schema]
- id: tests
  task: Write integration tests for every endpoint.
  depends_on: [gen-schema, build-ui]

If the planner can’t produce a graph, a deterministic splitter falls back to a linear chain in the order you wrote the tasks.

Model routing

Name a model in the task text — Use Sonnet., run this with codex, Use luna. — and Baya resolves that name against the catalog to a real provider and model id at the model gate. Tasks with no stated model use your configured default; an unset model means the provider’s own default.

Model ids churn faster than the tool ships, so nothing is hard-coded. When a provider’s catalog is missing a model or the installed CLI rejects a built-in slug, add an override to ~/.config/baya/config.json:

{
  "modelAliases": {
    "cheap": "gpt-5.6-luna"
  },
  "modelCatalog": {
    "copilot": [
      {
        "id": "claude-sonnet-4.5",
        "aliases": ["sonnet45"],
        "description": "Anthropic Claude Sonnet 4.5"
      }
    ]
  }
}

modelAliases maps a nickname to a real id; modelCatalog adds or replaces a catalog entry, keyed by provider and model id. Set an alias from the CLI with baya config set modelAliases.cheap gpt-5.6-luna, and inspect the effective catalog with baya models.

Run

baya run takes a plain-text list of coding tasks and carries it through to a report. A model turns the list into a dependency graph; a scheduler walks the graph, routing each task to the provider and model that fit it and running independent work in parallel; each result feeds the tasks that depend on it.

baya <file> is the default form — the same as baya run <file>. baya plan <file> stops at the preview, and --plan-in executes a manifest you captured earlier.

baya ./tasks.md
baya run tasks.md --plan-in plan.json --yes
  • The planner builds the graph. A model turns your list into a DAG of tasks and dependencies; if it cannot produce a valid one, a deterministic splitter falls back to a linear chain in the order you wrote them.
  • Models are named in the task text Use Sonnet., run with codex. Each name resolves at the model gate to a real id and the provider that serves it; an unstated model uses your configured default.
  • A preview gate comes first (--dry-run or baya plan stops there). It shows every task, its resolved model, what waits for what, and what shares a process, before anything runs.
  • One process, many tasks. Tasks that share a provider, model, permission level, and directory are worked through in order in a single agent process, so the repo is read once. Decided separately from the graph — a six-step chain can still be one process. --group-size defaults to 3.
  • Parallel, but write-safe. Independent read-only tasks run concurrently (--max-parallel, plus a per-provider cap); every read-write task takes the single writer lock and runs alone.
  • Nothing paid-for is redone. A task already ticked off ([x], ) is read for context, never re-run; what earlier tasks discovered is handed forward to the tasks that could not share their process (--no-memory to disable).
  • Checkpointed. State is written before every transition. Run out of credits mid-graph and baya resume <runId> picks up the unfinished work, optionally on a different provider; baya runs lists what is resumable.

Everything lands in .baya/runs/<runId>/ — the manifest, each task’s request and result, the providers’ event streams — and state.json is written before every transition, so a crash or Ctrl+C is a pause, not a restart. The run ends with a report: task outcomes, flagged notes, token spend, and the exact command to resume what is left.

Scheduling, grouping, memory, locks, and interrupts in full: wiki-llm/execution.md. The stage-by-stage flow is the diagram further down.

AI Consensus

baya consensus puts one artifact — a spec, a diff, or a plain question — in front of several models at once and reports where they land. Each provider CLI reviews it independently; a moderator reconciles their findings into a new draft; the loop repeats until nothing blocking is left or the round ceiling is hit.

It is a separate command, not a mode of baya run: no dependency graph, no task list, no lock, and baya runs and baya resume never see a consensus run.

baya consensus ./spec.md --providers luna,sonnet,opencode/mimo-v2.5-free
baya consensus "should we split this module?" --providers luna,sonnet --kind idea --rounds 3
  • Reviewers are named by model --providers luna,sonnet. A bare provider id means that CLI’s own default model; an unstated reviewer uses your configured default, and the moderator defaults to your planner model.
  • The moderator never picks the answer. It writes the criteria, reconciles each round, and reports whether the reviewers agree — nothing it believes about the subject reaches the output. A plain question gets no criteria at all: agreement prints the first reviewer’s answer, disagreement prints every answer side by side.
  • Reviewers work blind. No reviewer sees another’s findings in the same round; rivals reach it under stable pseudonyms, so a finding stands on its evidence, not a brand name.
  • --rounds is a ceiling, not a count. The debate stops early when no reviewer raised a blocker or major finding, or when a round breaks no new ground the last one already fixed.
  • Access is the moderator’s call, not a flag. For a pure question the reviewers get no tools; for anything touching the tree they get the full tool set in your working directory and can run the suite to ground a finding.
  • A confirm gate comes first (skip with --yes). When the reviewers have write access it warns that agents run unsupervised, several at once, with nothing isolating them — commit or stash before you start. Consensus is for reviewing, not developing.

Everything lands in .baya/consensus/<runId>/ as each round settles — every critique, each reviewer’s append-only ledger, and the reconciled drafts — so Ctrl+C on round 3 leaves rounds 1–2 intact. The report adds per-provider token spend, agreement counts, and the disagreements that never resolved.

Every flag — --providers, --moderator, --rounds, --kind, --ledger-budget, --output, --no-diff — is in the CLI reference. Round loop, ledgers, compaction, and the access postures in full: wiki-llm/consensus.md. The round loop is the diagram further down.

Design principles

Five ideas do most of the work. Full detail lives in the architecture and protocol wiki pages.

JSON on the wire, both directions
Every exchange with a provider is a validated envelope, never prose. codex and claude enforce the result schema natively. A question from an agent is a status: "needs_input" field — not a question mark spotted in a stream.
The planner picks a provider, never a command
Manifests carry a provider name from a closed enum; adapters alone build argv. shell: true is banned repo-wide and lint-enforced.
A process is the unit, not a task
Tasks that share a provider, model, permission level, and directory go into one agent process and are worked through in order. Grouping is decided separately from the DAG shape, so a six-stage chain can still be one process.
Nothing paid-for is ever redone
Progress is checkpointed before each transition. Within a run, commands that worked, commands that failed, and files already touched are derived from the providers’ own logs and handed to every later task.
Providers are watched, not trusted
Their flag surfaces are live-probed and contract-tested, their output is ANSI-stripped and schema-validated before it is read or persisted.

CLI reference

A bare path argument is treated as baya run <file>. The full flag surface is in the CLI wiki page.

Commands

Baya commands and what each one does.
CommandPurpose
baya <file>Default form. Alias for run.
baya run <file>Plan, resolve models, confirm, execute.
baya plan <file>Plan and render the DAG; never executes. Same as run --dry-run.
baya doctorResolve every provider: path, version, capabilities. Reap stray process groups.
baya configRe-run the setup wizard. Subactions: --show, path, set <key> <value>, refresh-models.
baya models [id]Print the effective model catalog grouped by provider, each row tagged built-in or user.
baya upgrade [id]Run each resolved provider's self-update argv; optional provider filter.
baya resume <runId>Re-execute a run’s unfinished tasks; succeeded tasks are kept as context. --provider <id> re-runs elsewhere.
baya runsList resumable runs — running, paused, failed, interrupted — newest first.
baya consensus <file|"prompt">Several provider CLIs debate one artifact until they agree. Alias con. Not a run — invisible to runs and resume.

Frequently used run flags

The most common baya run flags and their meaning.
FlagMeaning
--dry-runRender the DAG with resolved models and exit. Nothing runs.
--yesAuto-confirm the plan gate; at the model gate take a best match ≥ 0.85. Never answers a task question.
--max-parallel <n>Global concurrency budget, default min(4, cpus). Per-provider caps apply on top.
--group-size <n>Max tasks per provider process, default 3. 1 gives every task its own process.
--no-memoryDo not pass what earlier tasks learned. Every task starts blind — the A/B control for measuring memory.
--default-provider <id>Fallback provider for tasks with no stated provider. Bypasses the first-run wizard.
--planner-provider <id>Provider that parses the task list into a manifest.
--jsonMachine-readable run report, models catalog, or runs list to stdout — always ANSI-free.

Consensus flags

baya consensus takes its own flags below, plus the shared --json, --verbose, --quiet, --log-level, --no-color, and --no-progress. Full surface: wiki-llm/cli.md.

baya consensus flags and their meaning.
FlagMeaning
--providers <models>Reviewers, named by model or alias — luna,sonnet. A bare provider id means that CLI’s own default model. Defaults to your configured default model; unset with no default exits 2.
--moderator <model>The reconciler, named the same way. Defaults to your planner model. May also appear in --providers, in which case it reviews blind before reconciling. --moderator-model is a synonym.
--rounds <n>Round ceiling, not a count — the debate stops earlier when nothing blocker or major is left. Default 5.
--kind <k>question | plan | spec | review | idea | prompt. Skips the moderator’s classification call and fixes the access posture: plan, idea, prompt, and question run tool-less; spec and review get the workspace. question answers the artifact; prompt rewords it.
--ledger-budget <n>Characters of its own prior rounds carried into each reviewer’s next prompt before compaction. Default 8000.
--output <f>Write the final document to a file instead of stdout. The banner stays on stderr, so a piped stdout stays clean.
--no-diffSuppress the change summary in the report.
--yesAuto-confirm the cost gate. A non-TTY without it exits 2 after spending only pass 0.
--tools allForce the full tool set regardless of the posture the moderator picked.

Exit codes

Baya exit codes and what each one means.
CodeMeaning
0All tasks succeeded, or --dry-run completed. Consensus: the debate converged, or the ceiling was reached with a document.
1A run had a task fail, skip, or park, or an uncaught exception (teardown still runs). Consensus: a reviewer or the moderator failed and no document survived.
2A run hit a planner, manifest-validation, or model-gate error. Consensus: bad input, unknown provider, or an unresolved moderator. Nothing was executed either way.
130SIGINT; children torn down.
143SIGTERM; same teardown.

Providers

Verified by live invocation, not from documentation. “Verified” means a task was run end to end and returned a valid task_result — a probed flag surface is not enough.

Baya provider support: non-interactive entrypoint, result-schema enforcement, and verification status.
ProviderNon-interactiveSchema enforcementStatus
codexcodex execfile in / file outVerified 2026-08-28
claudeclaude -pinline --json-schemaVerified 2026-08-28
opencodeopencode runNoneVerified 2026-08-31
copilotcopilot -pNonePartial
geminigemini -pNoneDeferred to v1.1
grokPlanned, unprobed

A task’s permission level is what it is allowed to do, not what it edits: a read-write task can write, run commands, and reach the network; a read-only task does none of the three. codex is the only provider that enforces this with an OS sandbox — a task that must not touch the tree belongs there.

Full flag surfaces, event shapes, and the capability matrix: wiki-llm/providers.md.

Configuration

Config is layered: built-in defaults, then ~/.config/baya/config.json (written by the first-run wizard), then per-directory .baya/config.json, then command-line flags — each overriding the one before.

  • baya config --show prints every effective value and the layer it came from.
  • baya config path prints the config file location.
  • baya config set <key> <value> writes a single value.
  • baya config refresh-models re-fetches the opencode model list and prunes catalog entries identical to a built-in one.

Full precedence rules and the first-run flow: wiki-llm/config.md.

Recovery & resume

The run is checkpointed to state.json before every transition — a crash never loses a step. If a provider fails, runs out of quota, or you press Ctrl+C, the unfinished work stays resumable.

  • baya runs lists resumable runs — running, paused, failed, or interrupted — newest first.
  • baya resume <runId> re-runs only the unfinished tasks in the run’s own directory; succeeded tasks are kept as context.
  • baya resume <runId> --provider claude picks the work up on a different provider — the answer to exhausted credits.

An unfinished run ends by printing that exact resume command, what it will re-run, and what to fix first. A quota failure halts the run cleanly rather than feeding the wall every remaining task.

Failure taxonomy and the full resume contract: wiki-llm/recovery.md.

How it works

Two commands, two shapes. A run turns a task list into a dependency graph and executes it; a consensus debate puts one artifact in front of several models and reports where they land.

A run

Freeform list to report, with the preview gate the only stop. Grouping is decided separately from the graph, so a chain can still be one process, and what each task discovers flows back to the scheduler for the tasks that follow. Detail: run and design principles.

flowchart TB
    MD["tasks.md: freeform text"] --> P["Planner: an LLM CLI"]
    P -->|JSON manifest| V{"Validate: schema, cycles, deps"}
    V -->|invalid| R["Repair once, then linear fallback"]
    R --> V
    V -->|valid| G["DAG: topological layers"]
    G --> GATE{"Preview and confirm"}
    GATE -->|approved| S["Scheduler: budgets, single write-lock"]
    S --> GRP["Group: same provider, model, access, cwd"]
    GRP --> PROC["Agent processes: several tasks each, in order"]
    PROC --> AD["Provider adapters: argv, prompt delivery, event parsing"]
    AD --> CLIS["codex / claude / opencode / copilot"]
    CLIS --> RES{"task_result JSON"}
    RES -->|ok| BUS["Context bus: feeds dependents and later tasks"]
    RES -->|needs_input| ASK["Bubble the question"]
    RES -->|failed| REC["Classify failure, mark resumable"]
    BUS --> S
    BUS --> OUT["Report: outcomes, flagged notes, resume command"]
    ASK --> OUT
    REC --> OUT
A run: plan, validate, gate, group, dispatch, reconcile results, report.

A consensus debate

The moderator writes the criteria and reconciles each round but never picks the answer; reviewers critique in parallel and never see each other’s findings in the same round. The loop stops when nothing blocking is left or --rounds is hit. For a plain question there are no criteria and the moderator only reports agreement. Detail: consensus and wiki-llm/consensus.md.

flowchart TB
    ART["artifact: a file or a prompt"] --> P0["Moderator pass 0: classify, write criteria, pick access posture"]
    P0 --> ROUND{"Round N: fan out to reviewers, in parallel and blind"}
    ROUND --> RA["Reviewer A"]
    ROUND --> RB["Reviewer B"]
    ROUND --> RC["Reviewer C"]
    RA --> MERGE["Moderator: reconcile findings into a new draft"]
    RB --> MERGE
    RC --> MERGE
    MERGE --> CONV{"any blocker or major finding, and rounds left?"}
    CONV -->|yes| ROUND
    CONV -->|no| OUT["Final document, rejected findings, unresolved disagreements"]
A consensus debate: classify, fan out blind, reconcile, repeat until it converges or the round ceiling is reached.

Contributing

Contributions are welcome. The work is broken into sequenced tasks in specs/001/02-plan.md, each with its own done-criteria, so there is plenty to pick up independently.

Before opening a PR:

npm run typecheck && npm run lint && npm test

A few rules are load-bearing rather than stylistic — the full list is in wiki-llm/conventions.md:

  • No shell: true, ever. Spawns take argv: string[].
  • Never document a provider flag you have not actually run.
  • Never regex a model’s prose for meaning — semantics come from validated JSON.
  • Update the affected wiki-llm/ page in the same commit as the change.
  • Read provider event shapes out of a recorded run in .baya/runs/, not out of a provider’s docs.
  • Tests never touch the network; the contract tier is opt-in via BAYA_CONTRACT=1.

Adding a provider is deliberately small: one adapter, one capability block, one section in providers.md, one contract test. New contributors should start with conventions.md, then the plan.

Use Baya on Baya. Every run leaves .baya/runs/<runId>/ behind — real provider event streams on a real repository, for free. That corpus is the best fixture set the project has. Mine it before inventing an input, then pin what you find with a committed test.

Open the repository on GitHub →

Still have a question? The FAQ covers why Baya sits alongside your existing CLIs, what it does for your bill, and whether parallel runs are safe.