Gittable version control for AI-agent trajectories

TrajFS

Make millions of AI-agent trajectory files gittable.
Faster for Git. Friendly to agents. Still files for humans.

AI-agent runs produce millions of tiny, duplicate-heavy files, and Git operations on them take hours. TrajFS packs a run into one compact, queryable store without changing the agents. Git handles the store in seconds, analysis agents query it, and humans still open it as files.

Terminal recording: a million-file agent run, git taking minutes on it, then traj pack and commit in seconds, and the store read back by an agent and by a human through a mount

Recorded on a synthetic 1,000,000-file run: 40 rounds of a coding agent, 5% distinct content, the rest byte-identical checkpoints. Every number shown is measured; demo/ in the repository regenerates it.

Faster for Git

Commit a store, not a million paths

A run becomes a handful of files: content-addressed packs and a columnar catalog. Status, clone, and checkout stop paying per trajectory file.

Friendly to agents

Query, don't walk

traj grep, traj find, and SQL over parsed event tables answer "which round first hit a Traceback?" without touching a million inodes. A shipped skill teaches coding agents the verbs.

Still files for humans

Mount it, open it in VS Code

A read-only FUSE mount serves the store as ordinary files. New batches appear without remounting; traj extract gives writable copies.

Generating agents write files; TrajFS packs them; analysis agents, Git, and humans read the store through their own interfaces

Quick start

Archive one task in three steps. The example is a coding agent that worked in rounds under /data/tasks/task-a; TrajFS does not require this layout.

1

Pack the raw tree into a store

The source stays where it is. The adapter parses the runner's event streams into queryable tables; build products are excluded by default. Run it again later and only what changed is appended.

traj pack /data/tasks/task-a --out stores/task-a.trajstore --adapter copilot-cli
2

Commit the store, not the raw files

A million-path run becomes a handful of files that Git handles in seconds.

traj commit --push stores/task-a.trajstore
3

Read it back

No Git checkout, no extraction. traj grep searches each distinct content once, so duplicate checkpoints cost nothing extra.

export TRAJ_STORE="$PWD/stores/task-a.trajstore"

traj ls -l rounds/round-0007/reviewer                    # list, like ls
traj cat rounds/round-0007/reviewer/review.json          # read one file
traj find --name review.json                             # every review, every round
traj grep -l -e Traceback --name '*.stderr'              # which logs failed
traj sql "SELECT tool_name, count(*) AS calls FROM events
          WHERE type = 'tool.execution_complete' GROUP BY 1 ORDER BY 2 DESC"
traj verify --deep                                       # every stored byte matches its hash

Agents use the same verbs

Export a skill and an AGENTS.md section into your repository, so Claude Code, Codex, or Copilot reach for traj grep, traj sql, and traj cat instead of walking a million files.

traj skill export --out .

Humans get files

On Linux, mount the store read-only and open it in a terminal or VS Code. Newly packed batches show up without remounting.

traj mount ~/traj-mnt/task-a --daemon
ls ~/traj-mnt/task-a/rounds/round-0007/reviewer
code -r ~/traj-mnt/task-a
traj umount ~/traj-mnt/task-a

How it works

TrajFS separates what the bytes are from where they appear. That single decision is what makes millions of duplicate-heavy files cheap.

Content-addressed packs

Every file is hashed with SHA-256 and stored once, in Zstandard-compressed pack frames. A checkpoint that repeats yesterday's workspace adds paths, not bytes.

A columnar catalog

Paths, directory summaries, exclusions, and the hash-to-pack index are Parquet segments. Listing or finding is a column scan, and the same segments are the tables behind traj sql.

Published batches

Each pack appends a batch and publishes it in the manifest at the end. Readers only ever see complete batches; an interrupted pack leaves nothing visible.

Derived event tables

Adapters parse Copilot CLI, Codex CLI, Claude Code, generic JSONL, or a TOML-described layout into event tables that embedded DuckDB queries. text(sha) reaches stored content from SQL.

A read-only file projection

traj mount serves the catalog through FUSE with a bounded memory budget, so editors, diff tools, and shell scripts see ordinary files without extraction.

