"""Runs going in circles (L2, X6), judged from the turns alone.""" from nisse_core.loop.circles import Turn, find_circles NUDGE = "Your last reply was prose. Every reply must be a tool call." def comment(n: int) -> Turn: return Turn( text="", calls=(("post_comment", f'{{"body": "Duplicate, please disregard. ({n})"}}'),), ) def test_near_identical_replies_in_a_row_are_a_circle() -> None: # The Flash-Next review loop of 2026-10-24: the same comment posted over # and over, differing only in a counter. turns = [Turn(calls=(("read_file", '{"path": "a.py"}'),))] turns += [comment(n) for n in range(6)] turns += [Turn(calls=(("finish", "{}"),))] [circle] = find_circles(turns) assert circle.kind == "repeated reply" assert circle.turns == (1, 2, 3, 4, 5, 6) def test_work_that_moves_on_is_no_circle() -> None: turns = [ Turn(calls=(("read_file", '{"path": "a.py"}'),)), Turn(calls=(("run_command", '{"command": "pytest -q"}'),)), Turn(calls=(("edit_file", '{"path": "a.py", "old": "x", "new": "y"}'),)), Turn(calls=(("run_command", '{"command": "pytest -q"}'),)), Turn(text="Done.", calls=(("finish", "{}"),)), ] assert find_circles(turns) == [] def test_two_alike_is_not_yet_a_circle() -> None: assert find_circles([comment(1), comment(2), Turn(text="done")]) == [] def test_one_correction_given_again_and_again_is_a_circle() -> None: # Invented tools met with the prose reminder turn after turn, the replies # themselves all different. replies = [ "I'll run the linter first: lint_fix", "read_file src/review.py", "Installing the dependencies with install(requirements.txt) now.", "Let me use the skill tool to summarise the diff for the review.", ] turns = [Turn(text=r, told=(NUDGE,) if i else ()) for i, r in enumerate(replies)] [circle] = find_circles(turns) assert circle.kind == "repeated correction" assert circle.turns == (1, 2, 3) assert NUDGE[:40] in circle.detail def test_apologies_over_and_over_are_a_circle() -> None: turns = [ Turn(text="Sorry, I made an error reading the file."), Turn(calls=(("read_file", '{"path": "b.py"}'),)), Turn(text="My apologies, that was the wrong path."), Turn(calls=(("list_files", "{}"),)), Turn(text="Please disregard my previous comment."), ] [circle] = find_circles(turns) assert circle.kind == "repeated apology" assert circle.turns == (0, 2, 4) def test_the_limits_are_tuning() -> None: turns = [comment(1), comment(2)] assert [c.kind for c in find_circles(turns, repeats=2)] == ["repeated reply"] assert find_circles([comment(1), comment(22), comment(333)], similar=1.0) == []