# unlazy: gate-based completion discipline for AI coding agents

> unlazy is an MIT-licensed skill for Claude Code and Codex CLI that makes an agent write a checkable acceptance ledger before it claims a task is done. The gate checker is the interesting part, and it is also where the design gets opinionated.

**Leonxlnx/unlazy** — Anti-laziness skill for AI agents. Core: the Depth Tree method, which splits a task N layers deep and gives every leaf the full time budget of the whole task, so effort multiplies with depth. Grounded in 2025-2026 research on model laziness, underthinking and premature completion.

- Repository: https://github.com/Leonxlnx/unlazy
- Stars: 3,720 · Forks: 274
- Language: JavaScript
- License: MIT
- Published: 2026-09-09 · Updated: 2026-09-09 · Language: en
- Canonical page: https://hysenlabs.com/projects/leonxlnx-unlazy

## The failure unlazy is aimed at: an agent declaring victory without evidence

The problem unlazy addresses is not that agents cannot write code. It is that they finish early and say so confidently. A model asked to refactor a module can produce a plausible diff, summarize it as complete, and never run the migration paths it just changed. The README frames this as completion discipline: "Write the acceptance ledger first. Execute reviewed checks. Reverify returned work. Report only what the evidence supports."

The intended user is someone running substantial agent work through Claude Code or Codex CLI, where a task has more than one acceptance condition and a wrong completion claim costs real time. It is not aimed at one-line edits. The README's own trigger example is a refactor plus verification of every migration path, which tells you the target is multi-step work with a verifiable outcome. If your task has no executable oracle, the gate machinery has nothing to bind to and you are left with the manual-gate path, which is a weaker guarantee.

## The gate contract: CHECK, EXPECT and EVIDENCE lines

A ledger is a Markdown file, conventionally GATES.md, made of gates. Each runnable gate carries an id, a human description, a CHECK line of shell code, an EXPECT line, an optional CWD, and an EVIDENCE line. The README gives this shape:

```markdown
# Gates: pricing behavior

- [ ] G1: pricing fixtures render the expected tiers
  CHECK: node scripts/verify-pricing.mjs
  EXPECT: pricing verification passed
  EVIDENCE: pending
```

A gate passes only when the process exits 0 and EXPECT matches the combined output. That conjunction is the whole mechanism, and it is stricter than it looks: a script that exits cleanly but prints nothing matching EXPECT stays unmet, and a script that prints the magic string but exits nonzero also stays unmet. The README is explicit that the checker "can prove only the command oracle you declare" and cannot infer that an English title and arbitrary shell code mean the same thing. That is an honest statement of the boundary, and it is the sentence to reread before trusting any green ledger.

Evidence is not free text. Canonical automatic evidence begins with a versioned full SHA-256 digest of the parsed CHECK, EXPECT and raw CWD definition, followed by the exit and successful-output fingerprint, then capped environment details. A checked runnable gate whose evidence is missing, ordinary prose, legacy, malformed, or definition-mismatched is treated as stale and unmet. The README states plainly that this unkeyed binding detects structural drift, not ledger tampering: anyone who can edit the ledger can forge canonical-looking evidence. Manual gates with ordinary human evidence remain compatible, so a mixed ledger is allowed.

## Installing unlazy and running a first ledger

The README points at the skills CLI for supported agents, with -g for a user-level install and --all for every detected agent:

```bash
npx skills add Leonxlnx/unlazy
```

Manual installs go into the agent's skill directory. The README lists Claude Code at ~/.claude/skills/unlazy and Codex CLI at ~/.codex/skills/unlazy; you clone the repository into that path. Invocation is /unlazy where slash skills are supported, $unlazy in Codex, or a natural-language trigger. The checker and the optional hook require Node 16 or newer and use no third-party runtime packages, which matches the engines field of >=16 in package.json.

For a solo task, copy templates/gates-leaf.md to GATES.md, replace every placeholder, and inspect it without executing anything:

```bash
node <path-to-skill>/scripts/gate-check.mjs --status GATES.md
```

The README calls --status the only mode that is always non-executing. On a new oracle with no exact approval record, a normal run prints the resolved command, expectation, working directory, shell and PATH without executing:

```bash
node <path-to-skill>/scripts/gate-check.mjs GATES.md
```

