chore: import upstream snapshot with attribution
This commit is contained in:
@@ -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
|
||||
|
||||
60–80% 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
|
||||
```
|
||||
@@ -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
|
||||
```
|
||||
@@ -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`
|
||||
@@ -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)
|
||||
@@ -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 2–100 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 2–100) |
|
||||
| `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)
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"title": "Compression",
|
||||
"pages": [
|
||||
"COMPRESSION_GUIDE",
|
||||
"COMPRESSION_ENGINES",
|
||||
"RTK_COMPRESSION",
|
||||
"COMPRESSION_LANGUAGE_PACKS",
|
||||
"COMPRESSION_RULES_FORMAT",
|
||||
"EXTENDING_COMPRESSION"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user