How RTK Tracks Token Savings Using SQLite in core/tracking.rs

RTK tracks token savings by persisting every command execution to a local SQLite database at ~/.local/share/rtk/tracking.db, calculating savings via the record() method, and aggregating statistics through filtered SQL queries.

The rtk-ai/rtk repository implements a self-contained telemetry system that monitors command-line efficiency. By leveraging SQLite in src/core/tracking.rs, RTK persistently logs execution metadata and derives token-saving statistics to power the rtk gain reporting interface.

Database Initialization and Schema

Resolving the Database Path

The Tracker struct initializes its storage layer through get_db_path(), which resolves the database location in a strict priority order. According to the source code in src/core/tracking.rs (lines 55-71), the system checks:

  1. The RTK_DB_PATH environment variable
  2. The configuration file via Config::load()
  3. The default platform-specific directory (~/.local/share/rtk/tracking.db on Linux)

This resolution strategy ensures developers can override storage locations without recompiling the binary.

Creating the SQLite Schema

When Tracker::new() executes (lines 49-76), it opens or creates the SQLite database and executes a series of schema definitions. The primary commands table stores:

  • Timestamps in UTC
  • Original command strings versus RTK-wrapped equivalents
  • Input and output token counts
  • Calculated savings percentage
  • Execution time in milliseconds (exec_time_ms)
  • Project path for multi-project filtering

The initialization routine includes migrations that add exec_time_ms and project_path columns if they are missing from existing databases, ensuring backward compatibility.

Recording Command Executions

The record() method (lines 52-84) serves as the core persistence engine. It receives the original command, its RTK counterpart, estimated input and output token counts, and elapsed time in milliseconds.

The method performs two critical calculations:

  • Absolute savings: saved = input_tokens - output_tokens
  • Percentage savings: pct = (saved / input_tokens) * 100 (or 0 if input_tokens is 0)

After computing these metrics, record() inserts a row with the current UTC timestamp and the current working directory as project_path. Immediately following insertion, the method prunes records older than the retention window—90 days by default—to prevent database bloat.

Querying and Aggregating Statistics

RTK exposes analytics through helper methods that prepare parameterized SQLite statements. The get_summary_filtered() function (lines 104-140) implements the core aggregation logic, using SQL SUM, AVG, and COUNT operations to compute totals and averages.

These queries respect project-scoped filtering via the project_path column, enabling per-directory analytics. Additional methods like get_by_command(), get_by_day(), and tokens_saved_30d() provide specialized breakdowns for daily, weekly, and monthly reports consumed by the rtk gain CLI.

Automatic Tracking with TimedExecution

For developer convenience, RTK provides the TimedExecution struct, which abstracts the entire tracking lifecycle. When TimedExecution::start() is called, it captures Instant::now() to measure elapsed time.

The struct implements estimate_tokens() (lines 120-123), a lightweight heuristic that approximates 1 token per 4 characters of text. This avoids expensive API calls while providing sufficiently accurate telemetry. Calling track() or track_passthrough() instantiates a Tracker on-the-fly and persists records without requiring manual database management.

Code Examples

use rtk::tracking::{Tracker, TimedExecution};

/// Manual recording of a command execution
fn manual_record() -> anyhow::Result<()> {
    let tracker = Tracker::new()?;                 // opens/creates the DB
    tracker.record(
        "git status",                // original command
        "rtk git status",            // RTK-wrapped command
        1200,                         // estimated input tokens
        300,                          // estimated output tokens
        45,                           // execution time in ms
    )?;
    Ok(())
}

/// Automatic tracking with built-in timer
fn timed_example() -> anyhow::Result<()> {
    let timer = TimedExecution::start();          // start stopwatch
    
    // Execute command and capture outputs
    let raw_output = std::fs::read_to_string("git_status.txt")?;
    let filtered_out = std::fs::read_to_string("rtk_status.txt")?;

    // Automatically estimates tokens and records execution
    timer.track("git status", "rtk git status", &raw_output, &filtered_out);
    Ok(())
}

/// Retrieve aggregated savings statistics
fn print_summary() -> anyhow::Result<()> {
    let tracker = Tracker::new()?;
    let summary = tracker.get_summary()?;          // aggregates all records
    println!(
        "Saved {} tokens (average {:.1}% savings over {} commands)",
        summary.total_saved,
        summary.avg_savings_pct,
        summary.total_commands,
    );
    Ok(())
}

Summary

  • RTK stores execution data in ~/.local/share/rtk/tracking.db using the Tracker struct defined in src/core/tracking.rs.
  • The record() method computes token savings as input_tokens - output_tokens and derives percentage savings automatically.
  • Automatic pruning removes records older than 90 days (configurable via DEFAULT_HISTORY_DAYS in src/core/constants.rs).
  • The TimedExecution helper provides zero-configuration tracking with heuristic token estimation (~4 characters per token).
  • Aggregation queries in get_summary_filtered() enable project-scoped analytics for the rtk gain CLI.

Frequently Asked Questions

Where does RTK store the token tracking database?

By default, RTK creates the database at ~/.local/share/rtk/tracking.db on Linux systems. The get_db_path() function checks the RTK_DB_PATH environment variable first, then falls back to the tracking.database_path value from Config::load(), and finally uses the platform-specific default directory.

How does RTK calculate token savings percentages?

The record() method in src/core/tracking.rs calculates absolute savings as input_tokens - output_tokens. It then derives the percentage using (saved / input_tokens) * 100, defaulting to 0 when input_tokens equals zero to prevent division errors.

What is the retention period for command history in RTK?

RTK automatically prunes records older than 90 days after each insertion. This retention window is controlled by DEFAULT_HISTORY_DAYS defined in src/core/constants.rs, ensuring the database remains compact while preserving recent analytics.

How does the TimedExecution struct estimate token counts?

The estimate_tokens() method (lines 120-123) implements a simple heuristic where approximately 4 characters equal 1 token. This inexpensive calculation avoids API latency while providing sufficient accuracy for telemetry aggregation.

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 →