Real-Time Filesystem Updates with Watchdog Integration in Code-Graph-RAG
Code-Graph-RAG keeps its knowledge graph synchronized with live codebases by monitoring filesystem changes through Watchdog and propagating updates instantly to Memgraph.
This architecture eliminates manual re-indexing after every edit. The realtime_updater.py module orchestrates the entire pipeline—from detecting file events to rebuilding graph nodes and edges—making Code-Graph-RAG suitable for interactive development workflows.
How the Watchdog Integration Works
Starting the File System Observer
The start_watcher() function in realtime_updater.py (line 33) initializes a Watchdog Observer that recursively monitors the target repository directory. This observer runs in a dedicated thread, ensuring non-blocking operation while the main process handles graph mutations.
python realtime_updater.py /path/to/repo
Event Handling with CodeChangeEventHandler
The CodeChangeEventHandler class extends FileSystemEventHandler and implements three critical responsibilities:
- Path filtering — Ignores files matching
IGNORE_PATTERNSandIGNORE_SUFFIXESdefined incodebase_rag/constants.py - Event debouncing — Coalesces rapid successive saves into single updates
- Change dispatching — Triggers the five-step graph update pipeline
Source: realtime_updater.py → CodeChangeEventHandler.__init__
Hybrid Debounce Strategy for Performance
Filesystem events fire aggressively during saves—especially from IDEs that write temporary files or auto-formatters that touch multiple files. The handler implements a hybrid debounce combining two thresholds:
| Parameter | CLI Flag | Default | Behavior |
|---|---|---|---|
| Debounce interval | --debounce (-d) |
5 seconds | Quiet period after last event |
| Maximum wait | --max-wait (-m) |
30 seconds | Hard ceiling before forced processing |
When an event arrives, the handler stores it and restarts the timer. If the max-wait limit expires, processing triggers immediately; otherwise, the debounce interval governs. Setting --debounce 0 disables this layer entirely, restoring legacy process-on-every-event behavior.
Source: realtime_updater.py → dispatch
CLI Configuration Examples
# Aggressive batching for high-frequency edits
python realtime_updater.py /path/to/repo --debounce 2 --max-wait 10
# Disable debouncing entirely
python realtime_updater.py /path/to/repo --debounce 0
The Five-Step Graph Update Pipeline
The _process_change_locked() method (line 30) executes atomic updates through a deterministic sequence:
- Delete stale module and file nodes from Memgraph
- Clear in-memory caches for the affected file
- Re-parse the file (for creates/modifies) and generate generic file nodes
- Re-compute all
CALLSedges, flushing resolution caches - Flush accumulated Cypher statements to Memgraph in a single transaction
This sequential design prevents orphaned edges and ensures referential integrity within the knowledge graph.
Source: realtime_updater.py → _process_change_locked
Thread-Safety Architecture
Two distinct locks protect concurrent operations:
self.lock— Guards debounce state including timers and pending eventsself._update_lock— Serializes expensive graph-update transactions
The dual-lock design allows rapid event accumulation without blocking, while ensuring that only one database transaction executes at a time. This prevents race conditions that could leave CALLS edges pointing to non-existent nodes.
Source: realtime_updater.py → __init__
Programmatic API Integration
Embed the watcher in larger Python applications:
from realtime_updater import start_watcher
start_watcher(
repo_path="/path/to/repo",
host="localhost",
port=7687,
debounce_seconds=3,
max_wait_seconds=15,
)
All CLI parameters have direct equivalents in the start_watcher() signature.
Initial Bootstrap Sequence
Before entering the watch loop, _run_watcher_loop() (line 70) invokes GraphUpdater.run() for a full repository scan. This guarantees complete base state—subsequent incremental updates build upon accurate foundation rather than partial data.
Source: realtime_updater.py → _run_watcher_loop
Supporting Files and Responsibilities
| File | Role |
|---|---|
realtime_updater.py |
Orchestrates Watchdog observer, debounce logic, and update pipeline |
codebase_rag/graph_updater.py |
GraphUpdater service for Memgraph interaction, parsing, and state |
codebase_rag/constants.py |
Debounce defaults, ignore patterns, Cypher templates |
codebase_rag/logs.py |
Centralized event logging (WATCHER_DEBOUNCE_ACTIVE, CHANGE_DETECTED) |
codebase_rag/cli_help.py |
Help strings for CLI options |
Why Watchdog Was Selected
- Cross-platform compatibility — Native operation on Linux, macOS, and Windows without platform-specific extensions
- Granular event types — Distinguishes
FileCreatedEvent,FileModifiedEvent,FileDeletedEventfor precise graph mutations - Low runtime overhead — Thread-based observation keeps the main process available for parsing and database I/O
Summary
realtime_updater.pyprovides the complete real-time filesystem update pipeline for Code-Graph-RAG- Hybrid debouncing balances responsiveness against database load through configurable quiet-period and max-wait thresholds
- Five-step atomic updates ensure graph consistency: delete, clear, re-parse, re-compute edges, flush
- Dual-lock architecture separates event handling from transaction serialization
- Full bootstrap scan precedes watching to establish complete initial state
- Watchdog enables cross-platform, low-overhead file monitoring without native dependencies
Frequently Asked Questions
What happens if I set debounce to zero?
Setting --debounce 0 disables the debouncing layer entirely. Every filesystem event triggers immediate graph processing, matching the legacy behavior. This maximizes responsiveness but increases database load during rapid edit sequences.
How does Code-Graph-RAG prevent duplicate processing during IDE auto-save?
The hybrid debounce strategy coalesces rapid successive events. When an IDE writes a file multiple times in quick succession (e.g., formatting on save, then actual save), the timer resets with each event. Processing only occurs after the configured quiet period or when the max-wait ceiling is reached.
Can I monitor multiple repositories simultaneously?
The current start_watcher() implementation accepts a single repo_path. For multiple repositories, instantiate separate CodeChangeEventHandler and Observer instances with distinct Memgraph database names, or run multiple realtime_updater.py processes.
What file patterns are ignored by default?
The IGNORE_PATTERNS and IGNORE_SUFFIXES constants in codebase_rag/constants.py filter common non-source paths including version control directories, build artifacts, virtual environments, and cache files. Modify these constants or extend the handler's filtering logic for repository-specific exclusions.
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 →