# How to Represent Code Relationships (CALLS, READS_FROM, WRITES_TO) in Code-Graph-RAG

> Learn to represent CALLS, READS_FROM, and WRITES_TO relationships in Code-Graph-RAG. Understand default function calls and optional I/O relationships for advanced code analysis.

- Repository: [Vitali Avagyan/code-graph-rag](https://github.com/vitali87/code-graph-rag)
- Tags: how-to-guide
- Published: 2026-08-15

---

**The CALLS, READS_FROM, and WRITES_TO relationships in Code-Graph-RAG are defined in the `RelationshipType` enum in [`codebase_rag/types_defs.py`](https://github.com/vitali87/code-graph-rag/blob/main/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`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/types_defs.py) as Python enum values. The source shows the `RelationshipType` enum starting at line 900, with key members including:

```python

# 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 flag `static_missed: true` for 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

```bash
export CGR_CAPTURE_IO=1
cgr load /path/to/repository

```

### Configuration via Python SDK

```python
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

```cypher
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

```cypher
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

```cypher
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

```cypher
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`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/tests/test_io_libc_edges.py), constants reference the enum values:

```python

# 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:

```python
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.py`](https://github.com/vitali87/code-graph-rag/blob/main/codebase_rag/types_defs.py) as `RelationshipType` enum 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.