The daemon and thin-client sessions
One process owns the audit log, the filters and the queue; every supervised session is a client of it over loopback HTTP.
grith runs one daemon per machine. It owns everything that has to be single-writer or shared, and every supervised session talks to it as a client. There is no second mode, no embedded fallback and no way to run a session without it.
What the daemon owns
- The audit database, as the exclusive writer. The lock is an
flockheld for the process lifetime, so the kernel releases it however the process dies. Everything else -grith audit,grith log,grith digest- opens the database read-only and says so. - The security proxy and all 18 filters, including the compiled secret-pattern set that is expensive to build and pointless to build twice.
- The digest queue.
- The session registry, the canary registry and the learned reputation table.
- The HTTP and WebSocket server on
127.0.0.1:3141, which serves the dashboard.
Because there is one filter registry and one reputation table, two sessions on the same machine score consistently and learn from each other's approvals.
Sessions are thin clients
grith exec, grith supervisor and grith reputation are thin-client commands. They
find the daemon, authenticate, and do their work through it.
The transport is loopback HTTP with a bearer token, not a unix socket. The token lives in
~/.config/grith/daemon.token at mode 0600 and is rotated on every daemon start; a
client that gets a 401 re-reads it from disk, so a daemon restart heals a live session
rather than killing it.
Inside a supervised session the digest store, the audit sink and the session registration are all remote shims. A supervised call is scored by asking the daemon over that channel, which is why the audit log is complete and the reputation table is shared.
grith exec fails closed
There is no in-process fallback daemon. If grith exec cannot reach a healthy,
version-matched daemon, it refuses to start and your tool is never spawned. The failure
modes it distinguishes are worth knowing:
| Condition | Meaning |
|---|---|
SpawnFailed | The daemon could not be started |
PortOwnedByForeignProcess | Something that is not grith holds port 3141 |
VersionMismatch | A daemon of a different build is running |
TokenRejected | The IPC token did not authenticate |
AuditQuarantined | The audit chain failed verification - no session is admitted |
AuditReadOnly | The audit database cannot be written |
AuditQuarantined is the sharp one. If the hash chain is broken, the daemon refuses to
admit sessions rather than write new records onto a chain it cannot vouch for.
grith audit diagnose is read-only and works in exactly that state.
Setting server.enabled = false makes grith exec refuse outright.
Starting and stopping
Most people never start it by hand. grith exec, grith run and the REPL auto-start a
daemon if one is not already listening, re-execing themselves detached and polling until
a daemon of the same version answers.
The difference between an auto-started daemon and grith daemon start is what happens
when the last session ends:
- Auto-started: a watchdog polls the session registry every 5 seconds and shuts the daemon down 30 seconds after the registry empties.
- Explicitly started: idle shutdown is disabled. It stays up until you stop it.
A separate reaper runs regardless, evicting sessions whose root process is dead and whose heartbeat has gone stale.
grith daemon stop asks over the authenticated shutdown route first and falls back to
SIGTERM. The command is also spelled grith dashboard - a retained alias, and several
messages the binary prints still use it. Both work.
Runtime files
All under ~/.config/grith/:
| File | What it is |
|---|---|
dashboard.pid | The daemon's pid and port |
daemon.token | The IPC bearer, 0600, rotated on every start |
dashboard.token | The browser token, 0600, stable across restarts |
daemon.json | Instance id, pid, port, version, protocol version, audit database path |
reputation.toml | The learned reputation table |
daemon.token and daemon.json are removed on exit. dashboard.token is deliberately
kept, so a browser tab you already paired stays authorised across a restart.
Capacity
supervisor.max_concurrent_sessions defaults to 4, but the licence caps it: a community
licence allows 2 concurrent supervised sessions, paid plans allow 64. The effective
limit is the lower of the two, which on a stock paid install means the config value of 4
is what bites, not the licence.
Capacity is reserved before your tool is spawned, so a rejection can never arrive after code has already run.