How to Represent Code Relationships (CALLS, READS_FROM, WRITES_TO) in Code-Graph-RAG
The CALLS, READS_FROM, and WRITES_TO relationships in Code-Graph-RAG are defined in the RelationshipType enum in codebase_rag/types_defs.py, where CALLS captures function-to-function invocations by default, while READS_FROM and WRITES_TO are optional I/O relationships that connect callables to external resources when the io capture group is enabled.
Code-Graph-RAG transforms software repositories into queryable knowledge graphs, making code relationships explicit and searchable. Understanding how the schema represents CALLS, READS_FROM, and WRITES_TO edges is essential for building accurate code intelligence queries, security audits, and dependency analyses. This guide covers where these relationships are defined, how they're generated, and practical Cypher queries to leverage them.
Where Code Relationships Are Defined in the Schema
The relationship types are centrally declared in codebase_rag/types_defs.py as Python enum values. The source shows the RelationshipType enum starting at line 900, with key members including:
# From codebase_rag/types_defs.py, lines 930-942
class RelationshipType(Enum):
# The calls capture group (static and dynamic)
CALLS = "CALLS"
# The *read* (IO) & *write* (IO) capture group (optional)
READS_FROM = "READS_FROM"
WRITES_TO = "WRITES_TO"
# The **flow** capture group (optional)
FLOWS_TO = "FLOWS_TO"
These string values are used throughout the codebase to tag edges in the graph database. The __repr__ method at lines 905-906 ensures clean string output for logging and debugging.
Understanding the Three Core Relationship Types
CALLS: The Default Function Invocation Edge
CALLS is the foundational relationship in Code-Graph-RAG. It represents any invocation from one callable entity to another, whether discovered through static analysis or runtime tracing.
Key characteristics:
- Captured by the default calls capture group — always enabled during repository ingestion
- Applies to functions, methods, classes, enums, and other callable nodes
- Static analysis discovers syntactic call sites via Tree-sitter parsing
- Dynamic tracing (
cgr trace) can augment edges with runtime provenance and flagstatic_missed: truefor calls only observed at execution time
READS_FROM: I/O Resource Consumption
READS_FROM connects a callable to a Resource node it reads from. This is the primary mechanism for tracking data flow into your code from external systems.
What triggers READS_FROM edges:
- Environment variable access (
os.getenv,process.env, etc.) - File read operations (
open(),fs.readFile, etc.) - Database queries and network reads
- Any API recognized by the I/O capture group analyzers
Unlike CALLS, READS_FROM requires explicit opt-in via the io capture group.
WRITES_TO: I/O Resource Production
WRITES_TO is the counterpart to READS_FROM, marking callables that produce output to external resources.
Common WRITES_TO scenarios:
- File write operations
- Network responses and database writes
- Standard output streams
- Logging frameworks writing to external systems
Both I/O relationships point to Resource nodes with qualified_name values following the pattern resource::<KIND>::<identity>.
Enabling READS_FROM and WRITES_TO Capture
The I/O relationships are not generated by default. You must enable the io capture group during graph construction.
Configuration via Environment Variable
export CGR_CAPTURE_IO=1
cgr load /path/to/repository
Configuration via Python SDK
from codegraph import GraphLoader, Config
# Enable io capture group explicitly
cfg = Config(capture_groups=['io'])
loader = GraphLoader(config=cfg)
loader.load_repo('/path/to/repo')
With this configuration, the Tree-sitter based analyzers scan for known I/O API patterns across supported languages and emit READS_FROM and WRITES_TO edges alongside the default CALLS edges.
Querying Code Relationships with Cypher
Once your graph is loaded in Memgraph, these Cypher patterns unlock powerful code intelligence.
Find All Callers of a Specific Function
MATCH (caller:Function)-[:CALLS]->(callee:Function)
WHERE callee.name = 'store_embedding'
RETURN caller.qualified_name AS caller_function,
caller.file_path AS source_file
ORDER BY caller.qualified_name;
This query identifies direct call dependencies — useful for impact analysis when modifying the target function.
Discover Environment Variable Dependencies
MATCH (fn:Function)-[:READS_FROM]->(res:Resource)
WHERE res.qualified_name STARTS WITH 'resource::ENV'
RETURN fn.name AS function_name,
res.qualified_name AS env_var,
fn.file_path AS location
ORDER BY fn.file_path;
Results show which code depends on which environment variables, aiding documentation and deployment configuration.
Trace Data Flow from Secret to Network Output
MATCH (fn:Function)-[:READS_FROM]->(secret:Resource)
WHERE secret.qualified_name = 'resource::ENV::API_SECRET'
MATCH (fn)-[:WRITES_TO]->(out:Resource)
WHERE out.kind IN ['NETWORK', 'STDOUT', 'FILE']
RETURN fn.qualified_name AS potential_leak_point,
out.qualified_name AS output_target,
fn.file_path AS location;
This security audit pattern identifies functions that read sensitive values and write to external destinations — a critical query for secret scanning.
Find Functions with Both Read and Write I/O
MATCH (fn:Function)-[:READS_FROM]->(src:Resource)
WITH fn, count(src) AS read_count
MATCH (fn)-[:WRITES_TO]->(dst:Resource)
RETURN fn.qualified_name AS io_heavy_function,
read_count AS sources_read,
count(dst) AS destinations_written,
collect(DISTINCT src.kind) AS read_kinds,
collect(DISTINCT dst.kind) AS write_kinds
ORDER BY read_count + count(dst) DESC;
Functions appearing in results with high counts are complex I/O coordinators — candidates for refactoring or careful testing.
Validating I/O Relationships in Tests
The test suite validates correct READS_FROM and WRITES_TO generation. In codebase_rag/tests/test_io_libc_edges.py, constants reference the enum values:
# Lines 15-16 of test_io_libc_edges.py
READS_FROM = cs.RelationshipType.READS_FROM.value
WRITES_TO = cs.RelationshipType.WRITES_TO.value
Line 120 shows validation logic checking for these relationship types in edge assertions:
if str(c.arg[1]) in (READS_FROM, WRITES_TO, FLOWS_TO):
# Test assertion logic...
These tests ensure language-agnostic I/O detection works correctly across C, Python, and other supported languages.
Comparing Relationship Characteristics
| Aspect | CALLS | READS_FROM / WRITES_TO |
|---|---|---|
| Capture group | Default (always on) | Optional io capture group |
| Source entity | Any callable | Any callable |
| Target entity | Callable nodes | Resource nodes |
| Static analysis | Yes — Tree-sitter call sites | Yes — I/O API patterns |
| Dynamic tracing | Yes — runtime augmentation | Limited — depends on tracer |
| Use cases | Call graphs, dead code, impact analysis | Data flow, security, resource mapping |
Summary
- CALLS relationships in Code-Graph-RAG are always captured, representing function-to-function invocations discovered through static and dynamic analysis
- READS_FROM and WRITES_TO relationships require enabling the io capture group and connect callables to Resource nodes representing external I/O
- All three are defined in
codebase_rag/types_defs.pyasRelationshipTypeenum values and used throughout the ingestion pipeline - Practical Cypher queries combining these edges enable security audits, dependency analysis, and data flow tracking across your codebase
Frequently Asked Questions
What's the difference between CALLS and FLOWS_TO relationships?
CALLS represents direct invocation between code entities, while FLOWS_TO tracks data propagation paths — often through intermediate variables and transformations. CALLS answers "who calls whom"; FLOWS_TO answers "where does this data end up". Both are defined in RelationshipType, but FLOWS_TO requires additional taint analysis to compute.
How do I know if READS_FROM or WRITES_TO relationships were generated for my repository?
Query your graph for Resource nodes connected via these edge types: MATCH ()-[r:READS_FROM|WRITES_TO]->(res:Resource) RETURN type(r), count(*). Zero results indicate the io capture group was disabled during ingestion. Re-run cgr load with CGR_CAPTURE_IO=1 or Config(capture_groups=['io']) to generate them.
Can CALLS edges distinguish between static and dynamic dispatch?
Yes. When dynamic tracing is enabled (cgr trace), CALLS edges include provenance metadata. The static_missed: true flag on an edge indicates the call was observed at runtime but not discovered by static analysis — typically polymorphic dispatch, reflection, or dynamic module loading.
Why are my WRITES_TO edges pointing to resources I don't recognize?
Code-Graph-RAG's I/O capture group uses pattern matching against known API signatures. Some standard library calls (like print() or logging frameworks) may be abstracted to canonical resource names such as resource::STDOUT::default or resource::FILE::<inferred_path>. Check resource.kind and resource.qualified_name properties to understand the abstraction layer applied.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →