Understanding RTK's Filter Pipeline and Command Routing Architecture

RTK (Rust Token Killer) uses a three-layer architecture where Clap routes commands to specific modules, core::runner executes them in one of three run modes, and a strategy-based filter pipeline processes output before it reaches the language model.

The RTK CLI proxy intercepts system commands and optimizes their output for LLM consumption through a deterministic pipeline. According to the rtk-ai/rtk source code, this architecture combines command routing, execution orchestration, and text filtering into a cohesive system that minimizes token usage while preserving context.

Command Routing: From CLI to Module Dispatch

The entry point in src/main.rs uses Clap to parse arguments and immediately dispatch to the appropriate command handler. This routing layer is deliberately thin to keep proxy logic centralized.

// src/main.rs
let cli = Cli::try_parse()?;   // Auto-generated Commands enum

match cli.command {
    Commands::Ls { args } => ls::run(&args, cli.verbose)?,
    Commands::Read { files, level, .. } => {
        // ... read module handles per-file filtering
    }
    // ... additional sub-commands
}

Each sub-command module in src/cmds/*/*.rs (such as ls.rs, git.rs, or cargo_cmd.rs) implements a run function that constructs a std::process::Command and passes it to the execution core. The helper function resolved_command in src/core/utils.rs resolves executables in PATH or validates full paths before execution begins.

Execution Core and Run Modes

The core::runner module orchestrates how commands execute and how their output is captured. Located in src/core/runner.rs, the run function accepts a RunMode that determines the I/O strategy.

// src/core/runner.rs
pub fn run(
    mut cmd: Command,
    tool_name: &str,
    args_display: &str,
    mode: RunMode<'_>,
    opts: RunOptions<'_>,
) -> Result<i32> {
    let timer = tracking::TimedExecution::start();

    match mode {
        RunMode::Filtered(filter_fn) => {
            let result = stream::run_streaming(&mut cmd,
                StdinMode::Null,
                FilterMode::CaptureOnly)?;
            // Apply filter_fn, optional tee, and track metrics
        }
        RunMode::Streamed(filter) => { /* ... */ }
        RunMode::Passthrough => { /* ... */ }
    }
}

RunMode variants control the execution flow:

  • Filtered: Captures complete output, applies a filter function, then prints
  • Streamed: Processes data through a StreamFilter while it arrives
  • Passthrough: Inherits the child process's stdio directly without capture

The core::stream module handles low-level I/O through run_streaming, which returns a StreamResult containing raw, raw_stdout, raw_stderr, and exit status. When commands fail, core::tee can display raw output alongside helpful LLM hints.

Filter Pipeline Architecture

The filter system in src/core/filter.rs implements a strategy pattern that processes captured text based on language-specific rules and user-selected aggressiveness levels.

Filter Strategies

Three concrete implementations of the FilterStrategy trait define the reduction levels:

// src/core/filter.rs
pub trait FilterStrategy {
    fn filter(&self, content: &str, lang: &Language) -> String;
}
Strategy Behavior
NoFilter Returns input unchanged (clone only)
MinimalFilter Strips line and block comments, removes empty lines, collapses whitespace, preserves documentation strings
AggressiveFilter Extends MinimalFilter by preserving only imports and type signatures, replacing implementation bodies with placeholder comments

The function core::filter::get_filter(level) instantiates the appropriate strategy based on CLI flags (--level minimal|aggressive|none) or TOML configuration.

Text Processing and Truncation

Language detection occurs via Language::from_extension, which drives the CommentPatterns system for per-language comment delimiters. After filtering, smart_truncate limits output to a maximum line count while preserving critical signatures and import statements.

use rtk::core::filter::{get_filter, FilterLevel, Language};

let src = r#"//! Example crate
fn main() {
    // Print hello
    println!("Hello");
}"#;

let filter = get_filter(FilterLevel::Minimal);
let out = filter.filter(src, &Language::Rust);
// Result: fn main() { println!("Hello"); }

TOML-Based Custom Filters

Before applying generic strategies, RTK checks src/core/toml_filter.rs for command-specific rules in .rtk.toml files. If a regex pattern matches the current command, the custom filter runs instead of the standard pipeline.


# .rtk.toml

[[filter]]
command = "^cargo test"
filter = """
    grep -E "test result: .*"
"""

The find_matching_filter function locates applicable rules, while apply_filter executes user-defined shell pipelines on captured output.

Complete Request Flow

The architecture follows a deterministic path from input to filtered output:

  1. Parse: main.rs uses Clap to generate the Commands enum
  2. Build: Sub-command modules construct std::process::Command objects
  3. Execute: core::runner::run selects the RunMode and invokes core::stream::run_streaming
  4. Filter: get_filter(level) returns a FilterStrategy applied to the StreamResult
  5. Output: core::tee optionally displays results with telemetry tracking via core::tracking

If command parsing fails, run_fallback in main.rs attempts TOML filter matching before falling back to passthrough execution.

Code Examples

Running a Filtered Directory Listing

rtk ls -la

Behind the scenes, Commands::Ls routes to ls::run, which builds the command and calls runner::run_filtered with a closure invoking compact_ls. The filter transforms verbose ls output into a compact directory tree with summaries.

Aggressive Filtering with Line Limits

rtk read src/index.ts --level aggressive --max-lines 50

This command:

  1. Loads the TypeScript file and detects Language::TypeScript
  2. Selects AggressiveFilter via get_filter
  3. Removes function bodies, keeping only signatures and imports
  4. Applies smart_truncate to limit output to 50 lines

Result:

export function fetchData(url: string): Promise<any> {
    // ... implementation
}

Implementing a Custom Filter Strategy

// src/core/filter.rs
pub struct CustomFilter;

impl FilterStrategy for CustomFilter {
    fn filter(&self, content: &str, _lang: &Language) -> String {
        content.lines()
            .filter(|line| !line.trim().is_empty())
            .take(100)
            .collect::<Vec<_>>()
            .join("\n")
    }
}

Summary

  • Command routing in src/main.rs uses Clap enums to dispatch to command modules that build std::process::Command objects
  • Execution core in src/core/runner.rs supports three run modes: Filtered, Streamed, and Passthrough
  • Filter pipeline implements a strategy pattern with NoFilter, MinimalFilter, and AggressiveFilter strategies based on the FilterStrategy trait
  • Language detection via file extensions drives comment stripping logic in src/core/filter.rs
  • TOML overrides in src/core/toml_filter.rs allow project-specific filter rules that supersede default strategies
  • Telemetry tracking records execution time and token savings for analytics via core::tracking

Frequently Asked Questions

How does RTK decide which filter strategy to apply?

RTK determines the filter strategy through a hierarchy of sources. First, it checks for a matching rule in .rtk.toml via src/core/toml_filter.rs. If no TOML rule exists, it uses the --level CLI flag (minimal, aggressive, or none) passed to get_filter(level) in src/core/filter.rs. Finally, if no flag is provided, it falls back to the default level specified in the global configuration.

What is the difference between Filtered and Streamed run modes?

Filtered mode captures the complete output into memory using run_streaming with FilterMode::CaptureOnly, then applies the filter function to the entire buffer before printing. Streamed mode processes data chunk-by-chunk through a StreamFilter as it arrives from the child process, enabling real-time display while still reducing tokens. Filtered mode suits command outputs that need context-aware reduction, while Streamed mode handles long-running processes.

Can I extend RTK with custom filtering logic for specific file types?

Yes, you can extend the filter pipeline by implementing the FilterStrategy trait in src/core/filter.rs and adding your implementation to the get_filter function's match arms. You would also need to update Language::from_extension or add custom detection logic if your file type isn't already supported. Alternatively, use the TOML-based system in src/core/toml_filter.rs to define shell-based filters for specific commands without recompiling.

Where does RTK handle command execution failures and output?

Execution failure handling resides in src/core/runner.rs and src/core/tee.rs. When a command exits with a non-zero status, the runner checks RunOptions flags like skip_filter_on_failure. If configured, it invokes the tee functionality to display raw output alongside helpful hints for the LLM, ensuring the model receives context about why the command failed even when filtering is active.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →