grithdocs

Daemon and thin-client sessions

Why grith optionally runs as a daemon, and what changes when it does.

The simplest way to use grith is one-shot per session: grith exec spawns the agent, supervises it, exits when the agent exits. That works fine for a single terminal, single agent, single user.

A persistent daemon is what you run for everything else — multiple concurrent sessions, the dashboard, notification channels, team sync. The daemon is a long-running grith process that owns the shared subsystems (audit, digest, reputation, server) and lets thin clients (each grith exec invocation) talk to them. Even a lone grith exec uses a daemon: when you haven't started one, it auto-starts a throwaway daemon that shuts down when the session ends.

Auto-started vs persistent daemon

Every grith exec runs against a daemon — the only question is whether it starts one for you or connects to one you already have running. There is no daemon-less mode: if a trusted, version-compatible daemon can't be established, grith exec fails closed and starts nothing.

Auto-started (no daemon already running):

grith exec -- claude
# No daemon found → grith starts one in the background, then registers this session

The daemon grith starts idle-shuts-down once the session ends. For the life of the session it still owns the shared subsystems — one audit database, one digest queue, one reputation table, one session registry.

Persistent (grith daemon start first):

grith exec -- claude
# Detects the running daemon, registers as a thin-client session

A persistent daemon outlives individual sessions, so:

  • The audit log is one central SQLite database across every session.
  • Digest is one central queue, reviewable from any terminal or the dashboard at http://127.0.0.1:3141.
  • Notification channels (configured once in the daemon) deliver review prompts.
  • The reputation table is shared across all sessions.
  • Concurrent sessions register together and count against your plan's concurrent-session limit.

What the daemon owns

  • The server (crates/grith-server/) — REST API, WebSocket, dashboard.
  • The digest queue — single SQLite table, atomic decisions.
  • The audit database — central, indexed, retention-managed.
  • The reputation table — saved to disk every 5 minutes by default.
  • The notification dispatcher — owns channel state and rate-limit budgets.
  • The license — refreshed in-band on Pro plans.
  • The canary registry — same tokens visible to every session.

A daemon restart can drop in-flight digest items (the process freeze in the thin client times out and auto-denies). On the Pro plan, restarts are clean — the daemon flushes pending state to disk before stopping and recovers it on start.

Thin-client sessions

When a daemon is running, grith exec becomes a thin client:

grith exec --profile claude-code -- claude

# Internally:
1. Connect to daemon UNIX socket
2. POST /ipc/sessions  → daemon assigns session id, returns config snapshot
3. fork + exec the supervised target, attach ptrace
4. On each interesting syscall, POST /ipc/proxy/evaluate with the call shape
5. Apply daemon's decision (allow / queue / deny)
6. On exit, DELETE /ipc/sessions/{id}

The supervisor (ptrace machinery) still runs in the thin-client process — it has to, because that's the process attached to the agent. But the filter pipeline runs in the daemon, which means scoring decisions can use global context (other sessions' recent behaviour, the central reputation table) that the thin client wouldn't have.

Starting and stopping the daemon

grith dashboard start   # background; prints connection details + opens/pairs the browser
grith dashboard status
grith dashboard pair     # (re)authorise a browser
grith dashboard stop

grith dashboard start spawns the daemon in the background and returns. It's also auto-started for you by grith run and grith exec (those instances idle-shut-down with the session; an explicit start persists until you stop it).

The daemon is local-only by default — binds to 127.0.0.1:3141. To expose it (for the dashboard from another machine, or for a webhook receiver), either configure [server.tls] with cert + key, or run a reverse proxy in front. See Reverse proxy & TLS.

When you'd not keep a persistent daemon

  • Single-user laptop, one terminal at a time — the daemon grith exec auto-starts is enough.
  • Containerised agents where each container is its own short-lived session.
  • CI runs where the agent runs once and exits.

When you'd keep a persistent daemon:

  • More than one supervised session in flight.
  • You want the dashboard.
  • You want notifications routed through Slack / Telegram / etc.
  • You're on Pro and using team policies / sync.

The auth model

The daemon binds loopback-only, but loopback reachability is not treated as authorisation:

  • IPC routes (/ipc/*, /server/shutdown) require the daemon bearer token (~/.config/grith/daemon.token, 0600), constant-time compared.
  • Browser routes require the per-server dashboard token — mutations and sensitive reads on every request, gated via the x-grith-csrf header (or ?token= for WebSockets). Low-sensitivity status stays open. The browser receives the token through the pairing flow, never the terminal.

Exposing beyond loopback additionally requires TLS (or a reverse proxy). For team setups, the Pro plan adds short-lived bearer tokens issued by the dashboard for cross-machine review. See Trust boundaries and Notification security.

See also

Last updated: 2026-05-14Edit this page on GitHub →