onit setup covers the common cases; this is the full reference for
~/.onit/config.yaml, environment variables, and project config files.
Everything a local Ollama setup needs, hand-written into ~/.onit/config.yaml:
serving:
host: http://localhost:11434/v1
model: qwen3:30b
max_context_tokens: 131072 # set it when the server doesn't report its own
# (unreported and unset falls back to 1000000)
think: true # reasoning mode; off by default
max_tokens: 32768
theme: white # or "dark"
timeout: 600
data_path: ~ # working directory (default: ~/sandbox)The rest of this page is the full set of keys.
onit setup is the recommended way to configure OnIt. It stores:
- Settings in
~/.onit/config.yaml(LLM endpoint, theme, ports, timeout) - Secrets in your OS keychain (API keys, bot tokens)
You can also use environment variables or a project-level YAML config:
# Environment variables
export ONIT_HOST=https://openrouter.ai/api/v1
export OPENROUTER_API_KEY=sk-or-v1-...
# Or a custom config file
onit --config configs/default.yamlPriority order: CLI flags > environment variables > ~/.onit/config.yaml > project config file.
serving:
host: https://openrouter.ai/api/v1
# Keys are per endpoint. 'onit setup' asks for one alongside each endpoint
# URL and stores it in the OS keychain, which keeps it out of this file —
# prefer that. Written here it is read first, ahead of the stored one:
# host_key: sk-or-v1-your-key-here # ('api_key' in an endpoints list)
# model: auto-detected from endpoint. Set explicitly for OpenRouter:
# model: google/gemini-2.5-pro
think: true # reasoning before answering; --no-think turns it off
# Per-UI overrides. A web run inherits everything above, exactly as the
# terminal does; anything named here applies to the browser only.
# web:
# think: false # reasoning off in the browser alone
# temperature: 0.7 # and different sampling, if you want it
max_tokens: 32768 # max output tokens per response (shipped default;
# clamped to whatever is left of the context window
# per request). 32768 fits any single answer and keeps
# the context-compaction threshold healthy (0.70 on a
# 128k window, 0.825 on a 256k one); raise it only for
# genuinely huge single responses.
# Both budgets are also CLI flags, which accept a k/M suffix:
# onit --max-tokens 1M --max-context-tokens 1M
# Sampling parameters (all optional — sensible defaults apply):
# temperature: 1.0
# top_p: 0.95
# top_k: 20
# presence_penalty: 1.5
# repetition_penalty: 1.0
# Additional model servers (any mix of vLLM / local Ollama or MLX /
# OpenRouter / Ollama cloud) go in an endpoints list — see MODEL_SERVING.md.
# load_balancer: sticky # or: round_robin, random, least_busy
# Ollama endpoints (cloud or local) are fallback-only: while any
# vLLM/OpenRouter endpoint is healthy they stay out of rotation. Set false
# to load-balance across them equally:
# ollama_fallback_only: true
verbose: false
timeout: 600
# Text UI only: fold each intermediate AI turn (the narration a model streams
# before a tool call) into a one-line dim step marker, so the terminal shows
# your question, the steps, then the final answer. Default: false (folding on).
# Set true to see every intermediate turn in full, as before.
# show_intermediate: false
# Stream tokens to the UI as they are generated (default: true).
# stream: true
web_port: 9000
theme: white # or "dark"
topic: ~ # default topic context, e.g. "machine learning"
template_path: ~ # custom prompt template YAML
data_path: ~ # working directory for file operations (default: ~/sandbox)
mcp:
servers:
- name: PromptsMCPServer
transport: stdio
module: onit.mcp.prompts.prompts
enabled: true
- name: ToolsLocalMCPServer # per-user: bash, files, document search
transport: stdio
module: tasks.tools
profile: local
enabled: true
- name: ToolsNetMCPServer # stateless: web search, weather
transport: stdio
module: tasks.tools
profile: net
enabled: trueEvery default server is a subprocess of the OnIt that started it, spoken to
over a pipe. There is no port to find, to collide with, or to expose: each
server runs as you, exits with you, and no other account on the machine can
reach it — which is also what lets several people run OnIt on one machine at
the same time. profile picks which half of the toolset a tools server
registers: local holds every tool that touches the session working
directory, net the stateless remote calls.
An MCP server that already runs somewhere else — not started by OnIt — is
listed with external: true. OnIt neither starts it nor re-ports it; tools are
discovered from the URL as written:
mcp:
servers:
- name: MyRemoteServer
url: http://gpu-box:8080/sse
external: true
enabled: trueReasoning is on by default, in the terminal and in the browser. The models
worth running locally reason before they answer, and a reasoning model told not
to behaves like a smaller one — the deliberation is the capability, not an
extra. It costs latency, and --no-think (or serving.think: false) is how
you trade that back:
onit --no-think # one run, no reasoningserving:
think: false # every run, no reasoningThe two UIs no longer default differently. There used to be a web-only set of
values — reasoning off, and the instruct-mode sampling to match — on the theory
that a browser turn is one iteration with no tool loop behind it, so the
reasoning is the wait. With reasoning on by default everywhere that trade has
no second side, and the instruct sampling would be the wrong half of a pair left
on a model that thinks. A browser run now inherits serving: exactly as the
terminal does.
serving.web remains as a per-UI override, which is a different thing from
a default. Anything named there applies to the web run only, so one config can
still serve the two UIs differently:
serving:
think: true # both UIs reason…
temperature: 0.6 # …at thinking-mode sampling
web:
think: false # …except the browser, which does notA key you name under serving.web is a stated preference and is applied as
written; keys you leave out are inherited from serving: unchanged. The block
is consumed while the config is resolved and never reaches the model server.
An explicit --think / --no-think on the command line wins over both, for one
run: onit --no-think serve web starts a browser session that does not reason,
without editing anything.
think_tool_turns is a separate, narrower switch, and it applies to both UIs:
with it false (the default), a thinking model reasons on the opening turn —
where the approach is decided — and not before each tool call after it. A loop
of N tool calls otherwise pays for N full reasoning passes.
An answer is written from whatever is left in the model's context by the time it writes — tool results from ten turns ago, already decayed to a summary, next to whatever the weights remember. That is where a figure drifts by a digit: the search result was right and the sentence quoting it is not.
So OnIt checks the answer after it is written — in two stages, because being careful and being quick want opposite things.
The stage you wait for is bounded at two seconds and usually costs nothing at all. Every figure in the answer that appears verbatim in a document the run read, or on a source trusted about that subject, is cleared by string comparison before any model is involved — that is where a drifting digit shows up, and it does not need an LLM to see. Whatever is left gets one small check against the gathered evidence, with no lookups behind it. Where the evidence contradicts the answer, the finding is flagged in a line underneath:
The 2019 filing puts revenue at 4.2M …
Correction after fact-check: revenue was 3.1M
Flagged rather than rewritten, because rewriting means generating the whole answer a second time, at the same speed it was written the first time. That is what used to double a turn.
The stage that runs behind you starts once the answer is yours and has no clock on it. It can make read-only lookups for claims the run gathered no evidence about (search, file reads — never a write or a shell command), and it does rewrite the answer when something is wrong. If it finds something, the answer is corrected where it stands and a line says what changed — in the browser, in place; in the terminal, at the top of your next turn, since a terminal that writes under a half-typed line is worse than one that waits. Ask anything else and the check is cancelled outright: a correction to an answer you have moved past is not worth the interruption. It only runs where there is somewhere to show it, so one-shot callers never start one.
Measured on Qwen3.6-27B, against 5–15s to write the answer itself:
| you wait | |
|---|---|
| every figure came from a document you gave it | 0.00s — no call at all |
| clean check against gathered evidence | 0.2–1.5s |
| two wrong figures found and flagged | 0.8–1.6s |
| endpoint slow or busy | 2.0s ceiling, then the draft stands |
The check runs with the model's chain of thought switched off: comparing a sentence against a source is recognition, not deliberation. The same verdicts that take 0.19s that way take 15–21s with a hybrid model left to reason its way through them, so a server whose chat template has no such switch is detected — by a genuine refusal of the parameter, never by a timeout or a bad gateway — and given room to think instead.
Answers with nothing checkable in them ("I've saved the file, let me know if you'd like it formatted differently") skip the check, and so do runs that gathered no evidence to check against. A check that fails, times out, or comes back unreadable leaves the answer exactly as written — it can correct an answer, never lose one. A claim the evidence simply does not cover is left alone rather than doubted, unless the background stage can look it up.
serving:
verify_answers: true # false hands back the answer unchecked
verify_timeout_s: 2 # the ceiling on what you wait for
verify_background: true # keep checking behind the answer
verify_max_tool_turns: 2 # lookups the background stage may make
verify_trusted_domains: # added to the built-in list
- "docs.internal.example.com"The per-run log line reports it alongside the rest of the timing:
… | fact-check 2.4s (1 claim(s) corrected).