mech.app
Dev Tools

Understand-Anything: How Knowledge Graphs Turn Codebases into Queryable Agent Memory

A deep look at the infrastructure that transforms arbitrary code into graph structures agents can query, search, and reason over.

Source: github.com
Understand-Anything: How Knowledge Graphs Turn Codebases into Queryable Agent Memory

Agents need memory that answers structural questions, not just semantic ones. When Claude Code asks “which modules call this function?” or Cursor wants to know “what are the dependencies of this class?”, vector search over documentation falls short. You need a graph.

Understand-Anything (30,011 stars, #1 trending TypeScript repo) builds interactive knowledge graphs from arbitrary codebases and exposes them as queryable memory for Claude Code, Codex, Cursor, Copilot, Gemini CLI, and OpenCode. The project’s tagline is “graphs that teach > graphs that impress,” which signals a focus on utility over visualization. The infrastructure question is how you parse heterogeneous code into a graph schema that multiple agent frameworks can consume without language-specific tooling.

The Memory Problem Agents Face

Agents working with code hit three memory bottlenecks:

  1. Structural queries require graph traversal. “Show me all callers of this function” is not a semantic search problem. It’s a graph walk.
  2. Context windows are finite. You can’t dump an entire codebase into a prompt. You need indexed, queryable representations.
  3. Agent frameworks differ in their query interfaces. Claude Code, Codex, and Gemini CLI each expect different input formats and tool call shapes.

Understand-Anything solves this by treating the codebase as a graph data structure. Nodes represent functions, classes, modules, and files. Edges represent calls, imports, inheritance, and references. The graph is serialized into a format that agents can query via natural language or structured API calls.

Architecture: From Code to Graph

The pipeline has four stages:

1. Language-Agnostic Parsing

The tool uses Tree-sitter, a parser generator that works across 40+ languages. Tree-sitter produces concrete syntax trees (CSTs) without requiring language-specific semantic analyzers. This means you can parse Python, TypeScript, Rust, and Go with the same infrastructure.

The parser extracts:

  • Function and class definitions
  • Import and require statements
  • Call sites and references
  • Type annotations (when available)

Tree-sitter grammars are maintained by the community, so adding a new language means adding a grammar file, not rewriting the parser.

2. Graph Construction

Parsed entities become nodes. Relationships become edges. The schema is intentionally flat:

Node TypePropertiesExample
Functionname, file, line, signatureparseCode() in parser.ts:42
Classname, file, methods, fieldsGraphBuilder in graph.ts:10
Modulename, path, exportsutils/parser
Filepath, language, sizesrc/index.ts

Edges are typed:

  • CALLS: function A calls function B
  • IMPORTS: module A imports module B
  • DEFINES: file A defines class B
  • INHERITS: class A extends class B

The graph is stored in an adjacency list format (JSON or SQLite, depending on size). This allows fast traversal without loading the entire graph into memory.

3. Semantic Layer

On top of the structural graph, Understand-Anything adds a semantic search layer. Each node gets an embedding (via OpenAI or local models like all-MiniLM-L6-v2). This enables hybrid queries:

  • Structural: “Find all functions that call authenticate()
  • Semantic: “Find code related to user authentication”
  • Hybrid: “Find authentication-related functions that call external APIs”

Embeddings are cached and updated incrementally when code changes. The system diffs the AST and only re-embeds modified nodes.

4. Query Interface

Agents interact with the graph via three mechanisms:

Natural Language Queries
The agent sends a question in plain text. A query planner (powered by an LLM) translates it into graph traversal operations. For example:

// Agent query: "What functions does parseCode call?"
const query = {
  operation: "traverse",
  startNode: { type: "Function", name: "parseCode" },
  edgeType: "CALLS",
  depth: 1
};

Structured API Calls
Agents can call graph operations directly if they know the schema:

// Direct API call from Codex
graph.traverse({
  from: "parseCode",
  via: "CALLS",
  filter: { language: "typescript" }
});

MCP (Model Context Protocol) Integration
For Claude Code and other MCP-compatible agents, the graph is exposed as a tool. The agent sees a function signature like:

query_graph(question: str) -> dict

The tool returns JSON with nodes, edges, and metadata. The agent can then reason over the structure or ask follow-up questions.

Incremental Updates and State Management

When code changes, rebuilding the entire graph is expensive. Understand-Anything uses a diffing strategy:

  1. File-level tracking. Each file has a hash. On save, the system checks if the hash changed.
  2. AST diffing. If the file changed, the system parses the new AST and compares it to the cached version.
  3. Node-level updates. Only modified nodes are re-embedded and re-indexed. Edges are updated if call sites or imports changed.

This keeps the graph in sync without full rebuilds. The trade-off is complexity: you need a state store (SQLite or Redis) to track file hashes and node versions.

Cross-Agent Compatibility

Each agent framework expects different query interfaces and response formats, which means the graph layer must translate between a unified internal representation and framework-specific protocols.

FrameworkQuery InterfaceResponse Format
Claude CodeMCP tool callsJSON with nodes/edges
CodexFunction callingStructured dict
CursorChat API with contextMarkdown with code links
CopilotLSP-style requestsHover text and definitions
Gemini CLICLI flags + JSONFormatted text or JSON

Understand-Anything ships adapters for each. The core graph engine is framework-agnostic. Adapters translate between the graph API and the agent’s expected input/output format.

Security and Observability

Security Boundaries

The graph is read-only by default. Agents can query but not modify the graph. This prevents memory poisoning attacks where an agent writes malicious nodes.

If you need write access (for example, to let an agent annotate code with learned insights), you enable a separate write API with authentication. The system logs all write operations for audit.

Observability

The graph engine exposes metrics:

  • Query latency (p50, p95, p99)
  • Cache hit rate for embeddings
  • Graph size (nodes, edges, memory usage)
  • Agent query patterns (most common operations)

These metrics help you tune the graph schema and identify bottlenecks. For example, if most queries are “find callers of X,” you can add reverse indexes to speed up that operation.

Deployment Shape

Understand-Anything runs in three modes:

  1. Local CLI. You run it on your laptop. The graph is stored in a local SQLite file. Agents query via HTTP or IPC.
  2. Shared server. You deploy it as a service. Multiple agents query the same graph over HTTP. This works for teams where everyone needs access to the same codebase. For teams using Claude Code or Codex in production, shared server mode with MCP is the standard deployment pattern.
  3. Embedded library. You import it as a TypeScript or Python package. The graph lives in-process. This is fastest but limits sharing.

For production, the shared server mode is most common. You deploy it behind an API gateway with rate limiting and authentication. Agents get API keys and query the graph over HTTPS.

Failure Modes

1. Parsing Errors

Tree-sitter can fail on malformed code. If a file has syntax errors, the parser skips it and logs a warning. The graph is incomplete but usable. You need monitoring to catch files that fail to parse.

2. Embedding Drift

If you switch embedding models (for example, from OpenAI to a local model), old embeddings are incompatible. You need to re-embed the entire graph. The system doesn’t handle this automatically. You run a migration script.

3. Query Timeouts

Complex graph traversals can take seconds. If an agent has a 5-second timeout, the query fails. You need to tune traversal depth limits and add caching for common queries.

4. Stale Graphs

If the graph isn’t updated when code changes, agents get outdated information. This is especially bad for fast-moving codebases. You need file watchers or CI hooks to trigger updates.

Code Example: Querying the Graph

Here’s how an agent queries the graph via the MCP interface:

// Agent sends this tool call
const result = await mcp.callTool("query_graph", {
  question: "What are the dependencies of the GraphBuilder class?"
});

// Response structure
{
  nodes: [
    { id: "GraphBuilder", type: "Class", file: "graph.ts" },
    { id: "Parser", type: "Class", file: "parser.ts" },
    { id: "Embedder", type: "Class", file: "embedder.ts" }
  ],
  edges: [
    { from: "GraphBuilder", to: "Parser", type: "IMPORTS" },
    { from: "GraphBuilder", to: "Embedder", type: "IMPORTS" }
  ],
  metadata: {
    queryTime: "120ms",
    nodesTraversed: 47
  }
}

// Agent uses this response to ask a follow-up question
const implResult = await mcp.callTool("query_graph", {
  question: "Show me the implementation of Parser"
});

The agent can then reason over the dependencies or drill into specific implementations based on the graph structure.

Technical Verdict

Use Understand-Anything when:

  • Your codebase exceeds 50,000 lines and agents make more than 10 structural queries per session (call graphs, dependency trees, inheritance chains).
  • You work with multiple agent frameworks (Claude Code, Codex, Cursor) and want a single graph backend instead of maintaining separate indexing pipelines.
  • You need incremental updates as code changes. If your CI runs take longer than 5 minutes, the diffing strategy saves rebuild time.
  • Your agents need to answer questions like “what would break if I change this function?” which require multi-hop graph traversal.

Avoid it when:

  • Your codebase is under 10,000 lines and fits comfortably in a single context window. The graph overhead isn’t worth it.
  • You only need semantic search over documentation, not structural queries over code relationships.
  • Your agents use native code execution tools (like Claude’s code_execution_tool) that already have AST access. Adding a graph layer duplicates functionality.
  • You can’t run a separate graph service (serverless environments with no persistent state, edge deployments with strict memory limits).
  • Your team doesn’t have the operational capacity to monitor graph freshness, handle embedding migrations, or debug query timeouts.

The project is strongest for teams building agentic coding assistants that need to reason over code structure at scale. It’s less useful for agents that only generate or refactor isolated code snippets without needing to understand the broader codebase.