Configure models
Ninja is multi-role. Different agents can use different base URLs, API keys, and model IDs.
Roles
| Role | Purpose | Default settings source |
|---|---|---|
| Planner (orchestrator) | Plans, delegates tools, main chat | OPENAI_* / openai_* |
| Coder | Implements changes | NINJA_SUBAGENT_*, else planner |
| Reviewer | Reviews coder work | subagent defaults or subagent_roles.reviewer |
| Final review | Product / requirements pass | subagent_roles.final_review |
| Researcher | Web research tools only | subagent defaults |
Environment variables
# Planner
export OPENAI_BASE_URL="https://api.example.com/v1"
export OPENAI_API_KEY="sk-..."
export OPENAI_MODEL="planner-model"
# Default for all subagents (unless overridden per role)
export NINJA_SUBAGENT_BASE_URL="http://localhost:8080"
export NINJA_SUBAGENT_API_KEY="sk-local" # optional
export NINJA_SUBAGENT_MODEL="coder-model"
Base URL tips
Use an OpenAI-compatible base URL (same idea as popular OpenAI SDKs):
- Bare host (e.g.
http://localhost:8080) — Ninja adds the usual/v1/...chat path - Already ending in
/v1— left as-is, then chat path is appended
Local servers often need no API key. Cloud providers need OPENAI_API_KEY (or the matching role key).
Per-role overrides (config.json)
{
"openai_base_url": "https://api.example.com/v1",
"openai_api_key": "REPLACE_PLANNER_KEY",
"openai_model": "planner-model",
"ninja_subagent_base_url": "http://localhost:8080",
"ninja_subagent_model": "coder-model",
"subagent_roles": {
"coder": {
"base_url": "http://localhost:8080",
"model": "coder-model"
},
"reviewer": {
"base_url": "https://api.example.com/v1",
"api_key": "REPLACE_REVIEWER_KEY",
"model": "review-model"
},
"final_review": {
"base_url": "https://api.example.com/v1",
"api_key": "REPLACE_FINAL_KEY",
"model": "review-model"
}
}
}
Useful role fields
| Field | Meaning |
|---|---|
base_url |
API base |
api_key |
Bearer token (optional for local) |
model |
Model id |
max_turns |
Cap how long a subagent may run |
context_window |
Hint for context sizing (0 = let the provider decide) |
persistent |
Keep that role’s agent available across steps when true |
Precedence: role override → NINJA_SUBAGENT_* → OPENAI_*.
Interactive /model (TUI)
| Command | Effect | |
|---|---|---|
/model |
Interactive picker | |
/model list |
Text list | |
| `/model set <target> <id\ | label>` | Override for this session (optionally save to config) |
Multi-model plan panel
Optional plan_panel entries power multi-model planning when you use the plan-debate skill. Each model drafts from a different angle, then they critique each other; the planner synthesizes one plan.
Add this to config.json (alongside your normal openai_* / subagent settings):
{
"plan_panel": [
{
"name": "risk-lens",
"base_url": "https://api.example.com/v1",
"api_key": "REPLACE_PANEL_KEY_1",
"model": "strong-reasoner-model",
"lens": "risk-first"
},
{
"name": "simple-lens",
"base_url": "https://api.example.com/v1",
"api_key": "REPLACE_PANEL_KEY_2",
"model": "another-strong-model",
"lens": "simplicity-first"
}
]
}
| Field | Required | Meaning |
|---|---|---|
name |
yes | Label for this panel seat (shown in UI/catalog) |
base_url |
yes | OpenAI-compatible API base for that model |
api_key |
yes* | Bearer key (*optional only if that endpoint needs none) |
model |
yes | Model id |
lens |
no | Planning bias for that seat (see below) |
Lenses
A lens is a short planning bias for one panel model in Round 1. It is not a model id — it only steers how that seat drafts (risk vs simplicity, etc.). Different lenses keep strong models from writing the same plan.
Five built-in defaults (used when lens is omitted — assigned round-robin, then wraps):
| Lens | Emphasis |
|---|---|
risk-first |
Safety, failure modes, edge cases, rollback |
simplicity-first |
Minimal surface, fewer moving parts |
test-first |
Verification, acceptance checks, testability |
performance-first |
Latency, throughput, resource cost |
maintainability-first |
Clarity, structure, long-term change cost |
You can set a custom string (e.g. cost-first); it is passed through as free text. The five names above are the recommended defaults, not a hard allow-list.
Notes:
- You need at least two working panel models (or Ninja falls back to single-model planning).
- Panel models can reuse the same gateway as the planner with different
modelnames, or call different providers. - After config is saved, ask in chat, e.g.
plan the auth refactor with the plan-debate skill.
First-run config.example.json on your machine includes a filled plan_panel block you can copy.
Recommended layouts
| Goal | Setup |
|---|---|
| All local | Only OPENAI_* → localhost |
| Smart plan, fast code | Cloud planner + local NINJA_SUBAGENT_* |
| Stronger review | Different model for reviewer / final_review |
| One gateway (LiteLLM, etc.) | Same base_url, different model names |