Git sees only stores

traj commit stages the store, a pre-commit hook refuses raw run trees and oversized artifacts, and every generated file stays under a size Git hosts accept.

A store is a portable directory

task-a.trajstore/
  MANIFEST.json                published batches and artifact inventory
  catalog/*.parquet            paths, directory summaries, exclusions
  packs/*.pack                 compressed content
  packs/index-*.parquet        hash-to-pack locations
  derived/<adapter>/*.parquet  rebuildable event tables

Layers

LayerTechnology
CLI and ingestionRust, Clap, Rayon for parallel hashing and compression
File contentSHA-256-addressed blobs in Zstandard packs
Catalog and eventsApache Arrow / Parquet
QueriesEmbedded DuckDB over the Parquet files
File accessOptional Linux FUSE through fuser
Runner integrationTOML adapters and rules, Git hooks, exported agent skill

Benchmarks

Synthetic data anyone can regenerate: a coding agent's task directory checkpointed every round, so only about 5% of a million paths have distinct contents. Whole-process wall time, warm cache, on a 40-core Xeon with ext4. Tables and reproduction commands.

30 min
to git add one more run
when the repository already tracks 2.7 M paths
11.5 s
traj pack of the same run
whatever the repository holds
263x
faster git clone
fresh repository, 1M files: 0.2 s instead of 59 s
38x
smaller than the raw tree
74 MB store for 2.8 GB of files
git add of one million-file run takes 118 seconds into an empty repository, 11 minutes with 1.3 million paths already tracked, 30 minutes with 2.7 million, extrapolating to hours; traj pack stays at 11.5 seconds
The case that hurts: a repository that already holds runs. Git keeps every tracked path in one index, so adding the next run costs per path already there. The same million-file run: 118 s into an empty repository, 11 min with 1.3 M paths tracked, 30 min with 2.7 M; a quadratic fit reaches about 1.5 h at 5 M and 6 h at 11 M. traj pack stays at 11.5 s. A cold disk adds a floor for any tool, about 80 min for the first read of a million files. Measurements and method.
Bar chart: git on the raw tree versus TrajFS for add+commit, status, push, clone, and checkout at 100k, 300k, and 1M files
Git's best case: a fresh repository, warm cache. Committing a million raw files works, but every later operation pays for them again: status walks the tree, clone and checkout materialize every file, push sends every object. The store is a handful of files.
Bar chart of raw tree, packed git objects, and TrajFS store sizes at three run sizes
Git deduplicates identical blobs too, so the honest comparison is against Git's packed objects: the store is 36 to 39x smaller than the tree and about the size of Git's own object store on this highly compressible text.
Horizontal bar chart of read latency for raw files, traj commands, and the FUSE mount
Point reads through the CLI cost a process start; the mount matches the raw filesystem for ls and cat. Whole-tree scans should use traj grep, traj find, or SQL, which read the catalog and each distinct content once.

Install

TrajFS runs on Linux. DuckDB is bundled; there is no service to install. Mounting needs FUSE, everything else works without it.

1. Let an agent do it

Paste this into Claude Code, Codex, or Copilot CLI in the repository where you keep experiments.

Install TrajFS from https://github.com/jingliu9/TrajFS: install the Rust toolchain if missing (rustup), the build prerequisites (build-essential, pkg-config, fuse3 on Debian/Ubuntu), then `cargo install --locked --git https://github.com/jingliu9/TrajFS traj`. Verify with `traj --help`. Then run `traj skill export --out .` here so agents in this repo use traj to read trajectories, and show me the three commands from its README quick start adapted to my run directory.

2. Manual

The first build compiles the bundled DuckDB and takes a few minutes. Add --no-default-features for a smaller binary with neither SQL nor mounting.

# Debian / Ubuntu prerequisites (fuse3 only for mounts)
sudo apt-get update && sudo apt-get install -y build-essential pkg-config fuse3
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh   # if you have no Rust toolchain

cargo install --locked --git https://github.com/jingliu9/TrajFS traj
traj --help

Documentation

Everything past the quick start lives in the repository.