Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Run the daemon

Add the optional daemon tier for lower latency, fallback detection of hookless agents, and deduplicated notifications. The daemon is strictly additive: every surface works without it, so run it only when you want what it adds.

The three tiers

The daemon is tier 3, the top of the three detection tiers (polling floor, hook tier, daemon); for what each adds and why none is required, see the detection model. Which tier a given pane is actually at, and why it is not higher, is what tma doctor reports (below).

Two setups where it stops being optional

“Strictly additive” assumes something else is driving the poll. Two common tmux setups leave nothing driving it, and on those the daemon is the only thing keeping state fresh.

Detached sessions. #() status jobs run only while a client is drawing the status line. A session started with new-session -d and never attached — a long-running agent you check on now and then, a fleet started by a script — has no client, so the #(tma status) driver never fires and stamped state ages until you run a tma command by hand. tma doctor names it:

clients: none attached — `#()` status jobs only run while a client draws the status line, so nothing polls this server (run the daemon or attach a client)

With a daemon running, the same line reads differently, because the gap is covered:

clients: none attached — `#()` status jobs do not run detached; the daemon is keeping state fresh meanwhile

The other fix is an external poll: tma status --format plain from a bar or a cron job reaches a detached server perfectly well (see Drive an external bar). Either works; doing neither is what leaves the server unpolled.

status off. Turning the status line off kills both tmux-side channels at once: #(tma status) never runs (no status line to expand), and display-message notifications have nowhere to render. Doctor flags it as a warning:

status:  the global `status` option is off — the `#(tma status)` driver never runs and `display-message` notifications are invisible (`tmux set -g status on`)

Here the daemon covers the freshness half by itself, and for the notification half point notify at a channel that does not need a status line — bell, osc, or a command hook (see Set up notifications).

Both of these do count against tma doctor --exit-code: a server with no attached client and no daemon covering it is a warning, and so is status off. What --exit-code deliberately ignores is a missing daemon on its own, which is a runtime choice rather than a misconfiguration, so a wired agent sitting at tier 2 gates green. The two above are the cases where something that should be driving the poll is not.

Start it

tma daemon --ensure spawns a detached daemon for the current tmux server if none is running, then exits. It is idempotent, so it is safe to run from a shell rc or a tmux hook:

$ tma daemon --ensure
$ tma daemon --ensure     # already running: still exit 0, no second daemon

To run it in the foreground instead (for debugging), use tma daemon with no flag. There is one daemon per tmux server, keyed by socket.

Autostart

If you ran tma install-keys (or tma init), this is already done. The managed keybindings file ends with a launcher tmux runs when a server loads its config:

run-shell -b 'tma --socket-path "#{socket_path}" daemon --ensure >/dev/null 2>&1'

#{socket_path} is what makes it multi-server-safe: each server starts a daemon for itself, never for the default server. It applies from the next server start, so run tma daemon --ensure to catch the one you are in now. Under Home Manager the same line comes from programs.tma.daemon.autostart = true.

tma install-keys --no-daemon omits it. Pair that with --check --no-daemon, or a plain --check will report the missing line as drift.

The other route, for people who do not let tma write their tmux config, starts the daemon the first time you use any surface (ls/status/jump/picker/watch/wait/subscribe):

[daemon]
autostart = true

That one is off by default. Either way the daemon stays strictly additive: nothing breaks without it, every surface falls back to polling.

The cadence knobs (sweep_secs, quiet_ms, demote_edges, and others) also live under [daemon]; see Configuration. They apply only while the daemon runs.

Reload config without a restart

A running daemon re-reads its config and manifests on tma reload (or a SIGHUP), swapping every derived setting in place while keeping its live state:

$ tma reload
tma: reloaded the daemon's config + manifests

If no daemon is running for this server it is a clean no-op (one-shot surfaces and the picker reload on their own each cycle):

$ tma reload
tma: no daemon running for this server (nothing to reload; one-shots and the picker reload on their own)

An invalid config or manifest on reload is kept-old and logged: a reload never kills or corrupts a running daemon. A user manifest that fails to parse is skipped and logged individually; the daemon keeps serving on the rest of the set.

tma reload re-reads config and manifests, not the binary.

Pick up an upgraded tma

After upgrading tma, the daemon already running is still the old build. It stays that way indefinitely — nothing replaces it on its own, and install-hooks repointing your hooks at the new binary does not change which build answers them. tma doctor says so, and gates on it under --exit-code:

$ tma doctor
daemon:  running (<tmpdir>/tma/<server>.sock)
         version 0.1.0 differs from this CLI (0.2.0) — `tma reload` only re-reads config and manifests; run `tma daemon --restart` to put this build in its place

With the automatic restart turned off, the fix is one verb:

$ tma daemon --restart
tma: stopped the running daemon
tma: daemon restarted (0.2.0)

--restart is unconditional and works in both directions: run it from the older binary to go deliberately back. It stops the daemon with SIGTERM and never escalates to SIGKILL (the daemon reaps its tmux -C control clients only on a clean exit), and it starts a daemon even when none was running.

tma init and tma install-hooks make the same offer for you when they find a resident daemon of another build, on the same confirm-before-changing terms as their config writes.

Most of the time you will not have to run it at all. [daemon] restart_on_upgrade is on by default, so an older resident daemon is replaced before your next surface (ls, status, the picker, watch, wait, subscribe), on the next tma event a hook fires, and on tma daemon --ensure. It never starts a daemon that was not running: that is autostart’s job, and it is still off.

Strictly newer replaces older: equal never restarts, and an older tma never touches a newer daemon, so two installs sharing a server cannot take turns evicting each other’s daemon. See Configuration for the full rule and its fail-safes.

To turn it off and replace daemons by hand instead:

[daemon]
restart_on_upgrade = false

What a restart costs, and what a skewed daemon costs

The daemon records its version next to its pid in the lock file, which is where doctor reads it from.

A restart is cheap. The socket is gone for roughly 35 ms, then bound again but not yet draining for up to two seconds while the daemon runs its control-mode behaviour probe. Nothing is lost across either window: a hook that cannot reach a daemon stamps the pane itself, tma wait sees the connection close and degrades to polling, and notification de-duplication lives in a pane option that outlives the process.

Leaving the skew in place is the more expensive choice, and not only in latency. An event the old daemon’s manifests map to nothing is refused rather than acknowledged, so the firing hook stamps it itself and only the latency is lost. But an event the old daemon maps to the old verdict is acknowledged happily — and the client then skips its own stamp. That is a wrong transition, not a late one, and no reload fixes it.

See the effective tier

tma doctor reports the tier per pane and the daemon’s status. With the daemon running, wired panes reach tier 3:

$ tma doctor
daemon:  running (<tmpdir>/tma/<server>.sock)
ambient: NOT polling — nothing invokes `tma status`; add `#(tma status)` to status-right (required ambient driver)
clients: 1 attached
watch:   no watcher running (`tma watch` advertises for SIGUSR1 nudges)
hooks:   after-select-pane ✓  session-window-changed ✓
wrapper: ~/.cargo/bin/tma-hook ✓
agents:  6 loaded, no issues
actions: 4 loaded, no issues

panes (2):
  %0   claude     s1:0.0       tier 3   working (hook, 40.6s ago)
       hooks: wired
  %1   claude     s2:0.0       tier 3   blocked (hook, 40.6s ago)
       hooks: wired

agents: is the manifest roster tma loaded; panes: is what it found running.

Without the daemon the same panes show tier 2 with the reason “daemon not running”. tma doctor --json emits the same diagnosis as a versioned schema for scripting.

Nested tmux sessions

There is one daemon per tmux server, so a nested tmux gets its own. Agents running inside an inner server are invisible to a tma on the outer one: their processes are not in the outer pane’s tree, and their state options live on the inner server. Doctor says so rather than leaving the pane unexplained:

nested:  1 pane(s) running a multiplexer client — agent state lives on the inner server; run tma there
  - %3 s1:0.1 (tmux)

Run tma from inside the nested session and it targets the inner server without any flag: tmux sets $TMUX in every pane it owns, and the client reads its socket from there. So tma daemon --ensure, tma ls, and the keybindings all work in the inner session exactly as they do in the outer one, each with its own daemon.