Architecture
This page is for engineers evaluating Travsr or contributing to it. It explains how every component fits together.
Overview
git commitCrate structure
Travsr is a Rust workspace. The dependency graph is strictly acyclic:
travsr-core Foundation. Node, Edge, EdgeKind, VName (Kythe address),
NodeId (BLAKE3), and ppr_weight(). No internal deps.
travsr-error Shared error taxonomy (StoreError, IndexError,
TravsrError). No internal deps.
travsr-ipc Control-plane transport (Unix socket / Windows named
pipe) between the CLI and the running daemon. No
internal deps.
travsr-plugin-protocol Depends on: core, error
Plugin trait, wire message types, length-prefixed frame
codec for sidecars (language and embed).
travsr-plugin-sdk Depends on: plugin-protocol, core
The harness a sidecar binary links against to implement
the protocol.
travsr-analysis Depends on: core
Phase A. Tree-sitter parsers for every language, the AST
"skeleton" builder used for embedding text, snippet
extraction, and data-format parsing (JSON, YAML, TOML,
XML).
travsr-store Depends on: core, error
SQLite (WAL) backend: nodes/edges/files/meta tables, FTS5
search, migrations, RBAC columns.
travsr-indexer Depends on: core, analysis, error
Orchestrates indexing: Phase A parsing, Phase B LSIF/SCIP
runners, the SCIP unifier, the sandbox, and file hashing.
travsr-retrieval Depends on: core, error, store
Algorithms: BFS, PPR (ppr / ppr_weighted), PCST, k-core,
BM25, 0-1 knapsack, and RBAC edge filters.
travsr-plugin-host Depends on: plugin-protocol, core, error, indexer,
analysis
Owns the trust boundary. Hosts native and sandboxed
Phase B language analyzers plus the embedding
sidecar/supervisor.
travsr-mcp Depends on: core, error, analysis, retrieval, store,
plugin-host
The Model Context Protocol server (stdio + SSE). The only
external interface.
travsr-daemon Depends on: core, ipc, mcp, indexer, retrieval, store,
plugin-host, analysis
Long-running orchestrator: git hook, file watcher,
incremental reindex, Phase B scheduler, query cache.
travsr-cli Depends on: core, ipc, indexer, daemon, mcp, store,
retrieval, plugin-sdk, plugin-host
The travsr binary: init, ask, graph, mcp, lang, embed,
synonym, serve, migrate, and more.
The edges above are the real ones read from each crate's Cargo.toml, not an
idealized summary. The "no cycles" rule still holds; the web is just richer than a
single spine (for example travsr-mcp uses store, analysis, and plugin-host
directly, not only retrieval).
Sidecar packages live under packages/: travsr-npm (the @travsr.com/travsr
binary wrapper published to npm), travsr-vscode (the VS Code extension),
travsr-lsif-ts (the built-in TypeScript/JavaScript LSIF emitter), and
travsr-lsif-py (the Python LSIF emitter).
Indexing pipeline
1. Tree-sitter (structural layer)
Tree-sitter parses every source file using language grammars into an AST without a compilation step. Travsr extracts:
- Import edges:
import X from Y/use crate::X/from X import Y - Definition nodes: functions, classes, structs, enums, traits, interfaces
- Call expression nodes: unresolved call sites (resolved by LSIF)
- Inheritance edges:
extends,implements,struct ... : Trait
Tree-sitter handles all languages at the structural level. LSIF provides deeper semantic information for supported compilers.
2. Semantic enrichment layer (Phase B)
Tree-sitter is Phase A. A second per-language pass — Phase B — resolves call targets, type references, and cross-module edges, then merges them into the same graph. Phase B is hosted by travsr-plugin-host: built-in languages run an in-process native analyzer, while the rest run an external LSIF/SCIP tool in a sandboxed subprocess installed via travsr lang install.
Built-in (native, no install, runs automatically):
| Language | Semantic source | Edges added |
|---|---|---|
| TypeScript / JavaScript | travsr-lsif-ts (LSIF) | Resolved call targets, type references, DFG |
| Rust | rust-analyzer (LSIF) | Resolved call targets, trait implementations |
| Python | native pass (scip-python optional upgrade) | Resolved call targets, type inference |
Installable (Standard sandbox — no network, no dependency downloads):
| Language | Underlying tool |
|---|---|
| Go | scip-go |
| Ruby | scip-ruby |
| PHP | scip-php |
| Swift | travsr-swift-index-emitter |
| Objective-C (macOS) | travsr-lang-objectivec |
| C / C++ | scip-clang |
| Dart | travsr-dart-index-emitter |
Installable (Elevated sandbox — build tool fetches dependencies over an approved host allowlist):
| Language | Underlying tool |
|---|---|
| Java | scip-java |
| Kotlin | kotlin-language-server |
| C# | scip-dotnet |
| Scala | sbt + scip-scala |
Notes:
- Phase B is gated per language against
~/.travsr/lang.toml; a language present in the repo but not registered stays at the Tree-sitter structural graph. - If an external tool is absent from
PATH, the pass is skipped and Travsr falls back to the Phase A structural graph rather than failing the index.
Semantic edges go into the same graph as Tree-sitter edges. Nodes are deduplicated by VName.
3. Kythe VNames: node identity
Every node is identified by a Kythe VName:
{
"corpus": "github.com/org/repo",
"root": "",
"path": "src/auth/jwt.ts",
"language": "typescript",
"signature": "#sign(string,Claims):string"
}
VNames are content-addressed and stable across renames (if the signature is preserved). Cross-repo edges reference VNames in other corpora.
Storage
MVP: SQLite + WAL
The graph is stored in .travsr/graph.db (SQLite). WAL mode enables concurrent reads during reindex. SQLite is the default backend and targets everyday repository sizes.
Schema highlights:
CREATE TABLE nodes (
id TEXT PRIMARY KEY, -- Kythe VName (JSON-serialised)
kind TEXT NOT NULL, -- 'function' | 'class' | 'module' | ...
language TEXT NOT NULL,
path TEXT NOT NULL,
span_start INTEGER,
span_end INTEGER
);
CREATE TABLE edges (
src TEXT NOT NULL REFERENCES nodes(id),
dst TEXT NOT NULL REFERENCES nodes(id),
kind TEXT NOT NULL -- 'calls' | 'imports' | 'inherits' | 'types' | ...
);
CREATE INDEX idx_edges_src ON edges(src);
CREATE INDEX idx_edges_dst ON edges(dst);
Retrieval algorithms
BFS (MVP)
Breadth-first search from a seed set of nodes to depth 3. Fast, simple, correct for direct neighbours. Used by get_dependencies, get_callers, and get_blast_radius.
Personalized PageRank (PPR)
PPR scores all nodes in the graph relative to a seed set. High-scored nodes are strongly connected to the seed via many short paths. Used by get_context to rank which parts of the graph are most relevant to a query.
Prize-Collecting Steiner Tree (PCST)
PCST finds the minimum connected subgraph that includes a set of terminal nodes (prize nodes) while minimising the total edge weight. Used by get_execution_path and get_context to extract the token-budget-optimal subgraph. Implementation follows the GW algorithm.
0-1 Knapsack (token budget)
After PPR and PCST produce a candidate subgraph, the knapsack solver selects which nodes to include in the final MCP response given a token budget. Each node has a weight (token count) and a value (PPR score). The solver maximises total value subject to the budget constraint.
MCP transports
stdio (local)
The default transport. The MCP server reads JSON-RPC from stdin and writes to stdout. Claude Desktop spawns travsr mcp --stdio as a subprocess.
SSE (cloud)
For Travsr Cloud, the MCP server runs as an HTTP server with Server-Sent Events. Clients connect to mcp.travsr.com via OAuth and receive a persistent SSE stream.
Incremental reindex
The git post-commit hook calls travsr hook-run --from-hook. The delta indexer:
- Reads the diff between
HEADandHEAD~1 - Re-parses only changed files
- Computes the SHA-256 hash of each changed file's AST
- Writes only changed nodes and edges to the graph
- Updates the edge index
Because only the changed files are re-parsed, an incremental reindex touches a small fraction of the graph rather than rebuilding it.