How to Configure Real-Time Graph Updates When Your Code Changes in Code-Graph-RAG

The vitali87/code-graph-rag repository provides a watchdog-based real-time updater that monitors code changes and automatically rebuilds the knowledge graph using configurable debounce and max-wait timers.

The real-time update system watches your source code repository and keeps the Memgraph knowledge graph synchronized whenever files are added, modified, or deleted. Built on the watchdog library, it balances responsiveness with efficiency through a hybrid debounce strategy that collapses rapid successive saves into single update cycles.

Architecture of the Real-Time Update System

Four core components work together to deliver reliable real-time synchronization:

Component Key Function Source Location
start_watcher Initializes Memgraph connection, creates GraphUpdater, runs full scan, starts filesystem observer realtime_updater.py lines 61-73
CodeChangeEventHandler Handles filesystem events, applies debounce logic, drives the five-step update pipeline realtime_updater.py lines 62-140
GraphUpdater Manages in-memory state, parsers, and database orchestration codebase_rag/graph_updater.py
Debouncing Constants Default timing values for update triggers codebase_rag/constants/core.py lines 68-71

According to the vitali87/code-graph-rag source code, the GraphUpdater owns three critical responsibilities: removing files from internal caches via remove_file_from_state(), re-parsing with definition_processor.process_file(), and re-running language-specific front-ends for Go, C#, and Python.

How the Debounce Mechanism Works

The CodeChangeEventHandler.dispatch method (lines 47-85) implements a two-tier timing strategy:

Debounce period — After the first change event for a file, the system waits debounce_seconds (default 5s) for the filesystem to become quiet. This collapses rapid successive saves into a single update cycle.

Max-wait ceiling — Even if edits keep arriving, the handler forces processing after max_wait_seconds (default 30s) to prevent indefinite postponement.

The handler computes remaining_wait = max_wait - time_since_first_event, then schedules with effective_delay = min(debounce_seconds, remaining_wait). If remaining_wait reaches zero, _schedule_immediate_processing triggers an instant update.

The Five-Step Update Pipeline

When a debounced timer fires, _process_debounced_change → _process_change_locked executes this sequence:

  1. Delete old graph data — Cypher statements CYPHER_DELETE_MODULE and CYPHER_DELETE_FILE remove previous file representations
  2. Clear in-memory caches — updater.remove_file_from_state() drops parser caches, import maps, and Rust path caches
  3. Re-parse the file — Language-specific parsers and process_generic_file rebuild the AST representation
  4. Re-compute CALLS edges — All CALLS edges are erased, resolution caches reset, and updater._process_function_calls() rebuilds relationships
  5. Flush to Memgraph — ingestor.flush_all() persists new nodes and edges

This pipeline guarantees graph consistency even during rapid edit bursts.

Command-Line Configuration Options

Start the real-time watcher through realtime_updater.py with these parameters:

python realtime_updater.py <repo_path> \
    --host <memgraph_host> \
    --port <memgraph_port> \
    --debounce <seconds> \
    --max-wait <seconds>
Option Description Default
--debounce, -d Quiet period before processing; set to 0 to disable 5s (DEFAULT_DEBOUNCE_SECONDS)
--max-wait, -m Hard upper bound on wait time; must be ≥ --debounce 30s (DEFAULT_MAX_WAIT_SECONDS)
--batch-size Cypher statements per Memgraph transaction From settings.resolve_batch_size()

The CLI validates that max_wait ≥ debounce; if violated, it logs a warning and auto-aligns values (see main lines 101-106).

Practical Configuration Examples

High-Responsiveness Mode (Demos/Development)

For near-instant feedback during active development:

python realtime_updater.py /path/to/repo --debounce 2 --max-wait 10

This reduces the quiet period to 2 seconds and caps total wait at 10 seconds.

Conservative Background Mode

For CI or background monitoring with fewer graph writes:

python realtime_updater.py /path/to/repo --debounce 10 --max-wait 60

Programmatic Python Configuration

Embed the watcher directly with custom timers:

from realtime_updater import CodeChangeEventHandler, GraphUpdater, MemgraphIngestor
from pathlib import Path

repo_path = Path("/path/to/repo").resolve()
ingestor = MemgraphIngestor(host="localhost", port=7687)
updater = GraphUpdater(ingestor, repo_path, parsers=[], queries=[])

# Aggressive 3s debounce with 20s safety ceiling

handler = CodeChangeEventHandler(
    updater,
    debounce_seconds=3,
    max_wait_seconds=20,
)

Disabling Debounce (Legacy Behavior)

Process every filesystem event instantly:

python realtime_updater.py /my/repo --debounce 0

With --debounce 0, the handler bypasses all timer logic at dispatch lines 42-45.

Key Source Files Reference

File Purpose
realtime_updater.py Watchdog observer, debounce logic, CLI entry point
codebase_rag/graph_updater.py Core service for parsing, state management, and updates
codebase_rag/constants/core.py Timing defaults and system constants
codebase_rag/tests/test_realtime_debounce.py Debounce behavior validation suite
codebase_rag/services/graph_service.py MemgraphIngestor for database writes

Summary

  • Real-time updates in code-graph-rag use a watchdog-based watcher with configurable debouncing to balance responsiveness and efficiency.
  • Hybrid debounce strategy combines a quiet-period wait (--debounce, default 5s) with a hard ceiling (--max-wait, default 30s).
  • Five-step pipeline ensures atomic, consistent graph updates: delete old data, clear caches, re-parse, re-compute edges, flush to Memgraph.
  • Fine-tune via CLI or Python API based on your latency requirements and write-frequency tolerance.

Frequently Asked Questions

What happens if I set --max-wait lower than --debounce?

The CLI detects this misconfiguration, emits a warning, and automatically adjusts max_wait to equal debounce. This validation runs in main around lines 101-106.

Can I run the real-time watcher without debouncing entirely?

Yes. Pass --debounce 0 to disable all timer logic. The CodeChangeEventHandler.dispatch method then processes each filesystem event immediately through the bypass branch at lines 42-45.

Does the watcher handle rapid multi-file edits correctly?

The debounce mechanism operates per-file, so simultaneous edits to different files trigger independent update cycles. Each file's changes are collapsed based on its own event timing, preventing redundant re-parsing.

Where are the default timing values defined?

DEFAULT_DEBOUNCE_SECONDS (5) and DEFAULT_MAX_WAIT_SECONDS (30) are constants in codebase_rag/constants/core.py lines 68-71, imported by realtime_updater.py.

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 →