-
-
Notifications
You must be signed in to change notification settings - Fork 1.1k
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:
rtk -> cavemanThat 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:
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%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.
RTK loads filters in this order:
- Project filters from
.rtk/filters.json, only when trusted. - Global filters from
DATA_DIR/rtk/filters.json. - 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.trustProjectFiltersistrue. -
OMNIROUTE_RTK_TRUST_PROJECT_FILTERS=1is set. -
.rtk/trust.jsoncontains the SHA-256 hash of.rtk/filters.json.
Trust file example:
{
"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.
Filters use the JSON schema described in Compression Rules Format. The runtime applies these stages in order:
stripAnsi -> filterStderr -> replace -> matchOutput -> drop/include lines
-> truncateLineAt -> head/tail/maxLines -> onEmptyImportant 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.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.
Global settings are available through /api/settings/compression. RTK-specific settings are also
available through /api/context/rtk/config.
{
"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
}
}enabledFilters and disabledFilters use filter ids, for example test-vitest or git-diff.
| 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:
{
"command": "npm test",
"text": "FAIL tests/example.test.ts\nAssertionError: expected true\nTest Files 1 failed",
"config": {
"intensity": "standard"
}
}Compression preview payload:
{
"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.
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:
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.
The focused verify gate runs built-in inline filter tests without shelling out to external commands:
node --import tsx/esm --test tests/unit/compression/rtk-verify.test.tsThe broader RTK gate is:
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.tsRun the broad compression gate before release:
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- Add or update a filter JSON file.
- Include at least one
tests[]sample that proves the important behavior. - Add a fixture under
tests/unit/compression/fixtures/rtk/for new command families. - Add command detection coverage when introducing a new output class.
- Run the verify and broad RTK gates.
- If the filter is project-local, commit
.rtk/filters.jsonand refresh.rtk/trust.jsononly after review.
RTK supports 3 intensity levels that trade off between compression aggressiveness and safety. The level is set via config.intensity in the engine config.
| 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 |
The truncation threshold affects lineFilter.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.
| 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 |
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
│ │ │
└──────┴─────────┘
Per-combo (in combo config):
{
"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:
import { updateEngineConfig } from "@omniroute/open-sse/services/compression/engines/registry";
updateEngineConfig("rtk", { intensity: "aggressive" });Use the Verify Gate (see below) to confirm your filter is safe at your chosen intensity:
import { runRtkFilterTests } from "omniroute/compression/engines/rtk/verify";
const result = runRtkFilterTests({ intensity: "aggressive" });
if (!result.passed) {
console.error("Filters failed at aggressive intensity");
}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.
{
"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
}
]
}{
"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"
}
]
}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:
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 });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.
-
Always include
tests[]— they prove your filter works and prevent regressions -
Use
matchOutputfor short-circuits — if a single line tells the story, replace the whole block -
Prefer
keepoverstrip— explicit "always preserve" rules are safer than "always remove" -
Test at all 3 intensity levels —
minimalshould be a no-op,aggressiveshould still preserve errors -
Use the
unlessfield — guard short-circuits with "don't trigger if X is present"
When RTK compresses output aggressively, you can recover the original text for debugging, audit, or replay.
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)
Per-request (in combo config):
{
"compression": {
"engine": "rtk",
"intensity": "aggressive",
"rawOutput": {
"enabled": true,
"maxBytes": 1048576 // 1MB cap
}
}
}Default: rawOutput.enabled: false (saves storage).
| 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.
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 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:
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:
- Every filter loads and passes schema validation
- Every
tests[]entry produces expected output -
minimalintensity is a no-op (preserves original, only applies structural filters) -
aggressiveintensity preserves errors, test failures, and stack traces - 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
- COMPRESSION_GUIDE.md — Full compression pipeline overview
- COMPRESSION_ENGINES.md — Engine registry and built-in engines
- EXTENDING_COMPRESSION.md — Custom engines, language packs, stacked pipelines
- Source:
open-sse/services/compression/engines/rtk/(63 files, ~70KB)
OmniRoute · Website · npm · Docker Hub
- Setup Guide
- User Guide
- Features
- Quick Start (Docker)
- Electron Desktop App
- Termux (Android)
- PWA Guide
- MCP Server
- A2A Server
- Agent Protocols
- OpenCode Plugin
- Webhooks
- Cloud Agents
- Skills
- Memory
- Evals
- Gamification
- Guardrails
- Compliance
- Error Sanitization
- Public Credentials
- Route Guard Tiers
- Stealth Guide
- CLI Token Auth