@wkqco33/pi-loop-guard

Pi extension that detects and stops agent loops: repeated tool calls, alternating call cycles, repeated errors, and repeated responses.

Packages

Package details

extension

Install @wkqco33/pi-loop-guard from npm and Pi will load the resources declared by the package manifest.

$ pi install npm:@wkqco33/pi-loop-guard
Package
@wkqco33/pi-loop-guard
Version
0.2.0
Published
Sep 12, 2026
Downloads
284/mo · 284/wk
Author
wkqco33
License
MIT
Types
extension
Size
50.3 KB
Dependencies
0 dependencies · 1 peer
Pi manifest JSON
{
  "extensions": [
    "./extensions"
  ]
}

Security note

Pi packages can execute code and influence agent behavior. Review the source before installing third-party packages.

README

pi-loop-guard

npm license pi-package

A pi extension that detects runaway agent loops and stops them before they burn tokens.

It watches the behaviour a model actually exposes — tool calls, tool errors, and final assistant responses — and breaks the loop when a repeat pattern appears. Long but productive runs are never interrupted.

Install

pi install npm:@wkqco33/pi-loop-guard

Try it without installing:

pi -e npm:@wkqco33/pi-loop-guard

Reload a running pi session with /reload, or restart pi.

What it detects

Signal Pattern Default Action
Repeated tool call A A A 3 in a row block + abort
Repeating tool cycle A B A B A B, A B C A B C … 3 full cycles (cycle length ≤ 3) block + abort
Window tool frequency A … A … A … A (interleaved) 4 in 10-call window block + abort
Repeated tool error same error 3× in a row 3 in a row abort
Repeated assistant response identical text 3× in a row 3 in a row abort
Turn count > 12 turns in a run 12 warn only
Elapsed time > 180 s in a run 180 s warn only

Tool calls are compared by tool name and a deterministic serialization of the arguments, so read(a.ts) and read(b.ts) are different calls.

Error keys are normalized (digits collapsed, UUIDs, timestamps, and hex pointers masked), so dynamic tokens share a key.

Turn count and elapsed time are deliberately soft: a long run that keeps making progress is allowed to finish. Only genuine repeat patterns hard-stop.

Modes

Mode Behaviour
on (default) Report and hard-stop loops
observe Report loops but never block or abort — use this first on a new machine
off Disabled entirely, including the system-prompt hint

Start with observe for a few days, then switch to on once you trust the signal on your workloads.

Commands

/loop-guard           show current status
/loop-guard status    same as above
/loop-guard inspect   show recent tool call history
/loop-guard on        enable blocking
/loop-guard off       disable
/loop-guard observe   report only, never block
/loop-guard reset     reset the current run's counters and tripped flag

Configuration

Config file

Create .pi/loop-guard.json in your project (or ~/.pi/loop-guard.json globally). Project settings override global settings.

{
 "mode": "on",
 "locale": "en",
 "maxTurns": 12,
 "turnTimeoutMs": 180000,
 "repeatToolThreshold": 3,
 "repeatErrorThreshold": 3,
 "repeatAssistantThreshold": 3,
 "cycleRepeats": 3,
 "maxCycleLength": 3,
 "repeatFrequencyThreshold": 4,
 "frequencyWindowSize": 10,
 "ignoreTools": ["sleep"],
 "toolThresholdOverrides": { "bash": 2 }
}

"enabled": false is accepted as an alias for "mode": "off".

Environment variables

Environment variables override the config file.

export PI_LOOP_GUARD_MODE=on            # on | off | observe
export PI_LOOP_GUARD_LOCALE=en          # en | ko
export PI_LOOP_GUARD_MAX_TURNS=12
export PI_LOOP_GUARD_TURN_TIMEOUT_MS=180000
export PI_LOOP_GUARD_REPEAT_TOOL=3
export PI_LOOP_GUARD_REPEAT_ERROR=3
export PI_LOOP_GUARD_REPEAT_ASSISTANT=3
export PI_LOOP_GUARD_CYCLE_REPEATS=3
export PI_LOOP_GUARD_MAX_CYCLE_LENGTH=3
export PI_LOOP_GUARD_REPEAT_FREQUENCY=4
export PI_LOOP_GUARD_FREQUENCY_WINDOW=10
export PI_LOOP_GUARD_IGNORE_TOOLS=sleep,wait
export PI_LOOP_GUARD_TOOL_OVERRIDES="bash=2,read=5"

A stricter setup:

export PI_LOOP_GUARD_REPEAT_TOOL=2
export PI_LOOP_GUARD_REPEAT_ERROR=2
export PI_LOOP_GUARD_REPEAT_ASSISTANT=2

Precedence

built-in defaults  <  config file  <  environment variables  <  /loop-guard command

How it works

  1. before_agent_start appends a short "don't loop, report blockers" hint to the system prompt.
  2. tool_call records a digested toolName + stableJson(input) signature and checks for consecutive repeats and repeating cycles.
  3. tool_execution_end tracks consecutive identical failures; any success clears the streak.
  4. message_end compares finalized assistant text.
  5. turn_start and a periodic timer emit soft warnings only.
  6. On a hard stop the run is aborted and the tool call is blocked with { block: true, terminate: true }.
  7. agent_settled and session_shutdown clear timers and counters.

The detector itself (src/detector.ts) is pure and has no pi dependency, which is why it is exhaustively unit-tested.

Safety

  • Runs with the same privileges as the pi process.
  • Never writes session content or API keys to disk or to logs.
  • Only counts and hashes in memory; nothing is persisted.
  • Hard-stops can interrupt a legitimate retry, so observe is the recommended starting mode.
  • Turn/time limits never stop a run on their own.

Development

npm install
npm run verify        # typecheck + format:check + tests
npm run coverage      # 100% line coverage on src/
npm run bench         # hot-path micro-benchmarks
npm run pack:check

Tests use the Node.js built-in test runner with native TypeScript type-stripping — no test framework dependency. Contributors and agents must follow AGENTS.md.

License

MIT © wkqco33