Each host runs one daemon (enowxd, or enowx daemon on a desktop), which owns its sessions. The app connects to it and can come and go.
Quitting the app leaves the daemon running: agents keep working, automations run, and the next launch finds every session as it was. To have them stop:
- Quit and stop all agents: in the app menu on macOS (⌥⌘Q) and in the command palette everywhere.
- Settings → Stop this device's agents when enowX quits, off by default.
- Stop all agents in the command palette stops the connected host's agents without quitting.
Stopping an agent ends its program the way hibernation does (daemon.stopAgents): the session stays, showing what it showed, and its agent comes back on its conversation when the session is shown. Plain terminals keep running.
Connecting installs the daemon at ~/.enowx-app/bin/enowxd and starts it through enowxd bridge. Where the server runs systemd, it starts as the user's service (~/.config/systemd/user/enowxd.service, core/userservice; a daemon of another state folder, ENOWX_HOME set elsewhere, gets a unit of its own, enowxd-<hash>.service, and never replaces the user's), and the app turns on lingering for that user while preparing the server (hostconn/linger.sh, as root, or with sudo or doas without a password). The daemon then starts again after a reboot without anyone connecting, its sessions come back, and automations keep running.
Where there is no systemd (Alpine, containers) or lingering couldn't be turned on, the daemon runs as a process as before: it lives until the server reboots, and the next connection starts it. Why a service didn't start is written to ~/.enowx-app/enowxd.log.
scripts/test-service.sh checks it in an Ubuntu container booted with systemd.
The app's main thread is watched: asked to answer every second, it leaves a report in watchdog/ under the data folder when it takes over 0.7 seconds (at most once a minute: lag) or 3 seconds (a stall). A report holds every goroutine's stack, whether a frame was being built, and what was on screen as of its last answer (the page, session, agent and style, never what's in them). The newest 30 stay. watchdog/app.log and watchdog/daemon.log get a line every 5 minutes with the Go heap, its objects, goroutines and slow frames, so growth over hours shows, and a mark when memory jumps. A panic's stack goes to app.log or enowxd.log; a process killed from outside leaves no report, and its next start says lastRun=unclean. All the logs, their limits and how to get them: diagnostics.md.
A session's program runs on a terminal the daemon holds, so a daemon that stops ends it. So an update waits while that would cost anything (updates.md). When it does stop (asked for, Settings' Restart the daemon, a server's reboot), what can come back does:
| What ran | After a restart |
|---|---|
| An agent, idle | Back on its conversation, in its mode, model and permissions |
| An agent mid-turn | The same, and told once to carry on from where it stopped, checking what already ran. Its prompt isn't sent again. Only when the daemon stopped in an orderly way: a crash notes nothing |
| An agent run by hand in a terminal | Back on its conversation (its --continue, or its --resume ID) and then the shell, with its mode, model and permission flags alone, so never a prompt or a key. A one-shot run (-p, exec) isn't brought back |
| A command in a terminal | Ended, and never run again. The terminal says what it was, the app says so as it connects, and the shell comes back |
| GOD of Agents' board, lines waiting for it and tasks queued | Kept on disk, and carried on |
Each step goes in the daemon's log (restart.*, diagnostics.md).
What each system gets today is the same on macOS, Linux and Windows, and on remote hosts. Keeping programs alive across a daemon's replacement isn't built yet: a small process holding the terminals would do it on macOS and Linux, and Windows' ConPTY needs its own answer.
core/cacheclean lists only screenshots/ (not those a note names), reviews/, unslop/, trash/, intercept/profiles/, canvas-chat/, notes-chat/, app-backup/, worktrees/, watchdog/ reports, older rotated logs and saved outputs by their exact names (diagnostics.md), and thins canvases' versions. Nothing else can be removed: the vault and its keys (vault/, vault-run/, configs-run/), cloud/, accounts, settings/, sessions.json, agent-sessions/, notes, canvases, tokens/, goa-board.json, usersync/, link-bases/, enowx.db, Traffic's certificate, sessions' terminal history, and anything a running session or process uses.
Notes' files nothing names aren't offered, as they go by themselves after 7 days (notes.SweepAssets), and the decisions' log keeps itself short.
Closing a session hangs up its terminal, which ends its program and foreground, but an agent's servers and helpers in process groups of their own don't hear it. So the daemon writes down each session's process tree every 15 seconds and just before a stop (session-procs.json), and on a close, a hibernation or a switch of account asks what's still there to end, then forces it 3 seconds later. Each process goes by its PID and start time together, so a PID used again is left alone, and only processes on that list are ever stopped: never one no session started, the daemon itself or what started it.
A process a session left and nobody looks after (its session closed or ended, or it moved out from under the session) shows in Resource usage under Left running, with Stop. What a daemon that crashed left behind ends when the next one starts.
On Windows a session's processes are told by ENOWX_SESSION in their environment, read from each process's memory, as its own user may (core/procstat/procstat_windows.go). Closing the console is what asks them to end, and what still runs 3 seconds later is forced. A parent started after its child is a PID used again, so it doesn't count as the parent. A process run elevated, which the daemon may not read, isn't seen. Environment tags (reap) also apply on Linux and Windows, where they catch what left a tree before it was seen. Resource usage measures processes on Windows too now.
enowX keeps its state in ~/.enowx-app (transport.HomeName), on this device and on servers; ENOWX_HOME names another (make dev uses ~/.enowx-dev). Its command line is enowx-app, linked at ~/.local/bin/enowx-app. Before, both were ~/.enowx and enowx, which another app uses too: the first start of a newer app or daemon stops the daemon there and moves enowX's own files across (hosts, projects, sessions and their history, worktrees, settings), repairing git's links to the worktrees, and leaves the rest of ~/.enowx alone.
The app connects to every saved host as it starts, in the background, while it shows the one it was on (ui/watch.go): each over a connection of its own, at once, so its sessions list under On other hosts as soon as it answers, without opening it. Opening one of those sessions connects there.
- Nothing waits on it. Each host connects on its own goroutine, and one that hasn't answered in 2 minutes (an install of the daemon on a slow link included) counts as offline. The app's start and the host shown never wait for them.
- Quietly. A host that is down shows "offline · retrying" in the Hosts list, with no dialog; it is tried again after 15 seconds, then twice as long each time up to 5 minutes, and at once when the window comes back into sight or the machine wakes from sleep (its wall clock then jumps ahead of its monotonic one).
- Never asking. What would ask the user waits until they open the host: a password not saved, a key's passphrase, a server key not trusted yet, whom to run as, a browser check (Tailscale SSH). The host shows "connect to sign in". A password saved in the keychain is read, once per run; on macOS the system may ask once to allow that, and a refused or missing one isn't asked for again that run.
- Connect at start, in Hosts › ⋯ › Edit…, is on by default. Off, the host connects only once the user opens it, and is followed in the background from then on until the app quits (
hosts.Host.NoStartConnectinhosts.json).
What makes a far server feel close, built in rather than through mosh (which serves one shell over UDP, and needs mosh-server and an open port on the server):
- Keepalives. Each SSH connection sends one every 15 seconds (
transport/ssh.go), so routers and NATs keep it while idle; three unanswered in a row close it, so a connection that died with the network is noticed at once. - Connecting again by itself. When a host's connection drops, the app tries again after 1 second and longer each time, up to 30, without asking anything, until the host answers or the user goes elsewhere; the view comes back as it was, the sessions having kept running.
- Resizes once settled. A window growing or zooming resizes its sessions once the size held for 120 ms, so their programs redraw once.