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

> Discover how RTK tracks token savings with SQLite in core/tracking.rs. Learn about persisting command executions, calculating savings, and aggregating stats via SQL queries.

- Repository: [rtk-ai/rtk](https://github.com/rtk-ai/rtk)
- Tags: internals
- Published: 2026-04-24

---

**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`](https://github.com/rtk-ai/rtk/blob/main/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`](https://github.com/rtk-ai/rtk/blob/main/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

```rust
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`](https://github.com/rtk-ai/rtk/blob/main/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`](https://github.com/rtk-ai/rtk/blob/main/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`](https://github.com/rtk-ai/rtk/blob/main/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`](https://github.com/rtk-ai/rtk/blob/main/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.