Skip to content
RAGRAF

Open Source · MIT · v0.1.0

Facts about code instead of blind grep

RAGRAF indexes the repository and answers the LLM agent through fixed operations: where it is defined, who calls it, what breaks on a change. Every fact — with a verifiable file:line reference.

Python 3.13+SQLiteMCP · 23 toolstree-sitter · 16 languagesGPU · torch.sparseMIT

92%

target-file hit on the first query

12 of 13, measured

100%

facts with a file:line reference

metric M6

1,0

tool calls per task

was 2.0–3.1

0

files read in full

was 14

Measured on a real PHP portal (14 navigation tasks): bench/results/ and the project README. Call and hit numbers are measurements; "realistically 43" is a modeled correction recorded in the artifact itself.

Problem

The agent works from memory of code it has never seen

Three symptoms known to everyone who has handed an LLM an edit in an existing project. All three are measured, not invented.

It cannot find the place

A name grep yields dozens of candidates, and the right one is not first. VulnGym: in chains longer than 9 steps, in 56.5% of failures the agent never found the file (26.8%) or found the file but not the line (29.7%). More than half of the failures are about navigation, not analysis.

source: measurement

It does not know the DB schema

The column was named "like the variable in the neighboring module" — and that surfaces only at runtime: SQLSTATE[42S22] Unknown column on the very first save. The schema from migrations must be checked before the code runs.

source: real defect

It does not know it is stale

The index was built, then the code was edited — the agent receives facts about a state that no longer exists. "No such function" and "the function appeared after indexing" are indistinguishable in the answer. The decision rests on a false fact.

source: core risk

Query languages do not help

