From Whiteboard to Code Graphs: Building an AI Context Layer
Your architecture diagram is a lie.
Not because you're malicious. Because the second someone merged that PR refactoring the payment service, your carefully crafted boxes and arrows became historical fiction. Three months later, that diagram sits in Confluence showing a monolith that's now six microservices, or depicting service boundaries that dissolved when the team reorganized.
We keep making these diagrams anyway. Whiteboard sessions. Lucidchart marathons. Mermaid diagrams in README files. Then we watch them rot.
The problem isn't documentation discipline. The problem is asking humans to maintain a parallel representation of something that already exists: the code itself. Your codebase is the architecture. It's just trapped in a format that requires significant cognitive load to parse.
What we actually need is a queryable, always-current graph extracted directly from source code. Not for compliance. For understanding. For onboarding. For AI that needs to reason about your system without hallucinating.
Why Architecture Diagrams Fail
Let's be honest about what happens. Product wants a feature. Engineering draws boxes showing how it'll work. Everyone nods. Implementation begins. Reality hits.
"Wait, the auth service can't handle that request pattern."
"The database schema doesn't support this relationship."
"We're already calling this service from seven places; adding an eighth creates a cycle."
So you adapt. You should adapt. Code evolves faster than documentation. The diagram never gets updated because updating it doesn't ship features, and the next sprint has already started.
Six months later, a new engineer joins. They find the diagram. They implement against it. Code review catches the mismatch. "Oh yeah, we don't do it that way anymore."
This isn't a people problem. This is a fundamental mismatch between the rate of code change and the economics of manual documentation maintenance.
What Code Graphs Actually Capture
A code graph isn't a pretty picture. It's a data structure representing the actual relationships in your system:
File dependencies: Which files import which. Not just direct imports—transitive dependencies matter when you're trying to understand blast radius.
Symbol relationships: Functions calling functions. Classes extending classes. Which modules export what, and who consumes those exports.
API boundaries: Routes, endpoints, RPC definitions. What your services actually expose versus what your API docs claim they expose.
Ownership topology: Which teams touch which code. Not from CODEOWNERS files (those lie too), but from git blame and commit patterns.
Runtime paths: How requests actually flow through your system. Not the theoretical happy path, but the code paths that production traffic follows.
This isn't UML. This isn't enterprise architecture tool output. It's a machine-readable representation of ground truth.
Building From Source, Not Diagrams
Here's the shift: instead of drawing what your system should look like, extract what it actually looks like.
Static analysis gets you surprisingly far. Parse imports, build a dependency graph. Track function calls through AST traversal. Map database queries to schema definitions. Python's ast module, JavaScript's Babel, Go's go/parser—every major language has tooling for this.
# Simplified example: mapping module imports
import ast
class ImportVisitor(ast.NodeVisitor):
def __init__(self):
self.imports = []
def visit_Import(self, node):
for alias in node.names:
self.imports.append(alias.name)
def visit_ImportFrom(self, node):
module = node.module or ''
for alias in node.names:
self.imports.append(f"{module}.{alias.name}")
tree = ast.parse(source_code)
visitor = ImportVisitor()
visitor.visit(tree)
Scale this across your entire codebase. Suddenly you have a graph of every import relationship. Query it: "What would break if I changed this interface?" The graph tells you exactly which files import it.
But static analysis has limits. Dynamic languages do dynamic things. Reflection, runtime imports, plugin systems—these create edges your parser won't catch. You need runtime instrumentation for complete graphs. Tracing, profiling, observability data fed back into the graph structure.
This is where tools like Glue become relevant. You could build all this yourself—parse every file, maintain the graph structure, handle incremental updates as code changes. Or you could use a platform that already solved the hard parts: incremental indexing, cross-language support, keeping graphs current as your main branch moves.
Why AI Needs Code Graphs
Large language models are impressive until you ask them about your specific codebase. Then they hallucinate. They'll confidently describe functions that don't exist, call APIs with wrong signatures, suggest patterns that contradict your actual architecture.
The problem is context. Even with long context windows, you can't dump your entire codebase into a prompt. You need to give the model relevant context—the specific files, functions, and relationships that matter for the task at hand.
A code graph makes this possible. Someone asks: "How does authentication work in our checkout flow?" Instead of the AI guessing or you manually finding the relevant code, you query the graph:
Find the checkout endpoint
Trace its call graph
Filter for auth-related functions
Pull those specific files
Now your AI has precise context. It can see the actual implementation, not imagine one.
This is what MCP (Model Context Protocol) integration enables. Tools like Cursor, Copilot, and Claude can query your code graph directly. They ask "show me all callers of this function" and get accurate answers. They understand your actual architecture, not a hallucinated approximation.
Glue does this out of the box—indexing your repo, building the graph, exposing it via MCP so your AI tools can query it. You get accurate code intelligence without building the infrastructure yourself.
Practical Applications Beyond AI
Code graphs solve mundane problems too.
Refactoring confidence: You want to rename a function. Your IDE finds references, but does it catch dynamic calls? String-based lookups? The graph shows you everything that depends on it.
Incident response: Production breaks. Which services are involved? Follow the graph from the error trace backward through the call chain to find the root cause.
Onboarding speed: New engineer needs to understand the payment flow. Don't send them on a grep adventure. Show them the graph: here's the entry point, here's where it touches the database, here's where it calls external services.
Tech debt prioritization: That ancient utility module everyone fears touching—the graph shows it's called from 47 places across 12 services. Maybe refactoring it isn't the quick win you thought.
Ownership gaps: Some code has no clear owner. The graph combined with commit history reveals which team actually maintains it, versus what CODEOWNERS says.
The Freshness Problem
A code graph is only useful if it's current. This is why manual architecture diagrams fail—maintenance is expensive.
Automation is non-negotiable. Your graph needs to update with every merge to main. Not nightly. Not weekly. Immediately. The moment code changes, the graph reflects it.
This requires infrastructure. Incremental parsing—only re-analyzing changed files. Efficient graph updates—adding/removing edges without rebuilding everything. Language-specific parsers for Python, TypeScript, Go, Java, whatever your polyglot stack includes.
You can build this. Large engineering orgs do. But most teams are better off using existing code intelligence platforms. Glue handles the indexing, parsing, and incremental updates automatically. You push code; the graph stays current.
What This Enables Long-Term
Once you have a living code graph, you can build on it:
Automated documentation: Generate architecture docs directly from code. Not written once and forgotten, but regenerated on every deploy.
Change impact analysis: Before merging, see what your changes affect. Not just test coverage, but actual runtime impact based on call graphs.
Security boundaries: Map which code can access what data. Enforce boundaries with tooling that understands actual code relationships, not assumed ones.
Performance optimization: Identify hot paths through the system. The graph shows which code paths handle the most traffic.
The graph becomes foundational infrastructure. Like observability, but for code structure rather than runtime behavior.
Getting Started
You don't need to build everything at once. Start simple:
Pick one language in your stack
Build a basic import graph for a single service
Make it queryable (even just grep-able JSON)
Solve one real problem with it
Then expand. Add more services. Include more relationship types. Integrate with your development workflow.
Or skip the build phase and adopt a platform designed for this. Glue indexes your entire codebase, handles multiple languages, keeps graphs current, and integrates with the AI tools your team already uses. It's infrastructure you don't have to build.
The Real Win
The real value isn't the graph itself. It's what the graph enables: understanding complex systems without heroic individual knowledge.
You stop relying on the one engineer who remembers how everything connects. You stop guessing about blast radius. You stop letting AI hallucinate about your architecture.
You start working with accurate, queryable, always-current representations of your actual system. Not what you wish it was. Not what it used to be. What it is, right now, in production.
Your architecture is already in the code. We just need to make it accessible. Code graphs do that. Everything else follows.