Skip to content

feat(loop-prevention): detect and escalate infinite clarification loops#3683

Open
gutierrezx7 wants to merge 8 commits intocode-yeongyu:devfrom
gutierrezx7:feat/loop-prevention
Open

feat(loop-prevention): detect and escalate infinite clarification loops#3683
gutierrezx7 wants to merge 8 commits intocode-yeongyu:devfrom
gutierrezx7:feat/loop-prevention

Conversation

@gutierrezx7
Copy link
Copy Markdown

@gutierrezx7 gutierrezx7 commented Apr 27, 2026

Summary

  • Detect when an agent asks for more instructions in plain text (without using the question tool) and prevent infinite continuation loops.
  • Implement a 3-tier escalation that progresses from normal continuation → warning prompt → hard stop with task cancellation.
  • Support detection across 11 languages covering ~92% of global language users.

Changes

New modules

  • clarification-detection.ts — 174 regex patterns across 11 languages detecting plain-text clarification requests
  • loop-state-persistence.ts — Save loop counters to .sisyphus/loop-state/{sessionID}.json with 1-hour expiry

Modified modules

  • idle-event.ts — Insert clarification detection after pending-question check; BLOCKED check runs before consecutiveFailures gate
  • types.ts — Add consecutiveClarifications, lastClarificationDetectedAt, inFlightSince
  • constants.ts — Add escalation prompts (WARNING + BLOCKED), MAX_CLARIFICATION_CONSECUTIVE (3), cooldown (10s), in-flight timeout (30s)
  • continuation-injection.ts — Accept promptOverride, track inFlightSince, persist loop state on failure/success; BLOCKED bypasses write-permission gate
  • countdown.ts — Thread promptOverride through countdown chain
  • handler.ts — Call setDirectory for persistence
  • session-state.ts — Load persisted state on init, persist on mutations, recover stuck inFlight (30s timeout), reset clarification counters on progress
  • task-message-analyzer.ts — Expand babysitter to all task types (not just Gemini/Minimax)

Escalation tiers

Counter Tier Behavior
1 Normal CONTINUATION_PROMPT (agent may resolve on its own)
2 Warning CLARIFICATION_ESCALATION_PROMPT ("Take action, don't ask again")
3+ Blocked CLARIFICATION_BLOCKED_PROMPT (cancel all tasks, write summary, hard stop)

Testing

  • bun test src/hooks/todo-continuation-enforcer/ — 132 tests ✅ (incl. clarity detection, persistence, stagnation, integration)
  • bun run typecheck
  • bun run build
  • Manual scenario simulation verified all 3 tiers fire at correct thresholds

Related Issues

N/A — feature request.


View in Codesmith
Need help on this PR? Tag @codesmith with what you need.

  • Let Codesmith autofix CI failures and bot reviews

Summary by cubic

Prevents infinite “please clarify” loops by detecting plain‑text clarification requests and escalating to a hard stop, with state persisted across restarts. Also adds native deepseek-v4 support with an optimized Sisyphus prompt and routing.

  • New Features

    • Detect plain‑text clarification seeking in 11 languages and escalate: normal → warning → blocked (cancel remaining tasks, write summary). Tracks consecutiveClarifications, applies a 10s cooldown, and times out stuck in‑flight injections after 30s.
    • Persist loop state in .sisyphus/loop-state/{sessionID}.json with a 1‑hour expiry; auto‑load/save via the session store so restarts don’t reset counters.
    • Native deepseek-v4 support: model detection (Pro/Flash/R1), V4‑specific Sisyphus prompt, enable thinking for Pro, and add to Sisyphus/Oracle fallback chains.
  • Bug Fixes

    • Ensure BLOCKED prompt is delivered before failure gates and bypass write‑permission when using prompt overrides; keep the todo list when overrides are used.
    • Reset clarification counters on progress/completion and expand the babysitter to all tasks; add tests for detection, persistence, and model guards.

Written for commit 832bd33. Summary will update on new commits. Review in cubic

gutierrezx7 and others added 8 commits April 27, 2026 15:56
…responses

Detects when an agent asks for more instructions in plain text (not using the question tool), which the existing pending-question-detection misses. Uses 20 regex patterns covering common patterns like 'I need more details', 'please clarify', 'what should I do', 'blocked on user input', etc.

Includes comprehensive test suite with 34 test cases covering positive matches, false negatives, question tool coexistence, and real-world NETBOX MCP CRUD scenario.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…eloads

Saves critical loop prevention counters (consecutiveClarifications, consecutiveFailures, stagnationCount) to .sisyphus/loop-state/{sessionID}.json. Prevents fresh-start loop resets when the plugin is reloaded — state survives restarts.

Integrated into SessionStateStore for automatic load on session init and persist on state mutations.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Adds SessionState fields for tracking clarification loops: consecutiveClarifications, lastClarificationDetectedAt, inFlightSince.

Adds escalation tiers:
- CLARIFICATION_ESCALATION_PROMPT: tier 2 warning (take action, don't ask again)
- CLARIFICATION_BLOCKED_PROMPT: tier 3 hard stop (cancel all tasks, write summary)

Adds constants: MAX_CLARIFICATION_CONSECUTIVE (3), CLARIFICATION_COOLDOWN_MS (10s), IN_FLIGHT_TIMEOUT_MS (30s).

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Wires the clarification detection into the todo-continuation-enforcer decision gate with a 3-tier escalation:

Tier 1 (1st clarification): allow normal continuation (agent might resolve)
Tier 2 (2nd): inject CLARIFICATION_ESCALATION_PROMPT (warn to take action)
Tier 3 (3rd+): inject CLARIFICATION_BLOCKED_PROMPT (hard stop, cancel todos)

Changes:
- idle-event.ts: adds clarification detection after pending-question check, escalation logic after resolvedInfo
- handler.ts: calls setDirectory for persistence
- continuation-injection.ts: accepts promptOverride, tracks inFlightSince, persists loop state
- countdown.ts: passes promptOverride through countdown chain
- session-state.ts: loads persisted state on init, persists on mutations, recovers stuck inFlight (30s timeout)

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…ypes

Removes the Gemini/Minimax model restriction from isUnstableTask(). The babysitter's existing 2-minute idle timeout already prevents false positives, so expanding to all model types ensures loop behavior is caught regardless of which model is running.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…ing todo list, state expiry

Three bugs found during code review:

1. resetContinuationProgress() did not reset consecutiveClarifications — after all todos completed, the counter persisted and could trigger premature escalation on a new task session.

2. BLOCKED prompt injection used promptOverride which REPLACED the entire prompt, including the todo list. The agent was told to cancel all remaining tasks but had no visibility into what tasks remained.

3. loadLoopState() had no time threshold — persisted loop state from hours/days ago could trigger immediate escalation after a plugin restart. Added 1-hour expiry on persisted state.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
… write-permission

Three bugs found in deep review:

1. BLOCKED injection was positioned AFTER the consecutiveFailures check (line 177). When failures hit MAX_CONSECUTIVE_FAILURES (5), the function returned before reaching the escalation logic. The BLOCKED prompt could never fire until the 5-min cooldown expired.

Fix: Moved BLOCKED check to immediately after the inFlight recovery check, BEFORE the failures check. This ensures the one-way BLOCKED instruction is always delivered, even when regular continuation is failing.

2. injectContinuation had a write-permission gate that blocked BLOCKED injection silently. Since BLOCKED uses void (fire-and-forget), idle-event.ts already returned and never learned the injection was skipped. The agent was stuck in limbo: no continuation prompts, no BLOCKED instruction.

Fix: hasWritePermission check now allows promptOverride to bypass it. BLOCKED/WARNING prompts are system instructions, not implementation work — they must be delivered regardless of write tool permissions.

3. Also removed the now-redundant duplicate BLOCKED check from its old position (after resolvedInfo) since it moved earlier in the flow.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…anguages)

Expanded clarification-seeking patterns from English-only to 11 languages covering the top 10 most spoken languages worldwide:

- English (21 patterns, existing)
- Portuguese pt-BR (16 patterns)
- Spanish (16 patterns)
- French (16 patterns)
- German (16 patterns)
- Japanese (15 patterns, CJK)
- Korean (14 patterns, Hangul)
- Chinese Mandarin (15 patterns, Hanzi)
- Russian (16 patterns, Cyrillic)
- Hindi (14 patterns, Devanagari)
- Arabic (15 patterns, Arabic script)

Total: ~174 patterns across Latin, CJK, Cyrillic, Devanagari, and Arabic scripts.

