iterate-harness
iterate is an open-source project that gives AI coding assistants the ability to repeatedly review and fix code in multi-round autonomous loops. It targets a concrete pain point:
AI assistants tend to "talk a lot but do little": a single conversation only touches a few lines, stops caring about the rest of the repo after glancing at one file, and rarely double-checks what they broke. iterate automates these closing chores — itemized review, per-dimension triage, fix, validate, and iterate again — so AI actually finishes changes and gets them right.
Within that ecosystem, iterate-harness is a dedicated agent harness for the
iterate review/fix loop: repeated multi-dimension code review until findings
converge, deterministic aggregation, atomic fixes validated every round,
and an append-only decision log that makes every iteration auditable. It is
one of three interchangeable components that share the same
iterate.config.yaml and dimension system:
- Core Skill + CLI — a portable AI skill
/iterate+iterateCLI. Conversation-driven multi-round iteration inside Trae / Claude Code / Cursor / Copilot / Codex and 25+ assistants. - iterate-harness — a standalone headless engine (
ih, npm:iterate-harness). This repo — runs the same loop in terminal / CI / git hooks, no conversational assistant needed. - iterate-plugin — a dsh desktop-client plugin. Surfaces the iterate dashboard / review progress inside the dsh UI.
Related project: GlassPane. This harness proves what
the code does; GlassPane proves what the running app does — a macOS runtime-verification engine
whose gp_* tools measure an accessibility tree, a pixel change and crash signals, and return an
evidence pack. The two exchange verifiable records through one published contract layer, so a round
here can treat a measured interface outcome as verified — see
Two-way integration with GlassPane.
It is a standalone agent harness built around the iterate review/fix loop: the kernel agent loop, React TUI, tool/skill/plugin systems and permission layer are iterate-native, with the iterate semantic layer (ported from the iterate skill's TypeScript implementation) plus the engine-level convergence policy at its core.
The harness has been through two major version lines:
- 2.0 dual-mode architecture —
task_modeflips betweeniterate(the 1.x review/fix loop) andcode(a general-purpose agent mode hardened by the defensive kernel) — see #dual-mode-architecture. - 2.1 headless expansion — new CLI-first commands for unattended use:
validate(one-off whitelisted validation in CI),sessions/resume --session(pick up any past run),--templatereview presets (standard/strict/quick),batch(multi-repo ranking),schedule+cron(scheduled reviews),hook install(pre-commit gate), andreport --pr/--html(CI-friendly reporting).
⭐ If this project helps you, please consider giving it a GitHub star — it means a lot to open-source maintenance!
📑 Table of Contents
- 🚀 Quick Start
- 🔌 Model providers
- 🧭 Dual-mode architecture
- 📖 Daily usage
- ✨ Iterate features
- 🔧 The seven iterate tools
- 🔬 Two-way integration with GlassPane
- 🧭 Architecture
- 📦 Install & tests
- 🚑 Troubleshooting
- 📄 License & disclaimer
🚀 Quick Start
# install (npm wrapper; needs Node + Python >= 3.10)
npm install -g iterate-harness
# ...or no-Node one-liner (macOS / Linux / WSL, Python only)
# curl -fsSL https://raw.githubusercontent.com/jingzhao-l/iterate-harness/main/scripts/install.sh | bash
Set your API key first:
export ANTHROPIC_API_KEY=your_key # or use an OpenAI-compatible provider, see below
Launch the TUI to iterate interactively:
ih
Inside the REPL:
/iterate review # dry-run: read-only multi-round review until convergence
/iterate personalize # directional-key 9-category wizard (interactive select + question modals)
/iterate run # normal mode: review → fix → validate → loop
/iterate status # config + onboarding state + drift check
/iterate log # tail the decision log
CLI-first instead — a minimal headless loop:
ih iterate onboard # model-driven project scan → ITERATE.md + config + fingerprints
ih iterate review # dry-run review until convergence
ih iterate run # autonomous fix loop
The first command to run in any project is
ih iterate onboard(or the detection-onlyih iterate onboard --no-aiwhen you don't want a model call). It generates the project knowledge base — the sameITERATE.md+iterate.config.yamlthe skill ecosystem uses. Everything below assumes that's done.
🔌 Model providers
🐳 OrcaRouter — built-in gateway with free models
OrcaRouter is a
built-in OpenAI-compatible gateway provider with free models — e.g.
deepseek/deepseek-v4-flash-free or the orcarouter/free router — billed at
$0, no token cost. All you need is an API key.
No key yet? Just press Enter at the key prompt and the signup page opens in your browser (register through the link above, supporting the project):
ih setup orcarouter # paste an existing key, or press Enter to open the signup page
Already have a key? Activate the built-in profile — only one environment
variable is required (ORCA_KEY); the base URL
(https://api.orcarouter.ai/v1) and default model (orcarouter/auto) are
baked in:
ih provider use orcarouter
export ORCA_KEY=sk-orca-...
Free-tier note: free models still require an API key; long-context prompts can exceed the free-tier prompt cap (HTTP 429 without
Retry-After), so they suit CI / lightweight reviews best.
Other providers
Any OpenAI-compatible provider works. Manage them with:
ih provider list # see available providers and their default models
ih provider use <name> # activate a profile (interactively re-enter the key)
ih provider edit <name> # fix base_url / api_format / default_model / auth_source
🧭 Dual-Mode Architecture
Since v2.0, task_mode decides what the agent does (orthogonal to
permission_mode, which decides what it may touch):
iterate(default) — the classic 1.x loop: deterministic multi-dimension review, per-dimension triage, atomic fixes validated every round, append-only decision log.code— general-purpose agent mode: the full toolset stays available, with the defensive kernel layered on so every edit is safe by construction.
Switch modes with --task-mode on the CLI or press Tab on an empty input
in the TUI (the vertical mode bar on the input's left edge and the mode label
beneath it recolor — primary for code, amber for iterate). Code-mode
workers spawned by the leader inherit the same defensive kernel, so subagents
write with the same guarantees (CLI → AppState → agent_tool → subprocess
backend, design §20.5).
Defensive kernel (code mode)
Three mechanical guarantees (design §20.3.2) — enforced by code, never by prompt:
- Atomic mutations — every mutating file tool is snapshotted before it runs; a failed edit is rolled back automatically (fail-fast + atomic transaction).
- Invariant guarding — after each successful mutation the project
invariants are re-checked; a violation rolls the edit back and surfaces as a
tool error the model must respond to. Invariants come from the
invariantssection ofiterate.config.yaml(ensurefile assertions +commandsper-module command lists), falling back tovalidation.commandswhen noinvariantssection is configured. Commands run only on an EXACT match and refuse shell-chaining metacharacters. - Assumption audit — assumptions the agent declares via
record_assumptionare written to the decision log; a falsified assumption is a fail-fast signal.
# iterate.config.yaml — declare project invariants for code mode
invariants:
ensure:
- pyproject.toml
- src/main.py
commands:
syntax:
- python -m py_compile src/main.py
tests:
- pytest -x
📖 Daily Usage
Onboarding & project knowledge
ih iterate onboard # model-driven scan → ITERATE.md + config + fingerprints
ih iterate onboard --no-ai # detection-only fallback (no model call, channel=cli)
ih iterate status # config + onboarding state + drift check
ih iterate refresh # re-fingerprint manifests, report drift, refresh metadata
ih iterate reonboard # backup, full model re-scan, preserve your user-owned region
ih iterate personalize # 9-category wizard: constraints → config + ITERATE.md user region
ih iterate init # detect the project, generate iterate.config.yaml (config only)
Onboarding note: ih iterate onboard first gates on a configured model
credential, then lets the model explore the project with its read tools
(manifests, 2-3 level directory tree, specs/tests/CI, README — never
.env/keys) and write ITERATE.md with byte-exact AI-maintained /
user-owned region markers. The harness validates the markers, captures SHA-256
manifest fingerprints and writes iterate.config.yaml — untrusted model
output never touches trusted config structure. Both files are byte-compatible
with the skill's onboarding (same markers, same onboarding.fingerprints
schema), so projects onboarded by either ecosystem interoperate. Every later
loop kickoff injects the ITERATE.md knowledge base into the system prompt,
and a drifted manifest (dependency bump, stack change) triggers a
non-blocking warning before reviews.
Review & fix
ih iterate review # headless dry-run (stream-json output available)
ih iterate review --changed # quick review: only files changed vs --ref (default HEAD)
ih iterate review --template strict # review template: standard (default) / strict / quick
ih iterate run # headless autonomous fix loop
ih iterate run --template quick # fix loop with a different review template
ih iterate validate "pytest -x" # run a preconfigured validation command; prints allowed / reject_reason / exit_code
ih iterate resume # resume the last session
ih iterate sessions # list saved sessions (--limit N, --json), then `ih iterate resume --session <id>`
Reports, logs & audit
ih iterate log # tail the decision log
ih iterate log --trend # thin cross-run library summary
ih iterate trend # per-run new/fixed/regressed history + stubborn/open findings
ih iterate trend --json # machine-readable trend report (for dashboards/CI)
ih iterate trend --fail-on-regression # CI gate: fail if a fixed finding came back
ih iterate trend --fail-on-new high # CI gate: fail on new high/critical findings
ih iterate diff # compare two runs: new / fixed / regressed / unchanged findings
ih iterate diff --json # machine-readable comparison (for scripts/CI)
ih iterate diff --from 2 --to 5 # pick any pair of finished runs
ih iterate log --replay # replay the whole run chronologically (relative timestamps)
ih iterate report # render the final report (CI mode, see below)
ih iterate report --pr # post/update the report as a PR comment (gh CLI, idempotent)
ih iterate report --html # single-file HTML report (convergence curve, diffs, shareable)
ih iterate doctor # skill↔harness dimension-system consistency check
Team & live workers
ih iterate agents # every worker the harness is tracking, running first
ih iterate agents --json # machine-readable roster (id/team/kind/status/duration/output)
ih iterate agents --tail 0 # sizes only, no output payloads (cheap for polling)
ih iterate agents --status running --limit 50
ih iterate agents --stop reviewer@team1 --confirm # stop one worker by task id or name@team
Batch & scheduling
ih iterate batch a/ b/ # review multiple repos sequentially, rank worst-first
ih iterate schedule add "0 9 * * 1-5" # daily changed-only quick review (cron, UTC)
ih iterate cron start|stop|status|history # manage the background cron scheduler daemon
Git-hook integration
ih iterate hook install # managed pre-commit hook: 1-round changed-only gate
TUI equivalents:
/iterateslash commands expose the same loops through different entries;/iteratewith no argument prints the project's status, and a subcommand that does not exist prints the current list.
✨ Iterate Features
Review engine & loop
- Deterministic review engine —
iterate_reviewplan / aggregate / meta-review: cross-round dedupe,known_intentionalfiltering, severity sort, convergence math, 6-check report audit — all pure computation, zero LLM judgment. - Two modes —
dry-run(read-only review, never touches files) andnormal(review → atomic fix → validate → loop, validation failure rolls the round back via git isolation). - Engine-enforced convergence —
IterateLoopPolicylives in the kernel query loop: round caps, convergence auto-stop and next-round steering cannot be prompt-injected away. - Findings triage —
iterate_triage: walk findings withyfix /nskip /aalways-ignore;apersists toknown_intentionalso future rounds filter it automatically. - Per-fix diff approval —
require_fix_approvalroutes every file write during a normal-mode loop through an interactive prompt with an inline diff preview — even in full-auto mode; hard denials are never downgraded. - Esc intervention — press Esc mid-loop: the loop pauses at the next round boundary and opens a directional-key menu (skip top finding / narrow dimensions / stop / resume); a second Esc force-interrupts the turn.
- Breakpoint resume — the TUI startup panel summarizes the last finished run (verdict, rounds, severity buckets, last intervention) and
/iterate resumecontinues from the decision log with re-verification of still-reproducing findings. - Finding trend library — every finished run fingerprints findings (
file\|line\|dimension) into.iterate/trend-library.json;ih iterate log --trend//iterate trendreport new / fixed / regressed / stubborn (3+ runs) findings across runs. - Per-run trend history —
ih iterate trendreconstructs every finished run from the decision log and shows what that run added, fixed, or regressed, plus open/stubborn findings;--fail-on-regression/--fail-on-new <severity>turn it into a CI gate. The same view is a page in the WebUI (see below). - Run comparison —
ih iterate diff [--from N] [--to M] [--json]answers "what changed between these two runs?" by classifying every finding against the earlier run: new / fixed / regressed / unchanged. It joins the decision log to the engine's fingerprint diff, and regressions are resolved against the trend library's fixed records, so a finding fixed earlier and present again is never reported as merely "new". The same comparison is a WebUI page (below). - Team roster & stop channel —
ih iterate agentsis the one place that could answer "which worker is stuck and why": subprocess teammates, in-process teammates and plain shell tasks all register the same task-manager store, and the roster is that store's projection — running first, with kind/backend, elapsed time, output size and a bounded output tail.--stop <id> --confirmis the matching intervention channel (task id orname@team, audited server-side). The WebUI has the same view as a live page.
Reports & integration
- Convergence dashboard — live React TUI panel: per-round findings trend, per-dimension counts with per-dimension USD estimates, running metered cost, converged badge.
- CI / PR mode —
ih iterate report --github --fail-on highturns the final report into GitHub Actions annotations with a severity-based exit-code gate for PRs;--prposts (and on later runs UPDATES) a Markdown report comment via the gh CLI (marker lookup paginates, so giant PRs stay idempotent) — every failure mode degrades gracefully, never breaking the exit-code policy. - HTML single-file report —
ih iterate report --htmlrenders the run as ONE offline.htmlfile: SVG convergence curve, severity/dimension bars, findings table with failure scenarios, and colorized per-fix diffs — share it as a CI artifact. - Decision replay —
ih iterate log --replayre-plays the run chronologically with relative timestamps ([+90s] r1 review_result newFindings=3) — watch how the loop unfolded like a recording. - Changed-only quick review —
--changed [--ref <ref>](CLI +/iterate review --changed) pins the whole loop to the git delta: the kickoff, review plan and every reviewer prompt carry the explicit changed-file listing. - Batch ranking —
ih iterate batch repoA repoB …reviews multiple repos sequentially and ranks them worst-first by a severity-weighted score; one failing repo never kills the batch. - Scheduled review —
ih iterate schedule add "0 9 * * 1-5"registers a cron job that runs the changed-only quick review daily (UTC) with--clean-ok; new-vs-stubborn findings surface via the trend library. The WebUI Schedules page (g s) shows the registry, the next run and the last executions, so "did last night's run pass?" no longer needs a terminal. - Cron scheduler daemon —
ih iterate cron start|stop|status|historymanages the background daemon that executes scheduled jobs — start it once, scheduled reviews keep running unattended, historizable (--limit,--json). - Environment self-check —
ih doctordiagnoses the local machine in one network-free pass: interpreter floor, package import, config parse, active-profile credential presence (never the secret itself), data-dir writability, project/git state, iterate config + trusted validation commands, run-lease state, Docker sandbox availability and the pre-commit hook — each with an executableremedy. Exit code is CI-friendly (1 on a hard failure, or on any warning with--strict);--jsonis for scripts. The WebUI 自检 page (g k) renders the same report, so "is this machine ready to iterate?" no longer needs a terminal.
WebUI management console
ih web serve starts a loopback-only console (--project <root> to pin a
project, --no-browser for servers). Pages: Dashboard, Runs timeline, Trend,
Run diff, Gate, Schedules, 自检 (doctor), Workspaces, Team,
Checkpoints, Budget & Rate, Config, Reports. The Trend
page (g t shortcut) renders the same per-run new/fixed/regressed history and
stubborn/open findings as ih iterate trend, live-updating as new decision-log
entries arrive — no terminal needed to tell whether a loop is actually
converging. It also exports the window it is showing: a Markdown digest to paste
into a PR or weekly report, a one-row-per-run CSV for a spreadsheet, and the
exact page payload as JSON. The Run diff page (g v) picks any two finished
runs from dropdowns and shows the exact findings that appeared, disappeared,
came back or stayed, worst-first — the same classification as ih iterate diff,
without copying run indices into a terminal. The Team page (g a) shows every worker the serving process is
running right now — subprocess teammates, in-process teammates and shell tasks
side by side, with elapsed time, output size and the last thing each one said —
and is the only way to stop a single stuck worker from the browser (二次确认 +
audited); it polls while any worker is live and stops polling once the roster is
quiet. The Gate page (g j) is the console's half of the GlassPane gate:
the latest recipe-gate answer, the refusal code with its executable remedy, and
every round that published one — click a round to inspect its verdict, steps and
fix evidence, or copy the exact ih iterate gate text. With no record the page
says 尚未记录 (absence, not a pass), so a project that never asked cannot look
like one that passed.
The Schedules page (g s) is the console's view of the machine-level cron
registry: every registered job with its cron expression, timezone, freshly
computed next run and last result, the scheduler's liveness (a stopped daemon
is stated as stopped — the page says nothing will fire), and the recent
execution history with exit code, duration and bounded stdout/stderr tails. Jobs
are still added and removed from the CLI (ih iterate schedule); the page is
read-only and machine-scoped, so it does not follow the project-root selector.
The 自检 page (g k) is the console's half of ih doctor: a deterministic,
network-free report of the local machine's readiness — interpreter floor,
package import, config parse, active-profile credential presence (never the
secret itself), data-dir writability, project/git state, iterate config + trusted
validation commands, run-lease state, Docker sandbox availability and the
pre-commit hook — each with an executable remedy the operator can copy. It is
read-only and shows a failure-first headline, so a broken machine is visible
before a wall of green checks.
Cost, resources & gates
- Cost transparency — token usage → per-round and cumulative USD from a built-in price table (overridable per model).
- Token budget enforcement —
token_budgetcaps the whole run at the engine level (hard-stop + closing report);iterate_review(operation="aggregate", dimension_usage=…, dimension_usage_io=…)audits per-dimension usage, relays reviewer-reported totals into the engine cost meter — dimensions reporting an input/output split bill at exact prices, bare totals at the blended price — and steers the next round away from exhausted dimensions. - Per-dimension resources —
dimension_resourcesiniterate.config.yamlsets per-dimensionmodel/concurrency(1–8) /token_budget— a strong model for security, a fast one for style-tests; the plan carries them into every reviewer spawn. - Threshold gates —
thresholds.max_critical/max_high/max_medium/max_low(global or per dimension) cap finding counts in the final report — a violation flips the verdict toneeds_revisionand fails theih iterate reportexit code (threshold gate: FAIL). - CLI validation runner —
ih iterate validate "<command>"runs a preconfigured validation command as a one-off from the shell and printsallowed/reject_reason/exit_code— ideal for scripting defensive pre/post-checks in CI, the same runner the tools layer invokes. - Session listing —
ih iterate sessionslists saved sessions (summary / model / timestamp / message count,--limit/--json) thenih iterate resume --session <id>picks one up — no need to remember which run was which. - Review templates —
--templateonreview/run(and editable prompt presets) switches betweenstandard(default),strict(conservative, safety-first) andquick(impact-only) review prompts per invocation.
Onboarding, security & config
- Detection-driven init —
ih iterate initprobes marker files (package.json / pyproject / go.mod / Cargo.toml / …), infers the test command from real evidence, suggests dimensions (frontend deps unlockfrontend-backend/ui-ux), previews the yaml and writes it only after confirmation —/iterate initdoes the same in the TUI. - Model-driven onboarding —
ih iterate onboardchains auth gate → detection evidence → model scan →ITERATE.mdknowledge base (AI/user region markers) + manifest fingerprints;refreshre-fingerprints,reonboardre-scans while preserving your notes; every kickoff injects the knowledge base and warns on drift — skill-compatible artifacts. TUI onboarding gets its fingerprints auto-captured on the next review/run — no manualrefreshneeded. - Personalization wizard —
ih iterate personalizewalks the skill's 9 categories (protected paths, risk areas, known-intentional, dimension focus, fix priority, forbidden fixes, notes, conventions, extra validation commands): structured rules land initerate.config.yaml(protected paths ALSO enforced by the kernel permission layer), free text lands in theITERATE.mduser region, and every kickoff carries the constraints; extra commands pass a strict whitelist before merging intovalidation.commands. - Security boundaries as code —
protected_pathsandforbidden_fix_patternsfrom settings are auto-assembled into the permission layer (deny path rules + write-payload regex); validation commands run through an EXACT-match allowlist. - Pre-commit hook —
ih iterate hook installwrites a MARKED managed.git/hooks/pre-committhat runs a 1-round changed-only review and gates the commit on--fail-onseverity; refuses to touch foreign hooks, skippable viaITERATE_SKIP_HOOK=1/--no-verify. - Dimension doctor —
ih iterate doctorchecks the whole dimension system in one shot: bundled canonical definitions vs harness internals vs youriterate.config.yaml(unknown dimension keys, inert resource/threshold entries, personalization references outside the enabled set); exits 1 on drift so CI can gate on it. - Schedule timezones —
ih iterate schedule add "0 9 * * 1-5" --timezone Asia/Shanghaievaluates the cron in local time (stored UTC-normalized) so "daily at 9" means 9 where you live.
Recording & dual-mode
- Decision log — append-only
.iterate/decision-log.jsonl: every round, fix, validation and triage decision is recorded. - Project knowledge —
ITERATE.mdproject knowledge + per-project structured personalization (9 categories). - Dual-mode architecture —
task_mode(code/iterate) orthogonal topermission_mode:iteratekeeps the 1.x review/fix loop,codeis a general-purpose agent mode hardened by the defensive kernel —--task-modeon the CLI, Tab in the TUI. - Defensive kernel (code mode) — atomic mutations (snapshot → auto-rollback on failure), invariant guarding (
invariants.ensure+invariants.commands, falling back tovalidation.commands; EXACT-match, metachar-refusing commands) and assumption audit (record_assumption→ decision log) — all enforced mechanically. - Worker defensive inheritance —
--task-modethreads through CLI → AppState →agent_tool→ subprocess backend (design §20.5): code-mode subagents run the same defensive kernel.
🔧 The seven iterate tools
iterate_config— effective config (defaults +iterate.config.yamloverrides)iterate_validate— run a preconfigured validation command (EXACT match only)iterate_review— deterministic engine: plan / aggregate / meta-reviewiterate_decision_log— append-only decision logrecord_assumption— declare / verify an assumption, persisted to the decision log (code-mode audit trail)iterate_context— SKILL.md / ITERATE.md / personalization contextiterate_triage— interactive y/n/a findings triage withknown_intentionalpersistence
Slash command /iterate and the bundled iterate skill provide the same loops through different
entries — /iterate with no argument prints the project's status, and a subcommand that does not
exist prints the current list (status, config, onboard,
personalize, review, run, resume, log, trend, diff, gate, glasspane-ledger, report, init, doctor,
dimensions, validate).
🔬 Two-way integration with GlassPane
When the code under review is a GUI app, "the tests pass" says nothing about whether the interface
changed. GlassPane answers that on macOS, and this harness
is its counterpart on the review side. The exchange runs in both directions — measured evidence
comes in, a gated verdict goes back — and in both directions what crosses is a verifiable record
rather than prose, through the same published contract layer (iterate-kernel).
From GlassPane into the loop:
- Evidence lands in an audit chain.
gp_last_evidenceis the call that answers with an evidence pack — the othergp_*tools return no pack, which is why a dimension declared on them stays reported as unverified — and the harness appends a hash-chained ledger entry for every decision recorded from a pack.fix_proofreads such a chain through the kernel's verifier — this project's.iterate/decision-log.jsonl, or GlassPane's own ledger, named once withih iterate glasspane-ledger <absolute path>(/iterate glasspane-ledger <path>in a session). The link is written only after the whole chain verifies, and it pins the chain's first entry: a ledger that was replaced rather than extended stops qualifying. A broken chain is refused with the line number where it breaks — there is no lenient fallback for the question "is this claim verifiable". - A review dimension travels with the call. The run's plan is published to
.iterate/run-plan.json— that file is what this project writes, andGLASSPANE_RUN_PLANis the operator's explicit hand-off for a run whose plan lives elsewhere —dimensionis declared on the verification calls that accept it, and what the surface you called echoed back is witnessed. The worddimensionis stripped before a request leaves for the engine, so the echo is the shell's or the fork's own line, never a claim the engine made. A decision naming a different dimension for an operation id that was already witnessed under one is refused (KERNEL_E_DIMENSION_CONTEST) instead of becoming a second, contradictory record — and the loop's own coverage accounting reports an unverified dimension as unverified. - A failing check re-verifies rather than re-guesses. A round whose invariant re-check fails
publishes a kernel-built re-verification request over the host's MCP transport. The requests it
builds are
gp_last_evidence+gp_diagnoseper recorded operation; the allow-list that decides what may be asked at all is the five read-only tools (gp_probe_status/gp_observe/gp_diagnose/gp_last_evidence/gp_recent_reports), and every answer is classified per content item. A request is built from a failing report only — so a passing round sends nothing, and says why. - A fix is gated, not taken on trust. Before a repair recipe may advance, the loop reads three
measured things: this round's invariant verdict, the project's recipe
(
recipeConfigPathfrom the tool surface's project registry, orGLASSPANE_RECIPE), and a chain-verified ledger line recording a passing outcome against an operation id. Anything absent or unmeasured is refused by name (RECIPE_GATE_E_*). The answer is recorded in the project's ledger — on the round that sends, and on a round that passed and therefore sent nothing — and both doors read it back:/iterate gatein a session,ih iterate gatein a terminal, and the WebUI's Gate page (g j) in the console. A contract refusal writes no ledger bytes, because a decision that was never made must not become an audit entry.
From the loop back to GlassPane:
- A gate answer both sides can read. The verdict is a ledger line, not a message.
ih iterate gatein a terminal and/iterate gatein a session read the same record — on the round that sent the re-verification request and on the round that passed and therefore sent nothing — and "refused by contract" stays distinguishable from "no decision was made", because only the latter leaves no bytes. - GlassPane's own repositories run through this loop.
ITERATE.mdanditerate.config.yamlin the GlassPane repository are onboarding artifacts of this ecosystem, so an/iteraterun there reviews the engine and the MCP shell against the same validation commands its CI enforces — measured 2026-09-22:iterate doctor18 checks ok,iterate fingerprintno drift.
The division of labour is the point: GlassPane decides what the application did, this harness decides whether a round may treat that as verified, and neither judges the other's evidence. The gate reports and withholds — it does not execute a recipe, because neither side ships a recipe executor; the progress pointer is written by whoever performs the fix.
The exchange from the other side — the ledger format, what a gp_* transcript records, and how
compaction keeps the anchors — is documented in the GlassPane Harness README
(Two-way integration with iterate-harness),
and from the engine's own side in
GlassPane's two-way integration section.
🧭 Architecture
src/iterate_harness/
├── iterate/ # semantic layer (Python port of the TS skill)
│ ├── review.py # dedupe / known_intentional filter / severity sort / convergence
│ ├── meta_review.py # 6-check report consistency audit
│ ├── config_loader.py# Master + Overrides merge
│ ├── validate.py # EXACT-match validation runner
│ ├── decision_log.py # append-only JSONL
│ ├── loop_policy.py # engine-level convergence enforcement + cost meter
│ ├── personalization.py # 9-category per-project store
│ ├── worktree_flow.py# git isolation: enter/commit/exit + rollback
│ └── prompts.py # canonical dry-run/normal loop templates
├── defensive/ # code-mode defensive kernel (design §20.3.2)
│ ├── kernel.py # per-query coordinator: snapshot → post-check → commit/rollback
│ ├── transaction.py # atomic file transaction buffer
│ ├── invariants.py # ensure assertions + exact-match command guard
│ └── assumptions.py # assumption audit trail → decision log
├── engine/ # kernel agent loop (upstream + iterate control block)
├── permissions/ # checker + iterate auto-assembly (protected_paths …)
├── tools/iterate_tools.py # the seven iterate_* tools
└── ui/ # React TUI backend host + review_progress protocol
📦 Install & Tests
Choose one install path:
- npm (easiest):
npm install -g iterate-harness— a thin wrapper that pip-installs the release tarball into a managed venv (~/.iterate-harness-npm) on first run and keeps the version in lockstep with the npm package - macOS / Linux / WSL:
bash scripts/install.sh(clone + venv + editable install, linksihanditerate-harnessinto~/.local/bin) - Windows (PowerShell):
scripts/install.ps1 - From a checkout:
bash scripts/install_dev.sh - Requires Python ≥ 3.10; Node.js ≥ 18 enables the React TUI (skipped otherwise — the plain fallback UI still works)
Run the tests:
python -m pytest tests/test_iterate -q # semantic layer + kernel integration
python -m pytest -q # full suite
# the same process watchdog CI arms, for hanging a run on purpose:
ITERATE_TEST_WATCHDOG=420 python -m pytest -q
CI does not run the suite as one process. scripts/split_suite.py turns
pytest --collect-only -q into buckets of whole test modules (first-fit-decreasing) and the
Python tests job fans out as python-version × bucket, so each leg is small enough to end —
which is the precondition for its log existing at all. The split cannot quietly shrink coverage:
the script refuses unless pytest's own printed total equals the node ids it parsed, each bucket
re-collects and checks its count against its plan row (--expect-total), and more buckets than
modules is a refusal rather than a green job that tests nothing.
Four bounds nest, innermost first: a test (timeout in pyproject.toml) < the process
watchdog (ITERATE_TEST_WATCHDOG, unset locally, 420 s in CI) < the step budget
(PYTEST_HARD_STOP) < the job ceiling. tests/test_ci_workflow_shape.py,
tests/test_split_suite.py, tests/test_ci_suite_step_program.py and tests/test_test_watchdog.py
pin that shape — the last one runs the CI step's own bash against stubbed setsid/uv/ps, and the
one before it hangs a test on purpose to prove the watchdog names it.
One closed defect worth naming, because it was invisible from every instrument pointed at it. The
bucket 06 legs used to leave the runner unable to complete the step — no log, no artifact, nothing any
in-step bound could end. A dispatch-only diagnostic line (suite-bisect.yml: one module per leg, with the
module list taken from CI's own plan rather than typed in) put it on a single file,
tests/test_services/test_cron_scheduler.py — 31 of 32 legs ended, that one did not, and it was the only
leg that left no log. The cause was a signal aimed at a number instead of at a process.
_kill_process_group ran os.killpg(os.getpgid(pid), SIGKILL), and the mock process in test_timeout_job
answers pid.__index__() with 1, which on Linux is kill(-1, SIGKILL) — "every process the caller may
signal", the step's own host among them. And read_pid() trusted whatever number the pid file held, so
stop_scheduler() would SIGTERM a recycled stranger; measured, it killed a live process that had never
seen this daemon and then logged that the pid had "survived SIGKILL", because a zombie answers signal 0.
Now the pid file records the claiming process's command line, read_pid() requires liveness (a zombie is
not alive) and continuity — compared by argv tail, since interpreters rewrite argv[0] after exec — and
killpg may only signal a group this job created with start_new_session. Python tests is 12/12 green
on that fix: bucket 06 now prints pytest_exit=0 stopped=finished elapsed=38s and deletes its own
diagnostic ref. If a bucket ever stops ending again, suite-bisect.yml is the first move, because a named
module is a fixable one.
🚑 Troubleshooting
TLS / SSL Certificate Errors
Symptom: SSL: CERTIFICATE_VERIFY_FAILED or certificate verify failed during API calls.
Causes & fixes:
- System CA bundle outdated — Run
pip install --upgrade certifior update your OS certificates. - Corporate proxy / MITM — Set the
REQUESTS_CA_BUNDLEorSSL_CERT_FILEenv var to your enterprise CA cert. - Self-signed local endpoint — If using a local model server (ollama, lmstudio), set
auth_source: localin the provider profile (which disables cert verification for localhost).
Authentication / API Key Errors
Symptom: 401 Unauthorized or 403 Forbidden during model API calls.
Causes & fixes:
- Missing or expired key — Run
ih provider use <profile>and follow the interactive prompt to re-enter the key. - Wrong auth source — Verify the provider profile's
auth_sourcematches your credential slot. Useih provider listto check, thenih provider edit <name>to correct. - Rate limited — See "Rate Limiting / Quota" below.
Rate Limiting / Quota Exceeded
Symptom: 429 Too Many Requests or quota exhaustion errors.
Causes & fixes:
- Too many requests per minute — Set
max_turns_per_minutein the harness settings oriterate.config.yamlto throttle the loop. - Token budget exceeded — Set
token_budgetorbudget_usdiniterate.config.yamlto cap per-run spend. - Provider account quota — Check your provider's usage dashboard and upgrade the plan if needed.
Checkpoint / Resume Failures
Symptom: Resume cannot find the last checkpoint, or the checkpoint is stale.
Causes & fixes:
- Checkpoint cleared — A checkpoint is cleared after a successful run. Only incomplete/interrupted runs have valid checkpoints.
- Stale worktree — If
worktree_isolation: true, a previous abnormal exit may leave stale worktrees. Rungit worktree pruneto clean them up. - Manual intervention — If you modified files inside the worktree, the checkpoint may be invalid. Start a fresh run instead.
Provider / Model Not Found
Symptom: model not found or unknown provider errors.
Causes & fixes:
- Typo in model name — Run
ih provider listto see available providers and their default models. - Custom provider misconfigured — Run
ih provider edit <name>to verify thebase_url,api_format, anddefault_modelfields. - Local endpoint not running — For local/ollama providers, verify the server is running:
curl http://localhost:11434/api/tags.
📄 License & Disclaimer
License & Attribution
MIT. iterate-harness is maintained at jingzhao-l/iterate-harness. The iterate semantic layer originates from the iterate-skill project.
⚠️ Disclaimer
This project is provided "AS IS", without warranty of any kind, express or implied, including but not limited to the warranties of merchantability, fitness for a particular purpose and noninfringement.
Automated code review and fixing carries inherent risk. All changes produced in normal mode are generated by AI models and may introduce bugs, regressions, or unintended behavior. Before merging, you should:
- Review every diff before applying it to your main branch or pushing.
- Make sure your project is under git control and can be rolled back (
git restore, revert, or restore from backup). - Run your project's own test suite and build checks after each round of fixes.
- Never run this on secrets, credentials,
.env, or files that must not be modified — configureprotected_pathsaccordingly.
Users are solely responsible for the code that is generated, modified, or committed as a result of using this project. By using it, you acknowledge that neither the maintainers nor contributors are liable for any loss, damage, or legal consequences arising from its use.
Metadata
Release files for iterate-harness 2.16.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| iterate_harness-2.16.0-py3-none-any.whl | Python 3 | none | any | Details for iterate_harness-2.16.0-py3-none-any.whl |
Release files / iterate_harness-2.16.0-py3-none-any.whl
| Download URL | iterate_harness-2.16.0-py3-none-any.whl |
|---|---|
| Size | 1.1 MB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
64fb0a40232230f766c1f4aee9fac4445f0a3bf3337ca412a0a69f4524b23cbd
|
|
BLAKE2b-256 checksum How to use checksums |
b0e2895f7afdbbef3a4193e3d0e2dffbdc5a82095a303752f4eb994959b6f028
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
twine/7.0.0 CPython/3.11.17
|
Security for iterate-harness 2.16.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.