Skip to main content

Architecture

This page is for engineers evaluating Travsr or contributing to it. It explains how every component fits together.

Overview

git commit
post-commit hook
travsr-daemon
scheduler
file watcher
changed files
Tree-sitter
structural AST
+
LSIF compiler
semantic edges
nodes + edges
travsr-store
SQLite WAL.travsr/graph.db
Kythe VNamesnode identity
graph query
travsr-retrieval
BFSget_dependencies · get_callers
PPRget_context ranking
PCSTget_execution_path
token budget
0-1 Knapsack
MCP tool response
travsr-mcp
stdiolocal daemon
SSETravsr Cloud
Local MCP client
Claude Desktop · Cursor · any IDE agent
Remote MCP client
via Travsr Cloud

Crate 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):

LanguageSemantic sourceEdges added
TypeScript / JavaScripttravsr-lsif-ts (LSIF)Resolved call targets, type references, DFG
Rustrust-analyzer (LSIF)Resolved call targets, trait implementations
Pythonnative pass (scip-python optional upgrade)Resolved call targets, type inference

Installable (Standard sandbox — no network, no dependency downloads):

LanguageUnderlying tool
Goscip-go
Rubyscip-ruby
PHPscip-php
Swifttravsr-swift-index-emitter
Objective-C (macOS)travsr-lang-objectivec
C / C++scip-clang
Darttravsr-dart-index-emitter

Installable (Elevated sandbox — build tool fetches dependencies over an approved host allowlist):

LanguageUnderlying tool
Javascip-java
Kotlinkotlin-language-server
C#scip-dotnet
Scalasbt + 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:

  1. Reads the diff between HEAD and HEAD~1
  2. Re-parses only changed files
  3. Computes the SHA-256 hash of each changed file's AST
  4. Writes only changed nodes and edges to the graph
  5. 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.