Skip to content

Latest commit

 

History

History
269 lines (218 loc) · 10.9 KB

CONFIGURATION.md

File metadata and controls

269 lines (218 loc) · 10.9 KB

Configuration

onit setup covers the common cases; this is the full reference for ~/.onit/config.yaml, environment variables, and project config files.

A minimal config

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.

Where settings come from

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.yaml

Priority order: CLI flags > environment variables > ~/.onit/config.yaml > project config file.

Example config (configs/default.yaml)

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: true

Every 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: true

Thinking, and turning it off

Reasoning 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 reasoning
serving:
  think: false        # every run, no reasoning

The 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 not

A 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.

Fact-checking the answer

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).