Chimera — Dynamic Multi-Model Deliberation Gateway
One API call. A team of models. One answer.
Chimera takes your prompt, dispatches it to a hand-picked team of LLMs (each with a custom subtask scoped to their strengths), and an aggregator merges their outputs using dispatcher-written instructions. One dispatcher model call designs the entire deliberation at once.
Quickstart
# Install (choose one)
pip install chimera-deliberation[full] # full CLI + server in a venv
pipx install chimera-deliberation[full] # same, isolated in its own env
pip install chimera-deliberation[server] # API server only
# Configure — the repo ships NO chimera.yaml. Your live config is local-only
# (gitignored, never committed), so a fresh clone has none — create it:
chimera config init # copies chimera.yaml.example → chimera.yaml
# (from a repo checkout: cp chimera.yaml.example chimera.yaml works too)
# Add your API keys (at minimum: DEEPSEEK_API_KEY)
# Run
chimera "What is the capital of France?" # CLI deliberation
chimera run "Compare React and Vue" # explicit `run` subcommand (same as above)
chimera --quiet "Compare React and Vue" # stdout = the raw answer only
chimera --json "Compare React and Vue" # stdout = one JSON object (answer + trace)
chimera --formation simple "Compare …" # pick a formation (an unknown name exits 2)
chimera --version # print the package version
chimera serve # REST API + web UI (see Server & MCP)
chimera-mcp # MCP tools for agents
python -m chimera --version # fallback when the `chimera` script is not on PATH (fresh clone, non-activated venv, bare wheel)
Machine-readable output
--quiet and --json are mutually exclusive group flags — place them
BEFORE the subcommand. Both work for the implicit-prompt form and the
explicit run form, and in both modes stdout carries exactly ONE payload
(diagnostics and logs stay on stderr):
chimera --quiet "Summarize this changelog" # stdout: the answer + "\n"
chimera --quiet run "Summarize this changelog" # same, explicit run form
ANSWER=$(chimera --quiet "Name one HTTP status code for 'not found'")
chimera --json "Compare React and Vue" | jq .answer
chimera --json run "Compare React and Vue" | jq '.trace.total_tokens'
--json writes one object per invocation (span fields abridged here for
readability — the real trace is the complete DeliberationTrace dump):
{
"answer": "merged output from multiple models",
"trace": {
"request_id": "a71b3f2c...",
"formation": "auto",
"source": "auto",
"dispatch": {"stage_id": "dispatch", "kind": "dispatch", "model": "deepseek/deepseek-v4-flash", "latency_ms": 1234},
"stages": [{"stage_id": "worker_1", "kind": "worker", "model": "anthropic/claude-sonnet-4.6", "tokens_input": 300, "tokens_output": 1200}],
"aggregator": {"stage_id": "aggregator", "kind": "aggregator", "model": "deepseek/deepseek-v4-flash"},
"answer_stage_id": "aggregator",
"total_tokens": 12345,
"total_cost": 0.012,
"total_duration_ms": 15234,
"worker_failures": [],
"dispatch_note": null,
"dispatch_repairs": [],
"answer_degraded": false,
"answer_error": null
}
}
The trace value is the COMPLETE trace serialization (model_dump(mode="json"))
— the same object the REST API returns. Unicode answers are preserved
(ensure_ascii=False), so café stays café.
Top-level answer_degraded / answer_error mirror the REST API's degraded
handling: when the answer stage itself failed upstream (e.g. no provider
credentials — the engine's placeholder [stage ... unavailable: ...] became
the "answer"), the run exits 2, answer_degraded is true,
answer_error carries the upstream reason, and --quiet writes NOTHING to
stdout — so ANSWER=$(chimera --quiet ...) can never capture an error as the
answer. Check the exit code (or the marker) before consuming $ANSWER.
When the dispatcher had to repair a malformed auto DAG (an edges entry
pointing at a stage missing from stages, or a worker-only DAG with no
aggregator), trace.dispatch_note carries the human-readable summary and
trace.dispatch_repairs carries the same fact in machine-readable form — one
record per repair with kind, action, stage_ids, depends_on and
reason. Clean dispatches and fallbacks serialize [].
Operational warnings are never suppressed: dropped-worker and
dispatch-degradation/repair warnings (which the human mode prints next to the
panel) move to stderr under --quiet / --json, so
chimera --json "..." > out.json cannot be corrupted. Combining both flags is
a usage error (exit code 2); --verbose is ignored in the machine modes.
The flags cover deliberation output — chimera models / chimera formations
tables are unchanged (use GET /v1/models / GET /v1/formations for a
machine-readable catalog).
Open http://localhost:8765/web/ for the web UI with live DAG visualization.
Python:
# If chimera.yaml is missing, create it before calling load_config():
chimera config init # finds the template: local copy, wheel copy, or repo checkout
# Missing template? Copy chimera.yaml.example from a repo checkout or reinstall:
# error: chimera.yaml.example not found — copy the template from a chimera-v2 repo checkout (chimera.yaml.example at the repo root) or reinstall chimera-deliberation to restore the packaged template.
load_config() finds the config in this order: explicit path argument, then the CHIMERA_CONFIG env var, then a walk-up from the current directory.
import asyncio
from chimera import Engine, LiteLLMGateway, load_config
config = load_config()
engine = Engine(config, LiteLLMGateway(config))
result = asyncio.run(engine.deliberate("Explain quantum computing."))
print(result.answer) # merged output from multiple models
OpenAI-compatible:
curl -X POST http://localhost:8765/v1/chat/completions \
-H "Content-Type: application/json" \
-d '{"model": "auto", "messages": [{"role": "user", "content": "Hello"}]}'
If auth is enabled (auth.enabled: true with CHIMERA_API_KEY), every call
needs Authorization: Bearer <key> or X-API-Key: <key> — see
Authentication.
Note: exporting
CHIMERA_API_KEYalone does not enable authentication — the env var only takes effect withauth.enabled: true(or theCHIMERA_AUTH_ENABLEDtoggle). Without it the server starts fully unauthenticated (achimera servestartup warning says so).
Configuration
chimera.yaml is found in this order: an explicit path argument, then the
CHIMERA_CONFIG env var, then a walk-up from the current directory.
${VAR} environment-variable substitution
Any string in chimera.yaml may reference an environment variable as
${VAR}. Tokens are substituted at load time from the process
environment, so no API key has to be written into the YAML:
api_keys:
deepseek: "${DEEPSEEK_API_KEY}"
openrouter: "${OPENROUTER_API_KEY}"
A token is resolved from, in order:
- the process environment;
~/.hermes/.env(Hermes' dotenv file), as a fallback for variables that are not in the process environment — so cron/agent sessions resolve keys they never exported.
A variable that resolves nowhere is replaced with the empty string: the config still loads, but every call to that provider fails auth. Substitution is recursive — it applies to nested mappings and list items, not just top-level scalars.
.env auto-load
Before substitution, load_config() loads a .env file sitting next to
chimera.yaml into the process environment. It is best-effort: only
variables that are not already set are populated (the real environment keeps
precedence), and a missing file, a missing python-dotenv, or a malformed
file is tolerated (logged, never fatal). Full key resolution order:
process environment > repo .env (next to chimera.yaml) > ~/.hermes/.env
Environment-variable overrides
Common knobs can be set from the environment instead of editing the YAML (env vars win):
CHIMERA_CONFIG— config file pathCHIMERA_HOST,CHIMERA_PORT— server bind addressCHIMERA_DISPATCHER,CHIMERA_WORKER,CHIMERA_AGGREGATOR— model defaultsCHIMERA_LOG_LEVEL— observability log levelCHIMERA_AUTH_ENABLED,CHIMERA_RATE_LIMIT_ENABLED,CHIMERA_AUTO_ALLOW_ALL_CATALOG—"true"/"1"to enableDEEPSEEK_KEY/DEEPSEEK_API_KEY,OPENROUTER_KEY/OPENROUTER_API_KEY,OPENAI_*,XAI_*,ZAI_*,ANTHROPIC_*,GEMINI_*— populateapi_keys.*for that provider
First-run remedies
| Symptom | The message names | Do this |
|---|---|---|
error: No chimera.yaml found. … (one line, exit 2, no traceback) |
chimera config init |
Run chimera config init — it bootstraps chimera.yaml from the shipped chimera.yaml.example (local copy, repo checkout, or the copy inside the installed wheel, so a bare pip install works). --force overwrites an existing file. |
A provider rejects the call (401, Missing credentials, invalid api key) |
the provider and its own env var, e.g. provider 'deepseek' rejected the credentials or none were found: set DEEPSEEK_API_KEY |
Set that variable (or your providers.<name>.api_key_env) and re-run. |
The env var is resolved from the provider that actually served the model —
providers.<name>.api_key_env if set, else the canonical name in the table
above (google → GEMINI_API_KEY), else <PROVIDER>_API_KEY. LiteLLM's own
error prose is provider-blind (a DeepSeek call is served through the OpenAI
SDK and mentions OPENAI_API_KEY), so Chimera puts its accurate remedy in
front of it on every surface: CLI, REST body, MCP tool result, trace and logs.
Keyless loopback providers (lmstudio, ollama) keep their raw error text —
Chimera never names a key variable for an endpoint that needs none. A custom
base_url provider follows the same rule: a failure from the Hermes gateway
names API_SERVER_KEY, because that is what providers.hermes.api_key_env
declares — not OPENAI_API_KEY, even though the call itself is served over
the OpenAI SDK.
See docs/CONFIG.md for the full chain.
Documentation
Detailed guides live in docs/:
| Guide | Contents |
|---|---|
| docs/CONFIG.md | Full configuration reference (chimera.yaml, categories, model selection) |
| docs/USAGE.md | CLI usage and examples |
| docs/INTEGRATION.md | Integrate Chimera into your app (deployment, clients, auth, errors) |
| docs/OPENAI_API.md | OpenAI-compatible API contract |
| docs/SECURITY.md | Security model and credential handling |
| docs/EDGE_CASES.md | Edge cases and behavior notes |
| docs/FAILURE_RESILIENCE.md | Failure handling and resilience design |
| docs/RESILIENCE.md | Resilience guarantees and degradation semantics |
| docs/LOCAL_CI.md | Run the CI suite locally with act (ruff + tests, same workflow as CI) |
Full docs index, including historical artifacts and dogfood logs: docs/README.md.
Architecture
flowchart TB
subgraph Client
A[User Prompt]
end
subgraph Dispatcher["Dispatcher (1 call)"]
B[Designs DAG<br/>Picks models by category<br/>Writes custom prompts<br/>Writes merge instructions]
end
subgraph Workers["Workers (parallel)"]
C1[Worker A<br/>domain-scoped task]
C2[Worker B<br/>domain-scoped task]
C3[Worker C<br/>domain-scoped task]
end
subgraph Aggregation
D[Aggregator<br/>merges with dispatcher instructions]
end
A --> B
B --> C1
B --> C2
B --> C3
C1 --> D
C2 --> D
C3 --> D
D --> E[Final Answer]
Formation Types
flowchart LR
subgraph Simple["Simple (2 workers)"]
S1[W1] --> SA[Aggregator]
S2[W2] --> SA
end
subgraph Debate["Debate (3 workers + merge)"]
D1[W1] --> DA1[Agg 1]
D2[W2] --> DA1
D2 --> DA2[Agg 2]
D3[W3] --> DA2
DA1 --> DM[Merge]
DA2 --> DM
end
subgraph Custom["Custom DAG (client-defined)"]
C1[Researcher] --> C2[Critic]
C2 --> C3[Polisher]
C3 --> C4[Final]
end
Flow: Request to Answer
sequenceDiagram
participant Client
participant Engine
participant Dispatcher
participant Workers
participant Aggregator
Client->>Engine: POST /v1/chat/completions
Engine->>Dispatcher: Design formation
Dispatcher->>Dispatcher: Pick models by category weights
Dispatcher->>Dispatcher: Write per-worker prompts + merge instructions
Dispatcher-->>Engine: DAG + prompts + instructions
par Workers (parallel)
Engine->>Workers: Worker A (custom prompt)
Engine->>Workers: Worker B (custom prompt)
Engine->>Workers: Worker C (custom prompt)
Workers-->>Engine: responses
end
Engine->>Aggregator: Merge with dispatcher instructions
Aggregator-->>Engine: Final answer
Engine-->>Client: JSON response + trace
Server & MCP
chimera serve needs the [server] extra (fastapi/uvicorn) — it is part
of the [full] extra too. On a bare pip install chimera-deliberation the
command does not crash with a traceback: it exits 2 with the exact remedy
(pip install chimera-deliberation[server], or [full] for the web UI and MCP
extras). chimera serve --help works either way.
chimera serve starts the REST API plus the web UI:
- http://localhost:8765/v1/chat/completions — OpenAI-compatible endpoint
- http://localhost:8765/docs — OpenAPI docs
- http://localhost:8765/web/ — web UI with live DAG visualization
Health probes (all return JSON):
- http://localhost:8765/health — alias of the liveness probe (always 200 when the process is alive)
- http://localhost:8765/v1/health — health check (
healthy/degraded); see below - http://localhost:8765/v1/health/ready — readiness probe (200 when ≥1 provider reachable, 503 otherwise)
- http://localhost:8765/v1/health/live — liveness probe (always 200 when alive)
Agent discovery (A2A-style, keyless like the health probes):
http://localhost:8765/.well-known/agent-card.json
returns the server's name, description, version, base URL, capabilities, and
the MCP tools (chimera_deliberate, chimera_formations, chimera_models)
as a skills[] list.
/v1/health status semantics. healthy means every configured provider's
live probe succeeded. degraded means at least one provider probe failed —
missing credentials, an auth error (401/403), a quota/billing failure
(429, insufficient balance, "no resource package"), an API error, a probe
timeout, or an exception inside the check itself. The endpoint always answers
HTTP 200 and reports the verdict in the status field (plus a details
object with per-provider healthy / error / model_tested), so monitoring
must read status, not the HTTP code. Any probe failure — including "all
providers failed" — reports degraded; an unhealthy status value is not
emitted.
Machine-readable degraded reason (DF-CHIMERA-V2-14). status alone tells
you that something is wrong, not what:
- the top-level
unhealthy_providerslist names the providers whose probe did not succeed, sorted. It is present in every response and is[]exactly whenstatusishealthy, so a client can branch on it instead of walkingdetails.providersand string-matchingerror. - every failed entry under
details.providers.<name>carries anerror_class:missing_credentials(no resolvable key — no live call was made),slow(the probe never answered insideserver.health_timeout_s— see below),timeout(a probe-side timeout: the call itself failed with a timeout),auth(401/403or auth prose),quota(429, insufficient balance, "no resource package"), orapi(anything else). A healthy provider has noerror_class; treat an unrecognized value as opaque. - top-level
slow_providersnames the providers whose probe never answered inside the budget. Such a provider is UNMEASURED, not proven broken, so it does NOT makestatusdegradedand is NOT inunhealthy_providers—slow_providers: ["hermes"]withstatus: "healthy"is the honest reading for a gateway that is alive but slower than the probe budget (DF-CHIMERA-V2-27). A provider that fails a different way (connection refused, 5xx, auth, quota) still degradesstatusexactly as before. - if the probe itself raises, the response is still
degradedwith every configured provider named inunhealthy_providers— none of them was proven healthy — and the underlying reason indetails.error.
A probe is one upstream attempt per model (DF-CHIMERA-V2-16). The health
probe does not use the retry ladder a normal completion uses: it makes
exactly one call per model and reports whatever the provider answers. A
provider that replies with a fast 429/401 is therefore reported as its real
class (quota / auth) carrying the provider's own message — a quota reset
timestamp, say — instead of being retried with backoff until the shared
server.health_timeout_s budget expires and the verdict turns into a
fabricated timeout. Retrying an answer that is already final buys nothing and
destroys the signal; the non-probe retry policy (config.retry) is unchanged.
Both are additive: status, details.config_loaded,
details.models_configured, details.providers_configured,
details.providers_discovered, details.commit,
and the per-provider healthy / error / model_tested / note fields keep
their names and meanings, and the HTTP status stays 200. GET /v1/health/ready carries the same unhealthy_providers list in its 200 body.
Configured vs discovered providers (CH-GAP-053). details.providers_configured
counts only the providers explicitly declared in chimera.yaml — not the
full provider map. Provider auto-discovery (on by default, provider_discovery: true) merges extra providers from models.dev into config.providers at load
time, and providers_configured used to silently include them. The
discovery-added names are now reported separately in the additive
details.providers_discovered list (sorted, names only — no credentials). The
per-provider map under details.providers still lists every provider, both
declared and discovered; a config built programmatically (no YAML load) keeps
the old behavior where everything in the map counts as configured and
providers_discovered is [].
The provider probe costs money. Each /v1/health (and /v1/health/ready)
call runs a real completion per configured provider — gateway.complete(model, "ping", max_tokens=1) — so it consumes a (small) number of tokens on every
provider it can reach, and takes real latency. Details worth knowing before you
poll it in a tight loop:
- providers with no resolvable credentials are reported immediately
(
missing-credentials) with no live call; - on a non-timeout failure up to 3 models of that provider are tried before it is marked unhealthy (one blocked model does not condemn the provider); a timeout is terminal for that provider after one model;
- probes run concurrently, bounded by
server.health_timeout_s(default10.0s); a probe still pending at that deadline gets one boundedserver.health_probe_grace_swindow (default1.0s, DF-CHIMERA-V2-17) to land and report its real verdict — set it to0for cancel-at-deadline. A probe still outstanding after the grace window is reported slow —error_class: "slow"with the measuredlatency_s— and named inslow_providers(see above): it does NOT degradestatusand is NOT inunhealthy_providers, so slow tail latency no longer reads as a dead provider (DF-CHIMERA-V2-27). The remedy isproviders.<name>.health_probe: false(skip the probe entirely) or raisingserver.health_timeout_s. - each of those probes is one upstream attempt per model — the retry ladder
is not used (DF-CHIMERA-V2-16), so a provider that answers with a fast
429/401is reported asquota/authwith the provider's own message (including a quota reset time) rather than retried into the timeout budget.
chimera-mcp runs the MCP server over stdio so AI agents (Hermes, Claude
Code, etc.) can call Chimera directly. Both read the same chimera.yaml
configured in Quickstart.
Live smoke test
/v1/health reporting healthy does not prove a deliberation works (billing,
auth and prompt-format regressions only surface on a real call). Run the live
smoke test after a deploy:
python scripts/smoke_live.py --base-url http://localhost:8765 # default base URL
python scripts/smoke_live.py --base-url http://myhost:8765 --formation auto
CHIMERA_API_KEY=... python scripts/smoke_live.py --base-url http://myhost:8765 # if auth is enabled
Note: the CHIMERA_API_KEY on that line only authenticates the smoke-test
request — exporting it does not enable auth on the server. The env var only
takes effect with auth.enabled: true (or the CHIMERA_AUTH_ENABLED
toggle); see Authentication.
The flag is --base-url (there is no --port); CHIMERA_BASE_URL is the env
default. Other flags: --formation (default simple), --prompt,
--api-key, --timeout, --expected-commit, --allow-stale. The script
checks liveness + running commit, classifies the deployment gap against an
EXPECTED commit (see below), probes /v1/health, then POSTs a real
/v1/deliberate and prints the merged answer. Exit 0 = merged answer
received AND the deployment is not stale, 1 = failure with an actionable
message (deliberation failure OR a stale deployment), 2 = usage/config
error. Stdlib-only.
Deployment parity (DF-CHIMERA-V2-60). The running commit is compared
against an expected commit, never silently against the checkout HEAD (a
mid-judge/worker checkout HEAD is arbitrary and would fabricate stale verdicts
from unmerged WIP). The anchor: --expected-commit <ref> when given, else
origin/main, else the checkout HEAD as an EXPLICIT FALLBACK (printed as
such). If nothing resolves, parity is UNVERIFIABLE — never an empty diff,
never CODE-CURRENT. A STALE verdict (a non-empty diff over
src/ scripts/ tests/ pyproject.toml) fails the run by default: no
SMOKE PASS, exit 1. --allow-stale is the documented opt-in for
intentionally stale deployments; the pass is attributed to the flag.
The provider-health block is class-aware (DF-CHIMERA-V2-4), because a fresh install and a real degradation used to print the same words:
providers: <healthy>/<total> healthy— counted from the payload's provider map, never fromproviders_configured(that counts providers explicitly declared in the config, excluding auto-discovery additions — seeproviders_discoveredfor those — and says nothing about whether any of them answered);INFO: providers without a configured API key (expected on a fresh install): …— the providers whoseerror_classismissing_credentials. Expected on a fresh install, so it is information rather than a warning: the deliberation below is the real proof that the deployment works;WARNING: providers reported unhealthy by /v1/health: <name> [<class>] …— every other class (timeout,auth,quota,api,unknown), naming each provider with its class and surfacing the provider's own message (a quota reset time, for example);INFO: providers slower than the probe budget (not a failure — the probe never landed; a real call may still work): <name> [slow]: …— the providers the server classifiedslow(DF-CHIMERA-V2-27), with the measured wait. Information, not a warning: catching a standing slow condition is useful, but it is not a degradation the operator must act on, and the server does not report it as one either;INFO: providers whose live health probe is disabled (health_probe: false): …— the providers explicitly opted out of probing. Nothing was measured, so the line exists to keep the omission visible.
None of these lines changes the exit code: the provider block never fails the run — only the deliberation decides pass/fail.
Model Selection
The dispatcher picks models using category-weighted scoring:
| Category | What it measures |
|---|---|
code |
Programming, debugging, software engineering |
analysis |
Data analysis, research, evaluation |
reasoning |
Logic, math, complex problem-solving |
design |
Creative work, UX, content generation |
audit |
Fact-checking, safety, correctness review |
Each model in the catalog has a percent score (0–100) per category, on the same
scale as the chimera.yaml.fleet operator reference catalog and the
categories map in
GET /v1/models. A catalog written on the historical 0.0–1.0 scale is accepted
and rescaled ×100 at load (with a warning); a score outside 0–100 or a
non-numeric score is rejected at load. The dispatcher matches
task domains to model strengths. You can override any model choice per request:
{
"model": "auto",
"messages": [{"role": "user", "content": "..."}],
"dispatcher_model": "deepseek/deepseek-v4-flash",
"aggregator_model": "zai-coding-plan/glm-5.2",
"worker_model": "deepseek/deepseek-v4-pro",
"allowed_models": ["deepseek/deepseek-v4-pro", "zai-coding-plan/glm-5.2"],
"stage_models": {"worker_1": "anthropic/claude-sonnet-4.6"}
}
dispatcher_model— model for the planning/dispatch call itself.aggregator_model— forces every aggregator/merge/audit stage.worker_model— forces every worker stage.stage_models— per-stage overrides; these win over the global ones above.- Config locks (
defaults.lock_aggregator: true/defaults.lock_dispatcher: true) force the configured default for that role and discard the request override. A discarded override is never silent: it is recorded in the trace'sdispatch_note(e.g.override discarded: aggregator_model='X' ignored (lock_aggregator=true)).
Custom DAGs
Send your own formation structure at request time. The example below is the
complete request body for POST /v1/chat/completions with
"model": "custom" — that endpoint is the custom-DAG contract
(POST /v1/deliberate does not accept formation custom):
{
"model": "custom",
"allow_custom_dag": true,
"dag": {
"stages": [
{"id": "researcher", "kind": "worker", "model": "anthropic/claude-sonnet-4.6"},
{"id": "critic", "kind": "aggregator", "model": "zai-coding-plan/glm-5.2", "depends_on": ["researcher"]},
{"id": "polisher", "kind": "worker", "model": "deepseek/deepseek-v4-pro", "depends_on": ["critic"]},
{"id": "final", "kind": "aggregator", "model": "anthropic/claude-sonnet-4.6", "depends_on": ["polisher"]}
],
"edges": [["researcher","critic"], ["critic","polisher"], ["polisher","final"]]
},
"messages": [{"role": "user", "content": "..."}]
}
The dispatcher writes custom prompts for each stage but uses YOUR structure exactly.
Interfaces
| Interface | Endpoint | Use |
|---|---|---|
| REST API | POST /v1/chat/completions |
OpenAI-compatible drop-in |
| REST API | POST /v1/deliberate |
Full control (DAG, overrides, trace) |
| REST API | GET /v1/models |
OpenAI-shaped model list (data) + chimera catalog map |
| REST API | GET /v1/formations |
Available formation presets |
| CLI | chimera run |
Command-line usage (add --quiet / --json for machine-readable output, --version for the package version) |
| MCP | chimera_deliberate |
Run a deliberation from an agent |
| MCP | chimera_formations |
List formation presets |
| MCP | chimera_models |
List models with weights |
When auth is enabled (CHIMERA_API_KEY), pass Authorization: Bearer <key>
or X-API-Key: <key> — see
docs/INTEGRATION.md.
As everywhere, the CHIMERA_API_KEY env var only takes effect with
auth.enabled: true (or the CHIMERA_AUTH_ENABLED toggle) — exporting it
alone does not enable authentication.
POST /v1/deliberate also accepts an optional idempotency_key request-body
field (a repeat call with the same key is replayed, not re-run) — see
docs/OPENAI_API.md. The same contract is
honored on POST /v1/chat/completions (replay of the cached chat-completions
envelope with X-Idempotent-Replay: true); keys are namespaced per surface,
so the same literal key on the two endpoints never cross-replays
(DF-CHIMERA-V2-84).
Response Trace
Every deliberation returns a full trace:
request_id: a71b3f2c...
formation: auto
source: auto ← not "fallback" — dispatcher designed it
total_tokens: 12345
total_cost: $0.012
total_duration_ms: 15234
dispatch: V4 Flash (1,234ms, 800+420 tok)
workers:
worker_rust: Claude Sonnet 4 (4,500ms, 300+1,200 tok)
worker_go: Kimi K2.7 (8,200ms, 250+800 tok)
aggregator: V4 Flash (2,100ms, 3,000+500 tok)
Providers
Chimera uses LiteLLM under the hood. Supported providers:
| Provider | Direct API | Via OpenRouter | Models |
|---|---|---|---|
| DeepSeek | ✅ | ✅ | v4-flash, v4-pro |
| Anthropic | — | ✅ | Sonnet 4, Opus 4.7/4.8, Haiku 4.5 |
| OpenAI | — | ✅ | GPT-5.5, GPT-5.5 Pro, GPT-5.6 (Luna, Sol, Terra variants) |
| xAI | — | ✅ | Grok 4.20 |
| — | ✅ | Gemini 3.5 Flash, 3.1 Pro, 2.5 Flash | |
| Z.AI | ✅ (direct) | ✅ | GLM-5.2 |
| MoonshotAI | — | ✅ | Kimi K2.7 Code, K2.6 |
| MiniMax | — | ✅ | M3 |
| Hermes gateway | ✅ (local) | — | any model the running Hermes serves, addressed as hermes/<model> |
| 9router fleet gateway | ✅ (tailnet) | — | 323 models across 16 upstream prefixes (measured 2026-10-19), addressed as router9/<upstream id> |
Custom OpenAI-compatible endpoints
Any provider Chimera does not route natively is called through its own
base_url, over the OpenAI-compatible SDK, using the provider's resolved key.
Exactly one leading <provider>/ segment is stripped before the request goes
out, so hermes/glm-5.3-flash reaches the endpoint as the bare
glm-5.3-flash — the same convention the zai provider uses.
Endpoints that serve namespaced model ids of their own keep the inner
slashes: the catalog id router9/ds/deepseek-v4-flash reaches 9router as
ds/deepseek-v4-flash (not deepseek-v4-flash), and
router9/openrouter/x-ai/grok-4.6 as openrouter/x-ai/grok-4.6.
That is how the local Hermes gateway (:8642, OpenAI-compatible) plugs in
as a first-class provider:
providers:
hermes:
base_url: http://127.0.0.1:8642/v1
api_key_env: API_SERVER_KEY # the var name in ~/.hermes/.env — never inline the key
models:
hermes/glm-5.3-flash:
provider: hermes
cost_tier: budget
Two things to plan for with the Hermes gateway: it adds its own large system
prompt to every request (~41k tokens of input), so the effective context budget
is the model's window minus that; and /v1/health probes providers with a real
completion under server.health_timeout_s (default 10.0s), so raise that
bound if your gateway is slower than that on a 1-token call. (The 9router
gateway adds no prompt of its own — its lanes are the upstream providers'
own endpoints.)
…and the same seam carries the 9router fleet gateway — one
OpenAI-compatible endpoint (http://master001:20128/v1, reachable on the
tailnet as master001) in front of a few hundred models spanning many
upstream prefixes (ds/, mmx/, minimax/, kimi/, openrouter/,
xai/, ollama/, … — see the 9router row above for the measured count):
providers:
router9:
base_url: http://master001:20128/v1
api_key_env: ROUTER9_API_KEY # the var name in ~/.hermes/.env — never inline the key
models:
router9/ds/deepseek-v4-flash:
provider: router9
cost_tier: budget
Each upstream lane is an account of its own, so an exhausted lane answers
429/403 for every model behind it while the gateway itself is healthy —
a provider-side quota condition, not a Chimera defect. Point a formation at
another prefix's model rather than re-dispatching at the same one.
See docs/CONFIG.md for the full contract.
Contributing
Thanks to everyone who has filed an issue, reported a provider regression, or sent a patch — Chimera gets better because of it.
The full guide lives in CONTRIBUTING.md. In short:
- Fork the repository
- Create a feature branch:
git checkout -b feature/my-change - Make your changes, and add tests where they apply
- Run the test suite:
.venv/bin/python -m pytest -x --tb=short -q - Run the linter:
ruff check . - Open a Pull Request against
mainwith a clear description of the change
Every PR is reviewed, and CI must pass before it can merge: the workflow runs
ruff plus the unit-test matrix on Python 3.11, 3.12 and 3.13. Commit messages
follow the repo convention (feat:, fix:, docs:, …), also described in
CONTRIBUTING.md. By participating, you agree to abide by
our Code of Conduct.
Metadata
Release files for chimera-deliberation 0.2.8
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| chimera_deliberation-0.2.8.tar.gz | 3.6 MB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| chimera_deliberation-0.2.8-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 4.8 MB
Release files / chimera_deliberation-0.2.8.tar.gz
| Download URL | chimera_deliberation-0.2.8.tar.gz |
|---|---|
| Size | 3.6 MB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
9e7801a0d291ac856b7c12bc4406f630c9aef3e45bc31917d4f4e5b60e8a1456
|
|
BLAKE2b-256 checksum How to use checksums |
6c197281da800d8177524af84c6e5e388de939205c5ccba7c1829eff7347dd48
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.17
|
Release files / chimera_deliberation-0.2.8-py3-none-any.whl
| Download URL | chimera_deliberation-0.2.8-py3-none-any.whl |
|---|---|
| Size | 1.2 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
33d0d5b9bef7c0e240e1310054ff15340c07f67c7fcf824c8c006d7603ed90d4
|
|
BLAKE2b-256 checksum How to use checksums |
00c1ba02d9282ceadebeb6fb18a19c732ad64db9332cfd2cd0228a3f1c666730
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.17
|
Security for chimera-deliberation 0.2.8
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.