| name | dootsabha |
| description | Orchestrates multiple AI coding CLIs (Claude, Codex, Antigravity, Grok) as a council. Use for a second opinion from another model, multi-perspective code review, peer review and synthesis across agents, validating a PRD or design for gaps, iterative refinement through reviewer feedback, or checking agent health. Replaces manual `codex exec` / `agy -p` / `grok -p` subprocess calls with structured JSON and exit codes built for agent control flow.
|
| allowed-tools | Bash(dootsabha:*), Bash(./bin/dootsabha:*) |
दूतसभा — Multi-Agent Council Orchestrator
Runs several AI coding CLIs against one prompt, cross-reviews their answers, and
synthesizes a result. Output is JSON with exit codes designed for branching.
Prerequisite: dootsabha on $PATH (or ./bin/dootsabha after make build
in the repo). Verify with dootsabha status --json.
Agents
| Agent | Tokens | Cost | Session ID | Default council member |
|---|
claude | ✅ | ✅ | ✅ | ✅ (standalone only) |
codex | ✅ | — | — | ✅ |
agy | ✅ | — | ✅ | ✅ |
grok | ✅ | ✅ | ✅ | ❌ opt-in |
Responses report Model as the backend id (grok-4.6-build), which differs from
the configured grok-4.6. Match on prefix, not equality.
agy reports tokens and a session id (its conversation id) but never cost — the
Antigravity CLI emits none, so CostUSD stays 0 rather than being estimated.
grok is opt-in. It never joins a council automatically — name it explicitly:
--agent grok, --agents claude,codex,grok, --chair grok, --reviewers codex,grok.
Check availability first; status lists it as not installed (optional) when the
grok CLI is absent, and that does not fail the command:
dootsabha status --json | jq -r '.data[] | select(.Name=="grok" and .Healthy) | .Name'
Non-empty output means grok is usable. Reach for it when a task benefits from a
fourth, independent perspective — it reports full token and cost data.
status proves liveness, not quota
status runs each CLI's --version. A Healthy: true / Reachable: ✓ row means the
binary is installed and answered --version — the column is named Reachable
because that is all it proves. It does not check credentials or remaining
quota. An agent can look healthy and still fail mid-call with a rate-limit or
quota-exhausted error. agy hits this most often.
Treat it as routine, not as a broken tool: the provider's own error text is
surfaced at exit 3 (that agent failed) or exit 5 (council — others
succeeded). Retry with a different agent, or read meta.providers to see who is
still working.
Always use --json
dootsabha <command> --json "<prompt>"
Stdout is always exactly one JSON document, including on failure. Other global
flags: --quiet, -v/-vv (stderr only), and the two budgets — --timeout 5m
bounds one agent call, --session-timeout 30m bounds the whole pipeline.
Commands
| Command | Purpose | Key flags |
|---|
council | Dispatch → peer review → synthesis | --agents, --chair, --rounds, --parallel |
consult | Query one agent | --agent (required), --model, --max-turns |
review | Author writes, reviewer critiques | --author, --reviewer |
refine | Sequential review + incorporation | --author, --reviewers, --anonymous |
status | Agent health | — |
config show | Effective config + source | --reveal, --commented |
plugin list | Discover extensions | — |
Inside a Claude Code session council defaults to codex,agy (Claude is already
the host); standalone it defaults to claude,codex,agy.
Full flags, output schemas, and the provider matrix:
references/command-reference.md.
Extracting results
Two envelope shapes — this trips up most callers:
| Commands | Shape | Extract |
|---|
consult, status, config, plugin | {meta, data}, PascalCase fields | jq -r '.data.Content' |
| …the same, on failure | data becomes {provider, error} — lowercase | jq -r '.data.error' |
council, review, refine | top-level object, snake_case fields | jq -r '.synthesis.content' |
Reading the wrong shape yields null, not an error — jq -r '.Content' on a
consult prints null and exits 0. If a field comes back null, check the envelope
before assuming the agent returned nothing.
RESULT=$(dootsabha council --json "Redis or Memcached for session caching?")
echo "$RESULT" | jq -r '.synthesis.content // (.dispatch[] | select(.error == null) | .content)'
echo "$RESULT" | jq '.meta.total_cost_usd'
echo "$RESULT" | jq -r '.dispatch[] | select(.error != null) | .provider'
synthesis is null when all agents failed. Per-agent errors live in
dispatch[].error and meta.providers.
Exit codes
One code, one action:
| Code | Meaning | Do |
|---|
| 0 | Complete, usable | proceed |
| 2 | Bad flags/args, unknown agent or chair | fix the command |
| 3 | Every requested agent failed — nothing usable (incl. status with no healthy agent) | retry, or pick another agent |
| 4 | Timeout | raise the budget the message names — invocation timeout → --timeout, session timeout → --session-timeout — or shrink the prompt |
| 5 | Some agents failed, output usable (incl. status degraded) | use it, note the gaps |
| 6 | Config missing or invalid | fix the config |
| 1 | Unexpected internal error | report a bug |
Precedence: 2 > 6 > 4 > 3 > 5 > 1 > 0.
3 vs 5 is the distinction that matters: 3 means nothing usable came back, 5
means you have an answer with gaps.
Branching patterns: references/exit-codes.md.
Configuration
Works with built-in defaults. ~/.config/dootsabha/config.yaml is auto-loaded if
present; --config <path> overrides it. Check which is in effect:
dootsabha config show --json | jq -r '.data.config_source.type'
Per-provider overrides use DOOTSABHA_PROVIDERS_<NAME>_{BINARY,MODEL}. Config
shape and every key: references/command-reference.md.
Resources