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_calls to 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 Tracer in codebase_rag/trace/tracer.py
  • Normalize events through Ingestor.process_trace in codebase_rag/trace/ingest.py
  • Merge edges via graph.merge_calls() in realtime_updater.py, using idempotent semantics to prevent duplicates
  • Automate with the --watch CLI 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →