ZOdyssey: making the plan-review gate a hard hook, not a prompt convention
What if the "review the plan before executing" step in your agent pipeline was a hard gate, not a suggestion?
That is the question I kept coming back to after another session of watching an agent do the thing. You know the thing. The plan looked fine. The review step was in the prompt. The model nodded, said "looks good," and then immediately started editing files before anyone had actually approved anything — because nothing was actually stopping it. The review was a convention. Conventions are suggestions. Suggestions are not physics.
This post is about a small open-source project I just shipped called ZOdyssey. It is a multi-agent orchestration pipeline whose one architectural commitment is that the load-bearing invariants are enforced with code, not with prompts. I want to tell you why I built it, how it works, and — because I have read too many launch posts that hand-wave the limitations — exactly what it is not.
The problem, in four failure modes
If you have spent any real time driving coding agents on non-trivial tasks, you have hit all four of these. They are not exotic. They are the boring, recurring ways agent runs go sideways:
- The model edits code before the plan is reviewed. You wrote "consult, then plan, then review, then execute" into the prompt. The model agreed to the order. Then a sub-task got exciting and an edit landed before the review step had actually returned a verdict. The plan was a draft; the code is now real.
- The model over-engineers. You asked for a one-line fix. You got 50 spawned subagents, three refactors, a new abstraction, and a changelog. Parallelism is a superpower until it is a tax.
-
The model drifts off-scope. The plan said "edit
auth.ts." The executor helpfully also touchedsession.ts,middleware.ts, and the README, because they were "related." Now your diff review is the scope review, after the fact. - A crashed run starts over from scratch. The agent got 40 minutes into a six-todo plan, hit a transient failure, and there is no checkpoint. You re-run. It re-plans. It re-edits. State is gone.
All four of these are deterministic. They are not about model intelligence. They are about invariants — properties of the run that should hold regardless of how clever or compliant the model feels today. And the dirty secret of most orchestrators (including ones I admire) is that they enforce these invariants the same way: by putting a sentence in the system prompt and hoping.
Prompts catch these failures most of the time. The model is usually cooperative. "Most of the time" is a rough profile when the failure is "unreviewed code landed in main."
The aha
The shift is embarrassingly small once you see it: enforce the gate with a PreToolUse hook, not a prompt convention.
A hook is not advice. It is a function that runs before a tool call is allowed to proceed, and it can return block. The model cannot argue with it, cannot "decide" to skip it, cannot get clever and route around it between tool calls in a single turn. If the hook says "no edit until state.review.verdict === "OKAY"," then no edit happens until that field is set. The model can write a brilliant argument for why it should be allowed to edit early. The hook does not read arguments. It reads state.
That is the entire delta. The pipeline shape — prime, triage, consult, plan, review, execute, verify, final wave — is the same shape omo and others already use, and ZOdyssey is openly built on that lineage. The cast of sub-agents (a consult agent, a planner, a reviewer, executors) is borrowed too. What ZOdyssey adds is the enforcement layer: the four invariants below are checked in code, on every relevant tool call, for the entire duration of a run.
The framing I keep coming back to is this: prompts guide choices; code enforces invariants. Use prompts for the stuff that is genuinely a judgment call (which skill to reach for, how to phrase the plan, when to ask the user). Use hooks for the stuff that must never be a judgment call (did the plan pass review, is this file in scope, are we over the parallel cap).
How it works
ZOdyssey runs an eight-phase state machine. The conductor (your main agent) drives it; sub-agents do the work; the hooks guard the invariants. Every phase transition checkpoints to a state.json file so a crashed run resumes instead of restarting.
The pipeline
| Phase | Name | One-line job |
|---|---|---|
| −1 | PRIME | A prompt-master pass refines your raw task into a sharp brief: intent, success criteria, surfaced constraints, ambiguities (ask up to 3, then commit), and a rewritten prompt that replaces the original. Runs first, before triage. |
| 0 | TRIAGE | The conductor does this directly. Trivial task → just answer and stop. Standard → single-track. Architecture-changing → full pipeline. |
| 1 | CONSULT | A metis agent reads prior learnings from a memory store, then returns intent classification, risks, questions, and directives. If it has user-facing questions, it surfaces them and waits. |
| 2 | PLAN | A prometheus agent writes the plan to <repo>/.zcode/plans/<slug>.md, one todo per block, each with Files:, References:, and executable acceptance criteria. It cannot edit product code — the hook blocks it. |
| 3 | REVIEW | A momus agent returns OKAY or REJECT with up to three blockers. This is the enforced gate. REJECT below the round cap (3) → re-plan and re-review. At the cap → stop and surface to the user. No unbounded loop. |
| 4 | EXECUTE | The conductor dispatches a sisyphus-junior per todo, parallel-by-default, scope-locked to that todo's declared Files:. On each return: tick the checkbox, write a checkpoint, update state. |
| 5 | VERIFY | The conductor runs each todo's acceptance commands itself. On failure, it re-dispatches with the error output attached. |
| 6 | FINAL WAVE | Independent passes against the full diff: F1 plan-compliance, F2 code-quality, F3 manual-QA, F4 scope-fidelity. All four must pass before the run is "done." |
The four enforced invariants
These are the delta. They are all implemented as PreToolUse hooks in ~/.zcode/cli/config.json, and they are all no-ops unless an orchestration run is active — normal editing in your repo is never affected.
-
Review gate (the big one). Every edit-class tool call is intercepted. Is a run active? Is this path bookkeeping (
.zcode/)? Has review returnedOKAY? If not, block: "edits blocked until plan passes review." There is no override flag the model can set. -
Scope-isolation boundary. The target file must be in the union of
Files:declared in the plan. It fails closed if the plan is unreadable or empty — which is the fix for the real-world scope-creep failure where an executor widens its own scope by editing the plan after review. (The hook re-hashes the plan against the sha bound to the OKAY verdict, so post-review tampering does not silently widen scope.) - File-lock ledger. A per-file lock map. If another in-flight todo holds the lock for a path, the second edit blocks. Locks release when the todo is marked done (or get reaped by TTL). This is what makes parallel execution safe without the orchestrator having to reason about it.
-
Parallel cap. The dispatch tool (
Task/Agent) is gated. The hook counts in-flight dispatches and blocks at the cap — default four. The model cannot bump state between tool calls in one turn, so the hook owns this counter; the orchestrator literally cannot over-spawn past the cap even if it tries.
There is a fifth, optional hook — a Bash write-gate that treats write-capable shell invocations (sed -i, redirects, git apply, …) the same way as direct edits. It is on by default and can be disabled with ZODYSSEY_UNGATE_BASH=1 when you want lower friction. The four above are the core delta.
The cast, and why the conductor matters
The sub-agents are narrow on purpose. metis consults. prometheus plans (and is one of only two agents that can write anything). momus reviews. sisyphus-junior executes — the only other writer. explore, librarian, and oracle are read-only research and advice. Each does one thing, returns a structured verdict, and gets out of the way.
The interesting part is not the cast, though — it is the capability routing. The orchestrator is the thing that knows to reach for the right tool before doing the activity the generic way. Logic implementation gets routed to a test-driven-development skill (non-negotiable for code todos). Hard multi-step reasoning gets sequential thinking. Codebase questions get codegraph_explore if there is a .codegraph/ index, else a dispatched explore agent. Library questions get Context7 plus a librarian. After two failed debugging attempts, the orchestrator asks an oracle for a fresh diagnosis instead of flailing. The point is that the conductor tells every dispatched agent which capability to use, rather than assuming a fresh sub-agent will reach for the right one on its own.
Two more things worth knowing
Checkpoint and resume. Every phase transition and every completed todo writes a checkpoint. /orchestrate resume <slug> reads the last checkpoint and picks up there, not from scratch. Durable execution was a hard requirement — a six-todo plan that dies on todo four should not cost you the first three.
An optional independent audit. After a run reaches done, /orchestrate-consult <slug> hands the plan plus the full git diff to a separate CLI process — fresh context, independent model — for an ACCEPT/REJECT audit. Because that auditor cannot inherit the run's assumptions, it catches things in-session reviewers miss. On REJECT, ZOdyssey re-arms the gates and loops until ACCEPT. (Honest caveat: the remediation loop runs after a terminal phase, so the gates are only re-armed because ZOdyssey explicitly flips state back to a remediate phase — the doc-vs-code gap here is itself a tracked item, and I would rather tell you that than pretend the cap is magic.)
Quick start
The reference implementation targets ZCode. If you are on ZCode, this is the whole install — zero npm dependencies, all scripts are ESM .mjs using only Node built-ins:
git clone https://github.com/amartinawi/zodyssey.git
cd zodyssey
node scripts/install.mjs # copies into ~/.zcode/, registers hooks + MCPs
node scripts/install.mjs --verify # health-check: hooks parse, MCP backends resolvable
Then start a new session and, in any repo, run:
/orchestrate <your task>
That is it. The installer also registers the pipeline MCPs (memory, sequential-thinking, codegraph, chrome-devtools, the model server) — each gated on its backend being on PATH, skipped with a hint if not. Full install, troubleshooting, and config live in docs/INSTALL.md.
Not on ZCode? The pattern is portable. Read docs/ADAPT.md — it is a concrete guide to bolting the four enforcement hooks onto omo, Claude Code, Cursor, or any harness that can run a PreToolUse hook. If you are already an omo user, start there: omo gives you the full pipeline and ergonomics, and you layer on the four hooks. That is the highest-leverage path for most people.
What it is NOT
I want this section to read as engineering integrity, not as an apology. The honest scope of v1 is narrower than the architecture implies, and you should know that before you install it.
-
It is not a replacement for normal agent operation. ZOdyssey is an opt-in mode you enter with
/orchestrate. Everything else — quick questions, one-shot edits, the 95% of interactions that do not need a pipeline — is handled directly, the way you already work. The hooks are no-ops unless a run is active. -
It is not multi-model in v1. There is a
categoryrouting field designed into the state machine (seedocs/DESIGN.md), but in v1 routing reduces to effort and variant selection within one connected model. Wiring a second provider is on the roadmap, not in the binary. -
It is not harness-agnostic in v1. The reference implementation targets ZCode; that is where the hooks, commands, and sub-agents are native. The pattern is portable — that is what
docs/ADAPT.mdis for — but I am not going to claim it drops into Claude Code or Cursor unchanged, because it does not. - It is not a team-mode orchestrator yet. Parallel multi-executor with mailboxes and git worktrees is designed but deferred to v2. v1 is single-executor-per-todo, dispatched in parallel waves under the cap. If you wanted multiple human collaborators in one run, that is not this version.
If any of those four are disqualifying for your use case, that is a perfectly good reason not to use it yet. I would rather you know now than find out mid-run.
What is next
The roadmap is the parts of the architecture that are designed for, not yet wired: multi-model routing (the category field becomes a real provider switch), team mode (parallel executors across mailboxes and worktrees), and broader harness support so the ADAPT path gets shorter. The enforcement pattern itself is stable — the four hooks are the load-bearing idea, and they are done. What changes next is what runs inside the gate, not the gate.
Provenance
ZOdyssey is a synthesis, not an invention, and I want the lineage visible because it is the honest framing. The pipeline shape and the sub-agent cast are modeled on omo — if you are not familiar, go read it; it is the cleaner expression of the orchestration pattern. The enforcement layer (the four hooks) is the differentiator, not a derivative. The thinking on multi-agent systems is grounded in Anthropic's multi-agent research post and the Building Effective Agents essay, with additional citations to LangChain's multi-agent architecture analysis and an arXiv context-engineering paper in docs/DESIGN.md. The routed skills (TDD, systematic debugging, brainstorming) come from obra/superpowers; the impact-analysis step uses codegraph. Full citations are in DESIGN.md §0 and §15.
It is MIT licensed. Take it, adapt it, use it. If the enforcement-gate pattern makes your orchestrator more reliable, that is the whole point.
The repo is github.com/amartinawi/zodyssey. The two things to read first are docs/DESIGN.md (the principle, load-bearing decisions, and research) and docs/ADAPT.md (how to bolt the delta onto whatever you are already running).
Top comments (1)
Turning the review step into a hook is the right direction. Prompt conventions are easy to skip under pressure; a hard gate makes the workflow observable and gives reviewers a real place to approve, reject, or ask for a smaller change.