pi-anti-repeat0.2.1
Detects and stops repeated work in the Pi coding agent, including loops inside streamed thinking
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.
Reports 0
No reports yet.
Readme
pi-anti-repeat
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.