chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:39:12 +08:00
commit d8dcd5f6d1
8604 changed files with 2479390 additions and 0 deletions
+330
View File
@@ -0,0 +1,330 @@
---
title: "Compression Engines"
version: 3.8.40
lastUpdated: 2026-06-28
---
# Compression Engines
OmniRoute compression is built around engine contracts. A mode can run one engine directly
(`caveman` or `rtk`) or a deterministic stacked pipeline that executes multiple engines in order.
## Modes
| Mode | Engine path | Intended input |
| ------------ | ---------------------------------- | -------------------------------------------- |
| `off` | none | Exact prompt preservation |
| `lite` | Caveman lite helpers | Low-risk always-on cleanup |
| `standard` | Caveman | Natural-language prompt condensation |
| `aggressive` | Caveman + history/tool summarizers | Long chat sessions |
| `ultra` | Caveman + pruning helpers | Context-limit recovery |
| `rtk` | RTK | Terminal, shell, build, test, and git output |
| `stacked` | Pipeline, default `rtk -> caveman` | Mixed tool logs and prose, max savings |
## Engine Registry
The registry lives in `open-sse/services/compression/engines/registry.ts`. Engines expose a shared
contract:
- `id`: stable engine id such as `caveman` or `rtk`
- `apply(text, config)`: legacy execution path used by stacked pipelines
- `compress(input, config)`: primary execution path returning text + stats
- `getConfigSchema()`: returns the JSON-Schema-like shape of valid config
- `validateConfig(config)`: returns `{ valid, errors[] }`
Registration uses `registerCompressionEngine(engine)` (or `registerEngine` for advanced cases),
which calls `assertValidEngine()` and `validateConfig(defaultConfig)` before accepting.
Use `unregisterCompressionEngine(id)` to remove an engine at runtime.
`strategySelector.ts` registers the built-in engines before compression runs. This lets preview,
runtime compression, stacked mode, tests, and future engines use the same execution path.
### MCP description compression (related)
A separate registry compresses MCP tool description metadata at registry-level — see
`open-sse/mcp-server/descriptionCompressor.ts` and [MCP-SERVER.md](../frameworks/MCP-SERVER.md). It reuses
Caveman rules but operates on tool metadata, not request payloads.
### Additional built-in engines
Beyond Caveman, RTK, and LLMLingua-2, the registry ships several specialized lossless /
structural engines (used by stacked pipelines, the playground, and tests):
| Engine | Id | What it does |
| ------------- | --------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| CCR | `ccr` | Content-Compress-Retrieve (H4): replaces large contiguous text blocks with content-addressed references, so repeated/large blocks are sent once and referenced thereafter. |
| headroom | `headroom` | SmartCrusher (H3 + N5): lossless tabular compaction of homogeneous JSON-array payloads into a columnar `[N rows]` form. |
| ionizer | `ionizer` | Head/middle/tail row sampling for very large homogeneous blocks, storing the elided middle as a CCR content-addressed reference. |
| session-dedup | `session-dedup` | Content-addressed cross-turn deduplication (TokenMizer-inspired): elides text already seen in earlier turns of the same session. |
## Caveman
Caveman mode focuses on semantic condensation of normal prose:
- preserves code blocks, URLs, JSON, paths, and structured data
- removes filler, hedging, repeated context, and verbose connective phrasing
- supports language-aware file rule packs in `open-sse/services/compression/rules/`
- remains available through the legacy `standard`, `aggressive`, and `ultra` modes
The dashboard surface is `Dashboard -> Context & Cache -> Caveman`.
Caveman upstream reports `~75%` fewer output tokens, `65%` average output savings in benchmarks
with a `22-87%` range, and a `~46%` input-compression tool. OmniRoute uses the Caveman input-side
number when documenting stacked prompt/context savings; Caveman output mode remains a separate
response-behavior feature.
## RTK
RTK mode focuses on command and tool output:
- detects output classes such as `git status`, `git branch`, `git diff`, Vitest/Jest/Pytest,
Cargo/Go tests, TypeScript/Vite/Webpack builds, ESLint, npm audit/installs, Docker logs,
shell `find`/`grep`, stack traces, and generic logs
- applies 49 JSON filters from `open-sse/services/compression/engines/rtk/filters/`
- supports the RTK-style declarative pipeline: ANSI stripping, replace, match-output short-circuit,
strip/keep lines, per-line truncation, head/tail/max-line truncation, and on-empty fallback
- supports trust-gated project filters in `.rtk/filters.json` and global filters in
`DATA_DIR/rtk/filters.json`
- strips ANSI sequences, progress noise, repeated lines, and unhelpful boilerplate
- preserves actionable failures, warnings, summaries, changed files, and tail context
- can optionally retain redacted raw output for recovery/debugging through authenticated management
routes
The dashboard surface is `Dashboard -> Context & Cache -> RTK`.
Operational details for custom filters, trust, verify, and raw-output recovery live in
[`RTK_COMPRESSION.md`](./RTK_COMPRESSION.md).
RTK upstream reports `60-90%` savings for command-output compression. Its README example shows a
30-minute Claude Code session going from `~118,000` tokens to `~23,900`, or `79.7%` saved.
## LLMLingua-2 (Semantic Pruning)
LLMLingua-2 mode performs **semantic token pruning** on prose using a small ONNX token
classifier, complementing the rule-based Caveman and RTK engines:
- compresses prose in non-system messages only; fenced code blocks and other preserved
constructs are never altered
- runs the `@atjsh/llmlingua-2` backend (ONNX via `@huggingface/transformers`) in a
worker thread, so model inference never blocks the request event loop
- is **stackable** (`stackPriority` 35): in a stacked pipeline it runs after the
structural engines (CCR, session-dedup, headroom, Caveman) but before `ultra`, since
semantic pruning is most effective on already-structurally-compressed text — e.g.
`rtk -> caveman -> llmlingua`
- **fail-opens on any error** (missing optional deps, worker spawn, model load, inference,
or timeout) → the original text is returned unchanged, never an error
Engine location: `open-sse/services/compression/engines/llmlingua/`. The dashboard surface
is `Dashboard -> Context & Cache -> LLMLingua`.
### Models
The default model is **TinyBERT** (`atjsh/llmlingua-2-js-tinybert-meetingbank`, ~57 MB,
fast). A higher-accuracy **BERT-base** model (`Arcoldd/llmlingua4j-bert-base-onnx`,
~710 MB) is available via the engine config `model` field. `@huggingface/transformers`
downloads the selected model lazily from the HuggingFace Hub into
`${DATA_DIR}/models/llmlingua` on the first call (`modelStore.ts`); a `modelPath` config
override points it at a local copy instead (offline / air-gapped installs).
### Optional dependencies & on-demand install
The prunable LLMLingua runtime peer stack is **optional**. Three packages are declared as
`optionalDependencies` in `package.json` and kept **external** by the production build
(`scripts/build/prepublish.ts` does not bundle them):
| Package | Version (pin) | Notes |
| -------------------- | ------------- | ---------------------------------------------- |
| `@atjsh/llmlingua-2` | `2.0.3` | Entry package; declares the others as peers |
| `@tensorflow/tfjs` | `4.22.0` | Heaviest dep — dominates the ~800 MB footprint |
| `js-tiktoken` | `^1.0.20` | Tokenizer |
`@huggingface/transformers` is pinned at `3.5.2` as an **optional** dependency (shared with
the local embeddings path and also traced into the standalone bundle). Keeping it optional prevents
`onnxruntime-node` CUDA provider postinstall failures on CUDA 11 hosts from aborting the whole
OmniRoute install; when the optional stack is absent, LLMLingua still fail-opens. Only the three
packages above are prunable SLM peers. A standard `npm install` (dev) installs the optional stack
automatically unless optional dependencies are omitted.
**Why on-demand:** the npm-published package, the standalone bundle, and the Docker image
ship **without** these deps to stay slim. When they are absent, the worker's dependency
gate (a `@atjsh/llmlingua-2` resolve probe in `worker.ts`) fails and the engine
**fail-opens silently** — selecting LLMLingua becomes a no-op (text returned unchanged, no
error logged). To activate it in a pruned environment, install the optional stack:
```bash
# pin to the versions declared in package.json optionalDependencies
npm install @atjsh/llmlingua-2@2.0.3 @tensorflow/tfjs@4.22.0 js-tiktoken
```
Roughly **~800 MB** total: the TensorFlow.js + transformers runtimes dominate; the
TinyBERT model adds ~57 MB downloaded at first use (not via npm).
Per environment:
- **Dev / `npm install`** — installed automatically unless you passed `--omit=optional`
(or `--no-optional`). No action needed.
- **Global npm (`npm i -g omniroute`) / standalone** — run the install command above inside
the installed package directory, or reinstall without omitting optional deps.
- **Docker** — add the install command in a derived image layer; the published image
ships slim by design.
- **VPS (PM2)** — install into the app's `node_modules`, then restart the process so the
worker re-probes the gate.
**Verify it is active:** with LLMLingua selected, real prose actually shrinks (the engine
stops fail-opening), and the first request triggers the model download into
`${DATA_DIR}/models/llmlingua`. The gate intentionally probes only `@atjsh/llmlingua-2`
the other peers are ESM-only and `require.resolve` throws on them even when present — so
the worker still fail-opens if any peer is genuinely missing at `import()` time.
## Stacked Pipelines
Stacked mode runs pipeline steps in order. The default is:
```txt
rtk -> caveman
```
Use this for coding-agent sessions where a prompt combines command output with human or assistant
prose. RTK reduces noisy tool logs first, then Caveman compresses remaining natural language.
Pipeline steps are configured with `stackedPipeline` in compression settings or through compression
combos.
When both engines reduce the same eligible payload, savings compound:
```txt
combined = 1 - (1 - RTK savings) * (1 - Caveman input savings)
average = 1 - (1 - 0.80) * (1 - 0.46) = 89.2%
range = 1 - (1 - 0.60..0.90) * (1 - 0.46) = 78.4-94.6%
```
## MCP Accessibility Tree Filter
The MCP accessibility-tree smart filter is a post-execution compression layer that runs on MCP
**tool results**, not on prompts or context. It targets the verbose accessibility-tree and browser
snapshot payloads returned by tools like Playwright, computer-use, and browser-automation MCP
servers.
### What it does
1. **Noise stripping** — removes empty generic/text entries (`- generic:`, `- text: ""`)
2. **Sibling collapse** — when ≥ `collapseThreshold` (default 30) consecutive lines are structural
repeats, collapses them into the first `collapseKeepHead` (default 10) lines + a count summary +
the last `collapseKeepTail` (default 5) lines
3. **Ref preservation**`[ref=eXX]` anchors required by Playwright/computer-use are never touched
4. **Hard truncation** — if the text after collapse still exceeds `maxTextChars` (default 50,000),
truncates with a navigation hint so the agent can continue working
### Engine location
```txt
open-sse/services/compression/engines/mcpAccessibility/
index.ts ← smartFilterText() entry point
collapseRepeated.ts ← sibling-collapse algorithm
constants.ts ← DEFAULT_MCP_ACCESSIBILITY_CONFIG
```
### Configuration
Controlled by `compression.mcpAccessibility` in global settings (migration 056). Default config:
```json
{
"enabled": true,
"maxTextChars": 50000,
"collapseThreshold": 30,
"collapseKeepHead": 10,
"collapseKeepTail": 5,
"minLengthToProcess": 2000
}
```
The filter is only applied to tool-result payloads whose `type` is `"text"` and whose length
exceeds `minLengthToProcess`. It does not affect prompt compression or request payloads.
### Expected savings
6080% on browser snapshot tool results, depending on page complexity. The collapse algorithm
is O(n) in line count and adds negligible latency.
### This filter vs the compression engines above
| Aspect | Caveman / RTK / Stacked | MCP accessibility filter |
| ----------- | ------------------------- | -------------------------------------- |
| Target | Request prompts / context | MCP tool results |
| Trigger | Compression mode setting | `compression.mcpAccessibility.enabled` |
| Scope | All SSE messages | Tool results only |
| Ref anchors | N/A | Preserved unconditionally |
---
## Compression Combos
Compression combos are named compression profiles that can be assigned to routing combos:
- `compression_combos`: stores mode, pipeline, RTK config, language config, and default marker
- `compression_combo_assignments`: maps a compression combo to a routing combo
- runtime integration resolves an assigned compression combo before generic combo overrides
- analytics include `compression_combo_id` and `engine`
Dashboard surface: `Dashboard -> Context & Cache -> Compression Combos`.
## API Surface
| Route | Purpose |
| -------------------------------------- | ---------------------------------------------------------------- |
| `/api/settings/compression` | Global compression settings (includes `mcpAccessibility` config) |
| `/api/compression/preview` | Preview any compression mode |
| `/api/compression/language-packs` | List available Caveman language packs |
| `/api/context/caveman/config` | Caveman settings alias |
| `/api/context/rtk/config` | RTK defaults and settings |
| `/api/context/rtk/filters` | RTK filter catalog |
| `/api/context/rtk/test` | RTK preview/test endpoint |
| `/api/context/rtk/raw-output/[id]` | Authenticated redacted raw-output recovery |
| `/api/context/combos` | Compression combo CRUD |
| `/api/context/combos/[id]/assignments` | Routing-combo assignment CRUD |
| `/api/context/analytics` | Compression analytics alias |
Management routes require management authentication or API-key policy checks.
## MCP Tools
Compression exposes five MCP tools:
| Tool | Scope | Purpose |
| ----------------------------------- | ------------------- | -------------------------------- |
| `omniroute_compression_status` | `read:compression` | Settings, analytics, cache stats |
| `omniroute_compression_configure` | `write:compression` | Update global settings |
| `omniroute_set_compression_engine` | `write:compression` | Set mode and optional pipeline |
| `omniroute_list_compression_combos` | `read:compression` | List compression combos |
| `omniroute_compression_combo_stats` | `read:compression` | Read combo/engine analytics |
## Known limitations
- **LLMLingua-2 (SLM) requires co-located optional deps.** The worker only runs in a
production build when `@atjsh/llmlingua-2` + peers are co-located into
`dist/node_modules` (see `scripts/build/colocateOptionals.mjs`, #4286). Without them the
engine fail-opens (returns the original text). Worker resolution no longer depends on
`import.meta.url` (it dies in the standalone bundle) — it anchors on the runtime
cwd / `argv[1]`.
- **Caveman language packs `de` / `fr` / `ja` are partial.** They ship `context` +
`filler` + `structural` rules but no `dedup` / `ultra` packs, so `ultra` intensity is
no stronger than `full` for those languages (they use only their own rules — there is no
silent fall-back to the English `dedup`/`ultra` rules, which would mangle foreign text).
`en` / `es` / `id` / `pt-BR` are complete. Contributions of `dedup.json` + `ultra.json`
for the partial packs are welcome.
- **Stacked telemetry only lists engines that compressed.** A stacked-pipeline step whose
engine ran but produced 0 % savings returns `stats:null` and so does not appear in
`engineBreakdown` — indistinguishable from a step that was skipped. Distinguishing
"ran, 0 %" from "skipped" would require a breakdown-model change and is deferred.
## Validation
The focused gates for this area are:
```bash
node --import tsx/esm --test tests/unit/compression/rtk-*.test.ts tests/unit/compression/pipeline-integration.test.ts tests/unit/compression/context-compression-api.test.ts
node --import tsx/esm --test tests/unit/compression/*.test.ts tests/golden-set/*.test.ts tests/integration/compression-pipeline.test.ts tests/unit/api/compression/compression-api.test.ts
node --import tsx/esm --test tests/unit/compression/mcpAccessibility*.test.ts
npm run typecheck:core
```
+512
View File
@@ -0,0 +1,512 @@
---
title: "🗜️ Prompt Compression Guide — OmniRoute"
version: 3.8.40
lastUpdated: 2026-06-28
---
# 🗜️ Prompt Compression Guide — OmniRoute
> Save 15-95% on eligible context automatically. For a quick overview, see the [README Compression section](../README.md#%EF%B8%8F-prompt-compression--save-15-95-eligible-tokens-automatically).
## Overview
OmniRoute implements a modular prompt compression pipeline that runs **proactively** before requests hit upstream providers. This means your token savings happen transparently — no changes needed to your workflow.
```
Client Request
→ Compression Strategy Selector
→ Combo override? → Use combo setting
→ Auto-trigger threshold? → Use auto mode
→ Default mode? → Use global setting
→ Off? → Skip compression
→ Selected Compression Mode
→ Off: No compression
→ Lite: Safe whitespace/formatting cleanup (~15%)
→ Standard: Caveman-speak filler removal (~30%)
→ Aggressive: History aging + summarization (~50%)
→ Ultra: Heuristic pruning + code-block thinning (~75%)
→ RTK: Command-aware terminal/tool-output filtering (60-90% upstream range)
→ Stacked: Ordered multi-engine pipeline, usually RTK then Caveman (78-95% eligible range)
→ Compressed Request → Provider
```
---
## Compression Modes
### Off
No compression applied. All messages pass through unchanged.
### Lite Mode (~15% savings, <1ms latency)
The safest mode — zero semantic change, only formatting cleanup:
| Technique | Description |
| ------------------------ | ------------------------------------------------- |
| `collapseWhitespace` | Merge consecutive blank lines and trailing spaces |
| `dedupSystemPrompt` | Remove duplicate system messages |
| `compressToolResults` | Compress verbose tool/function outputs |
| `removeRedundantContent` | Strip repeated instructions |
| `replaceImageUrls` | Shorten base64 image data URIs |
**Best for:** Always-on usage, safety-critical workflows.
### Standard Mode (~30% savings)
Inspired by [Caveman](https://github.com/JuliusBrussee/caveman) — removes filler words and verbose phrasing while preserving meaning:
- Removes filler words ("please", "I think", "basically", "actually")
- Condenses verbose phrases ("in order to" → "to", "as a result of" → "because")
- Strips polite hedging ("Would you mind...", "If you could possibly...")
- 30+ regex rules tuned for coding prompts
**Best for:** Daily coding workflows, cost-conscious teams.
### Aggressive Mode (~50% savings)
Smart history management for long sessions:
- **Message Aging** — older messages get progressively compressed
- **Tool Result Summarization** — long tool outputs replaced with summaries
- **Structural Integrity Guards** — ensures `tool_use` + `tool_result` pairs stay consistent
- **Context Window Awareness** — respects per-model token limits
**Best for:** Extended debugging sessions, large codebases.
### Ultra Mode (~75% savings)
Maximum compression for token-critical scenarios:
- **Heuristic Pruning** — removes messages below relevance threshold
- **Code Block Thinning** — compresses repetitive code examples
- **Binary Search Truncation** — finds optimal cut point for context window
- All Aggressive mode features included
**Best for:** When you're hitting context limits repeatedly.
### RTK Mode (60-90% upstream range)
RTK mode is optimized for verbose tool outputs that appear in coding-agent sessions:
- Detects command/output classes such as `git status`, `git diff`, `git log`, test runners,
TypeScript/Vite/Webpack builds, ESLint/Biome/Prettier, npm audit/installs, Docker logs, infra
output, and generic shell output
- Applies JSON filter packs from `open-sse/services/compression/engines/rtk/filters/`
- Ships 49 built-in filters with inline verify samples
- Removes ANSI control sequences, progress bars, repeated lines, and non-actionable noise
- Preserves failures, errors, warnings, changed files, summaries, and the tail of long output
- Supports trust-gated project filters, global filters, and optional redacted raw-output recovery
**Best for:** Agent sessions with shell, build, test, git, grep, and file-output transcripts.
### Stacked Mode (78-95% eligible range)
Stacked mode runs multiple compression engines in a deterministic order. The default pipeline is:
```txt
RTK -> Caveman
```
That order keeps terminal/tool output compact first, then applies Caveman semantic condensation to
the remaining natural-language prompt. Stacked pipelines can be configured globally or through
compression combos assigned to routing combos.
**Best for:** Mixed context with large tool logs plus human instructions or assistant summaries.
---
## Upstream Savings Math
OmniRoute documents compression savings from two sources: upstream project benchmarks and
OmniRoute's own engine composition.
| Source | Upstream README number used here |
| ------- | --------------------------------------------------------------------------------------------------------------------- |
| Caveman | `~75%` fewer output tokens, `65%` benchmark average output savings, `22-87%` range, and `~46%` input compression tool |
| RTK | `60-90%` command-output savings; sample session `~118,000 -> ~23,900` tokens, or `79.7%` saved (`~80%`) |
For overlapping tool/context payloads, the default OmniRoute combo stacks the engines:
```txt
RTK -> Caveman
```
The combined savings are multiplicative, not additive:
```txt
combined = 1 - (1 - RTK savings) * (1 - Caveman input savings)
average = 1 - (1 - 0.80) * (1 - 0.46) = 89.2%
range = 1 - (1 - 0.60..0.90) * (1 - 0.46) = 78.4-94.6%
```
That `78-95%` number applies when both RTK and Caveman can reduce the same input/context payload.
Caveman response output mode is separate: when enabled, use Caveman's own output savings (`65%`
average, `~75%` headline, `22-87%` range). Total billing savings depend on your prompt/output mix.
---
## Token Savings Visualization
```
Without compression: 47K tokens sent to LLM
With Lite: 40K tokens sent (15% saved — safe, always-on)
With Standard: 33K tokens sent (30% saved — caveman-speak rules)
With Aggressive: 24K tokens sent (50% saved — aging + summarization)
With Ultra: 12K tokens sent (75% saved — heuristic pruning)
With RTK: 19K-5K tokens sent (60-90% saved on command/tool output)
With Stacked: 10K-2.5K tokens sent (78-95% eligible RTK+Caveman range)
```
---
## Configuration
### Dashboard
Navigate to `Dashboard → Context & Cache`:
- **Caveman** — mode selection, language packs, preview, and global defaults
- **RTK** — command-filter preview, RTK safety settings, and filter catalog
- **Compression Combos** — named engine pipelines assigned to routing combos
- **Auto-Trigger Threshold** — automatically engage compression when token count exceeds threshold
### Per-Combo Override
In `Dashboard → Context & Cache → Compression Combos`, assign a compression combo to a routing
combo:
```txt
Combo: "free-forever"
Compression Combo: "coding-agent-stack"
Pipeline: RTK -> Caveman
Targets:
1. if/kimi-k2-thinking
2. qw/qwen3-coder-plus
```
This lets you use stacked compression on free/coding providers while keeping lite mode on paid
subscriptions.
### Per-request override
Send the `x-omniroute-compression` request header to override the compression plan for a single
request. It has the highest precedence — it beats the routing-combo override, the active profile,
auto-trigger, and the panel Default. Unknown values are ignored (the request is never rejected) and
the global master switch still gates everything: when compression is off globally, the header cannot
turn it on. Values:
| Value | Effect |
| ------------- | -------------------------------------------------------------------- |
| `off` | No compression for this request. |
| `default` | The panel-derived Default profile (ignores the active profile). |
| `engine:<id>` | A single engine when enabled, e.g. `engine:rtk`. |
| `<combo>` | A named combo, matched by name (case-insensitive) first, then by id. |
The applied plan is echoed back in the `X-OmniRoute-Compression: <mode>; source=<source>` response
header, where `<source>` is one of `request-header`, `routing-override`, `active-profile`,
`auto-trigger`, `default`, or `off`.
### API
```bash
# Get compression settings
curl http://localhost:20128/api/settings/compression
# Update compression settings
curl -X PUT http://localhost:20128/api/settings/compression \
-H "Content-Type: application/json" \
-d '{"defaultMode":"stacked","autoTriggerMode":"stacked","autoTriggerTokens":32000}'
# Preview a specific RTK/stacked payload
curl -X POST http://localhost:20128/api/compression/preview \
-H "Content-Type: application/json" \
-d '{"mode":"rtk","messages":[{"role":"tool","content":"npm test output here"}]}'
# List RTK filter packs
curl http://localhost:20128/api/context/rtk/filters
# Test RTK directly with optional command metadata
curl -X POST http://localhost:20128/api/context/rtk/test \
-H "Content-Type: application/json" \
-d '{"command":"npm test","text":"FAIL tests/example.test.ts\nError: boom"}'
```
---
## What Gets Protected
The compression engine **always preserves:**
- ✅ Code blocks (fenced and inline)
- ✅ URLs and file paths
- ✅ JSON structures and structured data
- ✅ Identifiers and protected technical tokens
- ✅ Mathematical expressions
- ✅ Tool/function call definitions
- ✅ System prompts (in lite mode)
RTK raw-output recovery redacts common API keys, bearer tokens, Slack tokens, AWS access keys,
passwords, tokens, and secrets before anything is persisted.
---
## Compression Stats
Every compressed request includes stats in the server logs:
```json
{
"originalTokens": 47200,
"compressedTokens": 40120,
"savingsPercent": 15.0,
"techniquesUsed": ["collapseWhitespace", "dedupSystemPrompt"],
"mode": "lite",
"engine": "caveman",
"compressionComboId": "coding-agent-stack",
"durationMs": 0.8,
"rtkRawOutputPointers": []
}
```
---
## Phase Roadmap
| Phase | Modes | Status |
| ------- | -------------------------------------------------------------------- | ---------- |
| Phase 1 | Off, Lite | ✅ Shipped |
| Phase 2 | Standard, Aggressive, Ultra | ✅ Shipped |
| Phase 3 | RTK, Stacked, Compression Combos | ✅ Shipped |
| Phase 4 | Output Styles, SLM-tier Ultra, adaptive context-budget, eval harness | ✅ Shipped |
---
## Acknowledgments
Standard mode compression rules are inspired by **[Caveman](https://github.com/JuliusBrussee/caveman)** by **[JuliusBrussee](https://github.com/JuliusBrussee)** (⭐ 51K+) — the viral "why use many token when few token do trick" project. Caveman reports `~75%` fewer output tokens, `65%` benchmark average output savings, a `22-87%` output range, and a `~46%` input-compression tool.
RTK mode is inspired by **[RTK - Rust Token Killer](https://github.com/rtk-ai/rtk)** by **[RTK AI](https://github.com/rtk-ai)** — the high-performance command-output compression project for terminal, build, test, git, and tool-output filtering. RTK reports `60-90%` savings, with its README sample session showing `~80%` saved.
---
## Advanced Compression Systems
Beyond the 7 standard modes, OmniRoute includes several advanced compression
systems that work automatically based on context.
### Cache-Aware Compression
Some providers (like Anthropic with prompt caching) support **prompt caching**,
which lets them cache parts of the prompt to reduce costs and latency. When
caching is enabled, aggressive compression can actually **hurt** performance
because it changes the cached tokens, invalidating the cache.
The `cachingAware.ts` module solves this by **detecting caching context** and
**adjusting the compression strategy** accordingly.
#### How it works
1. **Detect caching context** — Scans the request body for `cache_control` markers
2. **Identify caching providers** — Checks if the target provider supports caching
3. **Adjust strategy** — Downgrades `aggressive`/`ultra` to `standard` for caching providers
4. **Skip system prompt** — System prompts are usually cached, so don't compress them
5. **Use deterministic transformations** — Only use transformations that produce consistent output
#### Code example
```ts
import {
detectCachingContext,
getCacheAwareStrategy,
} from "@omniroute/open-sse/services/compression/cachingAware";
const body = {
model: "anthropic/claude-sonnet-4.5",
messages: [{ role: "user", content: "Hello" }],
cache_control: { type: "ephemeral" }, // ← Cache marker
};
const ctx = detectCachingContext(body, { provider: "anthropic" });
// → { hasCacheControl: true, provider: "anthropic", isCachingProvider: true }
const strategy = getCacheAwareStrategy("aggressive", ctx);
// → { strategy: "standard", skipSystemPrompt: true, deterministicOnly: true }
```
#### When to use
Cache-aware compression is **always on** — no configuration needed. It only kicks in
when:
- The request has `cache_control` markers
- The target provider supports prompt caching (Anthropic, OpenAI, etc.)
### Progressive Aging
Long conversations accumulate many message turns, but older turns become less
relevant. The `progressiveAging.ts` module **degrades messages by turn distance**:
- **Recent turns (0-3)**: Kept verbatim (full detail)
- **Medium turns (4-8)**: Lite compression (whitespace, formatting cleanup)
- **Old turns (9+)**: Caveman compression (filler removal, summarization)
- **Very old turns (20+)**: Heavily summarized or dropped
#### Code example
```ts
import { applyAging } from "@omniroute/open-sse/services/compression/progressiveAging";
const messages = [
{ role: "system", content: "You are a helpful assistant" },
{ role: "user", content: "What is 2+2?" },
{ role: "assistant", content: "4" },
// ... 50 more turns ...
];
const { messages: aged, saved } = applyAging(messages, {
verbatim: 3, // First 3 turns: verbatim
light: 8, // Turns 4-8: lite compression
moderate: 20, // Turns 9-20: caveman compression
// Turns 21+: heavy summarization
});
// saved = number of tokens saved
```
#### When to use
Progressive aging is **always on** for `aggressive` and `ultra` modes. It's
particularly effective for:
- Long-running coding sessions
- Multi-day conversations
- Agentic workflows with many tool calls
### Caveman Output Mode
The `outputMode.ts` module injects **system prompt instructions** to make the
model itself produce compressed, terse output (a "caveman" style).
#### How it works
Instead of compressing the input, this mode adds a system prompt like:
> "Reply in minimal words. Skip pleasantries. Use short sentences."
This works particularly well for:
- Code generation (terser output = fewer tokens)
- Quick Q&A (no need for elaborate explanations)
- Batch processing (maximize throughput)
#### When to use
Caveman output mode is **opt-in** — set it via the combo config:
```json
{
"strategy": "auto",
"config": {
"auto": {
"outputMode": "caveman"
}
}
}
```
### Tool Result Compression
The `toolResultCompressor.ts` module provides **5 specialized compression strategies**
for tool results (function calls, agent outputs, search results, etc.):
1. **Search result compression** — Removes redundant results, keeps top-N
2. **File read compression** — Truncates large files, preserves headers/imports
3. **Code execution compression** — Keeps only essential stdout/stderr
4. **Database query compression** — Limits rows, removes verbose metadata
5. **API response compression** — Strips null fields, condenses arrays
#### When to use
Tool result compression is **always on** when tool calls are present. No
configuration needed.
### Stacked Pipeline
The stacked mode runs **multiple engines in sequence** — usually RTK first
(60-90% savings on tool output), then Caveman (30% additional savings on the
remaining text). This achieves **78-95% total savings**.
#### How it works
```
Input (1000 tokens)
→ RTK (command-aware filter) → 200 tokens
→ Caveman (filler removal) → 140 tokens
→ Output (140 tokens, 86% savings)
```
#### When to use
Use stacked mode for:
- Tool-heavy workflows (agentic coding, research)
- Cost-sensitive batch processing
- When you need maximum token savings
Configure via combo:
```json
{
"strategy": "auto",
"config": {
"auto": {
"modePack": "stacked"
}
}
}
```
---
## Compression Combo Overrides
You can override the global compression mode **per combo** to fine-tune behavior
for different use cases:
```json
{
"id": "coding-combo",
"strategy": "priority",
"config": {
"auto": {
"weights": { "taskFit": 0.5 },
"modePack": "quality-first"
}
},
"compressionOverride": {
"mode": "aggressive",
"stackedPipelines": ["rtk", "caveman"],
"preserveToolDefinitions": true
}
}
```
This is useful for:
- **Coding combos**: Use `aggressive` mode for long sessions
- **Quick Q&A combos**: Use `lite` mode for fast responses
- **Tool-heavy combos**: Use `stacked` mode for max savings
- **Production combos**: Use `cache-aware` mode for caching providers
---
## See Also
- [Environment Config](../reference/ENVIRONMENT.md) — Compression environment variables
- [Architecture Guide](../architecture/ARCHITECTURE.md) — Compression pipeline internals
- [User Guide](../guides/USER_GUIDE.md) — Getting started with compression
- [RTK Compression](./RTK_COMPRESSION.md) — RTK filters, trust model, verify gate, raw-output recovery
- [Compression Engines](./COMPRESSION_ENGINES.md) — Caveman, RTK, stacked, APIs, MCP, dashboard
- [Compression Rules Format](./COMPRESSION_RULES_FORMAT.md) — JSON rule-pack format
- [Compression Language Packs](./COMPRESSION_LANGUAGE_PACKS.md) — Language-specific Caveman rules
@@ -0,0 +1,156 @@
---
title: "Compression Language Packs"
version: 3.8.40
lastUpdated: 2026-06-28
---
# Compression Language Packs
Caveman compression can load language-specific rule packs in addition to the built-in English rules.
This keeps the core engine stable while allowing Portuguese, Spanish, German, French, Japanese, and
future language packs to evolve independently.
## Location
Language packs live under:
```txt
open-sse/services/compression/rules/<language>/
```
Current shipped packs (verified against `rules/` directory contents):
| Language | Directory | Rule categories present |
| ------------------- | -------------- | --------------------------------------------------- |
| English | `rules/en/` | `context`, `dedup`, `filler`, `structural`, `ultra` |
| Spanish | `rules/es/` | `context`, `dedup`, `filler`, `structural`, `ultra` |
| Portuguese (Brazil) | `rules/pt-BR/` | `context`, `dedup`, `filler`, `structural`, `ultra` |
| Indonesian | `rules/id/` | `context`, `dedup`, `filler`, `structural`, `ultra` |
| German | `rules/de/` | `context`, `filler`, `structural` |
| French | `rules/fr/` | `context`, `filler`, `structural` |
| Japanese | `rules/ja/` | `context`, `filler`, `structural` |
> **Parity note:** `en`, `es`, `pt-BR`, and `id` packs have the full 5 categories; `de`, `fr`, `ja` ship 3 categories. The missing `dedup` and `ultra` categories silently fall back to the English built-ins. Contributions welcome to add `dedup.json` and `ultra.json` for the smaller packs.
>
> The `pt-BR` pack is based on **[Troglodita](https://github.com/leninejunior/troglodita)** by Lenine Júnior — a compression system designed from scratch for Brazilian Portuguese grammar (pleonasm reduction, PT-BR filler removal, technical abbreviations for the dev BR community).
>
> The canonical category list and per-category schema live in [`open-sse/services/compression/rules/_schema.json`](../../open-sse/services/compression/rules/_schema.json) (JSON Schema draft 2020-12).
## Language Detection
`languageDetector.ts` uses lightweight heuristics to infer the language from prompt text. The
configured default language is still respected, and detection can be disabled by config when exact
control is required.
Detection output is used only to choose rule packs. It does not change provider routing, locale
selection, or UI language.
## Config Shape
Compression settings can include:
```json
{
"languageConfig": {
"enabled": true,
"defaultLanguage": "en",
"autoDetect": true,
"enabledPacks": ["en", "pt-BR", "es", "id", "de", "fr", "ja"]
},
"cavemanConfig": {
"language": "en",
"autoDetectLanguage": true,
"enabledLanguagePacks": ["en", "pt-BR", "es", "id", "de", "fr", "ja"]
}
}
```
`languageConfig` controls dashboard/preview defaults. `cavemanConfig` is the runtime engine config
used when Caveman compresses message text.
## Adding a Language Pack
1. Create `open-sse/services/compression/rules/<language>/<pack>.json`.
2. Use the Caveman rule format from `docs/compression/COMPRESSION_RULES_FORMAT.md`.
3. Keep replacements conservative and avoid changing code, identifiers, URLs, or JSON.
4. Add or update tests for language selection and replacement behavior.
5. Expose new dashboard/i18n labels if the language appears in UI selectors.
## API
Available packs can be queried with:
```bash
curl http://localhost:20128/api/compression/language-packs
```
The preview endpoint accepts language config overrides:
```bash
curl -X POST http://localhost:20128/api/compression/preview \
-H "Content-Type: application/json" \
-d '{
"mode": "standard",
"text": "Por favor, eu gostaria que voce basicamente resumisse isso.",
"config": {
"languageConfig": {
"defaultLanguage": "pt-BR",
"autoDetect": true
}
}
}'
```
## SHARED_BOUNDARIES (v3.8.0)
All 6 language packs received a `SHARED_BOUNDARIES` clause in v3.8.0 that is applied at every
Caveman intensity (LITE, FULL, ULTRA). It instructs the engine to preserve these patterns verbatim,
regardless of surrounding filler removal:
| Pattern type | Example |
| -------------------------------- | -------------------------------------- |
| Fenced code blocks | ` ```python\n...\n``` ` |
| Inline code | `` `my_var` `` |
| URLs | `https://example.com/path` |
| File paths (absolute + relative) | `/etc/hosts`, `./src/index.ts` |
| Error headers | `Error:`, `TypeError:`, `SyntaxError:` |
| Stack trace lines | ` at functionName (file.ts:12:3)` |
These patterns are populated in `DEFAULT_CAVEMAN_CONFIG.preservePatterns` (previously `[]`). The
constant lives in `open-sse/services/compression/types.ts`.
### Why this matters
Without SHARED_BOUNDARIES, aggressive Caveman modes could strip content that looked like repetitive
prose but was actually a code snippet, file path, or error stack. SHARED_BOUNDARIES acts as a
language-agnostic safety net applied before filler rules run.
### Customizing preservePatterns
Additional patterns can be added at runtime via compression settings:
````json
{
"cavemanConfig": {
"preservePatterns": [
"```[\\s\\S]*?```",
"`[^`]+`",
"https?://\\S+",
"(?:/|\\./)[^\\s]+",
"\\b(?:Error|TypeError|SyntaxError|RangeError):",
"\\s+at\\s+\\S+\\s+\\(\\S+:\\d+:\\d+\\)"
]
}
}
````
Custom patterns extend (not replace) the 6 defaults.
---
## Operational Notes
- English built-in rules remain the fallback when a language pack is missing.
- Invalid built-in JSON packs fail validation so release assets do not silently degrade.
- Rule packs are data-only and should not import code or run arbitrary logic.
- The compression analytics layer records the selected mode and engine, not full prompt text.
@@ -0,0 +1,195 @@
---
title: "Compression Rules Format"
version: 3.8.40
lastUpdated: 2026-06-28
---
# Compression Rules Format
Compression rules are JSON files loaded at runtime. They are intentionally data-only so new
language packs and RTK command filters can be reviewed without changing engine code.
> **Canonical schema (source of truth):** [`open-sse/services/compression/rules/_schema.json`](../../open-sse/services/compression/rules/_schema.json) (JSON Schema draft 2020-12).
> The examples below are illustrative — when in doubt, validate your pack against `_schema.json`.
## Caveman Rule Packs
Caveman rule packs live under:
```txt
open-sse/services/compression/rules/<language>/<pack>.json
```
Each pack contains replacements that apply to normal prose after protected regions are isolated.
```json
{
"language": "en",
"category": "filler",
"rules": [
{
"name": "question_to_directive",
"pattern": "\\b(?:Can you explain why|Could you show me how)\\b\\s*",
"replacement": "Explain why ",
"replacementMap": {
"can you explain why": "Explain why ",
"could you show me how": "Show how "
},
"flags": "gi",
"context": "all",
"category": "context",
"minIntensity": "lite",
"description": "Convert verbose questions into direct requests."
}
]
}
```
### Caveman Fields
| Field | Required | Description |
| ------------------------ | -------- | ---------------------------------------------------------------- |
| `language` | yes | BCP-47-like language key such as `en`, `pt-BR`, `es` |
| `category` | yes | Pack category filename/category, for example `filler` or `dedup` |
| `rules` | yes | Array of regex replacement rules |
| `rules[].name` | yes | Stable rule name |
| `rules[].pattern` | yes | JavaScript regex source |
| `rules[].flags` | no | JavaScript regex flags; default `gi` |
| `rules[].replacement` | no | Replacement string or fallback when `replacementMap` misses |
| `rules[].replacementMap` | no | Match-specific replacements keyed by normalized matched text |
| `rules[].context` | no | `all`, `user`, `assistant`, or `system`; default `all` |
| `rules[].category` | no | `filler`, `context`, `structural`, `dedup`, `terse`, or `ultra` |
| `rules[].minIntensity` | no | `lite`, `full`, or `ultra`; default `lite` |
| `rules[].description` | no | Human-readable rule summary |
Use `flags` when case-sensitive matching matters, for example article removal before lowercase prose
without stripping `the OpenAI API`. Use `replacementMap` when one regex has multiple alternatives
that need different outputs; this keeps JSON rule packs data-only while preserving the behavior of
the richer built-in TypeScript replacement functions.
## RTK Filter Packs
RTK filters live under:
```txt
open-sse/services/compression/engines/rtk/filters/<filter>.json
```
Each filter describes how to recognize and compress a command-output family.
```json
{
"id": "test-vitest",
"label": "Vitest output",
"category": "test",
"priority": 92,
"match": {
"outputTypes": ["test-vitest"],
"commands": ["vitest", "npm test", "npm run test"],
"patterns": ["\\bFAIL\\b", "\\bPASS\\b", "\\bTest Files\\b"]
},
"rules": {
"stripAnsi": true,
"replace": [{ "pattern": "\\s+\\[[0-9]+ms\\]", "replacement": "" }],
"matchOutput": [
{ "pattern": "All tests passed", "message": "vitest: ok", "unless": "FAIL|Error:" }
],
"includePatterns": ["FAIL", "Error:", "Test Files", "Tests"],
"dropPatterns": ["^\\s*$", "Duration\\s+\\d+"],
"collapsePatterns": ["^\\s+at "],
"deduplicate": true,
"truncateLineAt": 240,
"maxLines": 160,
"headLines": 24,
"tailLines": 40,
"onEmpty": "vitest: ok",
"filterStderr": false
},
"preserve": {
"errorPatterns": ["FAIL", "Error:", "AssertionError"],
"summaryPatterns": ["Test Files", "Tests", "Snapshots"]
},
"tests": [
{
"name": "keeps failing tests",
"command": "vitest",
"input": "FAIL test/a.test.ts\\nError: boom\\nTest Files 1 failed",
"expected": "FAIL test/a.test.ts\\nError: boom\\nTest Files 1 failed"
}
]
}
```
### RTK Fields
| Field | Required | Description |
| -------------------------- | -------- | ------------------------------------------------------------------------------ |
| `id` | yes | Stable filter id |
| `label` | yes | Dashboard-readable name |
| `category` | yes | Filter family: git, test, build, shell, docker, package, infra, cloud, generic |
| `priority` | no | Higher priority wins when multiple filters match |
| `match.outputTypes` | no | Detector output ids that select this filter |
| `match.commands` | no | Command tokens that select this filter |
| `match.patterns` | no | Regex patterns that select this filter from output text |
| `rules.stripAnsi` | no | Remove ANSI escape sequences before regex stages |
| `rules.replace` | no | Ordered regex substitutions applied line by line |
| `rules.matchOutput` | no | Short-circuit output rules with optional `unless` guard |
| `rules.includePatterns` | no | Lines to prefer preserving |
| `rules.dropPatterns` | no | Lines to remove as noise |
| `rules.collapsePatterns` | no | Repeated matching lines that can be collapsed |
| `rules.deduplicate` | no | Collapse duplicate normalized lines |
| `rules.truncateLineAt` | no | Unicode-safe per-line character limit |
| `rules.maxLines` | no | Maximum retained lines before tail preservation |
| `rules.headLines` | no | Head lines retained during truncation |
| `rules.tailLines` | no | Tail lines retained for recent context |
| `rules.onEmpty` | no | Fallback message when filtering removes all content |
| `rules.filterStderr` | no | Normalize common stderr prefixes before later filtering stages |
| `preserve.errorPatterns` | no | Error lines that should survive truncation |
| `preserve.summaryPatterns` | no | Summary lines that should survive truncation |
| `tests[]` | no | Inline verification samples used by the RTK verify gate |
RTK applies declarative stages in this order: `stripAnsi`, `filterStderr`, `replace`,
`matchOutput`, `dropPatterns`/`includePatterns`, `truncateLineAt`, `headLines`/`tailLines`,
`maxLines`, and `onEmpty`.
Custom filters can be loaded from:
1. Project `.rtk/filters.json` files only after a matching `.rtk/trust.json` hash is present or
`trustProjectFilters` is enabled.
2. Global `DATA_DIR/rtk/filters.json`.
3. Built-in filters.
Project/global custom files may contain one filter object or an array of filter objects. Invalid
custom filters are skipped with diagnostics; invalid built-in filters fail validation.
Project trust file:
```json
{
"filtersSha256": "0123456789abcdef..."
}
```
The environment override `OMNIROUTE_RTK_TRUST_PROJECT_FILTERS=1` trusts project filters without a
hash and should be limited to controlled local development.
## Safety Rules
- Keep rules idempotent: running the same filter twice should not corrupt output.
- Preserve exact error text, file paths, line numbers, and command summaries where possible.
- Avoid rules that modify code blocks, JSON payloads, URLs, or secrets.
- Add unit coverage for new command families in detector/filter tests.
- Add `tests[]` samples to every built-in filter and to shared custom filters.
## Validation
Rule packs are validated before use. Built-in Caveman packs and built-in RTK filters fail fast
during validation so broken release assets are caught before shipment. Custom RTK filters are
skipped with diagnostics when parsing or trust validation fails.
Focused validation:
```bash
node --import tsx/esm --test tests/unit/compression/rule-loader.test.ts tests/unit/compression/language-packs.test.ts
node --import tsx/esm --test tests/unit/compression/rtk-verify.test.ts tests/unit/compression/rtk-dsl-pipeline.test.ts
```
+198
View File
@@ -0,0 +1,198 @@
---
title: "Delegated Context Editing (Anthropic)"
version: 3.8.40
lastUpdated: 2026-06-28
---
# Delegated Context Editing (Anthropic)
Delegated **Context Editing** is a Claude-only context-management feature. Unlike OmniRoute's local
compression engines (Caveman, RTK, LLMLingua, stacked pipelines) — which rewrite the request body
_before_ it leaves the proxy — Context Editing asks the **provider** to clear stale
tool-use / tool-result blocks from its own running context window. OmniRoute only attaches a body
parameter (`context_management.edits[]`); Claude does the actual clearing against its own tokenizer.
This is a delegated capability by nature: other providers reject the parameter, so OmniRoute scopes
it strictly to Claude and Claude-Code-compatible relays.
Source of truth: `open-sse/config/contextEditing.ts` (strategy ids, body injection, telemetry
extraction), `open-sse/executors/base.ts` (injection gate + 400-fallback), and
`open-sse/services/compression/types.ts` (config shape + default).
## What `clear_tool_uses` does
OmniRoute injects a single edit into the outbound Anthropic Messages body:
```json
{
"context_management": {
"edits": [
{
"type": "clear_tool_uses_20250919",
"trigger": { "type": "input_tokens", "value": 100000 },
"keep": { "type": "tool_uses", "value": 3 }
}
]
}
}
```
- `type: "clear_tool_uses_20250919"` — the dated Anthropic strategy id (`CLEAR_TOOL_USES_STRATEGY`).
- `trigger.value: 100000` — once the request's input tokens exceed this threshold, Claude begins
clearing old tool-use/result pairs (`CONTEXT_EDITING_DEFAULT_TRIGGER_TOKENS`, Anthropic's default).
- `keep.value: 3` — the N most recent tool-use/result pairs are kept untouched
(`CONTEXT_EDITING_DEFAULT_KEEP_TOOL_USES`).
The beta is advertised via the `anthropic-beta: context-management-2025-06-27` header, which
OmniRoute already emits on Claude requests.
Injection is performed by `applyContextEditingToBody()` and is **idempotent**: if a `clear_tool_uses`
edit already exists on the body (added by a previous call or supplied by the client), the body is
left as-is. If a `clear_thinking_20251015` edit is also present, OmniRoute stable-sorts the
`clear_thinking` edit to the front, because Anthropic requires `clear_thinking` to precede
`clear_tool_uses` in the `edits[]` array.
## The per-combo enable toggle
Context Editing is **off by default** and opt-in. The toggle is a single boolean carried in the
compression config:
- Setting key: `contextEditing.enabled` (camelCase — **not** `context_editing` / `context-editing`).
- Type: `ContextEditingConfig { enabled: boolean }` in
`open-sse/services/compression/types.ts`.
- Default: `DEFAULT_CONTEXT_EDITING_CONFIG = { enabled: false }`.
- Zod schema: `contextEditingConfigSchema` in `src/shared/validation/compressionConfigSchemas.ts`.
- Storage: persisted with the rest of the compression settings (normalized in
`src/lib/db/compression.ts`).
In the dashboard the toggle lives in the compression hub
(`src/app/(dashboard)/dashboard/context/combos/CompressionHub.tsx`) and writes
`{ contextEditing: { enabled: … } }` back through `saveSettings()`. Because it rides on the
compression-settings object, it composes with the per-combo compression profile rather than being a
fully independent surface — the config carries only the on/off flag; all thresholds (`trigger`,
`keep`) are the constants documented above.
## Claude-only gating
Injection only happens for genuine Claude or Claude-Code-compatible relays. The gate in
`open-sse/executors/base.ts` is:
```ts
if (
(this.provider === "claude" || isClaudeCodeCompatible(this.provider)) &&
contextEditing?.enabled &&
!contextEditingDisabled
) {
applyContextEditingToBody(transformedBody, { enabled: true });
}
```
- `this.provider === "claude"` — real Anthropic key/OAuth.
- `isClaudeCodeCompatible(this.provider)` — relays whose provider id starts with the
`anthropic-compatible-cc-` prefix (they advertise Claude Code compatibility, so they are the relays
most likely to accept the beta). See `open-sse/services/provider.ts`.
Deliberately **excluded**:
- `claude-web` — a browser relay with a `create_conversation_params` request shape that never sees
`context_management`.
- Generic `anthropic-compatible-*` relays (without the `-cc-` prefix) — third-party endpoints with
uncertain beta support.
Non-Claude providers never receive the `context_management` parameter even when the toggle is on.
## The 400-fallback / relay coverage
A Claude-compatible relay may advertise the beta but still reject the `context_management` parameter
with an HTTP 400. To degrade gracefully instead of failing the request, the executor strips the
parameter and retries the same URL **once**:
```ts
if (
response.status === HTTP_STATUS.BAD_REQUEST &&
contextEditing?.enabled &&
!contextEditingDisabled &&
transformedBody?.context_management !== undefined
) {
const errText = await response
.clone()
.text()
.catch(() => "");
if (/context[_-]management|context editing/i.test(errText)) {
contextEditingDisabled = true;
delete transformedBody.context_management;
let retryBody = JSON.stringify(transformedBody);
if (isClaudeCodeCompatible(this.provider) || this.provider === "claude") {
retryBody = await signRequestBody(retryBody);
}
response = await fetch(url, { ...fetchOptions, body: retryBody });
}
}
```
Behavior:
1. Fires only on a `400` while context editing is enabled and the body actually carries
`context_management`.
2. The 400 body is read via a `clone()` so the original response stays intact for the non-matching
path.
3. The error text must match `/context[_-]management|context editing/i` — an unrelated 400 (e.g.
`max_tokens must be >= 1`) does **not** trigger the fallback; the original error propagates.
4. On a match it sets `contextEditingDisabled = true` (which suppresses re-injection if a fresh
`transformedBody` is later built for a retry/fallback URL), deletes `context_management`,
re-signs the body for Claude / Claude-Code-compatible relays (`signRequestBody`), and retries the
same URL once.
Genuine Claude carries the beta in `ANTHROPIC_BETA_BASE` and does not hit this fallback path.
## `applied_edits` telemetry
After a Claude response, OmniRoute records how much context the provider actually cleared. This is
**not** streamed — it is extracted from the non-streaming response body, best-effort, and never
affects the response (telemetry failures are swallowed).
- Extraction: `extractContextEditingTelemetry(responseBody)` in `open-sse/config/contextEditing.ts`.
It probes `applied_edits` in three locations (defensive over the response shape):
- `context_management.applied_edits`
- `usage.context_management.applied_edits`
- `usage.applied_edits`
- Per-edit fields read from each entry: `cleared_input_tokens` and `cleared_tool_uses`
(snake_case, Anthropic-native), with `clearedInputTokens` / `clearedToolUses` camelCase fallbacks.
- Returns `null` when no `applied_edits` array is found or nothing was actually cleared.
The receipt shape is `ContextEditingTelemetry { editCount, clearedInputTokens, clearedToolUses }`.
Recording happens in `open-sse/handlers/chatCore.ts` (gated to `provider === "claude"`) via
`recordContextEditingTelemetry()` (`src/lib/db/compressionAnalytics.ts`), which writes a compression
analytics row tagged:
- `mode: "context-editing"`
- `engine: "context-editing"`
- `tokens_saved` / `original_tokens` = the cleared input-token count
- `request_id` suffixed with `::context-editing`
So delegated clearing shows up in compression analytics alongside the local engines, under the
`context-editing` engine label, and is distinguishable from RTK/Caveman/LLMLingua savings.
## Relationship to the local compression engines
| Aspect | Local engines (Caveman / RTK / LLMLingua / stacked) | Delegated Context Editing |
| ----------------- | --------------------------------------------------- | ------------------------------------------- |
| Where it runs | In OmniRoute, before the request leaves the proxy | In the provider (Claude), server-side |
| What it edits | Prompt / context / tool-result text | Old tool-use / tool-result blocks |
| Provider scope | All providers | `claude` + `anthropic-compatible-cc-*` only |
| Toggle | Compression mode settings | `contextEditing.enabled` |
| Failure mode | Fail-open (original text) | 400-fallback: strip param, retry once |
| Savings telemetry | `engine: <engine id>` | `engine: "context-editing"` |
The two are complementary: local engines compress the bytes OmniRoute sends; Context Editing lets
Claude prune the running context across turns. They can be enabled together.
## See Also
- [COMPRESSION_ENGINES.md](./COMPRESSION_ENGINES.md) — engine registry and the local compression
engines
- [RTK_COMPRESSION.md](./RTK_COMPRESSION.md) — command/tool-output compression
- [../frameworks/MCP-SERVER.md](../frameworks/MCP-SERVER.md) — MCP description compression and
tool-cardinality reduction
- Source: `open-sse/config/contextEditing.ts`, `open-sse/executors/base.ts`,
`open-sse/services/compression/types.ts`, `src/lib/db/compressionAnalytics.ts`
+615
View File
@@ -0,0 +1,615 @@
---
title: "Extending the Compression Pipeline"
version: 3.8.44
lastUpdated: 2026-07-02
---
# Extending the Compression Pipeline
> **TL;DR**: OmniRoute's compression engine is **pluggable** — you can register custom engines, ship language packs for new languages, and compose stacked pipelines. This guide shows how.
**Related guides:**
- [COMPRESSION_GUIDE.md](./COMPRESSION_GUIDE.md) — Full pipeline overview
- [COMPRESSION_ENGINES.md](./COMPRESSION_ENGINES.md) — Engine registry and built-in engines
- [RTK_COMPRESSION.md](./RTK_COMPRESSION.md) — RTK engine and custom filters
- [COMPRESSION_RULES_FORMAT.md](./COMPRESSION_RULES_FORMAT.md) — Rule pack format reference
---
## Overview
The compression system has **3 extension points**:
| Extension point | Use case | Difficulty |
| -------------------- | ------------------------------------------------------------------------ | ---------- |
| **Custom engine** | Add a brand-new compression algorithm (e.g., domain-specific summarizer) | Advanced |
| **Language pack** | Add support for a new natural language (e.g., Hindi, Arabic) | Medium |
| **Stacked pipeline** | Compose existing engines in a custom order | Beginner |
```
┌─────────────────────────────────────────────────────────────┐
│ Compression Strategy │
│ │
│ Input messages ──▶ getEffectiveMode() ──▶ mode │
│ │ │
│ ┌───────────────────────┼──────────┐ │
│ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │ │
│ "rtk" "lite" "standard" "stacked" │
│ │ │ │ │ │ │
│ ▼ ▼ ▼ ▼ │ │
│ RTK Lite Caveman engines[] │
│ engine engine engine chained │
│ │ │ │ │ │ │
│ └─────────┴─────────┴─────────┘ │ │
│ │ │
│ ▼ │
│ Compressed output │
└─────────────────────────────────────────────────────────────┘
The strategy selector is MODE-BASED: each request selects ONE mode
(rtk / lite / standard / aggressive / ultra / stacked / off).
Only mode "stacked" chains multiple engines in sequence.
Default auto-trigger mode is "lite" (not a 3-tier priority chain).
```
---
## Writing a Custom Compression Engine
The engine interface (`open-sse/services/compression/engines/types.ts`) is the contract every engine must satisfy. It has 5 required methods.
### The `CompressionEngine` Interface
```ts
interface CompressionEngine {
id: string; // Unique engine ID
name: string; // Display name
description: string; // Short description
icon: string; // Icon (emoji or URL)
targets: CompressionEngineTarget[]; // ["messages", "tool_results", "code_blocks"]
stackable: boolean; // Can be used in a stacked pipeline
stackPriority: number; // Order in stacked pipelines (lower = earlier)
metadata: CompressionEngineMetadata;
apply(body, options?): CompressionResult;
compress(body, config?): CompressionResult;
getConfigSchema(): EngineConfigField[];
validateConfig(config): EngineValidationResult;
}
```
### Minimal Example: Whitespace Engine
The simplest possible engine — strip extra whitespace from messages.
````ts
import type { CompressionEngine } from "omniroute/compression/engines/types";
import { registerCompressionEngine } from "omniroute/compression/engines/registry";
function preserveCodeBlocks(text: string): string {
// Split by code block markers and preserve whitespace inside them
const parts = text.split(/(```[\s\S]*?```)/);
return parts
.map((part) => {
if (part.startsWith("```")) {
return part; // Don't modify code blocks
}
return part.replace(/\n{3,}/g, "\n\n"); // Only apply to prose
})
.join("");
}
const whitespaceEngine: CompressionEngine = {
id: "whitespace",
name: "Whitespace Stripper",
description: "Removes extra whitespace and blank lines",
icon: "📝",
targets: ["messages", "tool_results"],
stackable: true,
stackPriority: 100, // Run AFTER caveman/rtk
metadata: {
id: "whitespace",
name: "Whitespace Stripper",
description: "Removes extra whitespace and blank lines",
inputScope: "messages",
targetLatencyMs: 5,
supportsPreview: true,
stable: true,
},
apply(body, options) {
return this.compress(body, options?.config);
},
compress(body, config = {}) {
let originalLength = 0;
let compressedLength = 0;
// Traverse message array — handle both string and multipart content
const compressedBody = (body.messages || []).map((msg) => {
if (typeof msg.content === "string") {
originalLength += msg.content.length;
let compressed = msg.content
.replace(/[ \t]+/g, " ")
.replace(/\n{3,}/g, "\n\n")
.replace(/^\s+|\s+$/gm, "");
compressedLength += compressed.length;
return { ...msg, content: compressed };
}
// Multipart content: traverse parts, compress text parts only
if (Array.isArray(msg.content)) {
const newParts = msg.content.map((part) => {
if (part.type === "text" && typeof part.text === "string") {
originalLength += part.text.length;
let compressed = part.text
.replace(/[ \t]+/g, " ")
.replace(/\n{3,}/g, "\n\n")
.replace(/^\s+|\s+$/gm, "");
compressedLength += compressed.length;
return { ...part, text: compressed };
}
return part; // preserve image_url, tool_use, etc.
});
return { ...msg, content: newParts };
}
return msg;
});
return {
body: { ...body, messages: compressedBody },
stats: {
originalTokens: Math.ceil(originalLength / 4),
compressedTokens: Math.ceil(compressedLength / 4),
savingsPercent: originalLength > 0 ? 100 * (1 - compressedLength / originalLength) : 0,
techniques: ["whitespace-collapse"],
engineId: "whitespace",
},
};
},
getConfigSchema() {
return [
{
key: "preserveCodeBlocks",
type: "boolean",
label: "Preserve code blocks",
defaultValue: true,
description: "Don't touch whitespace inside ```code``` blocks",
},
];
},
validateConfig(config) {
if (config.preserveCodeBlocks !== undefined && typeof config.preserveCodeBlocks !== "boolean") {
return { valid: false, errors: ["preserveCodeBlocks must be a boolean"] };
}
return { valid: true, errors: [] };
},
};
// Register globally
registerCompressionEngine(whitespaceEngine);
````
### Where to Place Custom Engines
```
~/.omniroute/compression/engines/my-engine.ts # User-level
<project>/compression-engines/my-engine.ts # Project-level (loaded on startup)
```
Or load programmatically from a plugin:
```ts
// In your plugin
import {
registerCompressionEngine,
unregisterCompressionEngine,
} from "@omniroute/open-sse/services/compression/engines/registry";
import { myEngine } from "./engines/my-engine";
export default definePlugin({
name: "my-compression-plugin",
// The plugin SDK exposes onRequest / onResponse / onError hooks. Register the
// engine when the plugin module loads (or on first onRequest); unregister it
// from your own teardown path.
onRequest: async (ctx) => {
registerCompressionEngine(myEngine);
},
});
// On teardown:
// unregisterCompressionEngine("my-engine");
```
### Testing Your Engine
Register your engine in a plugin or startup function. Once registered, the engine will be available
in the strategy selector via its `id`. Test integration by composing it in a stacked pipeline:
---
## Creating Language Packs
Caveman-style compression uses **language-specific rule packs** to handle fillers, hedging, and verbose patterns in each natural language. OmniRoute ships with **6 language packs**: `en`, `es`, `fr`, `de`, `ja`, `pt-BR`.
### Pack Structure
A language pack is a directory of **JSON files** under `open-sse/services/compression/rules/<language>/`:
```
open-sse/services/compression/rules/
├── en/
│ ├── filler.json # Pleasantries, hedging, politeness
│ ├── context.json # Context-reducing rules
│ ├── dedup.json # Deduplication rules
│ ├── structural.json # Punctuation, formatting
│ └── ultra.json # Aggressive compression rules
├── es/ (same structure)
├── fr/ (same structure)
├── de/ (same structure)
├── ja/ (same structure)
└── pt-BR/ (same structure)
```
### Rule Anatomy
Each rule has this shape (from `open-sse/services/compression/ruleLoader.ts`):
```ts
interface FileRule {
name: string; // Human-readable name (kebab-case)
pattern: string; // JavaScript regex pattern
replacement?: string; // What to replace the match with
replacementMap?: Record<string, string>; // OR a key→replacement map
flags?: string; // Regex flags ("gi" typically)
context?: "all" | "user" | "system" | "assistant";
category?: "filler" | "context" | "structural" | "dedup" | "terse" | "ultra";
minIntensity?: "lite" | "full" | "ultra"; // Skip below this intensity
description?: string; // Documentation
}
```
### Example: Adding Hindi Filler Rules
```json
{
"language": "hi",
"category": "filler",
"rules": [
{
"name": "polite_opener",
"pattern": "\\b(?:नमस्ते|नमस्कार|आदरणीय)\\b[,!\\s]*",
"replacement": "",
"context": "all",
"category": "filler",
"minIntensity": "lite",
"description": "Strip polite openers like 'नमस्ते'"
},
{
"name": "filler_actually",
"pattern": "\\b(?:असल में|वास्तव में|दरअसल)\\b\\s*",
"replacement": "",
"context": "all",
"category": "filler",
"minIntensity": "lite",
"description": "Strip 'actually' fillers"
},
{
"name": "verbose_plea",
"pattern": "\\b(?:कृपया|कृपया आप|अनुरोध है कि आप)\\b\\s*",
"replacement": "",
"context": "all",
"category": "filler",
"minIntensity": "full",
"description": "Strip 'please' in Hindi"
}
]
}
```
### Validation
Rule packs are validated against `_schema.json` on load. A pack with bad structure will fail to load and log an error:
```
RULE_LOADER: pack "hi/filler.json" failed validation:
- rules.0.pattern: Invalid regex
- rules.1.context: must be one of [all, user, system, assistant]
```
Validation runs automatically when a pack is loaded (against `_schema.json`); an
invalid pack is rejected and the error above is logged. There is no separate
`npm run` script for pack validation — load the pack (e.g. start the server or
exercise the compression path) and watch the logs.
### Loading a Custom Language Pack
```ts
import { loadRulePack } from "omniroute/compression/ruleLoader";
await loadRulePack("./my-custom-rules/hi/filler.json");
```
Or place in a recognized location:
```
~/.omniroute/compression/rules/hi/filler.json # User-level
<project>/.compression/rules/hi/filler.json # Project-level
```
### Best Practices for Language Packs
1. **Start with `filler`** — these are the highest-impact rules
2. **Use `minIntensity`** to gate aggressive rules — protects against over-compression
3. **Include test cases** — add `tests[]` array in the JSON to verify behavior
4. **Order matters** — earlier rules apply first; place high-impact rules first
5. **Be conservative with `replacement`** — empty string is usually correct; never introduce new content
### Translation Strategy
When localizing rule packs to a new language:
1. **Translate the rule names** — they appear in debug output
2. **Adapt the regex patterns** — direct translation often fails (word boundaries differ)
3. **Test against real conversations** — the pack should be safe on actual input
4. **Match cultural conventions** — Japanese packs, for instance, have more honorific fillers than English
---
## Stacked Pipelines
A **stacked pipeline** runs multiple engines in sequence, with each engine's output feeding the next. This is how `mode: stacked` works internally.
### How Stacking Works
```
Input (10,000 tokens)
┌──────────┐
│ Engine │ priority 10
│ A │ ──▶ output: 6,000 tokens (-40%)
└────┬─────┘
┌──────────┐
│ Engine │ priority 50
│ B │ ──▶ output: 2,400 tokens (-60%)
└────┬─────┘
┌──────────┐
│ Engine │ priority 100
│ C │ ──▶ output: 1,200 tokens (-80%)
└────┬─────┘
Final output (1,200 tokens, ~88% savings combined)
```
When `mode: "stacked"` is selected, engines execute sequentially in the order specified in the `pipeline` array.
The output of engine N becomes the input of engine N+1.
### Compression Modes
OmniRoute selects **ONE mode per request** based on configuration, auto-trigger thresholds, and combo overrides.
The available modes are defined in `open-sse/services/compression/types.ts` (type `CompressionMode`):
| Mode | Engines | Use case |
| ------------ | -------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `off` | None | Disable all compression |
| `rtk` | RTK only | Command-output heavy sessions (80%+ savings) |
| `lite` | Lite only | Conservative compression (fast, safe) |
| `standard` | Caveman | Prose compression with language packs |
| `aggressive` | Caveman + Aggressive | Aggressive prose + aggressive final pass |
| `ultra` | Ultra | Maximum compression (lossy, last resort). Optionally routed through the **LLMLingua-2** SLM engine when `ultra.modelPath` is set (fail-opens to the rule-based path when the model is unavailable). |
| `stacked` | Custom pipeline | Compose engines in any order (see below) |
> Beyond the mode engines above, the registry also ships specialized stackable engines —
> **CCR**, **headroom**, **ionizer**, and **session-dedup** — documented in
> [COMPRESSION_ENGINES.md](./COMPRESSION_ENGINES.md#additional-built-in-engines).
Mode selection is determined by `getEffectiveMode()` in `open-sse/services/compression/strategySelector.ts`:
1. If compression is disabled: `"off"`
2. If a combo override exists: use the override
3. If auto-trigger threshold is exceeded: use `autoTriggerMode` (default: `"lite"`)
4. Otherwise: use `defaultMode`
### The Default Stacked Pipeline
When `mode: "stacked"` is explicitly configured, the default pipeline composes:
1. **RTK** — strip command output noise (~80% savings on terminal output)
2. **Caveman** — remove fillers, terse-ify prose (~46% on remaining text)
3. **Lite** — final whitespace + dedup pass
This composition achieves **78-95% savings** on tool-heavy sessions.
### Configuring Stacked Pipelines
In combo config:
```json
{
"compression": {
"mode": "stacked",
"pipeline": [
{ "engine": "rtk", "config": { "intensity": "aggressive" } },
{ "engine": "caveman", "config": { "intensity": "full" } },
{ "engine": "lite", "config": {} }
]
}
}
```
You can omit engines, add custom ones, or reorder them.
### State Passing
Engines can read metadata from the request context (in `options`):
```ts
compress(body, config) {
// Read metadata from previous engines
const original = options?.compressionComboId; // "my-coding-combo"
// ...
}
```
The metadata is **read-only** — engines cannot mutate the request context, only their own body output.
### Execution Order Gotchas
| Engine order | Effect |
| ----------------------------------- | ------------------------------------------------------------------------------ |
| RTK → Caveman → Lite | **Recommended** (strips noise first, then language, then whitespace) |
| Lite → RTK → Caveman | Bad — Lite strips whitespace from raw output, making RTK pattern matching fail |
| Caveman → RTK | Bad — Caveman may rewrite text in ways that RTK doesn't recognize |
| Any order with `tool_results` first | Better — tool output is the noisiest content |
### When NOT to Stack
Stacking isn't always better:
- **Simple messages** (no tool output) — single Caveman or Lite is enough
- **Cost-sensitive** — each engine adds ~5-50ms latency
- **Specific tools** — RTK alone is usually sufficient for shell output
### Building a Custom Pipeline
There is no named-pipeline registry. A stacked pipeline is just an **inline array
of steps** passed to `applyStackedCompression()` (exported from
`@omniroute/open-sse/services/compression/strategySelector`):
```ts
import { applyStackedCompression } from "@omniroute/open-sse/services/compression/strategySelector";
const result = applyStackedCompression(body, [
{ engine: "rtk", intensity: "aggressive" },
{ engine: "caveman", intensity: "full" },
]);
```
When you don't pass a pipeline, it defaults to `rtk(standard) → caveman(full)`.
To drive it from config, set `mode: "stacked"` and provide the step array under
`stackedPipeline` (read from `config.stackedPipeline`):
```json
{
"compression": {
"mode": "stacked",
"stackedPipeline": [
{ "engine": "rtk", "intensity": "aggressive" },
{ "engine": "caveman", "intensity": "full" }
]
}
}
```
---
## Upstream Sync Policy
OmniRoute's compression engines credit several upstream projects in the README
("inspired by RTK, Caveman, LLMLingua-2, Troglodita"). A common contributor
question is: **when upstream RTK adds a new tool filter or Caveman adds a rule
pack, how does that reach OmniRoute?** This section is the authoritative answer.
### Vendored copies vs. independent implementations
| Engine | Relationship to upstream | Location |
| ---------------------------- | ------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------- |
| **RTK** | **Independent reimplementation** (inspired-by, not a copy) | `open-sse/services/compression/engines/rtk/` |
| **Caveman** | **Independent reimplementation** (inspired-by) | `open-sse/services/compression/engines/cavemanAdapter.ts` |
| **Headroom** | Mostly internal; only the `gcf/` codec is **genuinely vendored** from `gcf-typescript` (MIT, SPDX-marked, generic profile only) | `open-sse/services/compression/engines/headroom/gcf/` |
| **LLMLingua-2 / Troglodita** | Inspired-by (drive the `llmlingua` + `session-dedup` engines) | `open-sse/services/compression/engines/llmlingua/`, `session-dedup` |
Key point: **RTK and Caveman are clean-room TypeScript implementations of the
_ideas_ (filter rules, rule packs), not vendored source trees.** There is no
upstream copy to `git pull` from — which is exactly why the README says
"inspired by" rather than "bundled".
### How upstream improvements are merged
There is **no automated upstream-release tracking and no `compression-sync`
label** — by design. Because the engines are reimplementations, an upstream RTK
filter or Caveman rule pack is not merged as code; it is **re-expressed as a new
rule/filter in OmniRoute's own format** (see
[COMPRESSION_RULES_FORMAT.md](./COMPRESSION_RULES_FORMAT.md)) and lands ad-hoc via
a normal PR. The extension points above (custom engine, language pack, RTK filter)
are the sanctioned way to contribute one.
Recent examples of exactly this flow:
- RTK filters for Gradle & `dotnet` build output (v3.8.42)
- RTK filters for kubectl / docker-build / composer / gh (#2824)
- Caveman Indonesian language pack (#3975), plus German / French / Japanese / Chinese packs
### Headroom (input-compression proxy)
Headroom is **fully internal** — a pinned vendored `gcf` codec snapshot plus
OmniRoute's own `smartcrusher` / `toon` / `tabular` layers. There is no live
upstream to track beyond the vendored copy; updates to `gcf` are refreshed
manually when the codec changes and re-validated against the compression budget
gate (`check:compression-budget`).
### Proposing an upstream-inspired improvement
1. **Don't vendor** — re-express the upstream rule/filter in OmniRoute's format.
2. Add it via the matching extension point below (language pack, RTK filter, or
custom engine).
3. Reference the upstream project in the PR description (attribution), not by
copying its license-bearing source.
4. Include tests and confirm the `check:compression-budget` gate still passes.
---
## Best Practices
### Engine Development
1. **Always implement `validateConfig`** — engines without validation cause silent failures
2. **Set realistic `targetLatencyMs`** — used by the strategy selector to choose engines
3. **Use `getConfigSchema` for the dashboard** — never hide config from users
4. **Support `stackable: true` if your engine is pure** — engines with side effects shouldn't stack
5. **Write inline tests** — engines should be verifiable in <1s
### Language Pack Development
1. **Start with `lite` intensity** — your rules should be safe at the lowest setting
2. **Use `context` to scope rules** — `user` only rules can't accidentally affect system prompts
3. **Avoid capturing JSON keys** — `\\bword\\b` can match inside JSON, breaking structured data
4. **Test with edge cases** — empty input, unicode, RTL text, emojis
5. **Use existing packs as templates** — `en/filler.json` is the most-developed example
### Pipeline Design
1. **Profile before optimizing** — measure with `compression_stats` first
2. **Prefer composition over reimplementation** — extend Caveman rules before writing a new engine
3. **Document the order rationale** — comment why engine A before engine B
4. **Test at all 3 intensity levels** — `lite` is fast but lossy, `ultra` is slow but precise
---
## Reference: Built-in Engines
| Engine ID | Stackable | Default stackPriority | Targets |
| -------------------- | --------- | --------------------- | ----------------------------------- |
| `lite` | Yes | 5 | messages, tool_results |
| `rtk` | Yes | 10 | tool_results |
| `standard` (caveman) | Yes | 20 | messages, tool_results, code_blocks |
| `aggressive` | Yes | 30 | messages |
| `ultra` | Yes | 40 | messages, code_blocks |
### See Also
- [COMPRESSION_GUIDE.md](./COMPRESSION_GUIDE.md) — Pipeline overview
- [COMPRESSION_ENGINES.md](./COMPRESSION_ENGINES.md) — Engine registry reference
- [COMPRESSION_RULES_FORMAT.md](./COMPRESSION_RULES_FORMAT.md) — Rule format spec
- [COMPRESSION_LANGUAGE_PACKS.md](./COMPRESSION_LANGUAGE_PACKS.md) — Language pack details
- [RTK_COMPRESSION.md](./RTK_COMPRESSION.md) — RTK engine and custom filters
- Source: `open-sse/services/compression/` (117 files, ~250KB)
+674
View File
@@ -0,0 +1,674 @@
---
title: "RTK Compression"
version: 3.8.40
lastUpdated: 2026-06-28
---
# RTK Compression
RTK compression is OmniRoute's command-aware compression engine for terminal and tool output. It is
designed for coding-agent sessions where most context growth comes from test logs, build output,
package manager noise, shell transcripts, Docker output, git output, and stack traces.
RTK can run directly with `defaultMode: "rtk"` or as the first step in a stacked pipeline, usually:
```txt
rtk -> caveman
```
That order compresses noisy machine output first, then lets Caveman condense remaining prose.
Upstream RTK reports `60-90%` command-output savings. Its README sample session goes from
`~118,000` standard tokens to `~23,900` RTK tokens, which is `79.7%` saved (`~80%`). OmniRoute uses
that upstream average for the stacked savings calculation with Caveman input compression:
```txt
RTK average: 80% saved
Caveman input: 46% saved
Stacked: 1 - (1 - 0.80) * (1 - 0.46) = 89.2% saved
Range: 1 - (1 - 0.60..0.90) * (1 - 0.46) = 78.4-94.6%
```
## What It Compresses
The built-in catalog currently ships 49 filters across these categories:
| Category | Examples |
| --------- | ------------------------------------------------------------- |
| `git` | `git status`, `git branch`, `git diff`, `git log` |
| `test` | Vitest, Jest, Pytest, Playwright, Go tests, Cargo tests |
| `build` | TypeScript, ESLint, Biome, Prettier, Vite, Webpack, Turbo, Nx |
| `package` | `npm install`, `npm audit`, `pip`, `uv sync`, Poetry, Bundler |
| `shell` | `ls`, `find`, `grep`, generic shell logs |
| `docker` | `docker ps`, Docker logs |
| `infra` | Terraform, OpenTofu, `systemctl status` |
| `generic` | JSON output, stack traces, generic output fallback |
The detector in `open-sse/services/compression/engines/rtk/commandDetector.ts` classifies output
before filter selection. Filters can also match by command pattern or output regex when a command
class is not enough.
## Filter Resolution
RTK loads filters in this order:
1. Project filters from `.rtk/filters.json`, only when trusted.
2. Global filters from `DATA_DIR/rtk/filters.json`.
3. Built-in filters from `open-sse/services/compression/engines/rtk/filters/`.
Project filters are intentionally trust-gated because regex filters can change how tool output is
shown to agents. A project filter file is accepted when one of these is true:
- `rtkConfig.trustProjectFilters` is `true`.
- `OMNIROUTE_RTK_TRUST_PROJECT_FILTERS=1` is set.
- `.rtk/trust.json` contains the SHA-256 hash of `.rtk/filters.json`.
Trust file example:
```json
{
"filtersSha256": "0123456789abcdef..."
}
```
Custom filters can be one filter object or an array of filter objects. Invalid custom filters are
skipped and reported by `/api/context/rtk/filters` diagnostics. Invalid built-in filters fail fast.
## Filter DSL
Filters use the JSON schema described in [Compression Rules Format](./COMPRESSION_RULES_FORMAT.md).
The runtime applies these stages in order:
```txt
stripAnsi -> filterStderr -> replace -> matchOutput -> drop/include lines
-> truncateLineAt -> head/tail/maxLines -> onEmpty
```
Important fields:
| Field | Purpose |
| ---------------------------- | -------------------------------------------------------------- |
| `rules.stripAnsi` | Remove terminal color/control sequences before matching |
| `rules.filterStderr` | Normalize common stderr prefixes before matching/filtering |
| `rules.replace` | Apply ordered regex replacements |
| `rules.matchOutput` | Return a compact summary when output matches a known condition |
| `rules.matchOutput[].unless` | Skip the shortcut when an error/failure pattern is present |
| `rules.dropPatterns` | Remove noisy lines |
| `rules.includePatterns` | Prefer actionable lines |
| `rules.collapsePatterns` | Collapse repeated matching lines |
| `rules.deduplicate` | Per-filter opt-in: collapse consecutive duplicate lines |
| `rules.truncateLineAt` | Unicode-safe per-line truncation |
| `rules.onEmpty` | Fallback message if all lines are filtered out |
| `tests[]` | Inline samples used by the verify gate |
Built-in filters are expected to include inline `tests[]` samples. Custom filters should include
them too, especially when they are shared across projects.
## Line Deduplication (two layers)
RTK collapses duplicate lines at two independent layers:
1. **Per-filter `deduplicate` (opt-in, default `false`).** A filter can set `rules.deduplicate: true`
to collapse consecutive duplicate lines _within that filter's matched output_, before truncation.
This runs inside `lineFilter.ts`. For legacy filters, it is auto-enabled when the filter defines
`collapsePatterns`. Schema: `deduplicate: z.boolean().default(false)` in
`open-sse/services/compression/engines/rtk/filterSchema.ts`.
2. **Engine-wide `deduplicateThreshold` (default `3`).** After all filters run, the engine collapses
any run of `>= deduplicateThreshold` identical consecutive lines across the whole result
(`deduplicateRepeatedLines`, applied in `engines/rtk/index.ts`). The value is bounded to 2100 on
normalization.
The per-filter pass runs first (inside the filter), the engine-wide pass runs last (over the joined
output), so the two compose without double-counting.
## Line Grouping (`enableGrouping`)
When `rtkConfig.enableGrouping` is `true` (default `false`), RTK runs an additional `groupSimilarLines`
pass over the post-dedup result that collapses runs of _near-equivalent_ (not byte-identical)
consecutive lines. `rtkConfig.groupingThreshold` (default `3`) is the minimum run length that triggers
grouping. This is the structural counterpart to `deduplicateThreshold`: dedup handles exact repeats,
grouping handles "the same shape with small differences". Both flags are part of the `rtkConfig` JSON
persisted in the `key_value` table (see Configuration above), so the setting survives restarts.
## Code Comment Stripping (`stripCodeComments` / `preserveDocstrings`)
When `rtkConfig.applyToCodeBlocks` is enabled, RTK can also strip comments from fenced code blocks:
- `stripCodeComments` (default `false`) — opt-in. When `true`, RTK removes comments from JavaScript
and TypeScript fenced blocks. The flag was historically read but never applied, so the default stays
at "preserve" to avoid a silent production change.
- `preserveDocstrings` (default `true`) — when stripping comments, JSDoc/`/** … */` block comments are
kept (they carry API documentation worth more than the bytes they cost). Set to `false` to strip
those too.
Comment removal is implemented in `open-sse/services/compression/engines/rtk/codeStripper.ts`. It uses
the **TypeScript parser** (not a regex) so that string, template, and regex literals are never mistaken
for comments, and it bails out entirely when JSX is detected (so JSX expression-container comments are
never corrupted). Comment stripping currently applies to **JavaScript and TypeScript only** — other
languages in the stripper's `CodeLanguage` set (Python, Rust, Go, Ruby, Java) have empty-line and
whitespace collapse but no comment removal. The stripped-block run is tagged `rtk:code-strip` in
`rulesApplied`.
> **Note — GCF / tabular encoding is a separate engine.** RTK does **not** contain the "GCF"
> (Graph Compact Format) tabular/columnar JSON encoder. That encoder — which replaced an older
> `omni-tabular` encoder — lives in the **headroom** engine
> (`open-sse/services/compression/engines/headroom/`, with the vendored codec under
> `headroom/gcf/`). It is unrelated to the RTK filter pipeline documented here.
## Configuration
Global settings are available through `/api/settings/compression`. RTK-specific settings are also
available through `/api/context/rtk/config`.
```json
{
"defaultMode": "stacked",
"autoTriggerMode": "stacked",
"autoTriggerTokens": 32000,
"stackedPipeline": [
{ "engine": "rtk", "intensity": "standard" },
{ "engine": "caveman", "intensity": "full" }
],
"rtkConfig": {
"enabled": true,
"intensity": "standard",
"applyToToolResults": true,
"applyToCodeBlocks": false,
"applyToAssistantMessages": false,
"enabledFilters": [],
"disabledFilters": [],
"maxLinesPerResult": 120,
"maxCharsPerResult": 12000,
"deduplicateThreshold": 3,
"customFiltersEnabled": true,
"trustProjectFilters": false,
"rawOutputRetention": "never",
"rawOutputMaxBytes": 1048576,
"enableGrouping": false,
"groupingThreshold": 3,
"stripCodeComments": false,
"preserveDocstrings": true
}
}
```
`enabledFilters` and `disabledFilters` use filter ids, for example `test-vitest` or `git-diff`.
The full `rtkConfig` shape is defined by `RtkConfig` / `DEFAULT_RTK_CONFIG` in
`open-sse/services/compression/types.ts`. The whole object is persisted as a single JSON value in
the SQLite `key_value` table under `namespace = "compression"`, `key = "rtkConfig"`
(`src/lib/db/compression.ts`), and normalized on read by `normalizeRtkConfig`. So every field below
— including `enableGrouping`, `groupingThreshold`, `stripCodeComments`, and `preserveDocstrings`
round-trips through the same store and survives a restart.
| Key | Default | Purpose |
| ---------------------- | ------- | --------------------------------------------------------------------------- |
| `deduplicateThreshold` | `3` | Engine-wide: min consecutive identical lines to collapse (bounded 2100) |
| `enableGrouping` | `false` | Opt-in: collapse runs of near-equivalent consecutive lines |
| `groupingThreshold` | `3` | Min consecutive similar-line run that triggers grouping |
| `stripCodeComments` | `false` | Opt-in: remove comments from fenced code blocks (needs `applyToCodeBlocks`) |
| `preserveDocstrings` | `true` | When stripping comments, keep JSDoc/`/** … */` blocks |
## API
| Route | Method | Purpose |
| ---------------------------------- | ------ | -------------------------------------------- |
| `/api/context/rtk/config` | GET | Read RTK config |
| `/api/context/rtk/config` | PUT | Update RTK config |
| `/api/context/rtk/filters` | GET | List filter catalog and load diagnostics |
| `/api/context/rtk/test` | POST | Preview RTK compression for one text payload |
| `/api/context/rtk/raw-output/[id]` | GET | Read retained redacted raw output |
| `/api/compression/preview` | POST | Preview any compression mode |
RTK test payload:
```json
{
"command": "npm test",
"text": "FAIL tests/example.test.ts\nAssertionError: expected true\nTest Files 1 failed",
"config": {
"intensity": "standard"
}
}
```
Compression preview payload:
```json
{
"mode": "stacked",
"messages": [
{
"role": "tool",
"content": "FAIL tests/example.test.ts\nAssertionError: expected true\nTest Files 1 failed"
}
],
"config": {
"rtkConfig": {
"rawOutputRetention": "failures"
}
}
}
```
Management routes require dashboard management auth or the matching API-key policy.
## Raw Output Recovery
RTK normally returns only compressed text. For debugging, `rawOutputRetention` can retain redacted
raw output:
| Value | Behavior |
| ---------- | ------------------------------------------------------- |
| `never` | Do not retain raw output |
| `failures` | Retain only likely failure output |
| `always` | Retain every compressed RTK raw output, after redaction |
Retained files are written under:
```txt
DATA_DIR/rtk/raw-output/
```
Secrets are redacted before persistence, including common bearer tokens, API keys, Slack tokens,
AWS access keys, and assignment-style `token=...`, `secret=...`, `password=...` values. Analytics
stores only the pointer id, size, and hash metadata.
## Verify Gate
The focused verify gate runs built-in inline filter tests without shelling out to external commands:
```bash
node --import tsx/esm --test tests/unit/compression/rtk-verify.test.ts
```
The broader RTK gate is:
```bash
node --import tsx/esm --test \
tests/unit/compression/rtk-*.test.ts \
tests/unit/compression/pipeline-integration.test.ts \
tests/unit/compression/context-compression-api.test.ts
```
Run the broad compression gate before release:
```bash
node --import tsx/esm --test \
tests/unit/compression/*.test.ts \
tests/golden-set/*.test.ts \
tests/integration/compression-pipeline.test.ts \
tests/unit/api/compression/compression-api.test.ts
```
## Extending RTK
1. Add or update a filter JSON file.
2. Include at least one `tests[]` sample that proves the important behavior.
3. Add a fixture under `tests/unit/compression/fixtures/rtk/` for new command families.
4. Add command detection coverage when introducing a new output class.
5. Run the verify and broad RTK gates.
6. If the filter is project-local, commit `.rtk/filters.json` and refresh `.rtk/trust.json` only after review.
---
## Intensity Levels (v3.8.16+)
RTK supports **3 intensity levels** that trade off between **compression aggressiveness** and **safety**. The level is set via `config.intensity` in the engine config.
### The 3 Levels
| Level | Truncation threshold | Token savings | Risk | Best for |
| -------------------- | -------------------- | ------------- | -------- | -------------------------------- |
| `minimal` | 24 lines per section | ~20-40% | Very low | Production with critical context |
| `standard` (default) | 24 lines per section | ~50-70% | Low | Daily coding sessions |
| `aggressive` | 16 lines per section | ~70-90% | Medium | Long sessions, max savings |
### Where the Truncation Happens
The truncation threshold affects `lineFilter.ts`:
```ts
// From open-sse/services/compression/engines/rtk/index.ts:329-330
config.intensity === "aggressive" ? 16 : 24,
config.intensity === "aggressive" ? 16 : 24,
```
Both the **head** and **tail** of each section are preserved; middle content is dropped when truncation kicks in.
### What Stays vs. What Gets Cut
| Content | minimal | standard | aggressive |
| -------------------------- | ------------ | ------------ | ------------ |
| Errors / stack traces | ✅ preserved | ✅ preserved | ✅ preserved |
| Test failures | ✅ preserved | ✅ preserved | ✅ preserved |
| Build errors | ✅ preserved | ✅ preserved | ✅ preserved |
| Test passes (verbose) | ✅ preserved | 🟡 collapsed | 🟡 collapsed |
| Routine output (info logs) | 🟡 collapsed | 🟡 collapsed | ❌ dropped |
| Progress bars | 🟡 collapsed | ❌ dropped | ❌ dropped |
| Banner / ASCII art | 🟡 collapsed | ❌ dropped | ❌ dropped |
### Choosing the Right Intensity
```
Is losing context catastrophic?
┌───────────┼───────────┐
│ │ │
YES NO NOT SURE
│ │ │
▼ │ │
minimal │ │
│ │ │
│ ▼ ▼
│ How critical Try `standard` first
│ is throughput? (works for 80% of
│ │ cases)
│ ┌────┴────┐
│ │ │
│ LOW HIGH
│ │ │
│ ▼ ▼
│ standard aggressive
│ │ │
└──────┴─────────┘
```
### Configuring Intensity
**Per-combo** (in combo config):
```json
{
"combo": "my-coding-combo",
"routing": {
/* ... */
},
"compression": {
"engine": "rtk",
"intensity": "aggressive"
}
}
```
**Programmatically**:
`rtkEngine` (`@omniroute/open-sse/services/compression/engines/rtk`) is a
`CompressionEngine` and has no `updateConfig` method. Update an engine's config
through the registry helper instead:
```ts
import { updateEngineConfig } from "@omniroute/open-sse/services/compression/engines/registry";
updateEngineConfig("rtk", { intensity: "aggressive" });
```
### Verifying the Effect
Use the **Verify Gate** (see below) to confirm your filter is safe at your chosen intensity:
```ts
import { runRtkFilterTests } from "omniroute/compression/engines/rtk/verify";
const result = runRtkFilterTests({ intensity: "aggressive" });
if (!result.passed) {
console.error("Filters failed at aggressive intensity");
}
```
---
## Custom Filter Development (v3.8.16+)
The `engines/rtk/filters/` directory contains **49+ built-in filter JSON files**. You can add your own to compress output from custom tools not covered by the defaults.
### Filter Schema (Zod)
```ts
{
"id": "string", // Required. Filter identifier (kebab-case, e.g., "python-traceback")
"label": "string", // Required. Human-readable filter name
"description": "string", // Optional (default: ""). Short description of what filter does
"category": "git|test|build|shell|docker|package|infra|cloud|generic",
"priority": number, // Optional (0-100, default: 50). Execution order (higher = first)
"match": {
"commands": ["string"], // Command names to match (e.g., "python", "pytest")
"patterns": ["string"], // Regex patterns to match output
"outputTypes": ["string"] // Detected output classes (e.g., "test-failure")
},
"rules": {
"stripAnsi": boolean, // Optional (default: false). Strip ANSI color codes
"replace": [ // Find-and-replace rules (default: [])
{ "pattern": "regex", "replacement": "..." }
],
"matchOutput": [ // Short-circuit on pattern match (default: [])
{
"pattern": "regex",
"message": "short summary",
"unless": "regex" // Skip if this pattern matches
}
],
"includePatterns": ["string"], // Lines to keep (regex patterns, default: [])
"dropPatterns": ["string"], // Lines to drop (regex patterns, default: [])
"collapsePatterns": ["string"], // Lines to collapse to single occurrence (default: [])
"deduplicate": boolean, // Optional (default: false). Remove duplicate lines
"truncateLineAt": number, // Optional (default: 0). Truncate lines to max chars
"maxLines": number, // Optional (default: 0). Hard cap on total lines
"headLines": number, // Optional (default: 20). Keep first N lines of matched output
"tailLines": number, // Optional (default: 20). Keep last N lines of matched output
"onEmpty": "string", // Optional (default: ""). Fallback message if all lines filtered
"filterStderr": boolean // Optional (default: false). Also filter stderr output
},
"preserve": {
"errorPatterns": ["string"], // Patterns that must always be preserved (default: [])
"summaryPatterns": ["string"] // Patterns for final summary line (default: [])
},
"tests": [ // Inline tests for verification (default: [])
{
"name": "string", // Required. Test name
"input": "sample output", // Required. Sample input text
"expected": "expected output", // Required. Expected compressed output
"command": "optional command" // Optional. Command context
}
]
}
```
### Example: Python Traceback Filter
```json
{
"id": "python-traceback",
"label": "Python Traceback Filter",
"description": "Compresses Python tracebacks to essential file/line locations and error type",
"category": "test",
"priority": 60,
"match": {
"commands": ["python", "python3", "pytest", "uv", "poetry"],
"patterns": ["Traceback \\(most recent call last\\)", "Error", "Exception"],
"outputTypes": ["error-traceback"]
},
"rules": {
"stripAnsi": true,
"includePatterns": [
"Traceback \\(most recent call last\\)",
"^\\s*File \".+\", line \\d+",
"^\\s*[A-Z][a-zA-Z]+Error:",
"^\\s*[A-Z][a-zA-Z]+Exception"
],
"dropPatterns": ["site-packages/", "^\\s+[a-z_]+\\([^)]*\\)$"],
"headLines": 5,
"tailLines": 3,
"maxLines": 25,
"filterStderr": true
},
"preserve": {
"errorPatterns": ["Error:", "Exception:", "Traceback"],
"summaryPatterns": ["^[A-Z][a-zA-Z]+(?:Error|Exception):"]
},
"tests": [
{
"name": "preserves-error-type-and-location",
"input": "Traceback (most recent call last):\n File \"app.py\", line 42, in main\n do_thing()\n File \"lib/utils.py\", line 17, in helper\n return 1 / 0\nZeroDivisionError: division by zero",
"expected": "Traceback (most recent call last):\n File \"app.py\", line 42, in main\n File \"lib/utils.py\", line 17, in helper\nZeroDivisionError: division by zero",
"command": "python app.py"
}
]
}
```
### Loading Custom Filters
Place the file in a recognized location:
```
~/.omniroute/rtk/filters/my-filter.json # User-level
<project>/.rtk/filters/my-filter.json # Project-level
```
Filters are loaded automatically on startup via `loadRtkFilters()` in `open-sse/services/compression/engines/rtk/filterLoader.ts`. The loader discovers filters from:
- Built-in catalog: `open-sse/services/compression/engines/rtk/filters/`
- User directory: `~/.omniroute/rtk/filters/`
- Project directory: `<project>/.rtk/filters/`
To load filters programmatically:
```ts
import { loadRtkFilters } from "@omniroute/open-sse/services/compression/engines/rtk/filterLoader";
// Options: customFiltersEnabled (load user/project filters, default on),
// trustProjectFilters, refresh.
const filters = loadRtkFilters({ customFiltersEnabled: true });
```
### Validation
Filters are validated against the Zod schema on load. A filter with bad structure will fail to load and log an error:
```
RTK_FILTER_LOADER: filter "my-filter" failed validation:
- rules.replace.0.pattern: Invalid regex
- match.commands: must not be empty
```
To validate all installed filters, call `runRtkFilterTests()` which is exported from `open-sse/services/compression/engines/rtk/verify.ts`.
### Best Practices
1. **Always include `tests[]`** — they prove your filter works and prevent regressions
2. **Use `matchOutput` for short-circuits** — if a single line tells the story, replace the whole block
3. **Prefer `keep` over `strip`** — explicit "always preserve" rules are safer than "always remove"
4. **Test at all 3 intensity levels**`minimal` should be a no-op, `aggressive` should still preserve errors
5. **Use the `unless` field** — guard short-circuits with "don't trigger if X is present"
---
## Raw Output Recovery & Verify Gate
When RTK compresses output aggressively, you can **recover the original text** for debugging, audit, or replay.
### How Raw Output Recovery Works
```
Original output (10K tokens)
RTK compress (with rawOutput.enabled=true)
├─▶ Compressed output (2K tokens) ──▶ to LLM
└─▶ Original output (10K tokens) ──▶ stored in DB
(linked by request_id)
```
### Enabling Raw Output Storage
**Per-request** (in combo config):
```json
{
"compression": {
"engine": "rtk",
"intensity": "aggressive",
"rawOutput": {
"enabled": true,
"maxBytes": 1048576 // 1MB cap
}
}
}
```
**Default**: `rawOutput.enabled: false` (saves storage).
### Storage Cost
| Per-request | 1MB cap | 10MB cap |
| ------------------------- | ------------ | ------------- |
| Average compressed output | ~5KB | ~5KB |
| Raw output stored | ~50-500KB | ~500KB-5MB |
| With 1000 requests/day | 50-500MB/day | 500MB-5GB/day |
> **Recommendation**: Only enable raw output for **debugging sessions** or **sampled auditing**, not always-on.
### Recovering the Original
```ts
import { readRtkRawOutput } from "omniroute/compression/engines/rtk/rawOutput";
const raw = readRtkRawOutput(pointerId); // pointerId from compression stats
if (raw) {
console.log("Original output:", raw);
}
```
The `pointerId` is returned in `CompressionStats.rtkRawOutputPointers[]` after compression.
See `open-sse/services/compression/engines/rtk/rawOutput.ts:102` for the function signature.
### The Verify Gate
The **RTK Filter Verification** (`open-sse/services/compression/engines/rtk/verify.ts`) validates all filters against their `tests[]` and ensures behavior is correct at all 3 intensity levels.
**Call `runRtkFilterTests()`** to run verification:
```ts
import { runRtkFilterTests } from "open-sse/services/compression/engines/rtk/verify";
const result = runRtkFilterTests();
console.log(`Passed: ${result.outcomes.filter((o) => o.passed).length}`);
console.log(`Failed: ${result.outcomes.filter((o) => !o.passed).length}`);
if (!result.passed) {
console.error("Filters failed verification");
result.outcomes
.filter((o) => !o.passed)
.forEach((o) => {
console.error(
` - ${o.filterId} / ${o.testName}: expected "${o.expected}", got "${o.actual}"`
);
});
}
```
**What it validates**:
1. Every filter loads and passes schema validation
2. Every `tests[]` entry produces expected output
3. `minimal` intensity is a no-op (preserves original, only applies structural filters)
4. `aggressive` intensity preserves errors, test failures, and stack traces
5. Compressed output is never larger than original input
- Source: `open-sse/services/compression/engines/rtk/` (63 files, ~70KB)
- **Before merging a filter change** — always ensure tests pass
- **After upgrading RTK engine** — schema may have changed
- **Periodically in monitoring** — protects against drift in test fixtures
- **When adding a new tool/command family** — proves the new filter works
---
## See Also
- [COMPRESSION_GUIDE.md](./COMPRESSION_GUIDE.md) — Full compression pipeline overview
- [COMPRESSION_ENGINES.md](./COMPRESSION_ENGINES.md) — Engine registry and built-in engines
- [EXTENDING_COMPRESSION.md](./EXTENDING_COMPRESSION.md) — Custom engines, language packs, stacked pipelines
- Source: `open-sse/services/compression/engines/rtk/` (63 files, ~70KB)
+11
View File
@@ -0,0 +1,11 @@
{
"title": "Compression",
"pages": [
"COMPRESSION_GUIDE",
"COMPRESSION_ENGINES",
"RTK_COMPRESSION",
"COMPRESSION_LANGUAGE_PACKS",
"COMPRESSION_RULES_FORMAT",
"EXTENDING_COMPRESSION"
]
}