Detect a loop from hashes, then name which kind it is
Run three windowed traces through the chapter's novelty and repetition detectors and see an exact loop, a thrash and a retry storm separated by signature, not by step count.
An agent can burn ninety steps and never move. Step counts measure effort, not
progress, so a guard that watches the step budget watches the wrong thing. The
chapter’s answer is a novelty metric computed from hashes over a pre-registered
window, plus three repetition signatures that name the shape of the
non-progress: an exact loop, a thrash, and a retry storm. This program composes
the chapter’s window_novelty and repetition_shape detectors over three
constructed windows plus a progressing one.
Run it
Save this as loop_detection.py and run it with Python 3.11 or later. It uses the standard library only.
"""Chapter 39: detect non-progress from hashes, and name the loop shape.
The detectors are the chapter's. The trace is a local construction: three
windows built to match the chapter's stated signatures.
"""
from dataclasses import dataclass
@dataclass(frozen=True)
class Step:
state_hash: str
arg_hash: str
obs_hash: str
error: str | None = None
backoff_s: int = 0
def window_novelty(steps):
"""OBSERVATION: hashes, not impressions."""
return len({s.state_hash for s in steps}) / len(steps)
def repetition_shape(window):
"""The chapter's detector: H1 exact loop, H2 thrash, H3 retry storm."""
seen, cycles = set(), 0
for s in window:
key = (s.arg_hash, s.obs_hash)
if key in seen:
cycles += 1
seen.add(key)
if cycles >= 2:
return "H1-exact-loop"
args = {s.arg_hash for s in window}
states = {s.state_hash for s in window}
if len(args) > 1 and len(states) == 1:
return "H2-thrash"
errors = [s.error for s in window if s.error]
repeats = max(errors.count(c) for c in set(errors)) if errors else 0
delays = [s.backoff_s for s in window]
if repeats >= 2 and not all(b > a for a, b in zip(delays, delays[1:])):
return "H3-retry-storm"
return "progressing-or-UNKNOWN"
WINDOWS = {
"12-21 exact loop": [Step("h7a1", "q-same", "obs-same") for _ in range(10)],
"42-51 thrash": [Step("h7a1", f"q-{i % 6}", f"obs-{i}") for i in range(10)],
"62-71 retry storm": [Step("h7a1", "q-fetch", f"obs-{i}", error="parse_failed")
for i in range(8)],
"82-91 progressing": [Step(f"h7b{i}", f"q-{i}", f"obs-{i}") for i in range(10)],
}
for label, window in WINDOWS.items():
states = len({s.state_hash for s in window})
args = len({s.arg_hash for s in window})
print(f"{label:20} | states {states}/{len(window)} "
f"(novelty {window_novelty(window):.2f}) | args {args} | "
f"{repetition_shape(window)}")
counts, span, halted = {}, 0, False
for s in WINDOWS["12-21 exact loop"]:
counts[s.arg_hash] = counts.get(s.arg_hash, 0) + 1
if counts[s.arg_hash] >= 3:
halted = True
break
span += 1
print(f"H1 tabu: span 10 -> {span} steps, halt verdict UNKNOWN, halted={halted}")Expected outcome
12-21 exact loop | states 1/10 (novelty 0.10) | args 1 | H1-exact-loop
42-51 thrash | states 1/10 (novelty 0.10) | args 6 | H2-thrash
62-71 retry storm | states 1/8 (novelty 0.12) | args 1 | H3-retry-storm
82-91 progressing | states 10/10 (novelty 1.00) | args 10 | progressing-or-UNKNOWN
H1 tabu: span 10 -> 2 steps, halt verdict UNKNOWN, halted=TrueThree windows of ten, ten and eight steps, and three different verdicts. The step counts are nearly identical and useless; the hashes separate them. The fourth window moves, and the detector says so.
The signature, not the prose
The chapter’s detector reads a different signature in each branch, and each branch calls for a different guard: a tabu list on equivalent arg hashes for H1, a cap on state-equivalent windows for H2, and backoff with a branch requirement for H3. Its rule is direct: hashes and error codes detect, prose variety is inadmissible. The chapter’s record:
PROGRESS WINDOWS (constructed; window=10, hashes verbatim):
steps 12-21 | states: h7a1 ×10 (novelty 0.0) | args: q-hash identical ×10
-> H1 EXACT LOOP (2+ equivalent cycles, no new information)
steps 42-51 | states: h7a1 ×10 (novelty 0.0) | args: 6 distinct q-hashes
-> H2 THRASH (surface varies, state fixed, outcomes identical)Mechanism and limitations
Documented: the chapter states that a research agent can burn ninety steps with the working-state hash unchanged across ten cycles, that effort is not evidence and state novelty is, that exact loops repeat arg and observation hashes, thrashing varies surface form under state-equivalence, and retry storms repeat error-triggered actions without backoff growth or a branch, and that a halt verdict is UNKNOWN, not a fabricated synthesis.
Observed: the run above. The exact loop, the thrash and the retry storm were separated by signature, and the progressing window was classified as progressing. The tabu guard cut the exact loop’s span from ten steps to two and reported the halt verdict as UNKNOWN.
Not shown. The trace is constructed here, not recorded from a real agent, and the chapter reports no measured run for this case. The novelty figures are distinct state hashes per step on a window of ten, so a flat window reads 0.10 rather than the chapter’s 0.0; what matters is the contrast with the progressing window’s 1.00. The H2 cap and H3 backoff guards are described but not implemented here, and only the H1 tabu is exercised, once rather than over the three trials the chapter requires.
Understand this example
Copy this prompt into your AI tool. No code runs here.
Apply this example
Copy this prompt into your AI tool. No code runs here.