Lifecycle
Follow the daemon from launch to shutdown, and see which goroutines run concurrently in between.
Startup sequence
runServer launches the current executable with --daemon in a new session, redirects stdout and stderr to daemon.log, and polls for the endpoint file every 100 ms for up to 10 seconds. runServerDaemon then initializes in this order:
- Write
runtime.uidwith a new UID, the PID, and the start time. A failure, including another live daemon, is logged as a warning and startup continues. - Initialize the keychain context for service
kuradb. - Construct the OpenAI embedder; a missing credential exits the process.
- Open
global.db, preload query embeddings of the expected dimension, and register the hook that persists new query embeddings. - Initialize the gse tokenizer and the process-wide vector cache.
- Load the database registry from
db.json; a read error exits the process. - For each registered database: ensure its directories and
~/Kura_{db}symlink, opendata.db, create its vector bucket, and rebuild the bucket from embedded, non-dismissed rows. - Start one embedding scheduler and one watcher per loaded database.
- Start the HTTP server on
127.0.0.1, writeendpoint, and wait forSIGINTorSIGTERM.
A per-database failure before data.db opens skips that database. A vector-bucket failure leaves the database queryable by keyword but starts no watcher or embedder for it.
Concurrency model
| Work | Goroutines | Coordination |
|---|---|---|
| Watcher | One per loaded database, 10-second ticker | Snapshot writes to record.json run in a goroutine guarded by a mutex |
| Embedding scheduler | One per loaded database, 5-second ticker | Batches of up to 64 pending chunks |
| Search | Keyword and semantic branches run concurrently when no target is set |
sync.WaitGroup in search.Search, shared by all transports |
| Query-cache persistence | One short goroutine per new query embedding | 5-second timeout per write to global.db |
| HTTP server | One listener goroutine plus a shutdown watcher | Context cancellation |
SQLite provides a read pool of eight connections and a separate write connection through go-sqlkit. The vector cache uses a cache-level and a per-bucket RWMutex; the query cache has its own RWMutex.
Shutdown
A termination signal cancels the shared context. Each part reacts independently:
| Part | Action |
|---|---|
| HTTP server | Removes endpoint, then calls Shutdown with a 5-second timeout |
| Watchers and embedders | Leave their select loops |
| Main goroutine | Clears runtime.uid, closes every per-database connector, then closes global.db |
The main goroutine does not wait for the HTTP shutdown or the loops to finish, so the process can exit before in-flight requests complete. kura stop sends SIGTERM and escalates to SIGKILL if the process is still alive after 5 seconds.