CliffCompaction
Autocompaction for coding agents, as a transparent API proxy. Point an agent's base URL at it and long sessions stay under a token budget — the agent is unchanged and unaware.
Quick start
uv tool install cliffcompaction # or: pip install cliffcompaction
Or, from a clone:
uv tool install --force --editable . # or: pip install -e .
Then start the proxy — this adds the base-URL variables to your shell profile and installs a background service (launchd on macOS, systemd --user on Linux):
cliff enable # defaults to Claude Code
Open a new terminal and run your agent as usual — claude, or anything reading those variables.
Other commands:
cliff status— health.cliff restart— picks up an upgrade, keeping the daemon's flags. A running daemon keeps serving the code it started with, so an upgrade changes nothing until you restart;cliff statuswarns when that has happened.cliff watch— live view of requests, matches and compactions (recommended for Claude Code and Codex CLI; other scaffolds compact normally, but the watch display may mislabel).cliff disable— reverses everything.
More than one daemon
For a second provider, or a different threshold per client. Each instance gets its own port, flags and log; status, restart, watch and disable all take --name:
cliff enable --name kimi --port 8305 --anthropic-upstream https://api.kimi.com/coding
Named instances don't touch your shell env — point the client at the port yourself:
ANTHROPIC_BASE_URL=http://127.0.0.1:8305 claude
Codex CLI
Codex routes by config file rather than an env var, so it needs a provider block in ~/.codex/config.toml:
cliff enable --name codex --port 8448 --openai-upstream https://chatgpt.com
model_provider = "cliff" # top-level key: must go above the first [table] header
[model_providers.cliff]
name = "OpenAI via cliff"
base_url = "http://127.0.0.1:8448/backend-api/codex"
wire_api = "responses"
requires_openai_auth = true
One session without the proxy
Drop the env var for a single process:
env -u ANTHROPIC_BASE_URL claude # OpenAI-side clients: -u OPENAI_BASE_URL -u OPENAI_API_BASE
Codex has no env var to unset — override the key instead:
codex -c model_provider=openai # and again on resume: ... resume --last
Without installing a daemon
A proxy on a free port, env set for that command only, torn down after:
cliff run -- claude
cliff run --shadow -- claude # observe-only: logs what it would compact, changes nothing
--shadow also works on cliff enable — useful to confirm a scaffold's history is prefix-stable before going active.
Or run the server yourself:
cliff serve --port 8257 --threshold 200000 --keep-recent 3
export ANTHROPIC_BASE_URL=http://127.0.0.1:8257
export OPENAI_BASE_URL=http://127.0.0.1:8257/v1
Important: disable your scaffold's own compaction. Under the proxy it sees small prompt counts, so its triggers generally won't fire anyway — but some rewrite history in place, which breaks the prefix matching CliffCompaction relies on.
How it works
Every request an agent sends is history + latest step. The proxy:
- Canonicalizes and hashes each message, forming a hash chain.
- If the outgoing size exceeds the threshold, it rebuilds the history as
[head verbatim] + [one CliffCompaction summary message] + [last keep_recent turns verbatim], keyed by the original prefix's chain hash. - Later requests arrive with the original history. The proxy finds the longest stored prefix and substitutes the compacted version, forwarding
C + tailinstead ofS + tail. - If the provider still rejects with a context-length error, the proxy compacts and replays once.
The summary is mechanical, built by content class:
| Content | Treatment |
|---|---|
| Tool results | Kept verbatim iff ≤ 500 chars, dropped otherwise |
| Tool calls | One-line signatures (name + truncated arguments) |
| Assistant text & thinking | Kept in full by default |
| Human text | Verbatim in the current summary |
| Head (system + task) & recent turns | Untouched |
| Images | Dropped from summaries, verbatim in head and recent turns |
Re-compaction drops the previous summary.
Fail-open contract: any failure — unparseable body, no prefix match, store error — means verbatim passthrough.
Configuration
| Flag / env var | Default | Meaning |
|---|---|---|
--threshold / CLIFF_THRESHOLD_TOKENS |
200000 | compaction trigger, in estimated tokens. The default suits 1M-context models; on 200–250k ones use 100000–128000 |
--keep-recent / CLIFF_KEEP_RECENT |
3 | recent assistant-step turns kept verbatim |
--thought-max-chars / CLIFF_THOUGHT_MAX_CHARS |
0 (unlimited) | cap on assistant text in summaries |
--thinking-max-chars / CLIFF_THINKING_MAX_CHARS |
0 (unlimited) | cap on thinking text, independent of the thought cap |
--drop-thinking / CLIFF_KEEP_THINKING=0 |
keep | exclude thinking/reasoning text from summaries |
--result-max-chars / CLIFF_RESULT_MAX_CHARS |
500 | tool results longer than this are dropped |
CLIFF_HUMAN_MAX_CHARS |
20000 | sanity cap on human text in summaries |
--anthropic-upstream / CLIFF_ANTHROPIC_UPSTREAM |
https://api.anthropic.com |
|
--openai-upstream / CLIFF_OPENAI_UPSTREAM |
https://api.openai.com |
|
--shadow / CLIFF_SHADOW |
off | observe-only mode |
--strict / CLIFF_STRICT |
off | fail a request still over threshold after compaction (HTTP 400) instead of sending it anyway — for measurement runs |
--debug-dir / CLIFF_DEBUG_DIR |
off | dump each handled request's incoming/outgoing messages as JSON |
Supported dialects: Anthropic Messages (/v1/messages), OpenAI Chat Completions (/chat/completions) and OpenAI Responses (/responses), native tool calling. Everything else passes through verbatim, to the Anthropic upstream — or to the OpenAI one when that is the only upstream configured.
Development
Python 3.11+.
uv sync
uv run pytest
Citation
CliffCompaction: Cost-Efficient Compaction for Long-Horizon Coding Agents
@article{nguyen2026cliffcompaction,
title = {CliffCompaction: Cost-Efficient Compaction for Long-Horizon Coding Agents},
author = {Nguyen, Trang and Cho, Eulrang and Chen, Bingqing and Dettmers, Tim},
journal = {arXiv preprint arXiv:2609.26779},
year = {2026}
}
Metadata
Release files for cliffcompaction 0.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | Actions |
|---|---|---|---|---|
| cliffcompaction-0.1.0.tar.gz | 71.6 kB | Without provenance | Details for cliffcompaction-0.1.0.tar.gz |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Actions Reset |
|---|---|---|---|---|
| cliffcompaction-0.1.0-py3-none-any.whl | Python 3 | none | any | Details for cliffcompaction-0.1.0-py3-none-any.whl |
Total release size: 126.0 kB
Release files / cliffcompaction-0.1.0.tar.gz
| Download URL | cliffcompaction-0.1.0.tar.gz |
|---|---|
| Size | 71.6 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
c2f9c1ab7b3b4bcc64715a8a9458c92a0d32181f43dfc79a3514d8c093870d2d
|
|
BLAKE2b-256 checksum How to use checksums |
6a79950bacbc106bcd2d98455c1145828ce1dc9d2d3c8035ad61672525f77184
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.3
|
Release files / cliffcompaction-0.1.0-py3-none-any.whl
| Download URL | cliffcompaction-0.1.0-py3-none-any.whl |
|---|---|
| Size | 54.5 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
8d08b3de86e0dc7fbdedc0edc0cc7759f2a1d85b180a1ecd684029fdcc119101
|
|
BLAKE2b-256 checksum How to use checksums |
ea3d030beb8af42c45bfc4fadf8aa9a7c60f6398627bd8d48e4749043ec161a6
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
No |
| Uploaded via |
uv/0.9.3
|
Security for cliffcompaction 0.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.
To learn more about protecting yourself from malicious actors, visit our package security documentation.