Vendor OpenClaw source as Adolf fork baseline
Some checks failed
ClawSweeper Dispatch / dispatch (push) Has been cancelled
CodeQL / Security High (actions) (push) Has been cancelled
CodeQL / Security High (channel-runtime-boundary) (push) Has been cancelled
CodeQL / Security High (core-auth-secrets) (push) Has been cancelled
CodeQL / Security High (mcp-process-tool-boundary) (push) Has been cancelled
CodeQL / Security High (network-ssrf-boundary) (push) Has been cancelled
CodeQL / Security High (plugin-trust-boundary) (push) Has been cancelled
CodeQL / Security High (process-exec-boundary) (push) Has been cancelled
Docs Sync Publish Repo / sync-publish-repo (push) Has been cancelled
Docs / docs (push) Has been cancelled
OpenClaw Stable Main Closeout / Resolve stable release closeout inputs (push) Has been cancelled
OpenClaw Stable Main Closeout / Verify stable main closeout (push) Has been cancelled
Workflow Sanity / no-tabs (push) Has been cancelled
Workflow Sanity / actionlint (push) Has been cancelled
Workflow Sanity / generated-doc-baselines (push) Has been cancelled
CI / runner-admission (push) Has been cancelled
CI / preflight (push) Has been cancelled
CI / security-fast (push) Has been cancelled
CI / pnpm-store-warmup (push) Has been cancelled
CI / build-artifacts (push) Has been cancelled
CI / native-i18n (push) Has been cancelled
CI / ${{ matrix.check_name }} (push) Has been cancelled
CI / ${{ matrix.checkName }} (push) Has been cancelled
CI / checks-node-compat-node22 (push) Has been cancelled
CI / check-bundled-channel-config-metadata (push) Has been cancelled
CI / check-dependencies (push) Has been cancelled
CI / check-guards (push) Has been cancelled
CI / check-lint (push) Has been cancelled
CI / check-prod-types (push) Has been cancelled
CI / check-shrinkwrap (push) Has been cancelled
CI / check-test-types (push) Has been cancelled
CI / check-additional-boundaries-a (push) Has been cancelled
CI / check-additional-boundaries-bcd (push) Has been cancelled
CI / check-additional-extension-bundled (push) Has been cancelled
CI / check-additional-extension-channels (push) Has been cancelled
CI / check-additional-extension-package-boundary (push) Has been cancelled
CI / check-additional-runtime-topology-architecture (push) Has been cancelled
CI / check-session-accessor-boundary (push) Has been cancelled
CI / check-session-transcript-reader-boundary (push) Has been cancelled
CI / check-docs (push) Has been cancelled
CI / skills-python (push) Has been cancelled
CI / macos-swift (push) Has been cancelled
CI / ios-build (push) Has been cancelled
CI / ci-timings-summary (push) Has been cancelled
Native App Locale Refresh / Refresh native fa (push) Has been cancelled
Native App Locale Refresh / Refresh native fr (push) Has been cancelled
Native App Locale Refresh / Refresh native hi (push) Has been cancelled
Native App Locale Refresh / Refresh native id (push) Has been cancelled
Native App Locale Refresh / Refresh native it (push) Has been cancelled
Native App Locale Refresh / Refresh native ja-JP (push) Has been cancelled
Control UI Locale Refresh / plan (push) Has been cancelled
Control UI Locale Refresh / Refresh ${{ matrix.locale }} (push) Has been cancelled
Control UI Locale Refresh / Commit control UI locale refresh (push) Has been cancelled
Live Media Runner Image / Build live media runner image (push) Has been cancelled
Native App Locale Refresh / Refresh native ar (push) Has been cancelled
Native App Locale Refresh / Refresh native de (push) Has been cancelled
Native App Locale Refresh / Refresh native es (push) Has been cancelled
Native App Locale Refresh / Refresh native ko (push) Has been cancelled
Native App Locale Refresh / Refresh native nl (push) Has been cancelled
Native App Locale Refresh / Refresh native pl (push) Has been cancelled
Native App Locale Refresh / Refresh native pt-BR (push) Has been cancelled
Native App Locale Refresh / Refresh native ru (push) Has been cancelled
Native App Locale Refresh / Refresh native sv (push) Has been cancelled
Native App Locale Refresh / Refresh native th (push) Has been cancelled
Native App Locale Refresh / Refresh native tr (push) Has been cancelled
Native App Locale Refresh / Refresh native uk (push) Has been cancelled
Native App Locale Refresh / Refresh native vi (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-CN (push) Has been cancelled
Native App Locale Refresh / Refresh native zh-TW (push) Has been cancelled
Native App Locale Refresh / Commit native locale refresh (push) Has been cancelled
Plugin Init Scaffold Validation / Validate provider scaffold (push) Has been cancelled
Plugin NPM Release / preview_plugins_npm (push) Has been cancelled
Plugin NPM Release / Validate release publish approval (push) Has been cancelled
Plugin NPM Release / preview_plugin_pack (push) Has been cancelled
Plugin NPM Release / publish_plugins_npm (push) Has been cancelled
Sandbox Common Smoke / sandbox-common-smoke (push) Has been cancelled
Website Installer Sync / static (push) Has been cancelled
Website Installer Sync / linux-docker (push) Has been cancelled
Website Installer Sync / macos-installer (push) Has been cancelled
Website Installer Sync / windows-installer (push) Has been cancelled
Website Installer Sync / sync-website (push) Has been cancelled

Adolf is a fork/vendored clone of github.com/openclaw/openclaw (v2026.6.11),
free to diverge. Tree copied sans upstream .git; upstream remote added for
future syncs. Node pinned to 24 (.nvmrc); engines already require >=22.19.
Preserves docs/ARCHITECTURE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LeqyaxJF2nbRXJtae2kNB2
This commit is contained in:
2026-07-05 09:36:54 +00:00
parent 3216769225
commit bedb527145
21108 changed files with 6010766 additions and 0 deletions

View File

@@ -0,0 +1,193 @@
---
summary: "How to enable and tune guardrails that detect repetitive tool-call loops"
title: "Tool-loop detection"
read_when:
- A user reports agents getting stuck repeating tool calls
- You need to tune repetitive-call protection
- You are editing agent tool/runtime policies
- You hit `compaction_loop_persisted` aborts after a context-overflow retry
---
OpenClaw has two cooperating guardrails against repetitive tool-call patterns,
both configured under `tools.loopDetection`:
1. **Loop detection** (`enabled`) - disabled by default. Watches the rolling
tool-call history for repeated patterns and unknown-tool retries.
2. **Post-compaction guard** (`postCompactionGuard`) - enabled whenever
`enabled` is not explicitly `false`. Arms after every compaction-retry and
aborts the run if the agent repeats the same `(tool, args, result)` triple
within the window.
Set `tools.loopDetection.enabled: false` to silence both guardrails.
## Why this exists
- Detect repetitive sequences that make no progress.
- Detect high-frequency no-result loops (same tool, same inputs, repeated
errors).
- Detect specific repeated-call patterns for known polling tools.
- Break context-overflow -> compaction -> same-loop cycles instead of letting
them run indefinitely.
## Configuration block
Global defaults, with every documented field shown:
```json5
{
tools: {
loopDetection: {
enabled: false, // master switch for the rolling-history detectors
historySize: 30,
warningThreshold: 10,
criticalThreshold: 20,
unknownToolThreshold: 10,
globalCircuitBreakerThreshold: 30,
detectors: {
genericRepeat: true,
knownPollNoProgress: true,
pingPong: true,
},
postCompactionGuard: {
windowSize: 3, // armed after compaction-retry; runs unless enabled is explicitly false
},
},
},
}
```
Per-agent override (optional, at `agents.list[].tools.loopDetection`):
```json5
{
agents: {
list: [
{
id: "safe-runner",
tools: {
loopDetection: {
enabled: true,
warningThreshold: 8,
criticalThreshold: 16,
},
},
},
],
},
}
```
Per-agent settings overlay the global block field by field (including nested
`detectors` and `postCompactionGuard`), so an agent only needs to set the
fields it wants to change.
### Field behavior
| Field | Default | Effect |
| -------------------------------- | ------- | ------------------------------------------------------------------------------------------------------------------------------------------ |
| `enabled` | `false` | Master switch for the rolling-history detectors. `false` also disables the post-compaction guard. |
| `historySize` | `30` | Number of recent tool calls kept for analysis. |
| `warningThreshold` | `10` | Repeat count before a pattern is classified as warning-only. |
| `criticalThreshold` | `20` | Repeat count for blocking a no-progress loop pattern. Runtime clamps this above `warningThreshold` if misconfigured. |
| `unknownToolThreshold` | `10` | Blocks repeated calls to the same unavailable tool after this many misses. Not gated by `detectors`. |
| `globalCircuitBreakerThreshold` | `30` | Global no-progress breaker across all detectors. Runtime clamps this above `criticalThreshold` if misconfigured. Not gated by `detectors`. |
| `detectors.genericRepeat` | `true` | Warns on repeated same-tool + same-args calls; blocks once those calls also return identical outcomes. |
| `detectors.knownPollNoProgress` | `true` | Detects known no-progress polling patterns (`process` with `action: "poll"`/`"log"`, `command_status`). |
| `detectors.pingPong` | `true` | Detects alternating no-progress ping-pong patterns between two calls. |
| `postCompactionGuard.windowSize` | `3` | Attempts the guard stays armed after compaction, and the count of identical triples that aborts the run. |
For `exec`, no-progress hashing compares stable command outcomes (status,
exit code, timed-out flag, output) and ignores volatile runtime metadata such
as duration, PID, session ID, and working directory. Outbound message-send
results are hashed with volatile per-call ids (message id, file id, timestamp)
stripped, so a "sent" result does not look identical to a different "sent"
result. When a run id is available, history is evaluated only within that run,
so scheduled heartbeat cycles and fresh runs do not inherit stale loop counts
from earlier runs.
## Recommended setup
- For smaller models, set `enabled: true` and leave thresholds at their
defaults. Flagship models rarely need rolling-history detection and can
leave the master switch `false` while still benefiting from the
post-compaction guard.
- Keep thresholds ordered `warningThreshold < criticalThreshold <
globalCircuitBreakerThreshold`; the runtime nudges `criticalThreshold` and
`globalCircuitBreakerThreshold` upward if you set them at or below the
threshold they must exceed.
- If false positives occur:
- Raise `warningThreshold` and/or `criticalThreshold`.
- Optionally raise `globalCircuitBreakerThreshold`.
- Disable only the specific detector causing issues (`detectors.<name>: false`).
- Reduce `historySize` for a shorter historical window.
- To disable everything, including the post-compaction guard, set
`tools.loopDetection.enabled: false` explicitly.
## Post-compaction guard
After a compaction-retry following a context-overflow, the runner arms a
short-window guard on the next few tool calls. If the agent emits the same
`(toolName, argsHash, resultHash)` triple `postCompactionGuard.windowSize`
times within that window, the guard concludes compaction did not break the
loop and aborts the run with a `compaction_loop_persisted` error.
The guard is gated by the master `tools.loopDetection.enabled` flag with one
twist: it stays **enabled when the flag is unset or `true`**, and only turns
off when the flag is explicitly `false`. This is intentional - the guard
exists to escape compaction loops that would otherwise burn unbounded tokens,
so a no-config user still gets the protection.
```json5
{
tools: {
loopDetection: {
// master switch; set false to disable the guard along with the rolling detectors
enabled: true,
postCompactionGuard: {
windowSize: 3, // default
},
},
},
}
```
- Lower `windowSize` is stricter (fewer attempts before abort).
- Higher `windowSize` gives the agent more recovery attempts.
- The guard never aborts while results are changing; only byte-identical
results across the window trigger it.
- It only arms in the immediate aftermath of a compaction-retry, not at other
points in a run.
<Note>
The post-compaction guard runs whenever the master flag is not explicitly `false`, even if you never wrote a `tools.loopDetection` block. To verify, look for `post-compaction guard armed for N attempts` in the gateway log immediately after a compaction event.
</Note>
## Logs and expected behavior
When a loop is detected, OpenClaw logs a loop event and either warns or blocks
the next tool-cycle depending on severity, protecting against runaway token
spend and lockups while preserving normal tool access.
- Warnings come first.
- Blocking follows once a pattern persists past the warning threshold.
- Critical thresholds block the next tool-cycle and surface a clear
loop-detection reason in the run record.
- The post-compaction guard emits `compaction_loop_persisted` errors naming
the offending tool and identical-call count.
## Related
<CardGroup cols={2}>
<Card title="Exec approvals" href="/tools/exec-approvals" icon="shield">
Allow/deny policy for shell execution.
</Card>
<Card title="Thinking levels" href="/tools/thinking" icon="brain">
Reasoning effort levels and provider-policy interaction.
</Card>
<Card title="Sub-agents" href="/tools/subagents" icon="users">
Spawning isolated agents to bound runaway behavior.
</Card>
<Card title="Configuration reference" href="/gateway/config-tools#toolsloopdetection" icon="gear">
Full `tools.loopDetection` schema and merging semantics.
</Card>
</CardGroup>