The daemon
The single durable writer: what it owns, why it outlives the app, and the rules for adding state to it.
The daemon is the process that makes NALA's durability claims true. Clients come and go; the daemon holds the state.
What it owns
| Domain | Contents |
|---|---|
| Sessions | Terminal and agent sessions, scrollback, attachment state |
| Orchestration | Plans, plan revisions, runs, delegation edges |
| Work | Durable tasks and their states |
| Artifacts | Markdown outputs attached to work |
| A2A | Agent-to-agent messages and delivery status |
| Channels | Durable rooms and the mention inbox |
| Principals | Registered identities sessions run as |
The single-writer rule
The daemon is the single durable writer for everything above. Two clients can watch the same run without corrupting it because neither writes directly — they issue RPCs and the daemon serialises the result. Where several participants could write the same record, a writer lease decides who may.
IMPORTANT
If you are adding state that must survive a restart, it belongs in the daemon. State kept in the renderer is a view, and views are disposable. This is the most common architectural mistake in this codebase.
Why it survives app close
The daemon is a separate process from the Electron app. Closing the window detaches a client; it does not signal the daemon. That single fact explains:
- Terminals and scrollback returning after a restart
/detachleaving an agent working- The CLI seeing tasks created in the desktop
- A run continuing while no window is open
Instance isolation
Everything the daemon owns is namespaced by the data-suffix environment
variable: data directory, socket path, auth token, and pipes. Development builds
use -dev.
A client only ever reaches the daemon matching its own suffix. When the CLI reports that it cannot find a daemon while the app is plainly running, mismatched suffixes are the usual cause — not a crashed daemon.
Transports into it
| Caller | Path |
|---|---|
| Electron main | Named pipe / unix socket via a daemon client |
| TUI | Direct daemon RPC client |
| CLI | Pipe server, RPC router |
| MCP hosts | MCP server → pipe |
RPC method strings are namespaced (nala.*) and grouped into families —
sessions, tasks, artifacts, settings, providers, A2A, and more.
Structured errors
Failures carry codes, not just prose. Transport codes
(DAEMON_NOT_CONNECTED, DAEMON_TIMEOUT, DAEMON_CANCELLED,
DAEMON_RPC_ERROR) are a separate union from domain errors, and each carries a
retryable flag.
This matters for callers: "the daemon is unreachable" and "the model refused" should never be handled by the same branch.
Adding to the daemon
Before adding a new RPC family, check:
- Does it need to be durable? If not, it may belong in a client.
- Who is the writer? If more than one participant can write, it needs a lease.
- What is the failure code? New failure modes need to fit the structured error union, not return a bare string.
- Is it observable? State that cannot be inspected from
nala doctoror a task listing is state that will be debugged by guesswork.