Skip to content

Implement RecursionDepthDetector for cognitive loop detection#669

Merged
SHAURYASANYAL3 merged 1 commit into
sreerevanth:mainfrom
Akshaya-125:feature/recursion-depth-detector
Jul 26, 2026
Merged

Implement RecursionDepthDetector for cognitive loop detection#669
SHAURYASANYAL3 merged 1 commit into
sreerevanth:mainfrom
Akshaya-125:feature/recursion-depth-detector

Conversation

@Akshaya-125

@Akshaya-125 Akshaya-125 commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Summary

This PR implements the RecursionDepthDetector to identify excessive self-referential reasoning that may indicate an agent is stuck in a cognitive recursion loop.

Changes

  • Added RecursionDepthDetector.
  • Maintains a rolling buffer of recent planner outputs.
  • Detects self-referential reasoning using regex patterns.
  • Flags a cognitive violation when the configured threshold is exceeded.
  • Added unit tests for:
    • Detection of repeated self-reflective reasoning.
    • Normal reasoning that should not trigger detection.

Testing

  • Added unit tests in tests/test_safety.py.
  • Verified the new tests pass successfully.
  • Ran Ruff lint checks successfully.

Related to #663

Summary by CodeRabbit

  • New Features

    • Added detection for potential cognitive recursion loops in agent planning output.
    • Reports whether recursive patterns were detected, including match counts and matched patterns.
    • Supports configurable detection thresholds, pattern sets, buffer sizes, and reset behavior.
  • Tests

    • Added coverage for detecting repeated self-reflection.
    • Added coverage confirming normal reasoning does not trigger detection.

@vercel

vercel Bot commented Jul 25, 2026

Copy link
Copy Markdown

@Akshaya-125 is attempting to deploy a commit to the sreerevanth's projects Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

Adds RecursionDepthDetector, which buffers planner-output previews, matches configurable case-insensitive recursion patterns, and returns detection reports after threshold evaluation. Safety tests cover repeated self-reflection and normal reasoning.

Changes

Recursion detection

Layer / File(s) Summary
Detector contract and scanning
agentwatch/core/recursion_depth_detector.py
Defines RecursionDepthReport and RecursionDepthDetector, including bounded buffering, planner-event filtering, configurable regex scanning, threshold-based detection, reset behavior, and exports.
Safety test coverage
tests/test_safety.py
Tests detection of repeated self-reflective planner output and non-detection for normal reasoning output.

Estimated code review effort: 2 (Simple) | ~10 minutes

Possibly related issues

  • Issue sreerevanth/AgentWatch#663 — Directly covers the recursion-depth detector, rolling buffer, regex matching, threshold detection, and tests introduced here.

Suggested reviewers: pavsoss, mayurkharat0390, prachishelke1312

Sequence Diagram(s)

sequenceDiagram
  participant AgentEvent
  participant RecursionDepthDetector
  participant RecursionDepthReport
  AgentEvent->>RecursionDepthDetector: observe planner-output preview
  RecursionDepthDetector->>RecursionDepthDetector: buffer and scan configured patterns
  RecursionDepthDetector-->>RecursionDepthReport: return detection result
Loading

Poem

