[object Object]

← back to Wallco Ai

Fix overnight seam loop: (1) generator exits 0 on completed wave not 1-on-any-fail (was freezing manifest index → infinite re-run + orphan dupes); (2) idempotency guard skips roots with existing PASS candidate; (3) point loop at generalized reroll.py not scope-locked reroll-2.py; (4) retire unrescuable 43199 macaw + 54385 peacock

dd74023d1a1ce0166f6e20436d929bb4c0330178 · 2026-06-11 20:04:15 -0700 · Steve Abrams

Files touched

Diff

commit dd74023d1a1ce0166f6e20436d929bb4c0330178
Author: Steve Abrams <steve@designerwallcoverings.com>
Date:   Thu Jun 11 20:04:15 2026 -0700

    Fix overnight seam loop: (1) generator exits 0 on completed wave not 1-on-any-fail (was freezing manifest index → infinite re-run + orphan dupes); (2) idempotency guard skips roots with existing PASS candidate; (3) point loop at generalized reroll.py not scope-locked reroll-2.py; (4) retire unrescuable 43199 macaw + 54385 peacock
---
 data/seam-wave-exclude.txt            |  2 ++
 scripts/controlnet-anchored-reroll.py | 30 +++++++++++++++++++++++++++++-
 scripts/overnight-seam-loop.sh        |  8 +++++---
 3 files changed, 36 insertions(+), 4 deletions(-)

diff --git a/data/seam-wave-exclude.txt b/data/seam-wave-exclude.txt
index 08fd18c..8dca1bf 100644
--- a/data/seam-wave-exclude.txt
+++ b/data/seam-wave-exclude.txt
@@ -10,3 +10,5 @@
 
 54076   # orangutan — wine-glass-dominant; best roll PASS-seamless but no animal
 54266   # tree frog — champagne-coupe-dominant; best roll PASS-seamless but no animal
+43199   # macaw — Wave 1: 8 retries, motif edge never reached strict <=6; unrescuable
+54385   # peacock — Wave 1: fanned feathers cross the tile boundary; 8 retries, no clear
diff --git a/scripts/controlnet-anchored-reroll.py b/scripts/controlnet-anchored-reroll.py
index 3dcc71b..94a52a8 100644
--- a/scripts/controlnet-anchored-reroll.py
+++ b/scripts/controlnet-anchored-reroll.py
@@ -91,8 +91,29 @@ def main():
         print(json.dumps({'error': 'no ids'}), file=sys.stderr)
         sys.exit(2)
 
+    # Idempotency: roots that already have a PASS candidate in the queue are DONE.
+    # Re-processing them would INSERT a duplicate (all_designs is INSERT-only — dupes
+    # can't be cleaned up). Skip them. This makes the wave runner safe to re-invoke.
+    done_roots = set()
+    try:
+        with open(QUEUE) as _q:
+            for _line in _q:
+                try:
+                    _row = json.loads(_line)
+                    if _row.get('ok') and _row.get('edge_verdict') == 'PASS':
+                        done_roots.add(int(_row.get('root_id')))
+                except Exception:
+                    pass
+    except FileNotFoundError:
+        pass
+
     results = []
     for i, rid in enumerate(ids):
+        if rid in done_roots:
+            results.append({'root_id': rid, 'ok': True, 'skipped': 'already has PASS candidate',
+                            'edge_verdict': 'PASS', 'generator': 'controlnet-anchored-reroll'})
+            print(f"=> root {rid}: SKIP (already has PASS candidate)", file=sys.stderr)
+            continue
         ground = args.ground or cnar.GROUNDS[i % len(cnar.GROUNDS)]
         cfg = cfg_for(rid, ground)
         try:
@@ -116,7 +137,14 @@ def main():
               f"ground={r.get('ground')}", file=sys.stderr)
 
     print(json.dumps(results, indent=2))
-    sys.exit(0 if all(r.get('ok') for r in results) else 1)
+    # Exit 0 when the wave RAN — a root that "couldn't clear" after retries is a
+    # normal terminal outcome (it won't clear on a re-run), so the wave runner MUST
+    # advance its manifest index past it. Exit non-zero ONLY if every root threw an
+    # execution exception (e.g. ComfyUI down) — that's a real infra failure where the
+    # runner should NOT advance (it retries; the loop's zero-PASS-streak stop catches
+    # a sustained outage). Per-root non-clears never block manifest progress.
+    all_errored = bool(results) and all('error' in r for r in results)
+    sys.exit(1 if all_errored else 0)
 
 
 if __name__ == '__main__':
diff --git a/scripts/overnight-seam-loop.sh b/scripts/overnight-seam-loop.sh
index 0b31613..32f627a 100755
--- a/scripts/overnight-seam-loop.sh
+++ b/scripts/overnight-seam-loop.sh
@@ -52,9 +52,11 @@ MANIFEST="data/seam-wave-roots.txt"
 DTD_PANEL="$HOME/.claude/skills/dtd/scripts/panel.sh"
 CNCP="http://127.0.0.1:3333/api/wins"
 
-# Generator: the generalized retry-generator (confirmed by the Wave-1 agent).
-# Override at launch with SEAM_GENERATOR=... ; default is the reroll-2 retry generator.
-SEAM_GENERATOR="${SEAM_GENERATOR:-scripts/controlnet-anchored-reroll-2.py}"
+# Generator: the GENERALIZED retry-generator (arbitrary --ids). reroll-2.py is hard
+# scope-locked to {54076,54266} and refuses every other id — using it empties every
+# wave. reroll.py accepts any animal-forward root and has the idempotency guard.
+# Override at launch with SEAM_GENERATOR=...
+SEAM_GENERATOR="${SEAM_GENERATOR:-scripts/controlnet-anchored-reroll.py}"
 WAVE_SIZE="${WAVE_SIZE:-20}"
 DRY_RUN="${DRY_RUN:-0}"
 

← 4992ea4 seam-wave Wave 1: 1/3 roots cleared (55272 elephant -> 10000  ·  back to Wallco Ai  ·  Fix overnight loop DTD gate: capture only dtd_decide's final f591bcd →