A tool for getting second opinions.
Sometimes editing and code review are valuable because other people see things right away that have slipped by us. Maybe it's because we were too close to the writing, or didn't sleep well last night, or any number of reasons. This happens with agents too. A new model revision rolls out, your model writes some bad code. It can't see the bug right in front of it. The solution for both humans and agents is the same: get a second opinion.
kaibo is a suite of agents wrapped in an MCP server — kaibo agents running on model
families you choose, ideally not the one asking. Biases tend to be shared across
a model lineage: the Claudes share them, the GPTs share them, and spawning more
subagents from the same family repeats them. It's a bit like monoculture, leaving you
vulnerable to blight. A reviewer whose mistakes don't line up with
yours has a chance of catching what your model can't see. So you can bring that
outside perspective into a code review, design session, or research: your agent calls
consult, and kaibo's agents investigate your project and report back.
kaibo integrates as a stdio MCP server and supports Anthropic, Gemini, DeepSeek, OpenRouter (one key reaching every major model family), and any OpenAI-compatible endpoint, including local services like llama.cpp. It's a tool your agent uses — any MCP-capable client can drive it — and it's also a CLI for scripts, CI, and terminals that would rather shell out than speak MCP.
kaibo's agents reach your code through one tool: a kaish shell. kaish has all of its commands built in and mounts your project through a virtual filesystem layer that is read-only, so a kaibo agent can read files in your workspace and write nowhere.
You ask your agent for something. It calls consult with a question and a cast — the
two-model team you picked, from whatever family you like.
From there kaibo runs its own agents. The synth owns the investigation: it's the capable model, and it decides how to work. It reads spans through the read-only shell itself, and it can hand a broad sweep to the explorer — a cheaper, faster model that searches the repo and reports back what it found. Delegating is a tool call, and it's the synth's call whether to make one. Then the synth checks what matters, fills the gaps, and writes the answer.
So the chain runs: you → your agent → kaibo's synth → kaibo's explorer. If your agent spawned a subagent to do the work, that's one more link, and the inside is the same. Every link is a model plus the harness it acts through, working for the link above it, and every link hands back a summary instead of everything it saw. That's the whole idea: your agent gets one cited answer instead of an investigation, and the cheap model does most of the reading.
Your agent just reworked some concurrency-sensitive code and wants eyes from outside its own family. It calls:
One synthesized review comes back — cited, with the investigation noise left out of your context:
No. The
absorbclosure is the sole return gate, and it's correct (src/mcp_log.rs:250-267):wokeis only set when a record meetswake_floor— rank(Warning) — and the handler hardcodes that floor (src/server/mod.rs:2499); no caller input can change it. […] The drain→park race is handled:notify_one()stores a permit when no task is waiting, so a push that races ajob_wait's registration isn't missed (src/mcp_log.rs:271-286). […] The only real tension is the unconditionalnotify_one()inpush(src/mcp_log.rs:166) — every record, even Debug noise, briefly wakes the parked loop. An optimization concern, not a correctness one. [abridged]——— kaibo · cast
deepseek· explorerdeepseek-v4-flash· synthdeepseek-v4-pro
That's a real consult against this repo, and it ran about four minutes. The full answer also surfaced a benign spurious-wakeup path and an optimization wrinkle we hadn't written down anywhere — which is the point: a model that didn't make your model's mistakes, reading your real source, reporting back one summary.
Note the cast in that call — a cast names which models kaibo's agents run on.
kaibo can't tell what model your agent runs on, so picking a different family is up
to you. A call that names no cast falls back to the default, which ships as
anthropic — a good answer, but not an outside one when Claude is doing the asking.
Set your default once at registration ("args": ["--cast", "deepseek"]) or in
config.toml, and make sure that cast's key is the one you exported. That mismatch is
the most common first-run stumble.
kaibo ships as a single self-contained binary per platform — download it, check the
checksum, put it on your PATH, done. Linux builds are fully static musl and run on
any distro; macOS (Apple silicon and Intel) and Windows binaries are self-contained
too. Grab your platform's archive and its .sha256 from the
releases page:
sha256sum -c kaibo-*-x86_64-unknown-linux-musl.tar.gz.sha256
tar xzf kaibo-*-x86_64-unknown-linux-musl.tar.gz
install -m 0755 kaibo ~/.local/bin/ # anywhere on your PATHPrefer building from source? git clone https://github.com/tobert/kaibo and
cargo install --path kaibo with a Rust toolchain ≥ 1.85 produces the same binary.
Every release is born signed in public CI: the signing identity is the release workflow at that tag, witnessed by the Sigstore transparency log — no maintainer key to steal or trust. Two independent checks; either one is sufficient.
With the gh CLI, SLSA build provenance is one command
against any file from the release:
gh attestation verify kaibo-v0.2.0-x86_64-unknown-linux-musl.tar.gz -R tobert/kaiboWith cosign ≥ 3
(no GitHub tooling needed), verify the signed checksum manifest once and it
covers every file it lists. Grab checksums.txt and checksums.txt.sigstore.json
from the release, substituting the tag you downloaded in the identity:
cosign verify-blob \
--bundle checksums.txt.sigstore.json \
--certificate-identity "https://github.com/tobert/kaibo/.github/workflows/release.yml@refs/tags/vX.Y.Z" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
checksums.txt
sha256sum -c --ignore-missing checksums.txtThe bundle is self-contained — certificate, signature, and transparency-log entry in one file — so that verify works offline.
Each release also carries an SPDX SBOM (kaibo-<tag>-sbom.spdx.json) cataloging
the exact locked dependency tree the binaries were built from.
The same release ships as a multiarch (amd64/arm64) container image,
ghcr.io/tobert/kaibo — the fully-static binary
in a distroless, shell-less, non-root base.
Because the binary links against nothing, the image doubles as a COPY source — the easiest way to put kaibo into a devcontainer (or any image), with zero libc or package-manager concerns:
COPY --from=ghcr.io/tobert/kaibo:latest /usr/local/bin/kaibo /usr/local/bin/kaiboThat's the whole install: inside a devcontainer your workspace mount is already declared, so kaibo registers as a plain local MCP server from there.
To run the image itself as the MCP server, mount your project read-only at
/work (the image's working directory — kaibo scopes its read access to it)
and keep stdin open:
claude mcp add kaibo -- docker run --rm -i \
-u "$(id -u):$(id -g)" \
-v "$PWD:/work:ro" \
-e DEEPSEEK_API_KEY \
ghcr.io/tobert/kaibo:latest-iis load-bearing. kaibo is a stdio MCP server: without-i, stdin closes immediately and the container exits 0 in silence — and distroless has no shell to debug into.-uruns the container as you so the mount's file permissions line up; podman users want--userns=keep-idin its place.- Pass provider keys by name (
-e DEEPSEEK_API_KEY) — the value rides your shell environment, never your MCP config. - The
:romount is an OS-enforced belt under kaibo's own read-only sandbox: kaibo never writes to your project either way, this just makes the kernel agree.
The same image is a CLI too — append a subcommand and drop -i (there's no
stdio transport to hold open for a one-shot run):
docker run --rm -v "$PWD:/work:ro" -e DEEPSEEK_API_KEY \
ghcr.io/tobert/kaibo:latest consult "what does this project do?" --cast deepseekThe package is public — pulling (and COPY --from) needs no registry login.
The image is signed and attested by the same machinery as the archives — verify
with gh attestation verify oci://ghcr.io/tobert/kaibo:<tag> -R tobert/kaibo,
or cosign verify ghcr.io/tobert/kaibo:<tag> with the same two identity flags
shown above.
Then register it with your agent. kaibo is a standard stdio MCP server, so any
MCP-capable client works — Claude Code, Codex CLI, Cline, OpenCode, whatever you
run. The stanza goes in your client's MCP config; in Claude Code that's .mcp.json
in your project, and claude mcp add kaibo -- kaibo writes the same registration
for you:
{
"mcpServers": {
"kaibo": {
"command": "kaibo",
// No args needed — an MCP client launches kaibo with cwd = your workspace, and
// it scopes its read-only access there. Common overrides (pick what you need):
// "args": ["--root", "/path/to/project"] // pin the project (not repeatable)
// "args": ["--allow-path", "/extra/tree"] // ADD a tree; keeps the cwd too
// "args": ["--no-cwd"] // …unless you want only what you named
// "args": ["--cast", "deepseek"] // default cast when a call omits it
// "args": ["--no-run-kaish"] // drop a tool from the surface
// "args": ["--config", "/path/to/config.toml"] // use an explicit config file
"args": [],
// The consulted models need provider keys — but don't put them here; this
// file tends to get committed. Leave `env` empty and kaibo reads keys from
// your shell environment (ANTHROPIC_API_KEY, DEEPSEEK_API_KEY,
// GEMINI_API_KEY, OPENROUTER_API_KEY, …), or point a backend at a key FILE
// via `api_key_file` in config.toml. Config stores only the env-var NAME or
// the file PATH — never the secret. A missing key only matters when you
// actually call a cast that needs it.
"env": {}
}
}
}Every tool below is also a command — no MCP client needed, so scripts, CI, and a human at a terminal can drive kaibo directly:
| MCP tool | CLI equivalent |
|---|---|
consult |
kaibo consult "question" [--cast … --attach … --session … --json] |
oneshot |
kaibo oneshot "prompt" [--attach … --json] (context also via stdin: oneshot "review this" < diff.txt) |
explore |
kaibo explore "question" [--cast … --json] |
run_kaish |
kaibo kaish -c 'script' |
batch_submit, job_get/job_list (batch handles) |
kaibo batch submit | get | list |
kaibo://config resource |
kaibo config |
kaibo://config/example resource |
kaibo example-config |
configure prompt |
kaibo configure [goal] |
kaibo consult "does anything still busy-poll in job_wait?" --cast deepseekBare kaibo with no subcommand is the stdio MCP server. kaibo serve is the
explicit spelling for the same thing, and may become required in the future.
consult_submit/job_wait and deliberate's direct lane are not fully implemented
on the CLI yet — tracked as #82.
stdout is the answer, stderr is everything else. Progress, logs, and warnings
go to stderr, so piping stays clean; the answer (with the same provenance footer
the MCP tool appends) is the only thing on stdout. --json swaps that for a
structured envelope ({answer, cast, models, usage, warnings}) for a script
caller — its answer field is always the model's raw words, never a kaibo notice.
Exit codes have defined behavior, so a caller branches on the code instead
of parsing prose: 0 an answer, 2 a usage error (bad flag, unknown or
wrong-for-the-tool cast), 3 a setup/containment rejection (a path outside
the allowed set, a missing provider key), 4 the work ran and failed at
runtime (a provider error, a model-loop failure). kaibo kaish is the one
exception — it passes through kaish's own exit code (0 ok, 126 blocked,
124 timed out) instead, since a script branches on that to know what the
sandboxed command did. The same table is in kaibo --help.
The shared flags (--root, --allow-path, --cast, --config, house-rules
files, …) work before or after the subcommand and are documented in kaibo --help; each subcommand's own flags are in kaibo <subcommand> --help.
A --session NAME thread and a submitted batch job both stick around between
runs — see What kaibo remembers below for what that
means and where it lives.
Most of what kaibo does is a single question in, a single answer out — nothing to remember, nothing saved. Two things stick around on your machine, and both are things you opted into by name:
- A conversation you named. Pass
--session NAMEon the CLI, orsession_idover MCP, and kaibo keeps that thread's back-and-forth so you can pick it up later — from the CLI, from your MCP client, doesn't matter, they share the same memory. Ask a one-off question with no session name and nothing is kept. - A batch job you submitted.
batch_submit(orkaibo batch submit) hands back a handle right away; kaibo remembers that handle sokaibo batch list(orjob_list) can find it again later — even after a restart — without you having to keep it written down somewhere.
That's genuinely all of it — kaibo never snapshots your repository, and
nothing beyond those two things is written down. A saved session does hold the
questions and answers themselves, though, and those routinely quote source, cite
file:line spans, and describe your architecture. So a named session is a
record of a conversation about your project, and worth the same care as one.
It lives in one small file on your machine, separate from your project — by
default ~/.local/state/kaibo/state.db ($XDG_STATE_HOME/kaibo/state.db if
you have that set). The models can't read this file: it's never mounted in
their sandbox, and kaibo refuses to put the state db inside any project it's
allowed to read. Resuming a named session does replay that session's own turns
back to the model, which is how it picks up where you left off, but no model
can go browsing the file.
kaibo never deletes this file, even on errors — cleanup is your call. Delete
or move it and kaibo starts fresh: you lose named sessions and the batch
handles it remembered for you, and nothing else breaks. Prefer kaibo forgets
everything between runs? --no-persistence (or KAIBO_NO_PERSISTENCE) turns
it off entirely. Want it somewhere else? --state-db FILE (or
KAIBO_STATE_DB) moves it. The full technical contract — exactly which fields
are stored, what's deliberately left out, and the one platform-specific edge
case — is in
docs/config.md.
The fastest way to set up is to let your agent do it. kaibo ships a configure
prompt for exactly this; MCP clients expose prompts in different ways, commonly as
/kaibo:configure. Pass an optional goal such as
a local-only privacy cast. It walks the agent through reading kaibo's config, asking
which providers you have, and writing your config.toml — keeping keys in env vars or
files, never inline.
The file is written by your host agent with its own permissions, the same way it edits any file — not by kaibo's sandboxed sub-agents, which remain strictly read-only and never touch disk. The prompt just hands your agent the knowledge to do it.
If your host agent sandboxes local commands or MCP servers, give kaibo the host access
it actually needs: outbound network to your configured model APIs, and write access to
kaibo's own XDG state dir if you want persistent sessions/batch handles. The default
state db is $XDG_STATE_HOME/kaibo/state.db (else
~/.local/state/kaibo/state.db); artifact-producing tools use the media CAS under
$XDG_DATA_HOME/kaibo/cas (else ~/.local/share/kaibo/cas) when enabled. You can
also point state/CAS at separate per-client locations so Claude Code, Codex, and other
agents don't share history or generated artifacts. Codex commonly has a stricter
sandbox default than Claude Code, so its setup may need explicit network and writable
root entries where Claude Code just works.
The prompt leans on two MCP resources kaibo serves, which you can also read directly:
kaibo://config/example— the fully annotatedconfig.tomltemplate, every knob with its default and a comment, embedded in the binary.kaibo://config— the resolved runtime state: which casts and backends are live, what's gated, where each key is sourced from.
Prompts and resources are plain MCP — any client that surfaces them can use them. If
yours doesn't render configure as a command, or you're driving kaibo purely through
the CLI with no MCP client at all, both surfaces are also plain subcommands:
kaibo configure [goal] prints the same guided walkthrough (pointed at these
subcommands instead of MCP resource URIs you may not be able to reach), and kaibo example-config prints the annotated template verbatim — kaibo example-config > ~/.config/kaibo/config.toml is a starting point to edit by hand.
Each tool is gated independently via --no-<tool>. All are on by default. A server with
every tool off is refused at startup.
Ask a model outside your own family about a codebase; get a grounded, cited answer —
the synth-and-explorer investigation described above, returning the
report rather than the transcript. Describe what you did or want to know in prose — kaibo
reads the real, current source itself, so you don't need to paste a diff; optionally
seed it with context (a change summary or pasted source), which it trusts as starting
evidence while investigating for more. The answer carries a provenance footer naming the
cast and models that produced it.
When an answer surprises you, pass include_report: true to get the explorer's raw
findings back alongside it — the audit trail for how the consultation reached its
conclusion. (For deeper tracing, kaibo emits per-tool OpenTelemetry spans; see
docs/config.md.)
Args: question (required), context (optional seed), path (project dir; optional
with a default --root), cast, session_id, include_report, and per-call
explorer_model / synth_model (+ _backend) overrides.
The counterpart to consult for when you already own the context: prompt in, answer
out, no codebase access and no tools — exactly one upstream request. Use it to ask a
model outside your family a direct question (you've pasted what's needed, or it's
general). Pick the model with cast; the answer carries the same provenance footer.
Args: prompt (required), cast, and a per-call model (+ backend) override.
The same read-only investigation as consult, returning a structured, cited survey
report — what's where, how it fits together — instead of an answer. Reach for it
when you want the map.
A deep consult can run minutes; consult_submit runs the same investigation in the
background and hands back a job handle immediately, so your agent keeps working.
job_wait parks until something finishes; job_get collects the answer.
oneshot's async sibling: fan self-contained prompts to a top-tier model on the
provider's batch lane — offline, maximum thinking, and discounted against the
interactive rate. The built-in anthropic-batch and gemini-batch casts put Claude
Opus or Gemini Pro on your hardest questions; handles are durable across restarts.
A hosted OpenAI Platform backend can staff the lane too — declare lane = "batch"
on its synth slot (docs/config.example.toml), and GPT joins the same handles,
polling, and deliberate flow.
An investigation assembles a cited dossier from your repo, then a frontier (or big local) model reasons over it offline on the batch lane — depth over speed, at the batch discount.
Drive the read-only kaish shell from your agent, no model in the loop: returns exit code + stdout + stderr. For an agent that already has a shell tool, this mostly adds safety — writes and external commands are refused, so exploration leaves nothing to review: there's no diff, because nothing it runs can change your tree.
Short version: a cast is just a named team — a cheap explorer that sweeps plus a
strong synth that answers — and a call picks its team with the cast argument.
Everything below is how you wire your own.
Model diversity is the product, so configuration is first-class. kaibo works out of
the box with environment variables and built-in defaults, so a missing config file is
not an error. $XDG_CONFIG_HOME/kaibo/config.toml lets you wire your own roster. The
config has three concepts for configuring models:
- backend — a connection: which wire protocol (
anthropic|deepseek|gemini|openrouter|openai), base URL, and where its key comes from. Secrets never live in the TOML — only the name of an env var or the path to a key file.openrouteris a keyed gateway with a fixed endpoint — one key reaching every major model family, reasoning on by default via its unifiedeffortparam. - role — a job a model does:
explorer(fast sweeps) andsynth(the voice that answers). A slot that reads images carries avisionpin (seedocs/casts.md). - cast — a composition: a named team assigning models to roles. The
castcall argument selects the ensemble; the calling agent sees these names in its tool listing, so a descriptive name (local-only,deep-dive) lets it pick a team by intent without reading your config.
# ~/.config/kaibo/config.toml
[backends.gpt]
kind = "openai"
base_url = "https://api.openai.com/v1"
api_key_env = "OPENAI_API_KEY"
[backends.llama] # a local llama.cpp server, keyless
kind = "openai"
base_url = "http://localhost:8080/v1"
key_optional = true
# A cast that mixes model families — a different lineage per role, named as one team.
# (kaibo calls this a "chimera": local Qwen does the sweeping, hosted GPT writes
# the answer. Two families, not two flavors of one.)
[casts.mixed]
explorer = "llama/qwen2.5-coder-7b" # cheap, local sweeps — Qwen family
synth = { backend = "gpt", id = "gpt-5.6-sol", vision = true, effort = "high" }Some default casts ship in code so kaibo runs with zero config; your config.toml
merges over them by name. The built-ins are:
| cast | explorer | synth |
|---|---|---|
anthropic (default) |
claude-haiku-4-5 |
claude-sonnet-4-6 |
deepseek |
deepseek-v4-flash |
deepseek-v4-pro |
gemini |
gemini-flash-lite-latest |
gemini-3.5-flash |
openrouter |
qwen/qwen3.6-flash |
qwen/qwen3.7-max |
openai-local |
local Gemma (small) | local Gemma (large) |
anthropic-batch |
— | claude-opus-4-8 (batch lane) |
gemini-batch |
— | gemini-pro-latest (batch lane) |
openai-local points at your OpenAI-compatible server on localhost (llama.cpp,
Ollama, …) — you run the model server; kaibo ships no inference of its own. The
*-batch casts staff the offline lane for batch_submit and deliberate. And the
anthropic default is just the zero-config bring-up team — the whole point is a
reviewer outside your agent's family, so if your agent is a Claude, reach for
--cast deepseek or --cast gemini; if it runs on DeepSeek or GPT, go the other way.
Per-call overrides, env vars, and CLI flags all layer over the file
(per-call > CLI > env > file > built-in). The full surface — per-slot thinking
budgets, effort, sampling, system-prompt overrides, house-rules injection — is in
docs/config.md, with a commented template in
docs/config.example.toml; the design rationale behind the
backends/casts split is docs/casts.md. The live, resolved state is
always readable at the kaibo://config MCP resource.
your agent ──stdio MCP──▶ kaibo
│ consult(question or request)
▼
┌───────────────────────────┐ ┌───────────────────────────┐
│ synth (capable model) │ │ explorer (fast model) │
│ • reads files via kaish │ │ • reads files via kaish │
│ • delegates to explorer │-> │ │
│ • writes a summary │ <-│ • summarizes results │
└───────────────────────────┘ └───────────────────────────┘
│ synthesized answer (not the transcript)
▼
back to your agent
kaibo is an agent for your agent. Its models work the filesystem — and transform what they read — through kaish: it mounts your repository so they can read it, and has no write path, so nothing can change. Putting the bulk of that tool calling on a smaller, faster explorer is what keeps a consult quick and cheap — the big model spends its budget on the answer instead of the search. (oneshot() skips all of that — a single direct call when you already have the context.)
It is written in Rust on top of rmcp and
rig-core. kaish
comes as a rust crate and is embedded directly in kaibo, no exec() or repl shells involved.
Subagents spawned by your agent are the same model family. They inherit the same correlated failures — same training, same blind spots. kaibo's agents usually run on different models entirely, bringing the benefits of diversity of experience. They're also read-only by construction, so you can point them at things where you might not trust a read-write subagent to behave.
Is read-only actually guaranteed, or best-effort? It's structural, not best-effort.
kaibo compiles in only kaish's localfs axis — the subprocess, git, host, and
os-integration features are off, so exec/spawn/git/ps don't exist in the
binary — and mounts the project read-only on top. The same structure bounds the
network: kaish itself reaches nothing — no sockets, no subprocesses, no credentials;
only kaibo's provider clients dial out, to the model APIs you configure. The
docs/sandbox-probes.md runbook is how we live-test that
boundary — write/external-command/read-escape batteries run against the shipped binary.
What actually leaves my machine? Read-only covers modification; the other half is
disclosure. To answer a question, kaibo sends the provider you chose whatever its models
read while investigating: the spans they open, the shell output they get back, your
question, and any files you attach. So treat everything inside the paths you allow as
potentially provider-visible — if a tree holds .env, key material, or a customer dump,
scope kaibo away from it (--root / --allow-path) rather than counting on the model
not to look. kaibo never sends anything outside those paths, and it holds no credentials
for anything but the model APIs you configured. Provider retention and training terms
are the provider's, not kaibo's, so pick a backend whose policy you're happy with — and
note kaibo asks OpenRouter for no-collection routing by default
(docs/config.md has the specifics).
How long does a consult take? It's a multi-step investigation, not a single API
call — a deep one can run a few minutes, more with thinking on and a large repo to
sweep. kaibo emits MCP progress notifications as the explorer and synth work, so a
client that surfaces them shows live progress; whether you actually see those beats is
up to your agent's UI, which kaibo can't control. If you'd rather not block on it,
consult_submit starts the same investigation in the background and job_get picks
up the answer.
Can a runaway consultation melt my machine or my budget? Your machine, no — there
are hard ceilings. Every kaish script is capped at 30s wall-clock, 64 KiB of output, and
64 MB of in-memory scratch (a write past the cap fails loudly rather than growing
without bound), so a while true; grep -r / can't run away. Spend has limits but no
hard cap: the loops stop at a turn limit (100 and 200 by default) and each turn has an
output-token ceiling, so a confused model can't loop forever burning tokens — but
there's no dollar or total-token budget you can set, and kaibo won't stop a call partway
through for cost. If you want a harder bound today, lower the turn limits or
max_tokens. All of these are configurable in config.toml.
What providers are supported? Anthropic, DeepSeek, and Gemini natively; OpenRouter
as a keyed gateway that reaches every major model family through one key (~author/ family-latest aliases — available for the major model authors — keep the built-in
cast current as new models ship); and a generic openai kind for any
OpenAI-compatible endpoint: hosted GPT, Moonshot/Kimi, Zhipu/GLM, a local
llama.cpp / Ollama server, your org's internal gateway, ….
See Backends, Roles, and Casts.
Does it need network or credentials? The consulted models do — their provider APIs, with keys you supply. kaish itself reaches nothing: no network, no credentials. Telemetry is off by default and, when on, only opens an outbound OTLP connection you configure.
What happens if a provider is overloaded or down (a 429/529, a reset, a wedged
backend)? kaibo does not retry — a single completion is bounded by the backend's
request_timeout (default 15 min, the wall-clock for one model call) and a ≤10s
connect_timeout that fails a dead endpoint fast. When a provider fails, the consult
returns a clean tool-result error (is_error) naming the cast and the underlying
detail, rather than a protocol-level error — because a consult is an optional second
opinion, so your agent should read "the consult failed, here's why" and proceed without
it (or call again), not have its own turn fail. The message is tailored to the
failure: a transient condition (overload, rate-limit, timeout, reset) says so and
invites you to retry the call, so the calling agent can drive the retry; a non-transient
error (auth, bad request) doesn't, since a retry won't help; and a kaibo-side failure is
named as such rather than blamed on the provider. If a provider is reliably slow, raise
that backend's request_timeout_secs. (Automatic retry/backoff belongs in the HTTP layer
— tracked as an upstream rig contribution in docs/issues.md.)
What's the cost? consult spends tokens on the provider behind the chosen cast,
so the cast is what decides. A DeepSeek-class team is inexpensive enough to reach for
without thinking about it; a frontier Anthropic or OpenAI cast adds up a good deal
faster, especially with thinking on. Check your provider's current rates — they move,
and kaibo won't guess at them for you. What kaibo does do is spend as little as the
work allows: a family-mixing cast (cheap local explorer + hosted synth) keeps the
broad, token-heavy sweeping off the expensive model and pays it only for the answer,
and the agent conversations are set up to cache well on most providers. For the
strongest models, batch_submit rides the provider's discounted batch lane.
Agent contributions welcome. Every change lands through a pull request — for
transparency, so anyone using kaibo can see what changed and why — and every
user-facing change gets a CHANGELOG.md entry. See
AGENTS.md for the architecture, the PR-and-changelog workflow, and
working conventions, docs/issues.md for the live tracker of open
work, and docs/devlog.md for the shipped-work record.
Originally @tobert was combining 'kai' (会) with different words to find a name for this tool. kai + aibo sounded nice to align with gpal/dpal/cpal. It turns out kaibo (解剖) means dissection, autopsy, or postmortem examination and that was that.
MIT
consult({ "question": "We reworked job_wait to park instead of busy-polling — the return trigger is now any Warn+ record or a clean timeout. Review the implementation: does anything still return early at sub-warn levels or busy-poll? Cite spans.", "cast": "deepseek" })