The README warns against treating normal mode as a permanent dry run, because once the exact oracle is approved, normal mode can execute it. To actually run the ledger, read every command and called script first, then approve:

```bash
node <path-to-skill>/scripts/gate-check.mjs --approve GATES.md
```

Re-running everything, including gates already marked complete, uses --reverify:

```bash
node <path-to-skill>/scripts/gate-check.mjs --reverify GATES.md
```

--help documents the complete current CLI. Note the version caveat: the source targets 2.1.0, the README does not identify it as a tagged GitHub release, and it suggests pinning an exact commit when you need an immutable installation.

## What the checker refuses to do, and the limits that follow

The parser is deliberately fussy. It rejects zero-gate ledgers, duplicate ids, incomplete runnable gates, invalid expectations, and abandonment with a missing reason or an unknown gate id. It ignores fenced examples, preserves CRLF or LF when updating, and inserts a missing evidence line when needed. A valid abandonment is not a pass: the checker exits 1 with HANDOFF REQUIRED, and Stop allows the agent to exit while reporting the qualified ids. That is a sensible distinction, since abandoning a gate and satisfying it are different outcomes, but it means exit code 1 is not always a failure you should chase.

The 1 MiB output limit is a real constraint. Both the captured stdout/stderr payload and the canonical UTF-8 combined string used by EXPECT and its fingerprint must fit; the README states the checker never truncates a larger matcher string into success. A verbose test suite that dumps megabytes of logs will trip this, and the fix is on your side: make the verification script print a success-only marker rather than the full transcript. The README's own gate-writing advice points the same way, recommending gates that read the named artifact, print a success-only marker after all assertions pass, test an absence check against a known positive control, and measure supplied figures instead of copying them into EXPECT.

The sharpest limitation is stated by the project itself. Approval is consent, not a sandbox. Approval records live under ~/.unlazy/approved by default, and UNLAZY_APPROVAL_DIR can select another owner-private real directory whose canonical target stays outside the checked repository. Symlinked stores and linked, replaced or non-private records fail closed. Each record binds the absolute ledger and gate, exact CHECK and EXPECT, resolved CWD and shell, timeout, output and regex limits, regex worker limits, platform, and full inherited PATH. Editing any bound input requires approval again. But approval does not hash called scripts, fixtures, dependencies or other transitive inputs, and --status and Stop do not inspect those artifacts. If a dependency changes under an approved gate, the approval still stands. The README tells you to reinspect changed dependencies and run --reverify, which is manual work the tool will not do for you.

## Shell and PATH behaviour, especially on Windows

Shell resolution is a three-step chain: --shell first, then UNLAZY_SHELL, then Node's platform default, which is /bin/sh on Unix and process.env.ComSpec on Windows with the platform fallback. Checks inherit the launch environment, including PATH. The README gives a concrete consequence: a checker launched from Git Bash can see Unix-like tools that the same checker launched from PowerShell does not. Passing --shell changes the interpreter; it does not install grep, tail, tr or any other external program. The README's recommended portable style is to call repository-owned Node scripts, which is why the example gates use node scripts/verify-*.mjs rather than shell pipelines.

There is a second-order effect worth flagging. Parent re-verification should use the same declared shell and required toolchain, and the README treats a shell or PATH mismatch as a failed verification to resolve, not as successful evidence. So if a subagent or a different machine produces evidence under a different environment, you are expected to treat that evidence as unresolved rather than passing. That is defensible, but it makes unlazy a poor fit for teams that want to verify work across heterogeneous machines without normalizing the environment first.

## How unlazy differs from plain test suites and from agent frameworks

The obvious alternative is the one most teams already have: a test suite plus a rule that the agent runs it. The difference is where the acceptance claim lives. In a plain setup, the agent decides which command to run and then narrates the result; the human reads the narration. In unlazy, the ledger is written first, the command and its expected marker are fixed in the file, and the checker decides whether the gate is met. The agent cannot quietly substitute an easier command, because the approval record binds the exact CHECK and EXPECT strings and editing either one requires fresh approval.

That is a narrower and more mechanical contract than what agent orchestration frameworks offer. Those generally give you multi-agent routing, tool registries and tracing; unlazy gives you a ledger format, a checker, and an approval store. It is not trying to be a runtime. The README's orchestration section points at parallel work, but the enforcement surface is still the gate file. If you already have CI that runs the same commands, unlazy's value is mostly in the pre-completion step: catching the case where the agent reports done before CI ever runs. If your CI is fast and mandatory, the overlap is large and the extra ledger is overhead.

