Skip to content

Latest commit

 

History

4 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

mcp-trace-store

An append-only, JSONL-backed store that records one durable trace per MQO interaction — the question, how it bound, how well it grounded, and what executing it returned.

Why it exists

You cannot improve a query pipeline you cannot replay. To mine the gaps where MQO mis-binds, to score grounding quality over time, and to export training data, you need every interaction on disk in a form you can scan months later — not metrics aggregated at write time and thrown away. That argues for the simplest durable thing that survives a crash: a flat JSONL file, one record per line, appended and never mutated. This crate is that store, and nothing more. No database, no network, no async — a single file the rest of the toolchain reads.

The constraint that shapes the design is that the store is write-heavy and read-rarely, and writes happen on the live query path. So appends must be cheap and crash-safe, and a single corrupt line — the cost of a process killed mid-write — must never block a later read.

Install

A library, not a binary. Add it as a dependency:

[dependencies]
mcp-trace-store = { git = "https://github.com/joeyen-atscale/mcp-trace-store" }

Build and run the test suite:

cargo test                  # all acceptance criteria
cargo test --release        # also enforces the AC7 timing SLO (see below)

Quickstart

Open a store, append a record, scan it back through a filter:

use mcp_trace_store::{
    BindOutcome, ExecuteOutcome, QualitySignals,
    TraceFilter, TraceRecord, TraceStore, TraceStoreConfig,
};

let cfg = TraceStoreConfig::new("~/.local/share/mcp-traces/trace.jsonl");
let store = TraceStore::new(cfg)?;   // creates parent dirs if needed

// record_id and timestamp_ms are filled in at write time
let record = TraceRecord::new(
    "session-abc",
    serde_json::json!({ "entity": "Revenue" }),
    BindOutcome::Success,
    ExecuteOutcome::Success { row_count: 10, result_empty: false },
    QualitySignals {
        first_attempt_bind: true,
        bind_attempt_count: 1,
        total_latency_ms: 42,
        tokens_used: None,
    },
);
store.append(record)?;

// Read back only the first-attempt successes
let filter = TraceFilter { first_attempt_only: true, ..TraceFilter::default() };
let hits = store.scan(&filter)?;

scan returns records oldest-first across every rotation fragment. count returns the total record count with no filter applied.

How it works

One record per interaction. A TraceRecord captures the MQO JSON, the bind outcome, an optional grounding score and band, the execute result, and a small set of quality signals (first-attempt bind, attempt count, latency, tokens). Optional fields that weren't recorded deserialize as None; unknown fields in older or newer records are ignored, so the schema can evolve without breaking the corpus.

Appends are atomic. Each record is serialized in full, then written with O_APPEND in a single write_all. POSIX guarantees an O_APPEND write up to PIPE_BUF bytes is atomic against concurrent writers. A process killed mid-write leaves at most one partial line at the tail; every prior record is intact.

Corrupt lines are skipped, not fatal. scan reads line by line; a line that fails to parse is logged to stderr and skipped. One bad write never costs you the rest of the corpus.

Rotation by size. When the active file exceeds rotate_at_bytes (default 50 MB), it shifts to .1, the old .1 to .2, and so on. The next append starts a fresh active file. scan walks all fragments oldest-first — highest-numbered first, then the active file.

<path>          ← active file (newest writes)
<path>.1        ← previous rotation
<path>.2        ← two rotations ago (oldest)

Core types

Type Purpose
TraceRecord One persisted interaction
BindOutcome Success / Ambiguous / NotFound / Error(String)
GroundingBand Grounded / Partial / Ungroundable
ExecuteOutcome Success { row_count, result_empty } / Error(String) / Skipped
QualitySignals first_attempt_bind, bind_attempt_count, total_latency_ms, tokens_used
TraceFilter time range, grounding band, first-attempt, cluster, session, limit
TraceStore append, scan, count, rotate_if_needed

Where it fits

This is the corpus underneath the MQO toolchain. The store collects what the gap miner, quality scorer, and fine-tuning exporter all read; grounding scores come from mcp-grounding-eval. It is deliberately the least clever piece — a durable file the rest of the chain builds on.

Acceptance criteria

All seven ACs run under cargo test. AC7's timing bound is asserted only in --release (a SHOULD, not a MUST); debug builds run the work but skip the assertion.

AC Description
AC1 append creates the file and writes valid JSON lines
AC2 scan filters by grounding_band correctly
AC3 scan with first_attempt_only filters correctly
AC4 A corrupt or partial line does not break scan
AC5 Rotation works; scan reads all fragments in order
AC6 Missing optional fields → None; unknown fields ignored
AC7 10k append + scan completes under 2s (release build)

Status

Early — version 0.1.0. The library is complete against the acceptance criteria above and compiles clean; the surface is a flat-file store with no public API beyond the types listed here.

License

MIT OR Apache-2.0

About

Append-only JSONL store, one durable record per MQO interaction: bind, grounding, execute, quality signals

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages