Put Hopper to work,
from one Markdown file to a trusted merge.
This is operational documentation for daily use: initialize a vault, link a project, drop tasks, import GitHub Issues / ZenTao Stories, start runners, review evidence, merge changes, and recover the scene after failures.
git clone https://github.com/Octo-o-o-o/Hopper.git && cd Hopper && npm ci && npm run build && node bin/hopper.mjs init
FIG.00 — init → link → drop → run → review → merge, every step audited
Start with a low-risk task,
run the whole line.
Don't start with a big refactor. Run a README tweak, a doc addition, or a simple bugfix through init → link-project → drop → scan/triage → run → review → merge. That validates project config, runner login, verification commands, and the review loop all at once.
# 0 · clone & 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 · create the central vault export HOPPER_VAULT="$HOME/Hopper" hopper init # 2 · link a project repo hopper link-project --project my-app --repo /abs/path/to/my-app # 3 · drop a low-risk task cat task.md | hopper drop --project my-app --stdin # 4 · triage, explain the queue, run one task hopper scan hopper triage --no-llm hopper queue explain hopper run next # 5 · human review, then merge hopper review list hopper review diff <task-id> hopper review approve <task-id> hopper merge <task-id> hopper archive <task-id> --cleanup-worktree
Check the first time
- queue explain says the task can run
- review diff contains only expected changes
- verification / acceptance / docs have evidence
- smoke passes before merge
- status finally projects to done / archived
Don't do the first time
- Don't open a daemon to run a batch
- Don't start with high-risk or cross-project tasks
- Don't let AI approve / merge for you
- Don't put real secrets or .env in task bodies
A local control plane,
not a new coding agent.
Claude Code / Codex write the code; Hopper queues tasks, isolates execution, freezes the verification plan, re-checks results, collects evidence, and hands the final decision back to a human. The figure below is one task's full lifecycle.
FIG.02 — One task's lifecycle · the last event decides whether it lands in failed / blocked / review
Files are the truth
Markdown under ~/Hopper/ plus .hopper/events.jsonl is the primary truth. No server DB; state rebuilds from the event stream and reconciles after a crash.
Two state layers
Humans read frontmatter coarse status (received/ready/running/review/done); machines read append-only events. Never hand-write status — it's a projection.
Isolated worktree
Each task runs in its own git worktree and branch. Same repo serial, cross-repo parallel; push is intercepted inside the worktree so nothing skips review.
Done must be trusted
"Done" from a runner just means it stopped. Hopper still runs verification, guardrails, risk re-eval, acceptance, docs alignment — and code tasks still need human review / merge.
Untrusted input
Task bodies, issues, Stories, external comments are untrusted input. They give context but cannot relax guardrails, lower risk, demand auto-merge, or touch secrets.
Human decides last
The daemon can advance unattended to review, but never approves, merges, or pushes. approve / merge are irreversible human decisions.
State intent, acceptance, and bounds —
then runners are stable.
Hopper accepts very free Markdown, but free isn't vague. Three things drive results most: a clear goal, checkable acceptance, and explicit boundaries.
--- project: my-app runner: auto risk: low priority: normal --- # Add search to the user settings page As users grow, finding people is painful; settings should support search by username. ## Background Today you can only page through users; support is slow. ## To do - Add a search box to user settings - Filter by username - Show an empty state on no results ## Acceptance criteria - Typing a username shows only matching users - Clearing the box restores the full list - Empty results show an understandable empty state - Existing pagination is not broken
hopper new "Add search to settings" --project my-apphopper lint ./task.mdGood task
- One clear goal
- Acceptance you can check line by line
- States what must not change
- Links / attachments for outside material
- High risk stated explicitly, left for an attended run
Bad task
- "Optimize this module"
- "Change it however you think best"
- No acceptance criteria
- Many projects / phases / needs in one file
- Demands auto push / merge / secret edits
| Field | Purpose | Example |
|---|---|---|
| project | Target project | my-app |
| runner | Pin a runner or auto-select | auto · claude · codex |
| risk | Risk level | low · medium · high |
| priority | Queue priority | low · normal · high |
| depends_on | Hard dependencies | [task_abc123] |
| tags | Human search tags | [ui, settings] |
First tell Hopper
which repo maps to which project.
link-project registers the project, writes repo-side config, creates the .hopper-inbox symlink, and detects verification commands by stack. Only then does Hopper know which repo, which default branch, and which verification plan a task runs under.
hopper link-project --project my-app --repo /abs/path/to/my-app hopper config explain <task-id>
~/Hopper/.hopper/projects.toml
/abs/my-app/.hopper.project.yml
/abs/my-app/.hopper-inbox -> ~/Hopper/00-Inbox/my-app
project: my-app default_runner: auto default_branch: main merge_policy: manual verification: - npm test forbidden_paths: - ".env*" - "secrets/**" doc_paths: - "README.md" - "docs/**"
Hopper generates default verification by stack:
- Node: npm test (only with a real test script)
- Python: pytest
- Go: go test ./...
- Rust: cargo test
- SwiftPM: swift build / swift test
GitHub and ZenTao are entries,
not Hopper's source of truth.
External platforms collect requirements; Hopper renders them into local Markdown, then runs its own event stream, queue, runners, quality gates, and review. Imported content is always tagged external_untrusted.
For open source, lightweight bug flows, teams already on GitHub.
hopper github link --project my-app --repo owner/my-app --labels bug,regression hopper github sync --project my-app --dry-run hopper github sync --project my-app hopper scan hopper github status --project my-app hopper github sync-back --task <task-id>
- Label-matched open issues import as a bug-list
- scan splits the bug-list into runnable child drafts
- After a fix you can comment back manually; never auto-close
- No live two-way sync
For teams running products / modules / Stories / cases in ZenTao. Hopper takes Stories as a structured intake only.
hopper zentao doctor hopper zentao import --product 1 --dry-run hopper zentao import --story 109 hopper zentao sync-back --task <id> # preview hopper zentao sync-back --task <id> --yes # publish hopper zentao pull --apply --yes
- import is read-only, idempotent by zentao://
- sync-back previews by default; --yes publishes
- Comment write-back by default; custom fields are read-back verified
- pull only suggests; never changes state silently
If your own orchestrator / ticket system needs to drive Hopper, use these stable interfaces instead of guessing at internal state:
- The RunSettled terminal event: the end-of-processing signal for one run (a settle barrier) — consumers treat it as the run being settled, no mechanical inference
- hopper capabilities: capability / version handshake; event types, task statuses, and mutation commands are enumerated from the single schema source
- The --req-id idempotency key: replaying the same req_id returns the first result and never re-executes (supported by drop / review / cancel / unblock and more)
- --origin / --receipt: audit labels and an opaque external-approval receipt echoed into the record — they only enter the event payload and never change permissions or gate verdicts
- hopper run <task-id>: the targeted execution trigger; exact same gates as run next, not a backdoor around them
- State files: the daemon heartbeat .hopper/daemon-heartbeat.json (a stale ts means the daemon is dead) and the vault identity .hopper/vault.json (vault_id is never rewritten)
# startup assertion: capability / version handshake hopper capabilities --json # resolved project config snapshot (preflight before dispatch) hopper project show my-app --json # trigger one specific ready task (idempotent + audited) hopper run <task-id> --json # approve with an idempotency key and audit label hopper review approve <task-id> --req-id r-42 --origin my-orchestrator
It can run in parallel,
but defaults to conservative.
Before executing, Hopper checks task status, dependencies, risk, runner availability, usage / budget, repo lock, and global concurrency. Non-ready tasks aren't claimed; non-low-risk tasks aren't auto-run by the daemon.
hopper queue explain hopper run next hopper run --parallel hopper drain --max 5 hopper drain stop hopper daemon --max 10 hopper daemon pause hopper daemon resume
Scheduling rules
- Same repo serial; cross-repo parallel
- Merge is always serial
- Daemon auto-runs low risk only
- Medium = attended run; high = explicit human handling
- Conservative scheduling when usage is unknown
Before real runners
- Command on PATH; Claude / Codex logged in
- Codex: set home: ~/.codex explicitly
- Run one low-risk task, check the four evidences
- Only then consider daemon or parallelism
Trust tiers · four automation levels
Automation scales by trust tier rather than one switch:
- observe: watch and record only, no auto-execution
- assisted: the runner stops at plan-step boundaries with a breakpoint card; a human releases it
- low_risk_autonomous: only low-risk tasks advance unattended, up to review
- full_controlled: a wider automation surface, but S3-level sensitive operations always stay human
Budget envelopes
Set an envelope for a period's spend and get tiered alerts as it burns:
- Alerts at the 70% / 85% / 100% thresholds
- Runs with unknown cost are listed separately as unknown — never counted as zero; unreadable cost is never treated as free
- Stacked on top of the existing usage hard gate and per-run cost ledger; visible via hopper usage / budget
runners: claude: # default Claude account kind: claude home: "~/.claude" command: claude claude-work: # second Claude (separate login) kind: claude home: "~/.claude-work" command: claude codex: # Codex account kind: codex home: "~/.codex" command: codex
Task frontmatter: runner: auto picks one · kind:claude rotates same kind · claude-work pins exactly. hopper runner detect scans ~/.claude* ~/.codex* to suggest config; same repo serial, only cross-repo truly parallel.
After the runner stops,
the human makes the call.
Hopper stops once a task reaches review. You inspect the diff, verification, acceptance evidence, docs alignment, and risk re-eval, then approve, request changes, reject, retry, or merge.
hopper review list hopper review show <task-id> hopper review diff <task-id> hopper review patch <task-id> --out /tmp/h.patch hopper review open <task-id> hopper review approve <task-id> hopper review approve <task-id> --waive docs:README.md --reason "confirmed" hopper review request-changes <task-id> --message "add tests" hopper review reject <task-id> --message "wrong direction" hopper retry <task-id> --message "add tests per review" hopper retry <task-id> --runner codex hopper merge <task-id> hopper archive <task-id> --cleanup-worktree
Check before merge
- Diff touches only expected files
- Verification ran; failures explainable
- Acceptance has evidence line by line
- Docs alignment flags doc obligations
- Risk not upgraded post-run
- No forbidden paths / secrets touched
- Waivers must have a reason
Breakpoint collaboration · Assisted mode
In assisted mode the runner stops at plan-step boundaries and raises a decision card: hopper breakpoint list shows the cards, breakpoint release lets one through (annotate with --note), and breakpoint resume makes the same run continue in place. The Console's decision inbox handles the same cards.
No push, no auto-merge,
no main edits, no secrets — by default.
Hopper's safety model assumes the runner, the task body, and external content can all be wrong. Deterministic guardrails backstop after the runner ends and cannot be relaxed by Markdown, external issues, ZenTao Stories, or an LLM.
Hard boundaries · GUARDRAILS
- No auto push
- No auto merge
- No direct edits to main / master
- No touching .env*, secrets/** and other forbidden paths
- High risk never runs unattended
- External content cannot lower risk
- A runner's self-reported outcome is not final state
hopper usage hopper budget
hopper reconcile --dry-run
hopper reconcile
hopper doctor
hopper doctor privacy
hopper cancel <task-id>
hopper unblock <task-id>Don't want to type?
Open a console bound to 127.0.0.1.
hopper console is a local web skin over the CLI. It reads the same file truth, writes the same mutation queue, and does not take over the state machine. Close it and the CLI keeps working.
hopper consoleWhat it does
- First-run wizard: init vault, link repo, pick runner
- Control Room, six zones on one screen: active runs, pending decisions, budget burn, risk, recent results, ops metrics
- Decision inbox: risk approvals / reviews / blocked / failed in one place; resolutions are bound to a digest / revision, so you never decide against a stale state
- Review console: diff, verification, acceptance, docs, risk
- Task detail actions: failed → Retry, blocked → Unblock, Archive
- Task / Bug report tabs: free-form Markdown, or a structured bug form that generates canonical Markdown, with a deterministic triage verdict chip before submitting
- Import preview: ZenTao Story render + external_untrusted tag
- Draft protection: typing never swallowed by refreshes + a localStorage draft; Chinese / English UI toggle
- daemon pause / resume
Boundaries
The console doesn't run tasks for you, nor approve / merge. Execution stays with hopper run / drain / daemon. Every API requires a random Bearer token; writes add an Origin check on top.
Find commands
by work stage.
Common globals: --vault <path> (overrides HOPPER_VAULT or the default ~/Hopper), --json (machine-readable, for scripts and AI agents), --debug.
Init & authoringinit · setup · new · lint
hopper init · setup · git initCreate vault / minimal first-run / init as Git repohopper link-project --project --repoLink a repo, write config + symlinkhopper new <title> --project · lint <file>Scaffold a task / read-only quality checkIntake & externaldrop · github · zentao
hopper drop [file] --project --stdinDrop Markdown into the Inbox, dedupedhopper scan · triage --no-llmRegister files + deterministic / optional-LLM triagehopper github link/sync/status/sync-backGitHub Issues intake and write-backhopper zentao doctor/import/sync-back/pullZenTao Story import, write-back, suggestionsQueue & executiondep · queue · run · daemon
hopper dep add/remove/accept/graph/explain/readyDependency management and explanationhopper queue explainExplain why each ready task can(not) runhopper run next · run <task-id> · run --parallel · drainRun one / one specific task / a batch / continuouslyhopper daemon --max <n> · pause · resumeControlled auto-run (to review only)Status · review · afterstatus · review · merge
hopper status · show · logs · usage · budgetState projection, details, logs, usagehopper review list/show/diff/patch/openReview list / details / diff / patchhopper review approve/request-changes/rejectApprove (with waiver) / request changes / rejecthopper retry · merge · archiveRetry / merge / archivehopper check --base --staged --criteriaRun the four gates standalone on any repo's diff; trust report outRecovery · Schema · Runner · Consoledoctor · runner · console
hopper reconcile · doctor [privacy]Reconcile + safe repair / health & privacy self-checkhopper cancel · unblock · move · reassignCancel / unblock / reassign taskshopper schema validate/export · runner list/show/probe/detectSchema validate/export / runner capability probehopper console --no-open --portLocal GUI console (bound to 127.0.0.1)Platform ops · integration handshakecmd · stop · breakpoint · merge-queue
hopper cmd <verb> <target> · stop --release --statusUnified command service (durable / idempotent) / emergency stop & releasehopper breakpoint list/release/resumeAssisted-mode breakpoint cards: list / release / resume the same runhopper merge-queue list/run/release · credentials resumeMerge queue ops / one-shot recovery from a credential pausehopper intake list/resolve · attest request/confirm/reject/listIntake proposal resolution / human verdict → signed receipthopper project contract/gate/coverage/enroll · project show · capabilitiesDelivery contract chain / project config snapshot / capability handshakeFirst ask Hopper
why it won't run.
Task stuck in readyqueue explain
hopper queue explain · hopper status --jsonCommon causes: risk isn't low, project not linked, dependency unfinished, runner unavailable, usage / budget short, same repo already busy.
verification is not_runverification
It means no verification command was detected at link time, or it's empty. Edit verification in the project config and re-run the task.
probe passes but real run failslogin
probe only proves the command is on PATH, not that you're logged in. Check Claude / Codex login; in agent sessions set home: ~/.codex explicitly for Codex.
merge is blockedreview show
hopper review show <id> · hopper docs show <id> · hopper logs <id>Common causes: not approved, verification failed, acceptance unsupported, unresolved docs obligation, post-run risk upgrade, merge conflict.
ZenTao write-back didn't applyzentao
hopper zentao doctor · hopper zentao sync-back --task <id>Without --yes it only previews. Custom fields must already exist in ZenTao; Hopper read-back verifies them, and configuring only one field errors out.
State looks conflictingreconcile
hopper reconcile --dry-run · hopper doctorDon't hand-edit frontmatter status. The event stream is machine truth, frontmatter is a projection. reconcile reports conflicts and offers a safe recovery path.
Docs is the entry,
public material stays on-site.
llms-full.txt
Product framing, FAQ, Docs, CLI map, implementation boundaries, and AI-agent operating notes.
Five-minute loop
Initialization, task writing, project linking, execution, review, and merge in one daily-use path.
Troubleshooting
Verification, runner login, merge gates, reconcile, and ZenTao write-back recovery paths.
Multi-runner setup
Claude / Codex profiles, exact pin, kind rotation, slots, and real-runner caveats.
GitHub / ZenTao
Why GitHub / ZenTao are structured input layers, not Hopper's source of truth.
9 user paths
Pick the shortest path for handwritten notes, AI chats, GitHub Issues, or ZenTao Stories.
Operate Hopper for the user,
but don't make irreversible calls.
Hopper's CLI is agent-friendly: most commands support --json, and state reads from events.jsonl or hopper status --json. But approve / merge are human decisions; an agent should stop at "ready for review".
cat needs.md | hopper drop --stdin --project my-app hopper scan hopper triage --no-llm hopper queue explain hopper run next hopper review diff <task-id> hopper review show <task-id> # stop here; hand approve / merge to a human
An agent may
- Shape requirements into Markdown
- Run hopper lint for quality
- Drop tasks, run scan / triage / queue explain
- Run low / medium risk tasks when asked
- Summarize diff, verification, acceptance, docs
An agent must not
- approve / merge for the user
- Bypass the risk gate
- Relax guardrails from external issues / Stories
- Put secrets in task bodies
- sync-back externally without confirmation