Skip to content

Daemon Architecture

NestWeaver uses a background daemon process to own the database write lock, enabling multiple AI tools, CLI commands, and IDE integrations to query the same graph concurrently without lock contention. The daemon is the central coordination point — it handles writes, runs the filesystem watcher, generates embeddings, and serves all read queries through a gRPC interface.

LadybugDB uses a single-writer, multiple-reader (SWMR) architecture. Only one process can hold the write lock at a time, but any number of readers can query concurrently. Without a daemon, each CLI invocation or MCP server would need to compete for the write lock, causing failures when multiple tools try to access the same database.

Clients talk to one daemon CLI MCP Web UI Daemon writes, watch, gRPC LadybugDB one .lbug file
The daemon holds the write lock. CLI commands, MCP, and the web UI read through it at the same time.

The daemon solves this by:

  • Owning the write lock exclusively — all mutations (indexing, embedding generation, interaction tracking) go through the daemon
  • Serving reads via gRPC — multiple clients query the same database concurrently without blocking each other
  • Running background tasks — the filesystem watcher and embedding generation run inside the daemon process, keeping the graph current without manual intervention

The daemon follows a hands-off lifecycle — you rarely need to manage it directly.

Auto-start: The first CLI command that needs the database (e.g., nestweaver context, nestweaver mcp) automatically starts a daemon if one isn’t already running. The nestweaver-client crate uses an flock-based mechanism to prevent race conditions when multiple processes try to start the daemon simultaneously.

Idle timeout: The daemon self-terminates after 1 hour of inactivity (no active connections or in-flight queries). This prevents orphaned daemon processes from consuming resources on machines where NestWeaver isn’t actively in use.

Version mismatch restart: When a client connects to a running daemon and detects that the daemon binary is an older version than the client, it automatically restarts the daemon with the newer binary. This ensures that cargo install or npm updates take effect without manual daemon management.

Terminal window
# --db is required when NESTWEAVER_DB is unset. A bare daemon command
# does not fall back to ./nestweaver.lbug.
nestweaver daemon status --db ./nestweaver.lbug
nestweaver daemon stop --db ./nestweaver.lbug
nestweaver daemon start --db ./nestweaver.lbug

The daemon communicates with clients over gRPC on a Unix domain socket. Socket paths are derived from a stable hash of the database path, placed under $XDG_RUNTIME_DIR/nestweaver/<instance>/ on Linux or ~/.local/state/nestweaver/<instance>/ on macOS.

The nestweaver-client crate provides the client interface. It handles connection establishment, auto-start, version checking, and reconnection transparently. Most users never interact with the gRPC layer directly — the CLI and MCP server both use nestweaver-client internally.

The daemon dispatches read RPCs through the same tool dispatch layer used by the MCP server, avoiding any duplication of business logic. Every read query available via MCP is also available through the daemon’s gRPC interface.

The daemon is more than a database proxy. It hosts several background subsystems:

  • Filesystem watcher — monitors indexed repositories for changes and triggers incremental re-indexing with debouncing. Started via nestweaver watch or automatically when the daemon launches with watch mode enabled.
  • Embedding generation — lazily loads the embedding model (sentence-transformers/all-MiniLM-L6-v2, ~80MB) and generates vector embeddings for symbols, notes, and headings. Metal-accelerated on Apple Silicon (~5x faster than CPU).
  • BM25 index — maintains the Tantivy full-text search index as a sidecar alongside the database.
  • Git activity scoring — computes file-level churn scores and co-change pairs from git history, writing results to sidecar files.
  • PageRank cache — loads and maintains cached global PageRank scores.

The daemon maintains two GraphStore handles: a read-write handle for mutations and a separate read-only handle for all read queries, ensuring that write operations don’t block concurrent reads.

Published commands route through the daemon, including in CI. --no-daemon exists only so unpublished CI tests can open a database directly. Do not pass it, and do not set NESTWEAVER_NO_DAEMON. On a published binary the flag is ignored.

On macOS, the recommended way to run NestWeaver is the native .app bundle. It provides a menubar-only application (no Dock icon) that manages the daemon lifecycle:

  • Menubar status icon — quick access to the web UI and daemon status
  • Metal GPU acceleration — the app provides GUI session context so the daemon gets full Metal access (~5x faster embeddings: 7ms vs 37ms)
  • Automatic lifecycle — starts the daemon on launch, terminates on quit, no orphaned processes
  • Crash recovery — auto-restarts the daemon up to 3 times on unexpected crashes
  • Daemon coexistence — detects if a daemon is already running (via CLI or launchd) and connects to it instead of starting a second instance
  • Web UI at http://127.0.0.1:9377 — opens automatically on launch
app/build.sh
# calls Cargo and does not fetch Ladybug itself.
eval "$(scripts/fetch-lbug-source.sh)"
bash app/build.sh
open target/release/NestWeaver.app

On Linux and headless macOS environments, use nestweaver daemon start or let auto-start handle it.

The daemon loads and manages several sidecar files stored alongside the main .lbug database. These files use a naming convention of <database-name>.<suffix>:

FileDescription
*.lbugMain LadybugDB database (graph nodes and edges)
*.pagerank.jsonCached global PageRank scores
*.manifests.jsonParsed package manifests for workspace resolution
*.gitactivity.jsonFile-level churn scores from git history
*.cochange.jsonCo-change file pairs with Jaccard scores
*.tantivy/BM25 full-text search index directory
*.embeddings.binVector embeddings for semantic search
*.interactions.jsonAgent interaction memory (opt-in)

Sidecar files are loaded on daemon startup and updated as background tasks complete. They can be safely deleted — the daemon will regenerate them on next startup (though this may take time for large codebases).