omp packages

pi-anti-repeat0.2.1

Detects and stops repeated work in the Pi coding agent, including loops inside streamed thinking

GitHub Actions Published 6 days ago MIT ~474 downloads/mo

Untested on omp

This is a pi package. No one has reported whether it works on omp yet. If you try it, add your result below.

Did it work for you?

Result

One report per person. A new report replaces your old one.

Reports 0

No reports yet.

Readme

pi-anti-repeat

pi-anti-repeat: a Pi extension that stops an agent that is stuck repeating itself

pi-anti-repeat is an extension for the Pi coding agent that stops an agent that is stuck repeating itself. It watches the model's streamed thinking and its finished runs, and when the same work keeps coming back, it cuts the response off and tells the model to change its approach.

It catches loops inside a single thought as it streams, including the two ways streaming models collapse in practice: a phrase or single token repeated forever (such as = the = the = the), and reasoning that falls apart into runs of punctuation (such as = = = =). In one recorded case a model spent 74 seconds writing the same five sentences about 375 times. Replayed through the detector, that response is stopped after 866 words, about 5 seconds in. A response that collapsed into 72 KB of = the is stopped within the first 500 words. It also catches runs that end with the same outcome or the same error again and again.

Install

pi-anti-repeat is an npm package:

pi install npm:pi-anti-repeat

Or install from Git:

pi install git:github.com/osolmaz/pi-anti-repeat

Run /reload in an open Pi session to load it. Add @<version>, @<tag>, or @<commit> to pin a version. Anti-Repeat needs Pi 0.87.0 or later.

Anti-Repeat is on from the start of every session. It shows nothing in the footer while it is only watching; the status line appears after it corrects or stops a run. Use /anti-repeat off to turn it off for the current session. Nothing is saved between sessions.

Detection and correction

On the first detection in an epoch, Anti-Repeat sends one visible anti-repeat message. The message tells the model to stop the current approach and restate what it has verified before it chooses a different next action. If the repetition was in streamed thinking, the response is cut off first and the message starts a new turn. On a second detection in the same epoch, it stops the run and sends nothing else.

An epoch starts with each new instruction from you. Short prompts such as "continue" or "go ahead" keep the current epoch, so a loop cannot hide behind them.

These checks trigger a detection:

  • Three separate 96-word passages each appear three times in one streamed thought, or one sequence of up to 96 words repeats across a span of at least 288 words. Case, punctuation, whitespace, and Unicode composition do not matter, apart from separator runs: each 16 separator characters in a row count as one reserved word, and an emoji counts as two.
  • The same cycle of one to four run outcomes repeats three times.
  • The same error ends three runs in a row.
  • Four runs started by "continue" prompts repeat at least 85% of the same actions.

A long run on its own never triggers anything. Work that keeps making different progress continues.

Commands

/anti-repeat status
/anti-repeat off
/anti-repeat on
/anti-repeat reset

reset starts a new epoch, which also clears a stop.

Cost

The thinking check runs on every streamed chunk, so it is built to be cheap. Streaming 1 MB of thinking through the detector takes about 50 ms, and the cost per word does not grow with the windowWords option. The listener is removed while Anti-Repeat is off, paused, or stopped. Memory stays bounded at 2,048 tracked windows and 256 recent window keys per response, and twelve run summaries.

Anti-Repeat never stores raw model output, thinking text, or tool content. The corrective message is the only session entry it adds.

Use it from code

Pi distributions can create the extension with their own options instead of installing the default:

npm install pi-anti-repeat
import { createAntiRepeat } from "pi-anti-repeat";

export default createAntiRepeat({
  enabled: true,
  command: "anti-repeat", // or false for no command
  status: "anti-repeat", // footer status key, or false
  notify: true,
  detectors: {
    reasoning: { windowWords: 96, repeats: 3, matchedWindows: 3 },
    repeatedError: false, // turn one detector off
  },
  policy: ({ detection, corrections, activeResponse }) => (corrections === 0 ? "correct" : "stop"),
  message: (detection) => "You are repeating yourself. Try a different approach.",
  isContinuation: (text) => /^(continue|go on)$/i.test(text.trim()),
});

The policy returns ignore, notify, correct, or stop for each detection. The default corrects once and then stops. Invalid options throw when createAntiRepeat is called.

The detectors are also available without Pi from pi-anti-repeat/core:

import { RepeatDetector } from "pi-anti-repeat/core";

const detector = new RepeatDetector();
detector.startReasoning();
for (const delta of stream) {
  const detection = detector.observeReasoning(delta);
  if (detection !== null) break;
}

Events for other extensions

Anti-Repeat reports on the anti-repeat channel of pi.events. Each event has version: 1, a type (detected, corrected, stopped, reset, enabled, or disabled), the current epoch, and the detection when there is one. An extension that continues runs on its own should pause when it sees stopped.

Other extensions can control it on the anti-repeat:control channel:

import { ANTI_REPEAT_CONTROL_CHANNEL } from "pi-anti-repeat/protocol";

pi.events.emit(ANTI_REPEAT_CONTROL_CHANNEL, {
  version: 1,
  action: "ignore-next-run", // or "pause", "resume", "reset"
  source: "my-extension",
});

Send ignore-next-run just before you restart a run on purpose, for example after cutting off a response, so the regenerated thinking is not counted as a repeat.