A runtime-augmented code knowledge graph for Python codebases.
livegraph builds a graph of a Python project in Neo4j, fusing static
tree-sitter analysis with runtime observation of the project's pytest
suite. Every CALLS edge is tagged with provenance (static / runtime)
so the graph reflects what the code does, not just what it looks like.
docker compose up -d
cp .env.example .env
pip install -e ".[dev]"
livegraph build /path/to/python/project
See docs/superpowers/specs/ for the design.
After livegraph build /path/to/project, expose the graph to a
coding agent over MCP:
LIVEGRAPH_PROJECT=myproject livegraph mcpThe server runs over stdio. Configure your MCP host (Claude Code, Cursor)
to launch it. Example .mcp.json for Claude Code:
{
"mcpServers": {
"livegraph-myproject": {
"command": "livegraph",
"args": ["mcp", "--project", "myproject"],
"env": {
"NEO4J_URI": "bolt://localhost:7687",
"NEO4J_USER": "neo4j",
"NEO4J_PASSWORD": "livegraph-local"
}
}
}
}The server exposes 10 read-only tools, including the two that no purely static code-graph tool can run:
| Tool | What it answers |
|---|---|
find_symbol(query) |
Symbols matching a name |
get_source(qualified_name) |
Source + coverage for a symbol |
find_callers(qualified_name, provenance) |
Who calls this — static/runtime/any |
find_callees(qualified_name, provenance) |
What this calls — same filter |
runtime_only_calls(file?) |
Calls runtime caught that static missed |
dead_static_calls(file?) |
Predicted calls that never executed |
tests_for(qualified_name) |
Tests that cover a symbol |
untested_symbols(file?, kind?) |
Functions/methods no test exercised |
imports(file, direction) |
File-level import edges |
graph_status() |
Aggregate counts; call this first |
change_impact(diff, max_depth, provenance, limit) |
Given a git diff: changed symbols, transitive callers with per-edge provenance, and the tests to run |
Acceptance test: with the server registered, ask your agent
"show me the dynamic-dispatch calls in this project". A working
integration finds and calls runtime_only_calls.
After the first livegraph build, subsequent edits don't require a full rebuild.
Run:
LIVEGRAPH_PROJECT=myproject livegraph updateThe command walks the project, computes SHA-256 hashes of every .py file,
compares against the hashes stored on File nodes, and re-ingests only the
files whose content actually changed. Deletions are removed from the graph;
new files are added; unchanged files are skipped.
Runtime data (from livegraph trace) is preserved on changed-file symbols
but flagged runtime_stale=true. CALLS edges that were already verified at
runtime survive incremental re-ingest. A subsequent livegraph trace clears
the stale flag on every symbol that appears in the new observations.
Use --dry-run to preview the classification without writing to the graph:
livegraph update --dry-runKnown limitation: a function renamed in file A while file B still calls it
by the old name leaves an orphaned CALLS edge until file B is also touched.
Run a full livegraph build to fully recover.
For questions the 11 structured tools don't cover, livegraph exposes two "open" MCP tools so the agent can compose Cypher itself:
-
describe_schema()— returns the graph's labels, edges, properties, safety rules, the configured project name, and six example queries. Call it once per session. -
run_cypher(query, params?, row_limit?, timeout_seconds?)— runs a read-only Cypher query. Belt-and-suspenders safety:- Lexical pre-scan rejects writes (CREATE, MERGE, DELETE, SET, REMOVE, DROP, LOAD CSV, USING PERIODIC COMMIT, CALL) with a friendly error.
- Neo4j READ transaction enforces read mode at the engine.
- Per-transaction timeout (default 30s).
- Server-side row truncation (default 1000 rows) with a
truncatedflag on the response. $projectparameter is auto-injected so queries can use the configured project name symbolically.
Example agent prompt: "Show me functions in this project that are called
by something runtime-only and have no tests." The agent reads
describe_schema, composes the Cypher, calls run_cypher, and returns the
answer — all without livegraph needing an LLM dependency of its own.
The agent's LLM (Claude Sonnet, Opus, GPT-5, whichever) writes the Cypher. livegraph just provides the safe execution endpoint.
For questions where the concept is clear but the right names aren't, livegraph can compute vector embeddings of every Function and Method and serve them via a 14th MCP tool.
This stack is opt-in:
pip install 'livegraph[semantic]' # adds sentence-transformers + torch (~2 GB)After ingesting a project, compute its embeddings:
LIVEGRAPH_PROJECT=myproject livegraph embedThe default model is all-MiniLM-L6-v2 (384 dimensions, ~80 MB). Override
via LIVEGRAPH_EMBED_MODEL (any HuggingFace sentence-transformers model id).
Re-running livegraph embed is idempotent — only symbols whose source has
changed since the last run get re-embedded (tracked via
embedding_source_hash, mirroring Phase 5's content-hash pattern). To swap
to a model with different dimensions, pass --rebuild.
The MCP server exposes semantic_search:
semantic_search(query: str, limit: int = 10, kind: str = "any")
Example agent prompt: "Where do we handle JWT verification?". The agent
calls semantic_search("JWT verification token validation") and the top
results are ranked by cosine similarity to the query — even if no symbol
in the project literally contains those words.
If the [semantic] extra is not installed, the tool returns an empty
result list with a warning containing the install hint, and the rest of
livegraph keeps working.
Mirror source-file edits into the graph live — useful while an MCP client is connected and you're editing code.
livegraph watch --project myproj /path/to/repoFlags:
--embed— after each update, re-run embedding for symbols whose source changed (requirespip install 'livegraph[semantic]').--debounce-ms 300— coalesce file events within this window (default 300; tunable viaLIVEGRAPH_WATCH_DEBOUNCE_MS).--ignore PATTERN— glob patterns to ignore (repeatable). Layered on top of.gitignoreand builtin ignores (.git/,__pycache__/,.venv/,venv/,node_modules/).
The loop logs each update; Ctrl-C stops it cleanly. Backend errors trigger exponential backoff (1s → 30s cap) so the watcher stays alive if Neo4j blips. Runtime CALLS edges from prior pytest runs are preserved on unchanged files.
For when an agent wants more than "what matches my query" — it wants "where do I look and what do I run." The 15th MCP tool:
semantic_neighborhood(
query: str,
limit: int = 10, # number of semantic seeds
per_seed_limit: int = 10, # cap per expansion list
kind: str = "any", # "any" | "function" | "method"
include: list[str] | None = None, # subset of {"callers","callees","tests"}
min_score: float = 0.0,
)
For each top-K semantic match, returns the direct callers (with
static/runtime/both provenance), callees (same), and tests that
cover the symbol (Phase 2 coverage edges). One vector query plus up to
three batched expansion queries — same latency budget as
semantic_search plus 3 Cypher round-trips.
Example agent prompt: "Where does this codebase do JWT verification,
and what tests cover it?" The agent calls semantic_neighborhood
once and gets matching functions, their call sites, and the tests it
should run to validate a change.
Phase 10 attaches a time/authorship axis to the graph. Walk the
project's git history into (:Commit) / (:Author) nodes with
CHANGED_IN edges (file-level always; symbol-level when commit hunks
overlap the symbol's current source lines).
livegraph ingest-history --project myproj /path/to/repo
livegraph ingest-history --project myproj --since-last # incrementalThree new MCP tools (bringing the count to 18):
| Tool | What it answers |
|---|---|
symbol_history(qualified_name, limit) |
Recent commits + authors that touched a symbol. |
recent_changes(since, limit, kind) |
Symbols changed in commits since a timestamp. |
top_churn(window_days, limit, kind) |
Hotspot symbols ranked by commit count. |
Caveats:
- Attribution uses the current parse's line ranges, so symbols whose lines moved a lot through history may be over- or under-credited for old commits. Recent commits are accurate.
- Author identity keys on email; respects
.mailmapif present. livegraph updateandlivegraph watchleave history edges alone. Runingest-history --since-lastafter a long editing session to catch up.
Three read-only MCP tools (bringing the count to 21) for "is our architecture healthy?" questions, all over edges that already exist in the graph:
| Tool | What it answers |
|---|---|
find_cycles(scope, provenance, min_size, limit) |
Strongly-connected components in the call graph (scope="call", filterable by static/runtime/any) or import graph (scope="module"). |
layering_violations(layers, edge_kind, limit) |
Edges that go "up" the supplied layering. layers is an ordered list of {name, patterns} — top layers may depend on lower layers; the reverse is a violation. Files matching no pattern are unlayered and silently skipped; files matching multiple take the first. |
hubs(kind, min_fanin, limit) |
Symbols with high inbound CALLS — the "everything depends on this helper" detector. |
Example agent prompt: "Are any of our domain modules importing
infrastructure code by mistake?" The agent calls layering_violations
with the project's layering and gets back the specific edges to fix.
Phase 12 turns the existing analysis surface into something CI can
enforce. Drop a .livegraph.toml at the project root:
[project]
name = "myproj"
[checks.cycles]
enabled = true
scope = "module"
max_cycles = 0
[checks.layering]
enabled = true
max_violations = 0
layers = [
{ name = "web", patterns = ["web/**", "api/**"] },
{ name = "domain", patterns = ["domain/**"] },
{ name = "infra", patterns = ["infra/**", "db/**"] },
]
[checks.churn]
enabled = true
window_days = 30
hot_files_threshold = 10
ignore = ["tests/**"]
[checks.hubs]
enabled = false
min_fanin = 25
max_hubs = 0Then in CI:
livegraph update # refresh the graph
livegraph check # run all enabled checksExit codes: 0 = pass, 1 = at least one check failed, 2 = config
or environment error (or, with --strict, the graph is stale vs disk).
Output is human-readable by default; pass --format json for a
machine-readable payload, --fail-fast to stop at the first failing
check.
Each check wraps an existing MCP tool — find_cycles,
layering_violations, top_churn, hubs — so the agent's "show me"
question and CI's "fail the build" question read the same graph data.
Phase 13 makes livegraph multi-language. livegraph build /path/to/repo
auto-detects what's there:
*.py→ Python pipeline (Phases 1-2).*.ts,*.tsx,*.js,*.jsx,*.mjs,*.cjs→ TypeScript pipeline.
Both populate the same Neo4j schema, so an agent can ask
find_symbol, find_callers, find_cycles, semantic_search over a
mixed Python + TS monorepo and get unified results.
What gets captured for TS:
- Definitions:
function,const x = () => {},class C { method() {} },export function,export default function(recorded asdefault). - Imports: ES modules — relative paths (with extension inference
and
index.{ts,tsx,…}resolution), tsconfigpathsaliases (read fromtsconfig.jsonif present), bare specifiers (recorded asthirdparty). - Calls: direct calls (
foo()), method calls (obj.m()),new C(). Name-matched against project-defined symbols, per-language.
Qualified names follow the existing <file_path>::<dotted> scheme,
e.g. src/calc.ts::Calculator.add.
Out of scope for v1: CommonJS require(), dynamic import(),
JSX components as call edges, decorators, type-aware method
resolution, Node runtime tracing (TS CALLS edges are always
static=true, runtime=false).
Override auto-detection with --lang python or --lang typescript.