Introduction
zorp is a research agent for scientific discovery. It turns an uncertain question into a defensible answer, using evidence: question, investigation, sources, evidence, conflicting evidence, reasoning, validation, answer or artifact.
That covers more than academic research. A technical decision (should we migrate off Kafka), a competitive teardown, an investment thesis, a due-diligence package, a market question, and an academic hypothesis are all the same shape of problem to zorp.
Why
A confident answer is not a defensible one. An LLM will produce a fluent answer to a hard question in seconds. What it will not do is tell you whether to believe it, what evidence it weighed, or what it found that pointed the other way. zorp treats that gap as the actual problem: a question becomes an investigation, the investigation produces an evidence record, and the record is what the answer is accountable to.
The core primitive is the Kill Threshold, a number a human supplies that says, in advance, what would prove the investigation wrong. It is written to a file, hashed, and committed to git before any evidence is gathered, so a run cannot quietly rewrite what it set out to test.
What is here
- Guide: install zorp, run the agent, use the web UI.
- Concepts: the ideas the system is built on. Tracks, the four capabilities, the critique gate, the panel, the discovery layer.
- Reference: CLI, environment variables, the HTTP API.
Status
zorp is early, pre-alpha software. The execution harness, the research foundation, and all four capabilities (validate, investigate, co-write, deliver) are built and tested. See the roadmap for what is next.
Source: github.com/aviskaar/zorp. Built by Aviskaar, an applied AI research lab. MIT licensed.
Getting started
Install without a toolchain
curl -fsSL https://raw.githubusercontent.com/aviskaar/zorp/main/install.sh | bash
This downloads prebuilt zorp, zorp-agent, and zorp-web binaries for
your platform, verifies the published checksum, and installs them to
~/.local/bin. Linux and macOS, x86_64 and arm64. No Rust and no Node
needed.
Prebuilt binaries carry the default feature set. The four research
capabilities are behind the research feature and need a source build,
because zorp-track bundles DuckDB.
Build from source
Requires a recent stable Rust toolchain (rustup.rs).
git clone https://github.com/aviskaar/zorp.git
cd zorp
cargo build --workspace --exclude zorp-track
zorp-track bundles DuckDB, which compiles from source and takes a while
on a cold cache. The command above skips it, which is enough for the core
zorp and zorp-agent binaries. Drop the exclusion (or add
--features research) once you need validate, investigate, co-write, or
deliver.
Point it at a model
zorp talks to any OpenAI-compatible endpoint: a hosted API, or a local one (Ollama, LM Studio, vLLM).
export ZORP_BASE_URL="https://api.openai.com/v1"
export ZORP_API_KEY="sk-..."
export ZORP_MODEL="gpt-4o-mini"
See Environment variables for timeouts, retries, and the rest.
Run it
The core transport, one prompt in, one answer out:
cargo run -- "Summarize the second law of thermodynamics in one sentence."
The full agent, with tools, sessions, and verification:
cargo run -p zorp-agent -- "<task>"
The web UI:
cargo run -p zorp-web # http://127.0.0.1:7777
See The web UI for the settings panel, streaming, and the optional features.
Run a research capability
validate needs a search-capable tool connected, and deliver needs a
huiban-prefixed one. Connect either over MCP with --mcp, or configure
it once in .zorp/mcp.toml:
cargo run -p zorp-agent --features research -- --yes \
--mcp "stdio:brave-search:npx:-y:@modelcontextprotocol/server-brave-search" \
validate "Should we migrate off Kafka to Redpanda?"
See The four capabilities for what each one does and what it needs.
The web UI
A chat interface for the agent, with tool activity streamed as it happens and an approval prompt before anything is written or run.
cargo run -p zorp-web # http://127.0.0.1:7777
The server binds loopback by default. Binding anything else requires
--token and refuses to start without it, because a reachable
zorp-web is agent-driven shell access to whatever the process can see.
Choosing a model
The gear button opens a settings panel: pick a provider preset, point it
at a base URL, and choose from the models that endpoint actually lists.
Ollama and oMLX are presets rather than special cases, since both serve
an OpenAI-compatible /v1/models.
A setting saved here beats the matching ZORP_* environment variable,
which beats the built-in default, and every field says which of the
three it came from. The API key is the exception to what gets saved: it
is held in memory for the life of the server process and never written
to disk. Set ZORP_API_KEY in the environment if you want it to survive
a restart.
What you see
- Streaming. Text appears as the model produces it. Reasoning in
<think>tags is recorded and not shown. - Files pane. A read-only window on the workspace directory that renders markdown, office formats, PDFs, and images, and notices what a run wrote while it ran.
- Session titles. Once a session has a question and an answer, the model is asked once for a short name. The title is a label and nothing else; search and memory keep reading the verbatim first message.
Optional features
Each of these is a compile-time feature, off by default, and each is opted into on its own:
| Feature | Flag | What it adds |
|---|---|---|
| Web search | --features search | The web_search built-in tool (Tavily), the only built-in that touches the network |
| Conversation search | --features recall | Semantic search over your own conversations, embedded by a local Ollama model |
| Memory | --features memory | Recall quoted into a live turn, per message, opt-in per message |
| Voice | --features voice | Local Qwen3-ASR transcription into the composer |
| Research | --features research | The investigate endpoint and the aryabhatta ledger reader |
The recall, memory, and voice features share one rule: your data goes to a loopback address or it goes nowhere. There is no remote provider and no fallback. See Recall and memory.
Tracks and pre-registration
A track is zorp’s unit of investigation: one question, one evidence
record, one history. The zorp-track crate is the research foundation
everything else builds on.
What a track holds
- The registration. The hypothesis, the metric, and the Kill Threshold, written before any evidence is gathered.
- The evidence record. Every attempt, not just the one that worked. Each attempt records what was tried, what came back, and the conditions it ran under.
- Checkpoints. Points where a human decides whether the investigation continues.
Why git
The registration is written to a file, hashed, and committed to git. That makes pre-registration tamper-evident: a run cannot quietly rewrite what it set out to test, because the record of what it set out to test is in history that would show the edit.
Storage
Track data lives in DuckDB. A LanceDB vector library sits behind a
non-default library feature for retrieval work, and the research
feature does not pull it in.
Where to read more
The design specs live in
docs/superpowers/specs/
in the repository, one per capability.
The Kill Threshold
The Kill Threshold is zorp’s core primitive: a number a human supplies that says, in advance, what would prove the investigation wrong.
Before zorp gathers anything, the hypothesis, the metric, and the threshold are written to a file, hashed, and committed to git. After that:
- The agent never proposes the threshold. Only a human can set it, and only a human can move it.
- A run cannot rewrite what it set out to test. The registration is in git history, so an edit would show.
- Crossing the line ends the run. When an investigation crosses its threshold it is killed, and the record says why.
- Every attempt is recorded, not just the one that worked.
Nothing downstream is allowed to touch it either. The critique pass revises a draft against the evidence record but refuses to run if the record moved under it, so it cannot move the threshold or anything else that was pre-registered. Browser-launched investigations auto-approve their checkpoints, but the pre-registered kill threshold is still enforced in code regardless.
The point is falsifiability you cannot walk back. An investigation that can quietly redefine success when the evidence turns against it is not an investigation, it is a press release with extra steps.
The four capabilities
zorp’s research work is four capabilities, each a clearly bounded layer
on top of the track foundation, all behind zorp-agent’s research
feature.
validate
Is this question worth investigating? A novelty and feasibility check that searches for existing evidence before scoring the question.
It requires a search-capable tool: one whose name carries a search verb
(search, fetch, query, browse, find, lookup, retrieve), connected over
MCP or provided by the built-in web_search tool behind the search
feature. Without one it fails fast rather than scoring a question
against nothing. A tool that searches your own saved notes deliberately
does not count.
investigate
Gather evidence through staged, pre-registered attempts. Every attempt
is recorded, and every attempt records the conditions it ran under.
With ZORP_FORECAST set, the agent is asked for a forecast before each
attempt runs, and that is recorded too. Both happen before the attempt,
which is not a detail: a condition recorded afterwards describes a
different run, and an expectation recorded afterwards is a postdiction.
investigate is the only thing that writes to the
aryabhatta ledger.
co-write
zorp drafts the artifact from the track’s recorded evidence. A human is always the author of record. Between co-write and deliver sits critique, a gate that audits the draft against the evidence record.
deliver
Match a finished draft against real venues (conferences and journals, via live huiban search) and write a ranked shortlist for a human to review. It requires a huiban-prefixed MCP tool and fails fast without one.
What is not a capability
aryabhatta is a record plus readers, not a fifth capability. critique is a gate. panel is a reader. Each one is documented on its own page, and each one is smaller than a capability on purpose.
critique, the evidence gate
critique audits co-write’s draft against the track’s own evidence
record and revises what the record does not support. It is a gate on the
artifact, not a fifth capability.
How it works:
- The audit is code. The model’s only job is to inventory the claims in the draft; deciding whether the record supports each one is not left to a model’s opinion of its own writing.
- Revision happens within a bound you set:
--critique-rounds, orZORP_CRITIQUE_ROUNDS, default 2. - The pass refuses to run if the evidence record moved under it. That is what keeps it from touching the Kill Threshold or anything else that was pre-registered.
- What it found is written into the record.
The design stance is the same one the whole system takes: detection is code, and the model only interprets. A model asked whether it likes its own draft will like its own draft.
panel, adversarial review
panel is adversarial review: several reviewers read one target at
once, each from a code-defined lens, and none of them sees what the
others said. Agreement is counted in code afterwards. It produces
opinions and changes nothing, which makes it a reader, not a gate.
It is not critique. Critique audits a draft against a track’s evidence record and refuses if the record moved. Panel reads a target and reports what independent lenses found.
Two rules are not negotiable:
- A reviewer gets strictly less than the panel that launched it. A read-only allow-list of tools, so an opinion can never edit what it is reviewing.
- A panel is launched by a person, never by a model. There is no tool that spawns one, and the agent has a test saying so.
The web UI exposes it at POST /api/sessions/:id/panel on the existing
event stream. A running panel occupies the session exactly as a turn
does.
aryabhatta, the discovery layer
aryabhatta is zorp’s discovery layer: a record of what every investigation attempt expected and what actually happened, plus readers that look for structure in it. It is a record plus readers, not a fifth capability, and it ships no CLI command on purpose.
Who writes it
Only investigate. Every attempt records the conditions it ran under,
and, when ZORP_FORECAST is set, the agent is asked for a forecast
before doing the work and that is recorded too. Both happen before the
attempt runs. A condition recorded afterwards describes a different run,
and an expectation recorded afterwards is a postdiction, so the
expectations module refuses a forecast once its outcome exists. That
refusal is the one guarantee that separates a prediction from a
postdiction, and it has a mutation test because that test is the point.
Forecasting is off by default because it costs a model call on every attempt. Left off, the ledger stays empty, which is the honest state for a record nobody has fed.
Two rules
Neither is negotiable:
- Detection is code, and the model only interprets. The same split critique uses.
- No detector, and nothing in the search layer, may read a column holding model-authored text. Otherwise the agent’s own speculation becomes tomorrow’s observation.
Calibration before anything else
calibration is a go/no-go for whoever builds on the ledger. It
compares stated forecast confidence against actual outcomes, band by
band. No code enforces the verdict; a person reads it and decides. If
the stated intervals do not have real coverage, the right move is to
stop and not build the anomaly ledger.
A band with too few forecasts to judge is its own no-go and never a miss: a gap computed over three rows is arithmetic about three rows, and reporting it as a demonstrated miss makes it look exactly like one.
The modules
conditions, expectations, calibration, detectors, partition,
rerun, anomalies, families, and inquiry, all inside
zorp-track. The search layer can use erbga, a standalone genetic
algorithm for graph community detection, as its large-graph backend;
above the crossover a reported bundle is a floor on the confounding
rather than the whole of it, because the search can split a true bundle
but never invent one.
In the browser
“Zorp mode” in the web UI is one investigate attempt plus a read of
what landed in the ledger. A run is launched by a person and never by a
model, and the ledger reader names no model-authored text column.
Skills
zorp reads skills in Claude Code’s format, so skills you already have
work here without being ported. A skill is a directory holding a
SKILL.md: YAML frontmatter with a name and a description, then a
markdown body of instructions.
~/.claude/skills/code-review/SKILL.md # yours, everywhere
<repo>/.claude/skills/code-review/SKILL.md # this project's, wins on a name clash
$ZORP_SKILLS_DIR/code-review/SKILL.md # explicit for this run, wins over both
The model sees only names and descriptions, as one skill tool whose
description is the index. It loads a body by calling that tool, and the
body arrives as instructions for that turn. The two levels are the
point: descriptions are cheap enough to always carry, bodies are not.
Skills add guidance, never permissions
A skill body is a markdown file that can arrive with a git clone, and
it is treated that way:
- A skill cannot enable a tool, loosen an approval preset, or reach
past the
run_commanddenylist. - The
allowed-toolsfield some skills carry is read, reported, and ignored. - Names are single path components and never joined onto a path, and a
SKILL.mdthat resolves outside its own directory is skipped. - Files over 64 KiB are skipped, and a malformed skill is skipped with a message naming the file while its siblings still load.
Skills are not capsules. A capsule is loaded with /load and puts the
whole session in a mode; a skill is something the model reaches for mid
task and uses for that turn.
Recall and memory
Two features on one index. recall gives the web UI’s sidebar a
semantic search over everything you have ever asked zorp. memory
turns that same index into something a live turn can read, so a fact
from a thread you finished in March can be recalled in a thread you
started today.
The loopback rule
Conversation text goes to a loopback address or it goes nowhere. There is no remote embedding provider, no flag that adds one, and no fallback when the local model is missing. This corpus is your whole history with an agent that has been reading your files, and a feature that stayed working by posting it to an API would be worse than one that stops.
Four layers hold that up, because any one of them could be wrong. The
endpoint has to be a loopback literal or localhost, and it has to
still resolve to loopback. The addresses it resolved to are the only
ones the HTTP client can reach, through a resolver that performs no
lookup of its own. Redirects are refused. Proxy detection from the
environment is off, so HTTP_PROXY cannot route the text through
somebody else’s server. The tests for all of this count connections to
a loopback canary rather than checking for an error, because a failed
request and a request never made look the same from the caller’s side.
How memory stays honest
- The box is unticked on every message. Retrieval spends context and puts old text in front of the model, so it is a per-message decision. The model cannot ask for a recall; there is no tool for it.
- A memory is a quotation, never a summary. Nothing reads your history and writes down what it learned. There is no fact table and no stored sentence a model composed about your past, because that is the shape in which an agent’s guesses turn into its own evidence. Assistant-written lines are labelled as a model’s earlier output.
- Recalled text is data. It arrives inside a fence whose marker is minted for that one turn, under the same boundary sentence a skill body gets. It grants no tool, widens no approval, bypasses no denylist, and is never written back into the store, which is what stops the recalled block being re-embedded and recalled again.
Setup
ollama pull nomic-embed-text
cargo run -p zorp-web --features memory # memory turns recall on too
The server indexes existing conversations after startup, sweeps every
five minutes (ZORP_RECALL_SWEEP_SECS, 0 disables), and indexes an
active conversation after each turn.
CLI
Three binaries ship prebuilt: zorp, zorp-agent, and zorp-web.
Each answers --help with the full, current flag list; this page is the
map, not the territory.
zorp
The core transport. One prompt in, one answer out, against any OpenAI-compatible endpoint.
zorp "Summarize the second law of thermodynamics in one sentence."
zorp-agent
The full agent: tools, reasoning, verification, sessions, MCP.
zorp-agent "<task>" # run a task
zorp-agent resume # continue the previous session
zorp-agent --yes "<task>" # pre-approve tool prompts
zorp-agent --mcp "stdio:<name>:<cmd>:<args...>" "<task>"
With the research feature (source build), the four capabilities are
subcommands:
zorp-agent validate "<question>" # needs a search-capable tool
zorp-agent investigate "<track>" # staged, pre-registered attempts
zorp-agent co-write "<track>" # draft from the evidence record
zorp-agent deliver "<track>" # needs a huiban-prefixed tool
co-write’s critique pass is bounded by --critique-rounds (default
2).
MCP servers can also be configured once in .zorp/mcp.toml instead of
per run.
zorp-web
The web UI server.
zorp-web # http://127.0.0.1:7777
zorp-web --bind 0.0.0.0 --token ... # non-loopback requires a token
Optional features (search, recall, memory, voice, research)
are compile-time flags; see The web UI.
Environment variables
All zorp environment variables use the ZORP_ prefix. A setting saved
in the web UI beats the matching variable, which beats the built-in
default.
Model transport
| Variable | Default | What it does |
|---|---|---|
ZORP_BASE_URL | none | OpenAI-compatible endpoint, hosted or local |
ZORP_API_KEY | none | API key. Never written to disk by the web UI |
ZORP_MODEL | none | Model name |
ZORP_HTTP_TIMEOUT_SECS | 900 | Seconds of silence to wait for. On a streamed reply this bounds the gap between chunks, not the length of the answer |
ZORP_RETRY_ATTEMPTS | 4 | Total sends for a request answered 429 or 503. Nothing else is retried |
ZORP_RETRY_BUDGET_SECS | 30 | Seconds of added waiting the retries may spend in total |
ZORP_CONTEXT_TOKENS | unset | Context window size. Unknown unless you say, on purpose |
Research
| Variable | Default | What it does |
|---|---|---|
ZORP_FORECAST | unset | Ask for a forecast before each investigate attempt and record it |
ZORP_CRITIQUE_ROUNDS | 2 | Bound on critique’s revision rounds |
ZORP_TAVILY_API_KEY | none | Enables the web_search built-in (with the search feature) |
Web UI
| Variable | Default | What it does |
|---|---|---|
ZORP_WEB_TOKEN | none | Required when binding anything but loopback |
ZORP_WORKSPACE | cwd | Directory the agent works in and the Files pane shows |
ZORP_SESSION_TITLES | 1 | Set 0 to keep the verbatim first message as the sidebar label |
ZORP_SKILLS_DIR | unset | Extra skills directory, wins over user and repo skills |
Recall, memory, voice
| Variable | Default | What it does |
|---|---|---|
ZORP_RECALL_SWEEP_SECS | 300 | Full-store index sweep interval. 0 disables sweeps |
ZORP_RECALL_DB | next to the session store | Where the vector index lives |
ZORP_EMBED_URL | local Ollama | Embedding endpoint. Must be loopback; a remote host gets a refusal, not a remote embedder |
ZORP_EMBED_MODEL | nomic-embed-text | Embedding model |
ZORP_VOICE_URL | http://127.0.0.1:8000 | ASR endpoint. Loopback only |
ZORP_VOICE_MODEL | Qwen/Qwen3-ASR-0.6B | ASR model |
ZORP_VOICE_AUTOSTART | 1 | Set 0 to disable every install and spawn step |
Install script
| Variable | Default | What it does |
|---|---|---|
ZORP_INSTALL_DIR | ~/.local/bin | Where binaries land |
ZORP_INSTALL_FROM_SOURCE | unset | Set 1 to force a source build |
This table tracks the README and can lag it. When in doubt, the README
and --help win.
HTTP API
zorp-web’s API, served from the same origin as the UI. Loopback by
default; any other bind requires a token, and every route below sits
behind that check.
Routes for optional features exist in every build and answer with “off, and here is why” (or 501) when the feature is not compiled in, so a client can say why a button is disabled instead of interpreting a 404.
Sessions and turns
| Route | Method | What it does |
|---|---|---|
/api/health | GET | Liveness |
/api/sessions | GET, POST | List sessions, create one |
/api/sessions/:id | GET | One session |
/api/sessions/:id/turn | POST | Start a turn |
/api/sessions/:id/stop | POST | Stop the running turn |
/api/sessions/:id/events | GET | The event stream (SSE) |
/api/sessions/:id/approve | POST | Answer a tool approval prompt |
/api/sessions/:id/auto-approve | GET, POST | Read or set auto-approve for one chat |
Settings
| Route | Method | What it does |
|---|---|---|
/api/settings | GET, PUT | Read and update settings. The key is never sent back out, only has_api_key |
/api/settings/models | GET, POST | List the endpoint’s models. A candidate key travels in the POST body, never a query string |
/api/settings/test | POST | Probe the endpoint with a minimal real completion |
/api/capabilities | GET | Which optional tools are really there |
Research
| Route | Method | What it does |
|---|---|---|
/api/sessions/:id/panel | POST | Launch an adversarial review panel |
/api/panel/lenses | GET | The code-defined review lenses |
/api/sessions/:id/investigate | POST | Run one investigate attempt |
/api/investigate/status | GET | Whether investigate is available, and whether forecasting is on |
/api/investigate/ledger | GET | Read what landed in the aryabhatta ledger. Names no model-authored text column |
Files, recall, voice
| Route | Method | What it does |
|---|---|---|
/api/artifacts | GET | List workspace files |
/api/artifacts/raw | GET | Read one, allowlisted extensions only |
/api/recall/status | GET | Whether conversation search is on |
/api/recall/index | POST | Force an index pass |
/api/recall/search | GET | Semantic search over conversations |
/api/voice/status | GET | Voice runtime status, read-only |
/api/voice/wait | POST | Start readiness and wait for it |
/api/voice/transcribe | POST | Transcribe recorded audio (25 MB body limit) |
The authoritative list is the router in
zorp-web/src/api.rs.
Decision log
zorp keeps a product and architecture decision log in the repository:
docs/DECISIONS.md.
It is the source of truth for why things are the way they are, and this site deliberately does not copy it. A copy would drift, and a decision log that has drifted is worse than none.
The design specs behind each capability live in
docs/superpowers/specs/.