A second alternative is simply prompting harder: instruct the model to verify its work and show output. That costs nothing to adopt and fails in a specific way, because the same model that skipped verification is the one judging whether verification happened. unlazy moves that judgement into a Node script with an approval record. The trade is real: you gain an external arbiter and you take on a ledger to maintain.

## Maintenance, versioning and licence

The repository is not archived and the last push was on 2026-09-03. There are no retrieved GitHub releases, and the README says the current source targets 2.1.0 without identifying it as a tagged release, pointing at CHANGELOG.md for the unreleased change set. Practically: if you need an immutable installation, pin an exact commit, because the version string in package.json is not backed by a tag you can point at. package.json sets private to true, so this is not a package you publish or depend on as a library; the npm script runs a chain of test files (run-tests, dispatch-tests, hardening-tests, stress-tests, lint-tests, contract-tests, self-check) via node.

The runtime surface is small, which keeps upgrade cost low: Node 16 or newer, no third-party runtime packages, and a checker plus an optional hook. The cost that does not go away is your ledgers. Every gate you write is a maintenance item, and the README's own advice about absence checks needing a known positive control and figures being measured rather than copied into EXPECT means a careless gate can pass while proving nothing. The advisory gate-lint.mjs exists to catch mechanically weak ledger patterns, with --strict when warnings should fail; running it is the cheapest way to keep ledger quality from decaying.

Licence is MIT, per the repository's LICENSE file. That permits commercial and private use with the usual attribution and warranty disclaimer, but it says nothing about the security posture of the commands your ledgers execute. Since CHECK lines are shell code and approval is consent rather than isolation, the licence grant and the operational risk are separate questions. Nothing here is legal advice; read LICENSE and SECURITY.md, which the README references for the bounded digest pattern when you need user-designed dependency identity.

## Conclusion

Adopt unlazy if your agent work has an executable oracle (a test script, a build, a fixture comparison) and you want the completion claim bound to that oracle rather than to the model's own summary. Skip it if your acceptance criteria are genuinely subjective, or if you work on Windows without a stable shell and PATH story, since the README treats a shell or PATH mismatch as a failed verification rather than a passing one. Verify three things before you rely on it: that your gates pass the advisory gate-lint.mjs without --strict warnings, that node scripts/gate-check.mjs --status GATES.md agrees with what you think each CHECK line does, and that your approval directory under ~/.unlazy/approved is a real owner-private directory outside the repository.

## FAQ

### What does "unlazy" mean in this project?

It is the name of an anti-laziness skill for AI agents, built around a gate contract that makes an agent define checkable acceptance criteria before it reports work as complete. The README describes it as completion discipline for substantial agent work, backed by runnable gates.

### Is unlazy a real word outside this project?

The README does not address the word's status in general English, and the project does not claim it as a dictionary term. Treat unlazy as the name of the skill, not as a vocabulary entry.

### How do I install the unlazy skill?

The README gives npx skills add Leonxlnx/unlazy for supported agents, with -g for a user-level install and --all for every detected agent. Manual installs clone the repository into ~/.claude/skills/unlazy for Claude Code or ~/.codex/skills/unlazy for Codex CLI.

### Does unlazy execute the commands in my GATES.md file?

It can. The README states that --status is the only mode that is always non-executing, that a normal run on an unapproved oracle prints the resolved command without executing it, and that once the exact oracle is approved, normal mode can execute it. --approve and --reverify run the checks.

### Is unlazy a sandbox for agent commands?

No. The README states that approval is consent, not a sandbox, and that approval does not hash called scripts, fixtures, dependencies or other transitive inputs. It also notes that the unkeyed evidence binding detects structural drift, not ledger tampering.

## Sources

- [Issues](https://github.com/Leonxlnx/unlazy/issues)
- [Leonxlnx/unlazy on GitHub](https://github.com/Leonxlnx/unlazy)
- [License: MIT](https://github.com/Leonxlnx/unlazy/blob/main/LICENSE)
- [README](https://github.com/Leonxlnx/unlazy/blob/main/README.md)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/leonxlnx-unlazy
