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_PATTERNS and IGNORE_SUFFIXES defined in codebase_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:

  1. Delete stale module and file nodes from Memgraph
  2. Clear in-memory caches for the affected file
  3. Re-parse the file (for creates/modifies) and generate generic file nodes
  4. Re-compute all CALLS edges, flushing resolution caches
  5. 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 events
  • self._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, FileDeletedEvent for precise graph mutations
  • Low runtime overhead — Thread-based observation keeps the main process available for parsing and database I/O

Summary

  • realtime_updater.py provides 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:

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 →