ADR cartographer + completeness auditor. Read-only on code, reasonably
idempotent: observes what the code implements and emits committable Nygard
ADRs in docs/adr/, plus a feature-completeness audit; files optional
handoff issues to adr-rechallenge (re-challenge) and feature-gap-fill
(gap completion). Template: docs-refresh (Doki).
dev+239203525cc8 · no sandbox (host run, worktree: auto).--store-dir <workspace>/.iterion, --merge-into none, bundle_self_path=bots/adr-cartograph, diff_since=origin/main (prioritise the recent unpushed work), max_passes=3, --max-cost-usd 25 --max-duration 1h (mono claude, forfait). 15m39s wall.finished, gate.converged=true on the FIRST pass. Pipeline: scan_adrs 61 ADRs (dup ‘002’ flagged, next NNN correct) → survey_code 4 non-mechanic decisions + 2 gaps → manifest drift=4/orphans=0/coverage 93% → campaign authored 4 ADRs, one commit each in stride on storage branch iterion/run/meteor-ray-ashglyph-fb1c @edeae90e0c: docs(adr): 061 per-backend system-prompt composition mode, 062 system-reminder envelope, 063 post-compaction todo-reseed nudge, 064 worktree finalization delegated authority. scope_check clean (adr_dir-only); deterministic verify green (devbox-pinned go build); cache updated.diff_since=origin/main is an excellent default for recurring Adry runs on an active repo — it focuses the survey on fresh decisions; cherry-picking the storage branch is the operator follow-up (commits deliberately not FF’d here).iterion validate clean, catalog universality/typing/bundle-consistency green, stub e2e green where wired. NOT yet live-dogfooded in the v2 shape; treat the sections below as describing the RETIRED v1 shape.iterion run (CLI, branch dogfood/wave1, live-tree docs/adr scope). survey + review on glm-5.2 via z.ai; when z.ai’s 5h cap hit mid-review, reviewer_claude surfaced a graceful acknowledge_recovery human-pause → resumed → review/fix loop completed on anthropic forfait (gpt-5.5 reviewer + opus). 1233 events.86eeb81f9 “docs(adr): record ADRs 029-041 for sandbox, SSO, quota, and ultracode” — 13 ADRs, +683 lines, on dogfood/wave1 (atop current origin/main). Converged to done.survey_code ran ~900+ tool calls / ~20 min (many empty greps) before converging, vs opus’s tighter surveys; thorough but slow-to-stop on open-ended exploration. (2) graceful rate-limit recovery (acknowledge_recovery pause) worked here — contrast Nexie’s hard fail on the same 429; inconsistent rate-limit handling across nodes is the cross-cutting finding.Two prompt-level fixes for #6, each validated by a live re-run on the aligned tree (8 ADRs committed, 0 ADR-drift, coverage 100%):
7546d0d1 — streak_check.stop was stricter than its own approved
passthrough (it omitted the low-confidence clause); aligned it so a
low-confidence rejection is non-blocking (CLAUDE.md asymptote rule).
run4 (019ec2f3) still failed — the blocker was confidence: high,
not low, so the quick-win mis-aimed.61e995bb — the real root cause: the persistent high-confidence blocker
was a GAP (the survey’s file-diff-payload completeness finding), and
both reviewers were treating the unfixable gap as a blocker (“file an
issue for / address this gap”). Adry is read-only on code → it cannot
close a gap → permanent oscillation. review_system rule 7: gaps are
NEVER blockers (confirm-real → ride the deterministic handoff path to
feature-gap-fill; only ADR drift / orphans / format defects block).Result: partial. Rule 7 measurably helped — reviewers now sometimes vote
approved=true, blocker_count=0 (they never did before) — but run5
(019ec4ec) still exhausted review_loop(10/10): the LLM reviewers are
stochastic, and one still occasionally raises a gap as a blocker
(bot-marketplace-shallow-clone) despite rule 7, resetting the cross-family
streak. A prompt instruction alone is not a reliable gate.
Structural fix 09910359 — DONE, and run6 (019ec51f) CONVERGED. The fix:
stop passing gaps/total_gaps into the reviewers’ review_input entirely
(both alt -> reviewer_* edges) and clean the review prompts of gap-review
references — gaps now flow build_manifest -> prepare_commit’s handoff,
never through the review loop. With the reviewers structurally blind to
gaps (not just instructed to ignore them) and rule 6 calibrating ADR prose,
an aligned tree has nothing to block on. run6 result: reviewer_claude
approved → reviewer_gpt approved (cross-family, 0 blockers, 0 fixer runs)
→ streak_check stop → detect_changes → prepare_commit → commit_changes →
done. First clean convergence. prepare_commit ($0.83) committed no ADR
change (0 drift on the aligned tree — correct idempotent behaviour, the
git diff --cached --quiet guard skipped the commit) and filed the 3
type:feature-gap handoff issues to the board (source:adr-cartograph:
file-diff-payload, ShallowClone-tests, marketplace-transport) — the A→Fini
handoff is validated end-to-end. #6 is FIXED.
Minor follow-up: detect_changes counted the engine’s bot-catalog
regeneration (an unrelated side-effect) as a working-tree change, so it
routed through prepare_commit ($0.83) instead of the pure
update_cache -> done no-op; the outcome was still correct (no spurious
commit). Filtering the catalog regen (like the cache) in detect_changes
would make the aligned re-run a true zero-LLM-commit no-op.
Cost: 6 Adry runs this campaign; survey ~$3.58 + ~10 min each is the floor (survey_code always runs before the deterministic aligned-check).
09910359; Adry converged on run6 019ec51f — see the
2026-06-14 section).f3289632 (worktree worktree-adr-bot-suite, unmerged)iterion run bots/adr-cartograph/main.bot --var code_scope_globs='pkg/git/**',
forfait forced (ITERION_OPENAI_USE_OAUTH=1, API keys unset), claude_code
opus-4-8 (scan/survey/reviewer_claude/fix_claude/prepare_commit) + claw
openai/gpt-5.5 (reviewer_gpt/fix_gpt). No sandbox, no worktree:auto (the
repo-root sha-cache must persist across runs). Scoped to one decision-rich
package to bound cost.review_loop (10/10)
and hit the fail backstop. Survey ≈ $3.58 and ~10 min each (pkg/git, 14
files, tool_max_steps 80). The review loop never reached the cross-family
double-approval streak (see #6). But the run did the valuable work: it
authored 8 genuinely good ADRs (docs/adr/020–027, committed
b60106ba) recording real pkg/git decisions — arg/path-injection
hardening, IsAncestor-via-merge-base, empty-tree-SHA root baseline,
C-locale stderr classification, NUL-framed parsing, working-tree (not
index) panel semantics, main-repo-root gitdir resolution, git-CLI-shell-out
vs go-git. Format matched the repo’s Nygard convention exactly.is_mechanic self-critique correctly flagged 0
mechanical decisions (all 8 were real). scan_adrs correctly detected the
historical 002-* duplicate and computed next_adr_number.| # | Finding | Severity | State |
|—|—|—|—|
| 1 | build_manifest passed survey JSON via raw wrapped in literal quotes — LLM prose (apostrophes/parens) broke `bash -c` (`syntax error near ')'`) | real bug | **FIXED** `1bbca1c0` (drop `!` + quotes → routes through shellEscapeValue). docs-refresh shares the latent pattern (only passes apostrophe-free tokens) → cross-bot pitfall added to workflow_authoring_pitfalls.md |
| 5 | build_manifest computed coverage from the frozen scan_adrs.adrs (entry snapshot), so ADRs the fixers authored mid-loop were invisible → coverage_pct froze at 71% < 80% → the gate was unreachable → loop exhausted with 8 good ADRs on disk | real bug, convergence-breaking | FIXED f3289632 (re-glob docs/adr/*.md each pass, parse ../../<code> refs). Proven deterministically + LIVE on run3: coverage 71%→100%, drift 8→0 |
| 6 | Even with coverage=100% (aligned tree, 0 drift), the review loop never reaches cross-family double-approval — root cause: reviewers treated unfixable GAPS as blockers → oscillate to review_loop(10/10) → fail | real, FIXED | structural fix 09910359 (gaps bypass review_input); run6 (019ec51f) CONVERGED — see the 2026-06-14 section |
| 2 | fix_claude (claude_code, output-schema+tools → two-pass) hit StructuredOutput — No such tool available in Pass 1 (it’s a Pass-2 mechanism), then recovered | benign engine quirk, pre-existing (all fix-nodes; claude_code.go:341) | note only |
| 3 | enforce_fix_scope git checkout -- <p> reverts ALL tracked changes outside docs/adr/, incl. pre-existing uncommitted WIP (it ate my own mid-run hot-fix of main.bot) | known loop-bot behavior (docs-refresh idem) | mitigation: commit fixes BEFORE resume (in HEAD = not “changed”); don’t hot-edit tracked files during a run |
| 4 | reviewer_gpt → 401 OpenAI “token is expired” mid-run (forfait ~/.codex 10 days stale) | infra | operator re-authed codex → resumed on forfait. See workspace memory project_openai_oauth_token_invalidation |
1bbca1c0 — shell-safe data passing in build_manifest (finding #1).f3289632 — build_manifest re-globs docs/adr so coverage tracks authored ADRs (finding #5).b60106ba — the 8 dogfood-authored pkg/git ADRs (kept for review).The review loop must converge to an asymptote; right now it does not. The coverage gate (#5) is satisfied but the cross-family double-approval streak is unreachable because the LLM reviewers treat ADR-prose imperfections as blockers and re-litigate every iteration. Directions to try (each needs a live re-run to validate — tune one at a time):
review_system: an ADR is APPROVED when it factually and
accurately records the decision the code embodies (right sections, valid
Nygard format, code refs resolve). Prose polish / nuance-completeness must
NOT be blockers. This is the anti-Goodhart line: the gate is accuracy, not
style.streak_check (per
the CLAUDE.md asymptote rule): add || input.confidence == 'low' to the
stop condition so a noisy nitpick doesn’t reset the streak.prior_pushback feedback so a reviewer cannot re-raise a
resolved item without new evidence (already wired — verify it’s actually
suppressing re-litigation in the prompt).Other operational notes: run Adry on a clean tree (finding #3); the forfait token expires (~hours) so long runs may need a mid-run re-auth + resume (finding #4); survey cost is the floor (~$3.58/run on pkg/git) since survey_code always runs before the deterministic aligned-check.