A rabbit watched the thoughts repeat,
Then tucked the clues in buffers neat.
Patterns danced and counts grew tall,
A report arrived to flag them all.
“Hop, hop—safe reasoning wins!”

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: adding RecursionDepthDetector for cognitive loop detection.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@agentwatch/core/recursion_depth_detector.py`:
- Around line 36-47: Validate buffer_size and threshold at the start of
RecursionDepthDetector.__init__, rejecting values below 1 and 0 respectively
before compiling patterns or constructing the deque. Preserve valid
configurations and raise an appropriate value-related exception for invalid
inputs.
- Around line 62-66: Update the matching loop in the recursion-depth detector to
count every occurrence of each pattern within each buffered text preview, rather
than only the first match returned by pattern.search(text). Preserve recording
the matched pattern for each occurrence and ensure the accumulated count can
exceed the configured threshold so detected is set correctly.
- Around line 43-45: Update the pattern selection in the recursion depth
detector initializer to use DEFAULT_PATTERNS only when patterns is None,
preserving an explicitly provided empty list as empty. Keep the existing regex
compilation behavior unchanged for supplied patterns.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 772d9736-5628-40c5-aa8d-0585955618f5

📥 Commits

Reviewing files that changed from the base of the PR and between 0a2e15f and c4bd490.

📒 Files selected for processing (2)
  • agentwatch/core/recursion_depth_detector.py
  • tests/test_safety.py

Comment on lines +36 to +47
def __init__(
self,
buffer_size: int = DEFAULT_BUFFER_SIZE,
threshold: int = DEFAULT_THRESHOLD,
patterns: list[str] | None = None,
):
self.threshold = threshold
self.patterns = [
re.compile(p, re.IGNORECASE)
for p in (patterns or DEFAULT_PATTERNS)
]
self.buffer = deque(maxlen=buffer_size)

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Reject invalid detector configuration.

buffer_size=0 creates a deque that silently discards every observation, while threshold < 0 makes any planner event with zero matches detected. Validate buffer_size >= 1 and threshold >= 0 before constructing the detector.

Proposed fix
     ):
+        if buffer_size < 1:
+            raise ValueError("buffer_size must be at least 1")
+        if threshold < 0:
+            raise ValueError("threshold must be non-negative")
         self.threshold = threshold
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def __init__(
self,
buffer_size: int = DEFAULT_BUFFER_SIZE,
threshold: int = DEFAULT_THRESHOLD,
patterns: list[str] | None = None,
):
self.threshold = threshold
self.patterns = [
re.compile(p, re.IGNORECASE)
for p in (patterns or DEFAULT_PATTERNS)
]
self.buffer = deque(maxlen=buffer_size)
def __init__(
self,
buffer_size: int = DEFAULT_BUFFER_SIZE,
threshold: int = DEFAULT_THRESHOLD,
patterns: list[str] | None = None,
):
if buffer_size < 1:
raise ValueError("buffer_size must be at least 1")
if threshold < 0:
raise ValueError("threshold must be non-negative")
self.threshold = threshold
self.patterns = [
re.compile(p, re.IGNORECASE)
for p in (patterns or DEFAULT_PATTERNS)
]
self.buffer = deque(maxlen=buffer_size)
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 43-43: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.compile(p, re.IGNORECASE)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agentwatch/core/recursion_depth_detector.py` around lines 36 - 47, Validate
buffer_size and threshold at the start of RecursionDepthDetector.__init__,
rejecting values below 1 and 0 respectively before compiling patterns or
constructing the deque. Preserve valid configurations and raise an appropriate
value-related exception for invalid inputs.

Comment on lines +43 to +45
self.patterns = [
re.compile(p, re.IGNORECASE)
for p in (patterns or DEFAULT_PATTERNS)

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.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Honor an explicitly empty pattern list.

patterns or DEFAULT_PATTERNS replaces [] with the defaults, so callers cannot intentionally configure a detector with no patterns. Use patterns is None to distinguish omission from an empty configuration.

Proposed fix
-            for p in (patterns or DEFAULT_PATTERNS)
+            for p in (DEFAULT_PATTERNS if patterns is None else patterns)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
self.patterns = [
re.compile(p, re.IGNORECASE)
for p in (patterns or DEFAULT_PATTERNS)
self.patterns = [
re.compile(p, re.IGNORECASE)
for p in (DEFAULT_PATTERNS if patterns is None else patterns)
🧰 Tools
🪛 ast-grep (0.44.1)

[warning] 43-43: Regex pattern passed to re is built from a non-literal (variable, call, concatenation, or f-string) value. If that value is attacker-controlled it can introduce a malicious pattern with catastrophic backtracking (ReDoS). Use a hardcoded literal pattern, or validate/escape untrusted input with re.escape() and bound the regex complexity before compiling.
Context: re.compile(p, re.IGNORECASE)
Note: [CWE-1333] Inefficient Regular Expression Complexity.

(redos-non-literal-regex-python)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agentwatch/core/recursion_depth_detector.py` around lines 43 - 45, Update the
pattern selection in the recursion depth detector initializer to use
DEFAULT_PATTERNS only when patterns is None, preserving an explicitly provided
empty list as empty. Keep the existing regex compilation behavior unchanged for
supplied patterns.

Comment on lines +62 to +66
for text in self.buffer:
for pattern in self.patterns:
if pattern.search(text):
count += 1
matched.append(pattern.pattern)

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.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Count every pattern occurrence in each preview.

pattern.search(text) contributes at most one match per pattern per buffered output. A preview containing repeated self-reflective phrases is therefore undercounted, allowing detected to remain false despite exceeding the configured match count.

Proposed fix
-                if pattern.search(text):
+                for _ in pattern.finditer(text):
                     count += 1
                     matched.append(pattern.pattern)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
for text in self.buffer:
for pattern in self.patterns:
if pattern.search(text):
count += 1
matched.append(pattern.pattern)
for text in self.buffer:
for pattern in self.patterns:
for _ in pattern.finditer(text):
count += 1
matched.append(pattern.pattern)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@agentwatch/core/recursion_depth_detector.py` around lines 62 - 66, Update the
matching loop in the recursion-depth detector to count every occurrence of each
pattern within each buffered text preview, rather than only the first match
returned by pattern.search(text). Preserve recording the matched pattern for
each occurrence and ensure the accumulated count can exceed the configured
threshold so detected is set correctly.

@SHAURYASANYAL3 SHAURYASANYAL3 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Looks good, very clean and adheres to V2 constraints. Approving!

@SHAURYASANYAL3
SHAURYASANYAL3 merged commit 31f5132 into sreerevanth:main Jul 26, 2026
2 of 3 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