Concepts
Architecture
Sigil is one long-lived process per machine, and a set of thin clients that talk to it.
Claude Code hooks ─┐ sigil CLI ├── Unix socket ──┐ MCP stdio server ─┘ │ ├──► sigil daemon ──► your database MCP HTTP clients ──┐ │ ├ connection pool the dashboard ├── HTTP :7777 ───┘ ├ embedding cache │ ├ write queue paired devices ────┴── Iroh ────────────────┤ RPC registry └ Iroh endpointWhy a daemon
Section titled “Why a daemon”A hook has to be fast, because it runs on every prompt you type.
Cold-starting Node, opening a Postgres connection, loading the embedding client, and running a search costs 200–400ms. Doing that per prompt is something you’d feel on every single interaction.
The daemon keeps all of it warm. Hooks and CLI calls become a Unix socket round trip against an already-connected process, which is why search is 33ms rather than 300.
It also solves three things a per-invocation model can’t:
- One writer. A write queue serializes mutations, which matters enormously for the embedded PGlite database and still matters for Postgres under a burst of parallel ingests.
- Shared caches. The embedding cache is worthless if every process starts empty.
- One peer endpoint. Iroh needs a stable identity and a live endpoint; you can’t boot one per CLI call.
sigil daemon statussigil daemon start [--foreground]sigil daemon stopsigil daemon restartsigil daemon logs [--follow]sigil daemon open # the dashboardsigil daemon url # its URL, with auth tokenIt auto-starts on first use. You only need these for explicit lifecycle control.
Transports
Section titled “Transports”Four ways in, all reaching the same RPC registry.
| Transport | Address | Used by |
|---|---|---|
| Unix socket | ~/.sigil/sock | Hooks, CLI, the stdio MCP server |
| HTTP | 127.0.0.1:7777 | Dashboard, HTTP MCP clients |
| MCP stdio | spawned per client | MCP clients that prefer stdio |
| Iroh | peer-to-peer | Paired devices |
HTTP is loopback-only by default and gated by a bearer token at ~/.sigil/gui.token. Change http.host only if you know what you’re exposing.
MCP: two shapes
Section titled “MCP: two shapes”Sigil’s MCP tools are registered once, in a side-effect-free builder, and mounted by two different entry points:
- stdio (
src/mcp/server.js) — spawned per client. Registered through a stable shim at~/.sigil/bin/sigil-mcp. - HTTP (
/mcpon the daemon) — one long-lived transport, bearer token, URL that never changes.
sigil register # stdio launcher (default)sigil register --http # the daemon's HTTP transportsigil register --print # print config, write nothingHTTP is the more portable option for clients that support it — the URL survives Node version switches and reinstalls without any re-registration.
Launcher shims
Section titled “Launcher shims”~/.sigil/bin/ holds stable shell shims that resolve the real binary at runtime.
Every integration points at a shim, never at a versioned package path. That’s what keeps memory working across nvm/fnm switches, reinstalls, and updates — the thing agents were told about doesn’t move.
When memory mysteriously stops working after a Node upgrade, this is what broke, and this is the fix:
sigil connectIt re-pins the shims and re-syncs every client’s generated config. It touches no database, no provider, and no API key.
The database layer
Section titled “The database layer”Two drivers behind one interface.
| Mode | What it is |
|---|---|
embedded | PGlite — Postgres compiled to WASM, running in-process against ~/.sigil/db. Zero install. Single machine only. |
local / docker / url | Real Postgres 13+ with pgvector |
Migrations are ordered .cjs files run with Knex, applied on sigil migrate and automatically during sigil update. Pooled connection URLs are detected and migrations routed to the direct endpoint, because pooled endpoints can’t run DDL reliably.
Embedding dimension is a build-time constant (1024) rather than configuration, so the vector(N) columns and every provider are pinned together and can never drift into a dimension mismatch.
Configuration
Section titled “Configuration”~/.sigil/config.json is the single source of truth. Not .env — an earlier .env design was replaced because stale files broke upgrades.
The properties that matter:
- Sparse on disk. Only explicitly-set values are written. Defaults live in code and merge at read time, so defaults track the code instead of freezing into a file.
- Versioned.
schemaVersionplus ordered migrations run on load; unknown keys are dropped. - Atomic writes. Temp file plus rename,
chmod 600. - Live. The in-memory cache updates on every patch, so the daemon sees new values without a restart — except for the settings explicitly marked as needing one.
The only environment variables left are things that physically cannot live in the file: HOME (which locates the file), per-process identity for IPC, the PGlite path redirect for tests, the release-lane selector, and OS/debug flags.
Background work
Section titled “Background work”Ingestion runs as durable jobs rather than inline. Entity extraction and relation discovery are follow-up jobs by default, so a write returns as soon as chunks and facts are searchable.
ingest.workerConcurrency (default 2) sets the pool. Queue depth is visible on the dashboard’s Engine screen.
The managed-session engine
Section titled “The managed-session engine”An opt-in subsystem that keeps warm claude CLI workers in tmux, amortizing agentic cold-start across many ingest calls.
Off by default — when disabled, the provider transparently falls back to the one-shot path, so nothing depends on it. Only meaningful on a host with both tmux and the claude CLI. See Configuration.
Failure behaviour
Section titled “Failure behaviour”The invariant: a broken memory layer must never block a working prompt.
- Every hook wraps in a top-level try/catch that fails silently. Errors append to
~/.sigil/.hook-errors.log. - After five unacknowledged errors in 24 hours,
sigil doctorexits non-zero so CI can catch it. Acknowledge withsigil doctor --ack. - A circuit breaker stops repeated daemon spawn attempts rather than retrying forever.
- Concurrent
claudeprocesses are capped by a semaphore (llm.maxClaudeProcs, default 4). This exists because an unbounded ingest fan-out once produced 1600+ live sessions. - Network provider calls have a 60s timeout, so a hung socket can’t wedge the daemon or a hook.
Platform support
Section titled “Platform support”macOS, Linux, and WSL. Native Windows is unsupported and refuses to install rather than half-working — the launcher shims and Claude Code hooks are POSIX shell scripts, and the daemon’s path model assumes a POSIX environment.