Terminal multiplexers

Some runs are better watched than reported. An attended run and a take-over both put a live claude session in front of you — one you can read and steer directly — by opening it in a terminal multiplexer pane. loopkeep drives four: herdr, tmux, zellij, and cmux. The policy gate is unchanged inside a pane; the multiplexer only decides where the session shows up, not what it may do.

Choosing a multiplexer

One rule decides where a run opens: if the place is settled, open there; if it isn't, loopkeep asks. Three places can settle it, and the first one that names a multiplexer wins:

  1. the workflow's mux under x-loopkeep,
  2. multiplexer.id in this project's .loopkeep/config.yaml,
  3. multiplexer.id in config.yaml.
# .loopkeep/config.yaml (this project) or config.yaml (everywhere)
multiplexer:
  id: tmux # herdr | tmux | zellij | cmux — omit to leave it unset, "none" to disable

A multiplexer you name doesn't have to be running already. If it's installed, loopkeep starts it and opens the pane there — so a nightly attended run doesn't depend on you having left tmux up.

none is a decision, not a blank: loopkeep opens no pane at all. take-over inherits your current terminal, and an attended run takes the no-terminal path below — which, for a workflow that asks for attended by name, means asking you rather than quietly going headless.

If none of the three names one, loopkeep looks at what's actually running — and only at that, since it starts a multiplexer only when you named which one. One multiplexer running means the place is settled after all, and the run opens there. More than one, and it can't be settled without you: the run is held and your inbox asks which to use. Your answer is saved as this project's default, so you're asked once and never again — change it later under Project Settings, Terminal.

Naming a multiplexer that isn't installed doesn't quietly land you in a different one. The run is treated as having no terminal, which puts it on the on_no_host path below.

Where panes open

An attended run's pane opens at a stable location: a space (the multiplexer's session or workspace), a tab (a window within it), and a pane named after the run id — so you can find run run_000123 by name. A take-over of that run lands in the same place.

Space and tab each resolve on their own, first match wins:

  1. the workflow's mux.space / .tab under x-loopkeep,
  2. multiplexer.options.space / .tab in this project's .loopkeep/config.yaml,
  3. the same keys in config.yaml,
  4. the built-in defaults loopkeep and automation.
# config.yaml — defaults for every attended pane
multiplexer:
  options:
    space: loopkeep
    tab: automation

Both layers are editable in the app: Settings for everywhere, Project Settings → Terminal for one project.

Which session a pane opens in

herdr can run several named sessions side by side, each with its own windows. Which one a pane opens in resolves in this order:

  1. the workflow's mux.session under x-loopkeep,
  2. multiplexer.options.session in config.yaml,
  3. whichever session is running — one running session means that one; several means the one you used most recently,
  4. herdr's default session, when none is running.
# config.yaml — the session every attended pane opens in
multiplexer:
  options:
    session: work

A named session that isn't running is started for you. You can also pick the session in the app — Settings for the global default, Project settings for one project — from a list of your sessions, or type a new name.

Unlike space and tab, there is no built-in session name. Name none and panes follow you: they open wherever you are already working. Name one and they always open there — and if that session isn't running, loopkeep starts it rather than quietly using a different one. The other multiplexers have no named sessions, so they ignore this key.

When there's no multiplexer to open in

Two situations leave an attended run with nowhere to go: nothing is running and nothing is named, so there is nothing for loopkeep to start; or the named multiplexer can't be brought up — it isn't installed, or its server refused to start. Either way the run is treated as having no terminal.

What happens then is what on_no_host says, and asking for attended by name makes it ask: loopkeep holds the run and asks in your inbox — run it headless now, or wait for a terminal. Waiting really waits — the run stays held, taking no concurrency slot, and starts attended by itself once a multiplexer is running. Only on_no_host: headless, and auto mode (which never asks), drop to headless without a word.

What each mode needs

Multiplexers differ in what loopkeep can ask of them. An attended run needs one that can open a pane, inject text into it, and report whether it's still alive — the full set. A take-over only needs to open a pane, since you drive it by hand from there. One that can't meet an attended run's needs doesn't count as a terminal for it, so the run takes the no-terminal path above. take-over, needing less, falls back to your current terminal.