Metadata-Version: 2.4
Name: overloop
Version: 0.3.0
Summary: Stop your AI agent from looping. A deterministic Claude Code hook that blocks repeated identical tool calls — no model, no network, no API key.
Project-URL: Homepage, https://github.com/theadamdanielsson/overloop
Project-URL: Issues, https://github.com/theadamdanielsson/overloop/issues
Author-email: Adam Danielsson <the.adam.danielsson@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: agent,ai,claude-code,cost,guardrail,hook,llm,loop,tokens
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Topic :: Software Development :: Quality Assurance
Requires-Python: >=3.9
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == 'dev'
Description-Content-Type: text/markdown

# overloop

**Your agent already ran that.**

overloop is a small Claude Code hook that stops your agent from paying for work it already did. It watches every tool call before it runs, using nothing but a hash and a counter, and blocks two kinds of waste:

- **Loops** — the same call, unchanged, three times in a row. An agent that runs `npm test`, sees it fail, and runs the exact same `npm test` again — and again — is not making progress. It is burning tokens on a loop, and you find out when the bill arrives.
- **Redundant reads** — re-reading a file or re-running a search whose result is already sitting in the agent's context. Running it again just re-bills the same content.
- **Oversized output** — a command that dumps a 500KB log or file into the conversation, which you then pay for on every following turn. overloop keeps the top of it and files the rest away.

There is no model in the loop and nothing leaves your machine: it compares calls with a hash, which takes microseconds.

It is the runtime sibling of [overllm](https://github.com/theadamdanielsson/overllm). overllm catches the AI calls you didn't need in your *code*; overloop catches the ones your *running agent* keeps repeating.

## Install

```bash
pip install overloop
overloop install
```

`overloop install` adds the hook to `~/.claude/settings.json`. Start a new Claude Code session and it is active. Use `overloop install --project` to scope it to the current repo instead, and `overloop uninstall` to remove it.

## What it does

### Loops

The agent gets stuck retrying a failing command. On the third identical attempt, overloop steps in:

```
Bash  npm test
Bash  npm test
Bash  npm test
  ✗ blocked by overloop
    This exact Bash call has run 3x in a row with no change — you are in a
    loop. Change your approach: fix the underlying error, try a different
    command, or ask the user.
```

The block is fed back to the agent as the reason it cannot run the call, so it course-corrects instead of grinding. A *different* call at any point resets the count — normal work, where each step differs from the last, never trips it.

### Redundant reads

The agent reads a file, does some work, then reads the same file again although nothing has changed:

```
Read  src/config.py
Read  src/config.py
  ✗ blocked by overloop
    This exact Read call already ran and nothing has changed it since — the
    result is already in your context. Use it instead of re-running the call.
```

The safety here is invalidation, not guesswork. A read is only blocked while its result is guaranteed current. Editing a file clears the cached read of that file; a shell command — which could touch anything — clears every cached file read. So a file that actually changed is always read fresh, and a stale result is never served. This covers `Read`, `Grep`, `Glob`, and `WebFetch`; a different call is never blocked.

### Oversized output

A command dumps far more into the conversation than the model will use:

```
Bash  cat build.log        (312,480 chars)
  ↳ trimmed by overloop
    [first 4,000 chars kept]
    [overloop: this output was 312,480 characters and has been trimmed to the
    first 3,980. Full output saved to ~/.overloop/spill/... — read that file if
    you need the rest.]
```

The full output is written to a file before it is trimmed, so nothing is lost — the agent reads that file if it turns out to need more. Only plain-text output over the size limit is touched, and by default only for `Bash`, `Grep`, `Glob`, and `WebFetch`, where the size is incidental rather than something you asked for.

## It never breaks your agent

overloop fails open. If it hits a bug, can't read its state, or gets input it doesn't understand, it allows the tool call and stays quiet. The worst case is that it does nothing, never that it wedges your session. It runs no model, makes no network call, needs no API key, and sends no telemetry — it is a hash and a counter reading a small file under `~/.overloop`. Pull your network cable and it behaves exactly the same.

The whole thing is a few hundred lines of dependency-free Python, so you can read all of it before you trust it with your agent.

## Configure

Everything has a default and nothing is required.

```bash
export OVERLOOP_LOOP_THRESHOLD=5   # block a loop after 5 identical calls instead of 3
export OVERLOOP_DEDUP=0            # turn the redundant-read guard off
export OVERLOOP_TRUNCATE=0         # turn the oversized-output guard off
export OVERLOOP_MODE=ask           # ask you instead of blocking outright
```

Or in `~/.overloop/config.json`:

```json
{
  "loop_threshold": 3,
  "loop_exclude_tools": ["TodoWrite"],
  "dedup_enabled": true,
  "dedup_tools": ["Read", "Grep", "Glob", "WebFetch"],
  "truncate_enabled": true,
  "truncate_tools": ["Bash", "Grep", "Glob", "WebFetch"],
  "truncate_max_chars": 50000,
  "mode": "deny"
}
```

- `loop_threshold` — identical calls in a row before a block. Minimum 2.
- `loop_exclude_tools` — tool names to never loop-check.
- `dedup_enabled` / `dedup_tools` — block redundant reads, and which tools.
- `truncate_enabled` / `truncate_tools` — trim oversized output, and which tools.
- `truncate_max_chars` — the size above which output is trimmed.
- `mode` — `deny` blocks the call; `ask` escalates it to you.

Run `overloop stats` to see how many loops and redundant reads it has stopped and how much output it has trimmed.

## How it works

Everything runs off one fingerprint per call — `sha1(tool_name + canonical(args))`, with argument keys sorted so order doesn't matter.

The loop guard keeps one number: how many times in a row that fingerprint has repeated. Same call again, the count goes up; any different call, it resets to one. At the threshold, the call is blocked.

The dedup guard keeps the set of read-only fingerprints already seen, each tagged with the file it read. A repeat is a block. A mutating call removes the fingerprints it could have invalidated first — the edited file's read, or, for a shell command, every file read — so the guard only ever blocks a read whose result is still current.

The loop and dedup guards run on `PreToolUse`, before a call. The truncate guard runs on `PostToolUse`, after it, because that is the only point where the result exists and can be replaced. It writes the full output to a spill file, then hands the model a preview plus the file path.

State lives in one small JSON file per session under `~/.overloop`, keyed by Claude Code's session id, because the hook runs as a fresh process on every call and has to remember between them.

## Scope

overloop does three things — stop loops, stop redundant reads, trim oversized output — and nothing else. It does not route models, cache semantically, or touch the model's reasoning; it only gates and trims tool calls with exact, deterministic checks. There are no embeddings and no fuzzy matching anywhere in it: two calls are either identical or they are not. That boundary is deliberate. It is what keeps the whole thing small enough to read before you trust it, and safe enough to leave on.

## Contributing

The most useful thing you can send is a false positive: a real sequence of tool calls where overloop blocked work that was not a loop. A guard is only worth running if it is right. [CONTRIBUTING.md](CONTRIBUTING.md) covers how to report one and run the tests.

## License

MIT © Adam Danielsson