Give the agent Cypher/CPGQL? LLMxCPG (USENIX Security '25): base models generate a valid code-graph query in 19–132 cases out of 1278. A free-form query language turns into an error generator.

source: literature

The conclusion RAGRAF grew from: more than half of agent failures are not about analysis but about navigation. That part must move out of the model — into an external index that answers with facts and knows the boundaries of its knowledge.

Features

What RAGRAF does

A fixed contract instead of free-form retrieval: 22 facade operations, 23 MCP tools, three SQLite stores. Nothing superfluous — and nothing silently broken.

22 fixed operations

symbol, callers, impact, slice, schema, write_check, policy_trace… The agent chooses "what to ask", not "how to ask": there is no graph query language — by measurement, not by taste.

facade: 22 op_* · MCP: 23 tools

Every fact carries proof

path:line, provenance (stated — proven by an analyzer, derived — heuristic), confidence, index_rev. An edge without evidence does not live in the graph as a fact.

link verifiability: 100% (metric M6)

The index knows it is stale

The revision is immutable; edits accumulate in an append-only drift ledger; affected facts are marked stale in every answer; the checksum guard reindexes automatically.

drift ledger · guard · auto-reindex

Hybrid search, fully local

BM25 (FTS5) + 384-dimension vectors (paraphrase-multilingual-MiniLM-L12-v2 via fastembed/ONNX). No torch, no cloud, no subscriptions. The model downloads once (~240 MB).

SQLite: graph + FTS5 + vec0 in three files

DB schema from migrations + write_check

Tables and columns are parsed from migrations/*.sql without DBMS access. write_check verifies the write plan against the schema before running and suggests the nearest existing column names.

44 tables / 453 columns on the pilot

MCP server and honest degradation

23 ragraf_* tools over stdio (JSON-RPC). Without the Joern layer: capability=degraded, slice refuses with a reason; all other operations work. Silent degradation never happens.

optional CPG layer: Joern + JDK 21

GPU-accelerated compute

Two optional GPU paths: embeddings (sentence-transformers+torch, break-even from ~1,800 texts) and tensor graph traversal (torch.sparse CSR, from ~5,000 vertices). Everything runs on CPU without a GPU — the selected backend and its reason are recorded in metadata, never implied.

thresholds: 1,800 texts / 5,000 vertices · backend torch-gpu|torch-cpu|none

16 languages are parsed as trees (PHP, Python, JS, TS, Java, C#, C, C++, Ruby, Lua, Go, Perl, bash, PowerShell, CSS, HTML) and 10 more are attached as text (Markdown, SQL, YAML, JSON, TOML, INI, XML, RST, batch, plain text).

How it works

Two loops: indexing and answering

Indexing builds a snapshot revision. Answers read the snapshot and verify its freshness. Code edits during work go to the ledger, not to the index.

Indexing (one command)

  1. step 1scancorpus walk, sha256 of every file, the input checksum
  2. step 2asttree-sitter: symbols, calls, SQL literals, line boundaries
  3. step 3resolvename resolution: exact match → name+file → honest refusal
  4. step 4schematables and columns from migrations — no database connection
  5. step 5cpgJoern (optional): interprocedural calls; absent — degraded
  6. step 6lexvecFTS5 tokens and vectors; unchanged chunks reused by hash

The answer to the agent (every request)

  1. step 1operationthe agent calls a fixed operation via MCP or CLI
  2. step 2freshnessthe guard compares corpus_hash; on divergence — stale marks or auto-reindex
  3. step 3factsthe result with path:line references, provenance and confidence
  4. step 4envelopeindex_rev, capability, drift, limits, stale_warnings — in every answer

Three principles derived from measured defects

  • The index is immutable within a revision — facts from different moments in time never mix.
  • Staleness is shown, never hidden: a stale fact cannot be mistaken for a current one.
  • Degradation is never silent: capability and reason ride in every answer.

Architecture

Three SQLite files and zero infrastructure

The entire index state lives in the .ragraf/ directory inside the project. No services, no daemons, no cloud. Delete the directory — delete the index.

graph.sqlite

Nodes (class, function, method), edges (calls, inheritance, imports) with evidence, DB schema, routes. Everything marked rev_id.

lexvec.sqlite

FTS5 lexicon (BM25) and vec0 vectors (384) in one file; raw chunk text — fragments served without touching disk.

control.sqlite

Revisions, the append-only drift journal, reindex archives, call telemetry. Never deleted.

Corpus configuration
# ragraf init <project directory> — creates the config for you.
# The index lands in .ragraf/ inside the project (add it to .gitignore).
project: myproject
root: C:/path/to/your/project
languages: [python, javascript, markdown]
include: ["**/*.py", "**/*.js", "**/*.md"]
exclude: ["**/node_modules/**", "**/build/**"]
secrets_exclude:            # not indexed and not read
  - "**/.env"
  - "**/*.pem"
embedding:
  model: sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2
  dim: 384
index:
  retain_revs: 3
  joern_enabled: true       # no Joern — capability=degraded

Demo

What it looks like in practice

The scenarios are built on real product outputs (measurements and the project article). These are interface mockups, not screenshots: the CLI and MCP deliberately have no graphics — that is part of the philosophy.

agent request

ragraf -p config/portal.yaml query symbol --name z_agr_acl_audit

function · config/acl.php:112–148 · signature: function z_agr_acl_audit(array $data): voidconfig/acl.php:112
Parameter contract (from the docblock): array{action:string, user_id?:int, field?:string, to:?string, actor_id?:int} — only action is requiredconfig/acl.php:104

Before this, agents built the call from the signature "array $data" and got a TypeError: the array type says nothing about the key composition.

Mockup. The data is real product output from the project article and the call journal.

Quick start

Three steps to the first fact

No administrator rights needed: everything installs into the user profile. Verified platform — Windows + PowerShell 7.6; the common pip path works anywhere with Python 3.13+.

  1. Installation

    The script creates a venv in ~/.ragraf/venv, installs dependencies and verifies every step with a summary table. Log: ~/.ragraf/logs/.

    PowerShell
    git clone https://github.com/paulmann/RAGRAF.git
    cd RAGRAF
    pwsh -NoLogo -NoProfile -File scripts/install.ps1
  2. Corpus

    ensure finds the corpus, creates one for an unknown directory and rebuilds a stale index — in a single command.

    PowerShell
    ragraf ensure C:\work\myproject
  3. First query

    Global flags -p and --json go BEFORE the subcommand. selftest — a report on 11 components.

    PowerShell
    ragraf -p <config> query search "reindex threshold"
    ragraf -p <config> query symbol --name corpus_hash
    ragraf -p <config> selftest

Flag order

Global flags --project/-p and --json go BEFORE the subcommand (ragraf -p cfg query search …). Exception: ensure has its own --json, placed after it (ragraf ensure <dir> --json).

First indexing with vectors

Downloads the embedding model (~240 MB) once into ~/.ragraf/models. Without internet or with --no-vectors, lexical BM25 search keeps working.

selftest is not a gate

The command always returns exit code 0: it is a report, not a gate. Read the report itself: 9 required components + joern/java (optional).

Corporate TLS proxy

If the model download fails with certificate verify failed — install truststore: install.ps1 -WithTruststore or pip install truststore.

Full command list — ragraf --help. Documentation and measurement protocols — in the repository.

FAQ

Frequently asked questions

Short answers to the questions asked most often. Details — in the repository documentation.

MIT — free to use, including commercially. External dependencies keep their own licenses (tree-sitter — MIT, sqlite-vec — MIT/Apache-2.0, fastembed — Apache-2.0, PyYAML and tiktoken — MIT, ijson — BSD). Java and Joern are installed separately under Apache-2.0 and are not part of the distribution.

Python 3.13 or newer (verified on 3.14); no administrator rights needed — everything installs into the user profile. Disk: ~2.5 GB for the environment (ONNX Runtime, tree-sitter), the index ~1 MB per 1–2 source files. Verified platform — Windows + PowerShell 7.6; the common pip path on Linux and macOS is not verified. The optional Joern layer (interprocedural graph) adds JDK 21 and ~5–6 GB.

The local model sentence-transformers/paraphrase-multilingual-MiniLM-L12-v2 (384 dimensions) via fastembed and ONNX Runtime — no torch, no cloud. The model downloads once (~240 MB) at the first indexing with vectors into ~/.ragraf/models; the cache directory is overridden by RAGRAF_MODEL_CACHE. Read operations never download the model. Indexing without vectors (--no-vectors) works on lexical BM25 search.

Everything stays on your machine: three SQLite files in the project's .ragraf/ directory, the call journal lives there too. Files in the secrets_exclude list are never opened at all — not indexed, not read, not logged. Telemetry stores a hash of the argument and a short digest instead of full query texts. The only network traffic: PyPI at install time and the one-time model download; there are no API keys and no outbound data flows in the product.

Sixteen languages are parsed as trees (graph nodes and edges): PHP, Python, JavaScript, TypeScript, Java, C#, C, C++, Ruby, Lua, Go, Perl, bash, PowerShell, CSS, HTML. Ten more are attached as text (findable by search, but they yield no nodes): Markdown, SQL, YAML, JSON, TOML, INI, XML, RST, batch, plain text. PHP is the most deeply verified — the pilot and the CPG layer (Joern php2cpg) were built on it.

In three commitments. First: the index is immutable within a revision; divergences from the code accumulate in an append-only journal — a stale fact is marked stale in every answer instead of being silently served as current. Second: every fact carries evidence — path:line, provenance (stated/derived) and confidence; heuristics are never mixed with proven links. Third: instead of free-form retrieval answers — fixed operations with explicit limits and a capability state.

There is no graph query language and there will be none — that is architecture decision P4, backed by measurement (LLMxCPG: base models produce 19–132 valid CPGQL queries out of 1278). The 22 operations are a fixed set; extensions are discussed in the repository Issues. The project is developed for concrete tasks, so architecture proposals are welcomed more readily than large patches without a prior conversation.

RAGRAF is itself a local tool: no server is needed for the index or the answers. The MCP server is a stdio process spawned by your agent. The admin panel (optional) listens on 127.0.0.1:8777 only and is unreachable from outside. The only external component is Joern — a persistent server on localhost:9500 during CPG indexing; it stops automatically.

Give your agent verifiable facts

A code index is not "faster search". It moves part of the work out of the model into an external structure that knows what it does not know. RAGRAF's most valuable property: it refuses to answer confidently when it has no right to.

The signature array $data is honest and useless. A docblock with the key composition is useful. A stale: true mark next to a fact is mandatory.