clanker is a self-improving AI agent harness written in Zig 0.16. It improves its own source through a gated loop, and the same gates guard human changes: a change that cannot pass them does not land.
scripts/setup.shThat is the whole bootstrap: it checks the toolchain, runs
zig build --fetch=all and scripts/apply-patches.sh (re-applying
patches/*.patch to the fetched dependencies; the SIGWINCH patch is
load-bearing for clanker repl and the pty e2e journeys, and build.zig
refuses to compile against an unpatched tree), then zig build,
zig build tools and bun install --frozen-lockfile (the last one only
feeds the JavaScript half of the loop: bun run lint, bun run css:build and
the pre-commit hook's JS check resolve packages out of node_modules, and
bun run lint refuses outright rather than letting bunx fetch an unpinned
oxlint from the registry). The order is the
whole trap: --fetch=all extracts without compiling, and the patch script
cannot patch a tree that is not on disk yet. It prints the remaining steps
when it finishes: zig build test,
./zig-out/bin/clanker init, ./zig-out/bin/clanker gate, and the
repository hooks:
git config core.hooksPath .githooksRequirements are Zig 0.16.0, the release build.zig.zon pins. That pin is
exact rather than a 0.16.x range: zig build, scripts/setup.sh and the
pre-commit hook each refuse any other patch release. Git, Bash, and patch are
required too. Tests also need Bun for the JS suites
and Python 3 for the process fixtures. Full verification additionally needs
shellcheck and ruff; scripts/verify.sh installs the declared JS
dependencies locally and refuses to exit 0 while any of those five is
missing, so a machine without one of them reads as "not verified" rather than
as green. Ruff is pinned too, in ruff.toml as required-version, so a
different release on PATH refuses to run rather than checking a rule set CI
never ran.
Three speeds, slowest last:
zig build quick-check:zig fmt --checkplus a compile of the host binary, no tests. Seconds; enough to know a patch builds before spending a test run on it. It does not compile the WASM guests undertools/zig/(the dependency runs the other way round:zig build toolsdepends on the host, not the reverse), so an edit there needszig build tools(orzig build test, which depends on it) to show a green compile.zig build test -Dtest-filter="<substring>": only tests whose name contains the substring are compiled in. A filter that matches nothing passes with 0 tests; the JS suites still run.zig build test: the full suite (Zig + JS), which takes minutes.
zig build fmt-fix fixes what quick-check reports. Also:
- One JS suite:
bun test ui/app/core/scroll.test.mjs(or whichever.test.mjsyou changed). - Every JS suite, without the Zig half:
bun test ui(bun walks the directory itself; it coversui/appand every suite underui/plugins/, which thejs-suite-coveragegate keeps registered in the test step). - The e2e journeys (
zig build e2e) spawn the realclanker replon a pty, so they need the dependency patches applied: runscripts/apply-patches.shonce afterzig build --fetch=all(idempotent;scripts/verify.shdoes it for you). - Before pushing:
scripts/verify.shmirrors everything CI's verify job runs (shellcheck, oxlint, ruff, SBOM generation, AssemblyScript rebuild-and-diff,clanker gate, e2e), so a red CI run is never the first place you hear about it. Every tool CI uses is one it needs: a missing one printsverify: SKIPPEDand the script exits 1 rather than calling the run CI-equivalent. Install the tool named, or setCLANKER_VERIFY_ALLOW_SKIP=1to accept a partial run knowingly — the summary then names every check that did not run.
clanker gate: build, test, tools, fmt, lint, and the self-integrity gates (provider-kind, test-root-coverage, js-suite-coverage, tool-helper-coverage, webui-budget, sandbox-abi, tools-ts-toolchain, release-contract, reports-inventory, skills-inventory, dep-patches). This is what the self-improvement loop demands of its own proposals, so a human change must clear the same bar.dep-patchesis about your checkout rather than your diff: runzig build --fetch=alland thenscripts/apply-patches.shbefore compiling in each new worktree.- CI: the workflow in
.github/workflows/ci.ymladditionally checks shell scripts with shellcheck,ui/andtools/tswith oxlint, every tracked.pywith ruff (ruff.toml), the SBOM generation, and thattools/ts/dist/*.wasmmatches a clean rebuild (tools/ts/verify.sh).scripts/verify.shreproduces all of it locally. - The pre-commit hook (fast checks over staged files only). Its JavaScript
check needs the
bun installscripts/setup.shruns; without it the hook says it skipped and CI still runs oxlint. Bypass for WIP withgit commit --no-verify.
- Manual changes use a
area: summarysubject line (agent: ...,docs: ...,ci: ...). - Self-improvement promotions are committed as
clanker: <summary> [imp-<id>]by the loop; leave that form to it. - Consumer-visible changes get an entry under
[Unreleased]in CHANGELOG.md (Keep a Changelog style). Release notes are extracted from the changelog, so a shipped change without an entry never reaches the release notes. This obligation is convention, not mechanism: therelease-contractgate checks release-file structure only and never reads the diff, so a missing entry ships green; authors and reviewers are what enforce it. Records-only and internal-docs-only changes are not consumer-visible and need no entry. See the investigation.
tools/ts/dist/*.wasmis committed. After editingtools/ts/*.ts, runbun run build:allintools/ts/and commit the result;tools/ts/verify.shfails on drift otherwise.ui/app/tailwind.cssis generated fromui/app/tailwind.src.cssbybun run css:buildand is committed, becauseclanker serveembeds the file in the tree and has no build step. A utility class written intoui/app/orui/plugins/styles nothing until that runs, andui/app/tailwind.test.mjsfails on the stale sheet.ui/vendor/is vendored third-party JS; do not hand-edit.src/tui/mascot/mascot_frames.zigand the mascot PNGs are generated bysrc/tui/mascot/gen_frames.py; do not hand-edit.
AGENTS.md is the architecture and convention reference: module
layout, WASM-by-default, the sandbox/tool ABI, record stores, and the
self-improvement loop. Follow it for anything that touches those surfaces.
New src/ modules with test blocks must be referenced from the comptime
block in src/main.zig or their tests never run (clanker gate enforces
this; it is not optional).
Open a PR against main; the CI workflow runs on every push and PR. If a
change ships a fix for a recurring operational failure, record it with
clanker reports per the runbook convention in
docs/runbooks/.