Skip to content

feat(loops): no-progress / doom-loop detection — stop on repeated identical responses (#1157)#1368

Merged
vybe merged 9 commits into
devfrom
AndriiPasternak31/issue-1157
Jun 28, 2026
Merged

feat(loops): no-progress / doom-loop detection — stop on repeated identical responses (#1157)#1368
vybe merged 9 commits into
devfrom
AndriiPasternak31/issue-1157

Conversation

@AndriiPasternak31

Copy link
Copy Markdown
Contributor

Summary

Closes #1157.

A sequential agent loop that feeds {{previous_response}} forward can get stuck re-emitting the same response every iteration — a classic autonomous-agent "doom loop" — burning the entire max_runs budget while making zero progress. Iteration/time caps don't catch it. This adds no-progress detection: fingerprint each successful run's response and stop once K consecutive runs are identical.

What changed

  • New param no_progress_threshold (int; 0 disables; default 3 for new loops via API/MCP; NULL ⇒ disabled so in-flight loops are unaffected). 1 is rejected → 422 ("repeated identical" needs ≥2).
  • Runner (services/loop_service.py): _fingerprint(text) = sha256(" ".join(text.split())) (collapses whitespace, preserves word boundaries; empty/whitespace-only fold to one fingerprint). Runner-local last_fingerprint/repeat_count, no persistence. Stops stopped / stop_reason="no_progress". Exact-hash only — no fuzzy/semantic.
  • Precedence: stop_signal_matched wins (checked first); a pending user_stopped/deadline_exceeded also outranks no_progress (re-checked before the break) — an explicit stop is never relabeled.
  • Dual-track migration (Invariant Fix git pushing bug #9): SQLite _migrate_agent_loops_no_progress + Alembic 0007_agent_loops_no_progress (ADD COLUMN IF NOT EXISTS).
  • Three surfaces in sync (Invariant feat: SMARTS trading pipeline with Telegram notifications and Miro visualization #13): backend router/model, MCP tool (run_agent_loop + zod .refine mirror), frontend LoopsPanel.vue (form field + 0-sentinel handling + formatStopReason label).

Behavior change

New API/MCP loops stop after 3 identical responses by default. Operators of legitimately repeat-confirming loops should pass no_progress_threshold=0.

Testing

  • ~13 new unit tests (stop-at-K, near-miss counter reset, normalization, stop_signal/user_stop/deadline precedence, K>max_runs, WS contract, router default-on, K=1→422, GET round-trip).
  • Local: pytest tests/unit/test_loop_service.py tests/unit/test_loops_router_validation.py42 passed.
  • MCP server tsc --noEmit → clean.

Docs

requirements §38.3, architecture (Sequential Agent Loops + agent_loops schema/stop_reason), feature-flows/run-agent-loop.md, feature-flows index. CSO --diff audit: 0 findings.

🤖 Generated with Claude Code

AndriiPasternak31 and others added 8 commits June 28, 2026 03:21
Adds the nullable `agent_loops.no_progress_threshold INTEGER` column for
doom-loop detection. NULL = disabled (back-compat for in-flight loops).

Dual-track per Invariant #9: SQLite `_migrate_agent_loops_no_progress`
(idempotent `_safe_add_column`) + Alembic `0007_agent_loops_no_progress`
(`ADD COLUMN IF NOT EXISTS`, chained after `0006_agent_reports`). DDL in
both `schema.py` (SQLite) and `tables.py` (MetaData). `_loop_row_to_dict`
+ `LoopOperations.create_loop` carry the field through.

Refs #1157

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The runner fingerprints each successful run's full response
(`_fingerprint = sha256(" ".join(text.split()))` — collapses whitespace,
preserves word boundaries, empty/whitespace-only fold to one fingerprint)
and tracks consecutive identical results in a runner-local
`last_fingerprint`/`repeat_count` (no persistence). Once K consecutive
runs match, the loop stops with status `stopped` / `stop_reason="no_progress"`.

Precedence: `stop_signal_matched` is checked first and wins; a pending
`user_stopped` or passed deadline also outranks `no_progress` (re-checked
before the break) so an explicit stop is never relabeled. Exact-hash only —
no fuzzy/semantic similarity. NULL/0 threshold disables.

Refs #1157

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
`StartLoopRequest` gains `no_progress_threshold: Optional[int]`
(default 3, ge=0) with a `field_validator` rejecting `1` → 422
("repeated identical" needs ≥2). The router threads it into
`loop_service.start_loop()`, and `LoopStatusResponse` /
`_build_status_response` carry it back through `GET /api/loops/{id}`.

Refs #1157

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the `no_progress_threshold` param to the `run_agent_loop` MCP tool
with a zod `.int().min(0).refine(v !== 1)` validator mirroring the backend
(0 disables, default 3, 1 rejected). Threaded through `TrinityClient.startAgentLoop`.

Refs #1157

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Adds the "No-progress threshold" number input (default 3, 0 disables) with
helper text to the Run-loop form. The `0` disable sentinel is sent
explicitly — a truthy guard would drop it. `formatStopReason` maps the new
`no_progress` reason to a readable label.

Refs #1157

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
~13 unit tests: stop-at-K, near-miss counter reset, response
normalization (whitespace/empty collapse), precedence over stop_signal /
user_stop / deadline, K > max_runs, the WS event contract, router
default-on, K=1 → 422, and GET round-trip of the threshold.

Refs #1157

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
requirements §38.3, architecture (Sequential Agent Loops + agent_loops
schema/stop_reason enum), the run-agent-loop feature flow, and a
feature-flows index row.

Refs #1157

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Refs #1157

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@AndriiPasternak31
AndriiPasternak31 marked this pull request as ready for review June 28, 2026 02:33
@AndriiPasternak31
AndriiPasternak31 requested a review from vybe June 28, 2026 02:33
@AndriiPasternak31 AndriiPasternak31 self-assigned this Jun 28, 2026
…e-1157

# Conflicts:
#	docs/memory/feature-flows.md

@vybe vybe left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Validated via /validate-pr: clean. Issue #1157 (public P2 feat, status-ready), Closes-keyword present, security clean, dual-track migration (SQLite + Alembic 0007_agent_loops_no_progress, no collision with dev@0006), tests + docs present. Rebased onto dev (resolved feature-flows.md changelog conflict — kept both #1157 and #1085 rows). APPROVE.

@vybe
vybe merged commit 7f8ccd5 into dev Jun 28, 2026
19 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants