theoria
v2.3.1
Published
Local-first research and decision runs on the AI you already have — Claude, ChatGPT, Gemini, any OpenAI-compatible endpoint or a local model: a brief in, a cited, verified report out.
Readme
Theoria
A local-first research and decision app. Give it a brief, answer at most four questions, confirm the plan and budget, and it runs on its own to a cited, verified report — a decision record or a research report — that you review in a loopback web page and export for a person (TXT, Markdown, PDF) or for an AI (a JSON bundle).
Contributing? See CONTRIBUTING.md. To report a vulnerability, see SECURITY.md.
It runs on whatever AI you already have. Out of the box that is a Claude subscription through the Claude Agent
SDK; a role can be sent to a ChatGPT subscription (the codex CLI), a Google one (the gemini CLI), or any
OpenAI-compatible endpoint — OpenAI, OpenRouter, or a model running locally under Ollama or llama.cpp, which
costs nothing. Each of the six model roles, including the researcher, can use any provider and has its own model
and effort level — see Settings. A key is read from your environment only when a role or Brave Search names its variable.
Install
Needs Node 24 or newer and, by default, a logged-in Claude Code
(claude auth login). Settings can name another provider instead.
npm install -g theoria
theoria install # add Theoria to the desktop launcher
theoria uninstall # remove the launcher and iconOn Omarchy there is also a bar plugin — the running run's stage and spend in the top bar, a panel with the recent runs and a button that opens Theoria — in its own repository, off until you turn it on:
omarchy plugin add https://github.com/broken-branch/omarchy-theoria.git
omarchy plugin enable io.github.broken-branch.theoriaThen:
theoria doctorprints one line per requirement — ok:, warn: or fix: with the command that fixes it — and exits 1 while anything
needs fixing. It checks Node, Playwright's Chromium (the report page and the PDF export render in it; the fix
line is the one-off download), whether the desktop launcher is installed, that each provider named by the settings
is available and authenticated for the six effective roles, and that the data directory is writable.
Use
theoria open # starts the loopback server and opens it in your browser
theoria open --app # the same page as a window of its own (Omarchy's app mode when available)
theoria open --port N # a fixed port instead of a random one
theoria run --brief "Should a small team adopt pnpm workspaces?" --mode research --tier standard
theoria runs # every run: id, status, when, the brief's first line
theoria resume <id> # finish a run whose process died, from the last stage it savedtheoria open prints the one-time page URL it opens; the random port is visible, but the launch token never appears
in a process argument or browser URL. Opening Theoria again while a run is going returns to that run instead of
opening a second server; a run
waiting for clarification or plan confirmation reopens on that intake flow. A brief query parameter prefills the
New run brief without submitting it.
THEORIA_NO_BROWSER=1 prints the URL without opening anything. theoria run is the same engine on the
command line, asking its clarifying questions on stdin (--use-defaults skips them) and printing the report.
The brief is text, a file path, or a single http(s):// URL, in which case the page's text (tags stripped, at most
20,000 characters) is the brief and the URL is kept on the run.
Budget tiers are quick (three sub-questions, one researcher and round, four claims, six tool calls, 300k tokens)
for a narrow question, standard (eight sub-questions, 1.2M tokens) for most briefs, deep (eight sub-questions,
2.5M tokens) when the brief has several sides, and exhaustive (eight sub-questions, 5M tokens) when being wrong
is expensive. Quick never asks to extend its budget: it writes up gaps as open questions. Its research reports are
at most 250 words; Quick decision reports show only the recommendation and confidence, three-sentence rationale,
what would change the answer, up to two risks, and open questions. The JSON bundle retains the complete record.
Runs are kept in ~/.local/share/theoria/runs.sqlite ($XDG_DATA_HOME respected; $THEORIA_HOME overrides the
whole directory, settings included). A run whose server or CLI died mid-way keeps what it had saved: the GUI lists
it at the top of New run with Resume and Discard, and theoria resume <id> does the same headlessly. Resume
picks up from the last saved stage — research is done again from the saved scope when it had not finished — and a
run interrupted before its plan was confirmed is marked failed.
Settings
Open Settings in the app header to choose an AI vendor, a model from that vendor, and effort for every role.
Expand Per role settings to override one role. The screen shows sign-in fixes from theoria doctor,
checks a local or remote model endpoint, and offers SearXNG or Brave search when the researcher uses an
OpenAI-compatible model. Enter the name of a key environment variable, never its value. Saving replaces
the settings file atomically with mode 0600; an active run keeps its current settings and the next run uses the
new choice.
~/.config/theoria/settings.json (or $THEORIA_HOME/settings.json) is optional. Out of the box every role runs
claude-opus-5-5 on the Claude Agent SDK. Name a role to send it elsewhere — another provider, another model, or a
different effort level:
{
"roles": {
"critic": { "provider": "codex" },
"researcher": { "effort": "medium" }
}
}For example, local Ollama can run research through a SearXNG search service, while an OpenRouter critic names its key:
{
"search": { "provider": "searxng", "baseUrl": "http://127.0.0.1:8080" },
"roles": {
"researcher": {
"provider": "openai-compatible",
"model": "qwen3:30b",
"baseUrl": "http://127.0.0.1:11434/v1"
},
"critic": {
"provider": "openai-compatible",
"model": "anthropic/claude-opus-4.1",
"baseUrl": "https://openrouter.ai/api/v1",
"apiKeyEnv": "OPENROUTER_API_KEY"
}
}
}For Brave Search instead, set "search": { "provider": "brave", "apiKeyEnv": "BRAVE_API_KEY" }
and export BRAVE_API_KEY. Search returns at most ten titles, URLs and snippets per query.
An OpenAI-compatible role reads a key only from the environment variable that its own apiKeyEnv names; without
that setting, Theoria sends no authorization header. A role with apiKeyEnv must use HTTPS unless its endpoint is
on the loopback host. An openai-compatible researcher needs a search setting and a model that supports tool
calling. For Ollama, choose a tools-capable model and raise num_ctx in its Modelfile for long prompts;
Ollama's /v1 API cannot set num_ctx. For llama.cpp, start the server with --jinja.
The OpenAI-compatible adapter records token counts, but its dollar cost is unknown; a zero estimate does not mean a keyed endpoint is free.
Research caps: Claude and OpenAI-compatible check before each web tool, and fetch pages through Theoria's own
guarded fetcher. Gemini searches only (its CLI's page fetch is turned off, because Theoria cannot guard it), checks
each search with a BeforeTool hook, and its token ceiling is checked when final stats arrive. Codex counts live web_search events and stops after an
overage, returning the research already written; its token ceiling is approximate because usage arrives at
turn.completed.
Roles: intake, scope, lead, researcher, writer, critic. Providers: claude (default), codex (the
codex CLI, logged in with codex login), gemini (the gemini CLI; run it, then enter /auth), and
openai-compatible (OpenAI, OpenRouter, Ollama, llama.cpp, or another compatible /chat/completions endpoint).
Efforts: low, medium, high, xhigh, max.
Default models: Claude uses claude-opus-5-5, Codex uses gpt-6-luna, and Gemini uses gemini-2.5-pro.