Added 10 multilingual test cases (one per additional language) for regression coverage.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@gutierrezx7 gutierrezx7 force-pushed the feat/loop-prevention branch from b6b16ce to 832bd33 Compare April 27, 2026 18:57
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot left a comment

Choose a reason for hiding this comment

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

5 issues found across 18 files

Confidence score: 2/5

  • Merge risk is high because there are multiple medium-to-high severity issues (6–7/10) with strong confidence, including security-sensitive behavior changes.
  • Most severe: src/hooks/todo-continuation-enforcer/loop-state-persistence.ts builds filesystem paths from an unsanitized sessionID, which can enable path traversal outside .sisyphus/loop-state.
  • src/hooks/todo-continuation-enforcer/continuation-injection.ts appears to broaden prompt-override write bypasses beyond blocked-mode enforcement, potentially bypassing escalation-related permission checks; src/hooks/todo-continuation-enforcer/handler.ts may also miss persisted state when sessions are created before session.idle.
  • Pay close attention to src/hooks/todo-continuation-enforcer/loop-state-persistence.ts, src/hooks/todo-continuation-enforcer/continuation-injection.ts, src/hooks/todo-continuation-enforcer/handler.ts, src/hooks/todo-continuation-enforcer/clarification-detection.ts - security-sensitive path handling, permission bypass scope, persistence initialization ordering, and clarification-turn parsing correctness.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="src/hooks/todo-continuation-enforcer/continuation-injection.ts">

<violation number="1" location="src/hooks/todo-continuation-enforcer/continuation-injection.ts:151">
P2: Write-permission bypass is too broad: any prompt override now bypasses tool permission checks, including escalation warning prompts, not just blocked-mode enforcement.</violation>
</file>

<file name="src/hooks/todo-continuation-enforcer/handler.ts">

<violation number="1" location="src/hooks/todo-continuation-enforcer/handler.ts:64">
P2: Session-state persistence directory is initialized only on `session.idle`, so earlier `getState()` calls can instantiate sessions before persistence is configured, skipping persisted state load for that session.</violation>
</file>

<file name="src/hooks/todo-continuation-enforcer/loop-state-persistence.ts">

<violation number="1" location="src/hooks/todo-continuation-enforcer/loop-state-persistence.ts:17">
P1: Unsanitized `sessionID` is used in filesystem path construction, enabling potential path traversal outside `.sisyphus/loop-state`.</violation>
</file>

<file name="src/hooks/todo-continuation-enforcer/clarification-detection.ts">

<violation number="1" location="src/hooks/todo-continuation-enforcer/clarification-detection.ts:184">
P3: Duplicate regex pattern in the Hindi clarification-detection block causes redundant matching and future maintenance drift.</violation>

<violation number="2" location="src/hooks/todo-continuation-enforcer/clarification-detection.ts:243">
P2: Assistant messages without `parts` are skipped instead of treated as the terminal latest assistant turn, allowing stale older messages to drive clarification-loop detection.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

}

function getStatePath(directory: string, sessionID: string): string {
return join(directory, LOOP_STATE_DIR, `${sessionID}.json`)
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

P1: Unsanitized sessionID is used in filesystem path construction, enabling potential path traversal outside .sisyphus/loop-state.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/todo-continuation-enforcer/loop-state-persistence.ts, line 17:

<comment>Unsanitized `sessionID` is used in filesystem path construction, enabling potential path traversal outside `.sisyphus/loop-state`.</comment>

<file context>
@@ -0,0 +1,97 @@
+}
+
+function getStatePath(directory: string, sessionID: string): string {
+  return join(directory, LOOP_STATE_DIR, `${sessionID}.json`)
+}
+
</file context>
Fix with Cubic

}

