Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

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:

FeatureFlagWhat it adds
Web search--features searchThe web_search built-in tool (Tavily), the only built-in that touches the network
Conversation search--features recallSemantic search over your own conversations, embedded by a local Ollama model
Memory--features memoryRecall quoted into a live turn, per message, opt-in per message
Voice--features voiceLocal Qwen3-ASR transcription into the composer
Research--features researchThe 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, or ZORP_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_command denylist.
  • The allowed-tools field some skills carry is read, reported, and ignored.
  • Names are single path components and never joined onto a path, and a SKILL.md that 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

VariableDefaultWhat it does
ZORP_BASE_URLnoneOpenAI-compatible endpoint, hosted or local
ZORP_API_KEYnoneAPI key. Never written to disk by the web UI
ZORP_MODELnoneModel name
ZORP_HTTP_TIMEOUT_SECS900Seconds of silence to wait for. On a streamed reply this bounds the gap between chunks, not the length of the answer
ZORP_RETRY_ATTEMPTS4Total sends for a request answered 429 or 503. Nothing else is retried
ZORP_RETRY_BUDGET_SECS30Seconds of added waiting the retries may spend in total
ZORP_CONTEXT_TOKENSunsetContext window size. Unknown unless you say, on purpose

Research

VariableDefaultWhat it does
ZORP_FORECASTunsetAsk for a forecast before each investigate attempt and record it
ZORP_CRITIQUE_ROUNDS2Bound on critique’s revision rounds
ZORP_TAVILY_API_KEYnoneEnables the web_search built-in (with the search feature)

Web UI

VariableDefaultWhat it does
ZORP_WEB_TOKENnoneRequired when binding anything but loopback
ZORP_WORKSPACEcwdDirectory the agent works in and the Files pane shows
ZORP_SESSION_TITLES1Set 0 to keep the verbatim first message as the sidebar label
ZORP_SKILLS_DIRunsetExtra skills directory, wins over user and repo skills

Recall, memory, voice

VariableDefaultWhat it does
ZORP_RECALL_SWEEP_SECS300Full-store index sweep interval. 0 disables sweeps
ZORP_RECALL_DBnext to the session storeWhere the vector index lives
ZORP_EMBED_URLlocal OllamaEmbedding endpoint. Must be loopback; a remote host gets a refusal, not a remote embedder
ZORP_EMBED_MODELnomic-embed-textEmbedding model
ZORP_VOICE_URLhttp://127.0.0.1:8000ASR endpoint. Loopback only
ZORP_VOICE_MODELQwen/Qwen3-ASR-0.6BASR model
ZORP_VOICE_AUTOSTART1Set 0 to disable every install and spawn step

Install script

VariableDefaultWhat it does
ZORP_INSTALL_DIR~/.local/binWhere binaries land
ZORP_INSTALL_FROM_SOURCEunsetSet 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

RouteMethodWhat it does
/api/healthGETLiveness
/api/sessionsGET, POSTList sessions, create one
/api/sessions/:idGETOne session
/api/sessions/:id/turnPOSTStart a turn
/api/sessions/:id/stopPOSTStop the running turn
/api/sessions/:id/eventsGETThe event stream (SSE)
/api/sessions/:id/approvePOSTAnswer a tool approval prompt
/api/sessions/:id/auto-approveGET, POSTRead or set auto-approve for one chat

Settings

RouteMethodWhat it does
/api/settingsGET, PUTRead and update settings. The key is never sent back out, only has_api_key
/api/settings/modelsGET, POSTList the endpoint’s models. A candidate key travels in the POST body, never a query string
/api/settings/testPOSTProbe the endpoint with a minimal real completion
/api/capabilitiesGETWhich optional tools are really there

Research

RouteMethodWhat it does
/api/sessions/:id/panelPOSTLaunch an adversarial review panel
/api/panel/lensesGETThe code-defined review lenses
/api/sessions/:id/investigatePOSTRun one investigate attempt
/api/investigate/statusGETWhether investigate is available, and whether forecasting is on
/api/investigate/ledgerGETRead what landed in the aryabhatta ledger. Names no model-authored text column

Files, recall, voice

RouteMethodWhat it does
/api/artifactsGETList workspace files
/api/artifacts/rawGETRead one, allowlisted extensions only
/api/recall/statusGETWhether conversation search is on
/api/recall/indexPOSTForce an index pass
/api/recall/searchGETSemantic search over conversations
/api/voice/statusGETVoice runtime status, read-only
/api/voice/waitPOSTStart readiness and wait for it
/api/voice/transcribePOSTTranscribe 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/.