Local · offline-capable · delivery gate + cost control

AI writes the code.
Hopper verifies it — and watches the bill.

An agent's self-reported “done” can't be trusted. Hopper re-runs verification itself (restoring base tests when the agent touched them — anti-tamper), checks every acceptance criterion for evidence, and renders a shareable trust report; it also turns real Claude / Codex usage and budgets into enforceable gates. The orchestration layer (Markdown / GitHub Issues / ZenTao intake → scheduling → worktrees → review → merge) is the free foundation underneath.

$git clone https://github.com/Octo-o-o-o/Hopper.git && cd Hopper && npm ci && npm run build && node bin/hopper.mjs init
Blueprint illustration of the Hopper development production line

FIG.00 — Drop docs → triage → isolated worktree → quality gates → output

SHEET 01
What it is · what it isn't

A local delivery gate
for AI-written code.

Hopper is a CLI tool with no server. You drop Markdown into a central vault, and it automatically runs triage → queue → dependency/risk checks → pick a runner → isolated worktree → compile the prompt → execute → verification → acceptance → docs alignment → human review → merge/archive, with an event audit trail end to end.

The engineering view: the file system is the truth, Git is the audit trail, the runner is a replaceable execution backend, the scheduler manages queueing/concurrency/dependencies/locks, and the merge gate enforces the safety boundary.
Non-goals · what it doesn't do
  • Not another coding agent — it doesn't rewrite Claude Code / Codex
  • Not another Jira / Linear / Taskmaster / Backlog
  • Not a cloud Devin replacement, not general-purpose RPA
  • No heavy Web UI in the MVP — the document UI is left to Obsidian
01

Files are the truth

The Markdown under ~/Hopper/ plus .hopper/events.jsonl can rebuild all state. Even after a crash it reconciles and recovers.

02

Done has to be trustworthy

runner finished ≠ task done. It must pass Hopper's own verification, line-by-line acceptance, docs alignment, and human review — and code tasks still need a merge.

03

Untrusted-input model

All Markdown / external content is untrusted input and cannot relax the guardrails. Deterministic gates backstop everything after the runner finishes; an LLM can never lower a safety rule.

SHEET 02
Mental model · one assembly line

From a single Markdown
to one trustworthy merge.

Every task flows down the same line. The runner finishing is only a middle station — automatic quality gates, human review, and the merge gate still come after. The diagram below is all of Hopper.

1 DROP drop · dedupe 2 TRIAGE triage · compile prompt 3 QUEUE deps · risk · pick runner 4 ISOLATE worktree · run runner 5 POST-RUN 6 quality gates 6 REVIEW human · approve/retry 7 MERGE smoke · archive failed blocked verify ✗ guard ✗ events.jsonl — append-only event stream; every step leaves a trace, rebuilds all state, recovers from crashes

FIG.02 — Single-task lifecycle · the last event decides whether a task lands in failed / blocked / review

Stations 1–2

Drop → triage

Four entry classes drop into the Inbox and get deduped; a three-layer triage of parser → rules → optional LLM compiles a prompt with context and acceptance criteria.

Stations 3–4

Queue → isolate

The dependency DAG, risk, and usage/budget together decide whether it can run; RunReserved is written before the worktree is built, then Claude/Codex runs on its own branch.

Station 5

Quality gates

Hopper runs verification, deterministic guardrails, risk re-eval, acceptance evidence, and docs alignment — emitting events step by step, and the last event sets the state.

Stations 6–7

Review → merge

A human approves / adds a waiver / sends it back to retry; merge re-runs a smoke check first, conflicts keep the scene intact, and it's archived at the end.

SHEET 03
Files are the truth · two layers of state

State is never written directly —
it's a projection of the event stream.

Machines read the fine-grained state in events.jsonl; humans read the coarse state in the frontmatter. You never hand-write status: it's projected from the event stream, and the write-back is "protected" — editing the body in Obsidian won't be overwritten.

Machine event stream · events.jsonl append-only · fsync · fine-grained state RunReserved PromptCompiled VerificationPlanFrozen RunnerFinished VerificationFinished ←last projectTasks() last event sets state User-visible frontmatter · coarse state received → ready → running → review → done received running review projected back protected: when the body hash changes, it defers and won't overwrite your edits

FIG.03 — Two views of the same truth · events.jsonl can rebuild all state

Vault layout · ~/Hopper/
~/Hopper/
├─ 00-Inbox/ · drop entry _unassigned/ or <project>/
├─ 10-Projects/ · Tasks / Plans / Research / Reviews
├─ 20-Runs/ · human-readable run / verify / review summaries
├─ 80-Attachments/ · images / PDFs / logs
├─ 90-Archive/ · archive
├─ .obsidian/ · Obsidian config
└─ .hopper/ · machine state · gitignored by default
├─ events.jsonl source of truth
├─ config.yml · projects.toml
└─ locks/ runs/ worktrees/ state/ …
▬ User-visible (in the Obsidian graph) ▬ Machine state (don't touch by hand)
Safe to edit
  • The task Markdown body and acceptance criteria
  • priority / runner / depends_on / tags / do_not_run in the frontmatter
  • Browse, backlink, and read run summaries in Obsidian
Don't touch by hand
  • .hopper/events.jsonl · state/ · locks/ · requests/
  • The frontmatter status / updated_at / last_run_id (projected fields)
  • On a state conflict, run hopper reconcile --dry-run
SHEET 04
Four entry classes · one production line

Write requirements however
you like, then drop them in.

Handwritten notes, Obsidian, Claude Code / Codex sessions, and external requirement sources such as GitHub Issues / ZenTao Stories — all four entry classes ultimately converge into Markdown in the vault, handled by the same drop → scan → triage loop.

Isometric blueprint illustration of four entries converging into the central vault hub
PLATE 04 · Notes / graph / terminal / external requirement sources feeding into the central vault
Manual Markdown hopper drop ./task.md Obsidian browse · edit · graph Claude · Codex drop --stdin External sources github · zentao VAULT ~/Hopper/00-Inbox scan · triage split children · compile prompt → enter the main pipeline SHEET 02 Main pipeline →

FIG.04 — Four entry classes → central vault → the same loop

Entry 01

Maintain Markdown by hand

The most direct way: write a .md, put runner / risk in the frontmatter, the requirement plus an Acceptance checklist in the body, and drop it in.

$ hopper drop ./task.md --project my-app
$ hopper move <id> --to-project my-app

No project specified lands in _unassigned, and can be reassigned later.

Entry 02

Maintain it in Obsidian

The vault is an Obsidian vault. Filter status/project in the properties view, see dependencies in the graph, read 20-Runs/ summaries — it works out of the box, and Hopper builds no document UI of its own.

  • Open ~/Hopper as a vault
  • Edit the body / adjust priority, depends_on
  • Backlink across task ↔ acceptance ↔ run summary
Entry 03

Maintain it in Claude Code / Codex

Have the AI draft the plan, then pipe the body straight into Hopper; or drop from inside your project repo via a .hopper-inbox symlink.

$ cat needs.md | hopper drop --stdin \
--project my-app

The /hopper-drop slash command isn't implemented yet — piping stdin is the most reliable.

Entry 04

Bring in external sources

GitHub Issues can be imported as one-way snapshots and commented back; ZenTao Stories can be read-only imported, manually written back as comments/fields, and pulled for close/spec/assignedTo suggestions.

$ hopper github link --repo owner/app
$ hopper github sync && hopper scan
$ hopper zentao import --story 109
$ hopper zentao sync-back --task <id> --yes

External bodies are tagged external_untrusted and cannot relax the guardrails.

SHEET 05
Task state machine · 14 coarse states

Every task
travels across this map.

The main lifeline is received → ready → running → review → done. The rest are branches and loops: failed verification returns to failed, a guardrail hit goes to blocked, a human can send it back to retry… all explainable, all recoverable.

triage run post-run ✓ approve+merge verify ✗ guardrail merge conflict reject retry unblock re-triage not readydraft·plan_needed·research received deferred ready running failed blocked review conflict rejected done archived

FIG.05 — Solid = main path · dashed = classification/loops · colors grouped by state · invariant "the last event sets the state"

readydependencies met, runnable now
runningexecuting or passing quality gates
reviewawaiting a human decision (approve/send back)
failedexecution/verification failed, can retry
blockedguardrail hard-failed, needs a human
conflictmerge conflict, scene preserved
donemerged and passed every gate
archivedterminal: archived/rejected/deferred
SHEET 06
Quality gates · Done has to be trustworthy

The runner saying "done"
is only the start of the checkup.

The moment the runner finishes, Hopper runs 6 gates in order inside the isolated worktree. Order is priority: an earlier gate failing aborts immediately, and the last event decides whether the task lands in failed / blocked / review.

Isometric blueprint illustration of an inspection line of quality gates, with the current gate highlighted in amber and failed packages diverted to a side track
PLATE 06 · Inspection line · current gate highlighted · failed items diverted to a side track
runner outcome: completed / needs_review ↓ passpass 1 Verification runs the frozen plan (test/lint/build) · emit VerificationFinished hard gate 2 Deterministic Guardrails forbidden path / secret / push / change main · can't be relaxed by an LLM hard gate 3 Risk re-eval re-checks risk from the diff, only raises · an escalation forces review only raises 4 Acceptance evidence checks acceptance criteria line by line · emit AcceptanceReviewed records 5 Docs alignment do the changes make docs stale · emit DocsAlignmentChecked records 6 Run summary writes a human-readable summary to 20-Runs/ records failedaborts if verification fails blockedaborts on a guardrail hit → review (handed to a human)

FIG.06 — 6 gates run in order · each emits an event · the last event sets the state

Why freeze the verification plan

Verification uses the plan frozen at the task's start, in the base-commit state. Even if the runner edits the test scripts, it can't affect this gate — it has no way to cut itself slack.

Deterministic first, the LLM only advises

The first two are deterministic gates (no LLM) and can hard-block. Risk re-eval only raises; acceptance and docs only record any issues and hand the call to a human.

Breakpoint collaboration · Assisted mode

In assisted mode the runner stops at plan-step boundaries and raises a decision card. You release / annotate / veto it in the Console or CLI (hopper breakpoint release), and the same run resumes in place — no starting over.

Merge queue · optional

When enabled, approve enqueues the task and the queue worker is the only canonical landing writer, landing changes one by one; generation fencing keeps a stale run's result from landing. Approve is still yours.

Code tasks still need a merge. Passing review ≠ done — you must run hopper merge (or, with the merge queue enabled, the queue worker lands it), and a smoke check runs once more before the merge.
SHEET 07
Command reference · one spec sheet

Every command,
sorted by category.

The global flags --json (script-friendly), --debug, and --vault <path> (overrides HOPPER_VAULT or the default ~/Hopper) are available almost everywhere.

Init & onboardingsetup · link
hopper init [path]Create / idempotently update the central vault layout
hopper setup --link-repo --project -yMinimal-interaction first run: build the vault + optionally link a repo
hopper link-project --project --repoOnboard a project repo, writing .hopper.project.yml + a symlink
hopper git initInitialize the vault as a Git repo (not initialized by default)
Drop & external intakedrop · github · zentao
hopper drop [file] --project --stdinDrop Markdown into the Inbox and dedupe on the spot
hopper github link --project --repoAssociate a registered project with a GitHub repo
hopper github sync --project|--all --dry-runOpen issues matching a label → bug-list
hopper github status --projectView sync status / number imported
hopper github sync-back --task --dry-runComment the fix summary back to the source issue
hopper zentao doctorCheck ZenTao config, auth, and write-back carriers
hopper zentao import --product|--story --dry-runRead-only import Stories, idempotent by source_path
hopper zentao sync-back --task --yesManually write back comments or fields; preview by default
hopper zentao pull --apply --yesPull close / spec / assignedTo suggestions and apply after confirmation
Triage & compilescan · triage · prompt
hopper scanDiscover and register new Inbox files + deterministic triage
hopper triage [task-id] --all --no-llm(Re-)triage, with optional LLM suggestions
hopper prompt compile <task-id>Preview the compiled prompt + context manifest
Dependencies & queuedep · queue
hopper dep add <task> --after <dep>Add / remove a hard dependency
hopper dep accept <task> --reasonAccept a system-suggested dependency
hopper dep graph · explain · readyDependency graph / explain / ready set
hopper queue explainExplain why each ready task can (or can't) run
Executionrun · drain · daemon
hopper run nextExecute exactly one ready task
hopper run <task-id>Execute one specific ready task; exact same gates as run next
hopper run --parallelExecute the current ready set once, then exit
hopper drain --max <n>Keep pulling tasks until the queue is empty / stopped (concurrency comes from config)
hopper daemon --max <n>Controlled auto-execution (only up to review, no merge)
hopper daemon pause · resumePause / resume claiming new tasks
Status & evidencestatus · show · usage
hopper statusShow task status projected from the event stream
hopper show <task-id>Task detail: frontmatter + event projection
hopper logs <task-id>Raw log of the most recent run
hopper usage · budgetRunner usage / USD soft accounting
hopper project show <name>Resolved snapshot of one project's config (defaults filled in)
hopper capabilitiesCapability / version handshake: enumerates events, statuses, mutation commands; --json for machines
Review · merge · archivereview · merge · retry
hopper review list · show · diff · patch · openList / detail / diff / export patch / open the worktree
hopper review approve --waive <kind:target> --reasonApprove, with a structured waiver for gaps
hopper review request-changes · reject --messageRequest changes / reject
hopper merge <task-id>merge (smoke check first; conflicts keep the scene)
hopper retry --runner --message --keep-worktreeRetry, optionally switching the runner
hopper archive --cleanup-worktree --statusArchive (archived/rejected/deferred)
Authoring · docs · recovery · opsnew · lint · docs · reconcile
hopper new <title> --projectCreate a task template file without registering or triaging it
hopper lint <file>Read-only quality check for Hopper task documents
hopper check --base --staged --criteriaRun the four gates standalone on any repo's diff; produces a trust report
hopper docs check · show <task-id>Re-run / view the Documentation Alignment Gate
hopper reconcile --dry-runReconcile + safe repair (release stale locks, recover crashed runs)
hopper doctor [privacy]Health check + reconcile report / sensitive-data self-check
hopper cancel · unblock · move · reassignCancel / unblock / reassign tasks
Schema · Runnerschema · runner
hopper schema validate [path] · export --outValidate / derive JSON Schema from zod
hopper runner list · show · probe <id>List / detail / probe runner capabilities
hopper runner detect --homeScan ~/.claude* ~/.codex* and print suggestions
Platform opscmd · stop · breakpoint · merge-queue
hopper cmd <verb> <target>Unified command service: durable / idempotent / five terminal states, shared with the Console
hopper stop --release --statusEmergency stop: freeze intake, stop claiming, handle in-flight executors by severity
hopper breakpoint list · release · resume <decisionId>Assisted-mode breakpoint cards: list / release (with a note) / resume the same run
hopper merge-queue list · run · release <task>Merge queue: inspect / run the queue worker / release a manual_release item
hopper credentials resumeOne-shot recovery after a 401 / quota auto-pause
hopper intake list · resolve <proposalId> --choiceIntake proposal cards: list / resolve (accept registers the project + drops the first task)
hopper attest request · confirm · reject · listAttestation: human verdict → signed receipt
hopper project contract lint · propose · seal · refreshDelivery contract lifecycle
hopper project gate <task-id> · coveragePer-task delivery gate check / project coverage report
hopper project enroll Delivery-chain enrollment entry (siblings: project artifact-put / challenge / attest / lease)
SHEET 08
Local console · optional GUI

Don't want to type commands?
Open a local console.

A single hopper console starts a local web page bound to 127.0.0.1 only: a Control Room for the big picture, a decision inbox for reviews, and dual tabs for filing tasks / bug reports — all reading the same file truth and writing the same mutation queue. It's a skin over the CLI, not a takeover of the state machine — close it and the CLI keeps working.

$hopper console
What it can docontrol room · decisions · bug report
First-run wizardSet up an empty vault in a few steps: build vault → onboard a repo (auto-detect verification commands) → pick a runner
Control RoomSix zones on one screen: active runs · pending decisions · budget burn · risk · recent results · ops metrics
Decision inboxRisk approvals / reviews / blocked / failed in one place; each resolution is bound to a digest / revision, so you never decide against a stale state
Review consoleSee diff / verification / acceptance / docs alignment / risk, with one-click approve · merge · retry
Task detail actionsfailed → Retry, blocked → Unblock, Archive — with guidance based on the task's actual risk
Task / Bug report tabsFree-form Markdown, or a structured bug form that generates canonical Markdown; a deterministic triage verdict chip before you submit
Import previewPreview the rendered ZenTao Story and external-source markers before importing
Draft protectionTyping is never swallowed by refreshes, with a localStorage draft as backup
Bilingual UIChinese / English following the browser language, with a manual toggle

Bound to 127.0.0.1 only, with a random token + Origin checks, so data never leaves your machine; execution is still scheduled by the daemon / CLI — the console doesn't run tasks for you, nor approve / merge on your behalf.

SHEET 09
Get started · one minimal loop

Five minutes to
run your first task end to end.

Minimal loop · bash
# 0 · Clone and build (Node ≥ 22; install from source until the npm package ships)
git clone https://github.com/Octo-o-o-o/Hopper.git && cd Hopper
npm ci && npm run build && npm link

# 1 · Init the vault, onboard your repo
export HOPPER_VAULT="$HOME/Hopper"
hopper init
hopper link-project --project my-app --repo /abs/my-app

# 2 · Drop a task
cat task.md | hopper drop --project my-app --stdin

# 3 · Triage → queue → execute
hopper scan
hopper triage --no-llm
hopper queue explain
hopper run next

# 4 · Review → merge → archive
hopper review diff <task-id>
hopper review approve <task-id>
hopper merge <task-id>
hopper archive <task-id> --cleanup-worktree

Unattended, up to review

The daemon only advances up to review — it never approves / merges for you.

$ hopper daemon --max 10
$ hopper review list

How to start with a real runner

  • Test the waters with low-risk tasks first: README typos, small doc sections
  • Manually check diff / verification / acceptance / docs before scaling up
  • hopper runner probe claude|codex to verify capability first
The vault is an Obsidian vault. Open ~/Hopper in Obsidian to browse tasks, run summaries, reviews, and the archive — no extra UI needed.