if (!hasWritePermission(tools)) {
if (!hasWritePermission(tools) && !promptOverride) {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

P2: Write-permission bypass is too broad: any prompt override now bypasses tool permission checks, including escalation warning prompts, not just blocked-mode enforcement.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/todo-continuation-enforcer/continuation-injection.ts, line 151:

<comment>Write-permission bypass is too broad: any prompt override now bypasses tool permission checks, including escalation warning prompts, not just blocked-mode enforcement.</comment>

<file context>
@@ -145,14 +148,15 @@ export async function injectContinuation(args: {
   }
 
-  if (!hasWritePermission(tools)) {
+  if (!hasWritePermission(tools) && !promptOverride) {
     log(`[${HOOK_NAME}] Skipped: agent lacks write permission`, { sessionID, agent: agentName })
     return
</file context>
Fix with Cubic

const sessionID = props?.sessionID as string | undefined
if (!sessionID) return

sessionStateStore.setDirectory(ctx.directory)
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

P2: Session-state persistence directory is initialized only on session.idle, so earlier getState() calls can instantiate sessions before persistence is configured, skipping persisted state load for that session.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/todo-continuation-enforcer/handler.ts, line 64:

<comment>Session-state persistence directory is initialized only on `session.idle`, so earlier `getState()` calls can instantiate sessions before persistence is configured, skipping persisted state load for that session.</comment>

<file context>
@@ -61,6 +61,7 @@ export function createTodoContinuationHandler(args: {
       const sessionID = props?.sessionID as string | undefined
       if (!sessionID) return
 
+      sessionStateStore.setDirectory(ctx.directory)
       sessionStateStore.startPruneInterval()
       await handleSessionIdle({
</file context>
Fix with Cubic


if (role === "user") return { isAskingForClarification: false }

if (role === "assistant" && msg.parts) {
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

P2: Assistant messages without parts are skipped instead of treated as the terminal latest assistant turn, allowing stale older messages to drive clarification-loop detection.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/todo-continuation-enforcer/clarification-detection.ts, line 243:

<comment>Assistant messages without `parts` are skipped instead of treated as the terminal latest assistant turn, allowing stale older messages to drive clarification-loop detection.</comment>

<file context>
@@ -0,0 +1,287 @@
+
+    if (role === "user") return { isAskingForClarification: false }
+
+    if (role === "assistant" && msg.parts) {
+      const hasQuestionTool = msg.parts.some(
+        (part) =>
</file context>
Fix with Cubic

// ── हिन्दी (HINDI) ──────────────────────────────────────
/\u092E\u0941\u091D\u0947 \u0914\u0930 (जानकारी|विवरण|निर्देश|स्पष्टीकरण|मार्गदर्शन|विशिष्टताएं|संदर्भ) (चाहिए|की जरूरत है)/i,
/\u0915\u0943\u092A\u092F\u093E[, ]?(स्पष्ट करें|समझाएं|निर्दिष्ट करें|बताएं|विस्तृत करें|परिभाषित करें|प्रदान करें)/i,
/\u092E\u0948\u0902 (क्या करूं|क्या करना चाहिए|क्या कर सकता हूं)/i,
Copy link
Copy Markdown

@cubic-dev-ai cubic-dev-ai Bot Apr 27, 2026

Choose a reason for hiding this comment

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

P3: Duplicate regex pattern in the Hindi clarification-detection block causes redundant matching and future maintenance drift.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At src/hooks/todo-continuation-enforcer/clarification-detection.ts, line 184:

<comment>Duplicate regex pattern in the Hindi clarification-detection block causes redundant matching and future maintenance drift.</comment>

<file context>
@@ -0,0 +1,287 @@
+  // ── हिन्दी (HINDI) ──────────────────────────────────────
+  /\u092E\u0941\u091D\u0947 \u0914\u0930 (जानकारी|विवरण|निर्देश|स्पष्टीकरण|मार्गदर्शन|विशिष्टताएं|संदर्भ) (चाहिए|की जरूरत है)/i,
+  /\u0915\u0943\u092A\u092F\u093E[, ]?(स्पष्ट करें|समझाएं|निर्दिष्ट करें|बताएं|विस्तृत करें|परिभाषित करें|प्रदान करें)/i,
+  /\u092E\u0948\u0902 (क्या करूं|क्या करना चाहिए|क्या कर सकता हूं)/i,
+  /\u0915\u094C\u0928 \u0938\u093E (API|एंडपॉइंट|फ़ील्ड|पैरामीटर|तरीका|विकल्प)/i,
+  /(आपके|आपकी|आपका) (उत्तर|प्रतिक्रिया|फीडबैक|निर्णय|राय|जवाब) (का इंतजार है|प्रतीक्षा है|इंतजार कर रहा हूं)/i,
</file context>
Fix with Cubic

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.

1 participant