Under the hood
For developers who want to extend LazyZCode, contribute to it, or integrate
with it. Companion to the Documentation: there the workflow,
here the machinery. Everything on this page is derived from the actual
source (plugin/, core/, cli/) — no aspirations, just what ships.
System architecture
LazyZCode is three pieces with hard boundaries: a plugin the engine loads, a CLI that owns the loop state, and a state directory your project keeps.
config.json (enabling flows through the engine's official
plugins enable), the loop state lives in the project, and the
plugin ships zero runtime dependencies.Design rule of thumb, in order: Skill > MCP > Tool > Hook. Anything that survives being offline lives in skill text; hooks are the last resort and are written to fail open.
The goal loop, as a state machine
lzy loop is a plain state machine on disk (no daemon). The skill text drives
the model through it; the CLI gates every transition.
lzy loop register <slug> --title "…"
lzy loop plan <plan.md> --review "plan-reviewer: PASS …"
lzy loop start
lzy step done <ID> --evidence "curl /export -> 200, parses as CSV"
lzy loop finish
Evidence binding
The core trick of the whole product: proof is bound to the content snapshot of a commit, so it cannot quietly rot.
lzy loop finish refuses it;
lzy step done re-binds on recapture.Two deliberate consequences: .lazyzcode/ itself never counts as a dirty
worktree, and "tests are green" is just one surface among several, never a
substitute for the named F-item surface.
Hook lifecycle
Six hooks ride the engine's session timeline. All of them spawn through
plugin/hooks/run-hook, which resolves node from PATH, then nvm, then
Homebrew on POSIX (Windows resolves the same manifest line to the
run-hook.cmd twin via PATHEXT and falls back to nvm-windows/Program Files),
so a Dock-launched ZCode (whose hook environment has no node) still
works; the fallback path is reported by lzy doctor's hook-node check.
Extending it
Add a discipline role. Drop a Markdown file with frontmatter into
plugin/agents/; the engine auto-discovers the directory. Roles are
read-only contracts: explorer (recon with file:line evidence),
plan-reviewer (VERDICT: PASS | REVISE), qa-executor (observed raw output,
never inference). Keep any new role's output contract machine-checkable.
Trigger matching lives in plugin/hooks/trigger.js, stratified on
purpose:
| Pattern | Fires |
|---|---|
/^\s*zw(?![a-z0-9_-])/i |
start of prompt only |
/lazyzcode[::]zw/i |
anywhere, full-width colon included |
| `/(^ | [^a-z0-9_-])(ulw |
Hook output contract: only the Stop hook can continue a session, and only
with {continue:true, additionalContexts:[non-empty]} (the engine's own
contract); the other four hooks emit {additionalContext} — inject-only.
Anything else, including a crash, fails open and never traps the session.
Configuration surface: one environment variable. LZY_ZCODE_ENGINE
replaces the engine candidate list; everything else is derived from the repo
and engine state.
Build, test, preview
npm test # node:test contract suite, zero deps
cd scripts/docs-preview # dev-only toolchain (own package.json)
npm install
npm run build # docs/ -> dist/ (Jekyll emulation)
npm run check # link crawl + anchor integrity, exit 1 on any miss
CI runs the suite on node 22 / 24. The docs site builds on GitHub Pages
from /docs (Jekyll, GFM); scripts/docs-preview/build.mjs mirrors that
pipeline locally — Pages adds rouge syntax coloring on top, everything else
matches.
Compatibility notes
- Engine layout detection covers macOS, Windows and Linux (ADR-0011); the distribution matrix is arm64-live-verified, x64 follows the official download matrix by documentation.
- The engine surface this targets: 7 hook events, ≤3 stop-continuations (shared pool), native AGENTS.md injection.
- Headless driving needs the desktop's injected credentials; mechanism is probe-validated, live acceptance deferred.
LazyZCode is MIT. Workflow inspired by lazycodex (MIT); OmO (SUL-1.0) ideas only. Docs structure follows the lazycodex docs with credit.