How to Merge Dynamic Call Data Into the Knowledge Graph in Code-Graph-RAG
Use the three-step pipeline in codebase_rag/trace/tracer.py and realtime_updater.py to capture runtime call events, normalize them via Ingestor.process_trace, and merge them into the graph with graph.merge_calls(static_edges | dynamic_edges).
Code-Graph-RAG constructs a cross-language knowledge graph that links symbols through CALLS edges. While static AST analysis identifies most call relationships, many languages—JavaScript, Python, Rust—hide call targets behind runtime dispatch. These dynamic calls require runtime tracing and specialized merging logic. This article explains exactly how Code-Graph-RAG ingests dynamic call data and fuses it with static analysis results.
The Three-Stage Dynamic Call Pipeline
The codebase implements dynamic call merging through distinct capture, normalization, and merge stages.
Stage 1: Runtime Tracing with Tracer
The codebase_rag/trace/tracer.py module instruments program execution to record function-call events. The Tracer class captures caller file paths, callee simple names, and dynamic dispatch details including this rebinding, trait objects, and dynamic imports.
from codebase_rag.trace.tracer import Tracer
# Initialize tracer for a Python project
tracer = Tracer(language="python", project_root="my_project")
tracer.start()
# Execute instrumented code...
# tracer captures every call event with full context
events = tracer.stop() # Raw trace payloads
The tracer supports multiple language runtimes and emits structured events that preserve enough context to resolve symbols against the static graph.
Stage 2: Normalization via Ingestor.process_trace
The codebase_rag/trace/ingest.py module transforms raw trace events into the graph's canonical edge representation. The Ingestor class deduplicates observations and formats edges as (caller_file, callee_simple_name) tuples.
from codebase_rag.trace.ingest import Ingestor
ingestor = Ingestor()
dynamic_edges = ingestor.process_trace(events)
# Returns set of normalized (caller_path, callee_name) edges
This normalization ensures that runtime observations use identical identifiers to static analysis results, enabling conflict-free merging.
Stage 3: Graph Merge in realtime_updater.py
The realtime_updater.py module orchestrates the final merge. The RealtimeUpdater._process_function_calls method coordinates static analysis refresh, dynamic edge collection, and atomic graph updates.
# Inside realtime_updater.py
def _process_function_calls(self) -> None:
# 1️⃣ Refresh static analysis caches
self.updater.factory.call_processor.reset_resolution_caches()
self.updater._process_function_calls()
# 2️⃣ Collect normalized dynamic edges
dynamic_edges = self.updater.ingestor.collect_dynamic_calls()
# 3️⃣ Merge static and dynamic edges with idempotent semantics
self.graph.merge_calls(static_edges | dynamic_edges)
The graph.merge_calls() operation uses MERGE-style semantics (INGESTOR._use_merge = True). Existing (caller, callee) pairs are preserved; only missing edges are inserted. This prevents duplicate edges when the same call is observed repeatedly.
Automated Real-Time Merging
For continuous integration, Code-Graph-RAG provides a file-watching mode that automatically merges dynamic calls as you develop.
python -m codegraph_rag.realtime_updater \
--project my_project \
--language python \
--watch
The watcher triggers on every file change:
- Re-runs static analysis via
call_processor - Collects new dynamic trace records
- Executes
merge_callsto integrate updates
This debounced update cycle (RealtimeUpdater._process_debounced_change) balances responsiveness with computational cost.
Manual Dynamic Call Inspection
Query the graph to verify merged dynamic edges:
from cgr.graph import KnowledgeGraph
graph = KnowledgeGraph()
# Retrieve only dynamically-discovered calls
dynamic_calls = graph.query_calls(dynamic_only=True)
for caller, callee in dynamic_calls:
print(f"{caller} → {callee} (dynamic)")
The dynamic_only filter distinguishes runtime-discovered edges from statically-analyzed ones, useful for coverage analysis and debugging.
Key Source Files
| File | Purpose |
|---|---|
codebase_rag/trace/tracer.py |
Captures runtime call events across languages |
codebase_rag/trace/ingest.py |
Normalizes trace events; Ingestor.process_trace |
realtime_updater.py |
Orchestrates merge via RealtimeUpdater._process_function_calls |
evals/static_calls.py |
Reference oracle for static vs. dynamic coverage comparison |
codebase_rag/tests/test_javascript_dynamic_imports.py |
Validates dynamic import edge recording |
codebase_rag/tests/test_rust_trait_objects.py |
Validates trait object dispatch edge recording |
Summary
- Capture runtime calls with
Tracerincodebase_rag/trace/tracer.py - Normalize events through
Ingestor.process_traceincodebase_rag/trace/ingest.py - Merge edges via
graph.merge_calls()inrealtime_updater.py, using idempotent semantics to prevent duplicates - Automate with the
--watchCLI mode for continuous integration - Verify dynamic coverage with
graph.query_calls(dynamic_only=True)
Frequently Asked Questions
What file paths are used for dynamic call tracing in Code-Graph-RAG?
The tracing system resides in codebase_rag/trace/tracer.py for capture, codebase_rag/trace/ingest.py for normalization, and realtime_updater.py for merge orchestration. The call_processor symbol in the updater factory coordinates with these modules.
How does Code-Graph-RAG prevent duplicate edges when merging dynamic calls?
The merge operation uses MERGE-style semantics via INGESTOR._use_merge = True. When graph.merge_calls() executes, it checks existing (caller, callee) pairs and inserts only missing edges. This idempotent design allows repeated trace ingestion without edge explosion.
Can dynamic calls overwrite or conflict with static analysis results?
No. The merge logic preserves all statically-analyzed edges. Dynamic calls augment the graph rather than replace existing edges. When the same call appears in both static and dynamic sources, the merge operation recognizes the existing edge and skips insertion.
Which languages support dynamic call tracing in Code-Graph-RAG?
The Tracer class supports Python, JavaScript, and Rust runtimes. Test coverage includes JavaScript dynamic imports (test_javascript_dynamic_imports.py) and Rust trait object dispatch (test_rust_trait_objects.py), validating correct edge recording across these language features.
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 →