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

Working in tmux

Heph is built to be left alone. A turn takes minutes; you start one, go and do something else, and come back when the model needs an answer or has finished. tmux is where that way of working lives, and heph is made to fit it: the session takes one pane and stays out of the others, it announces itself when it needs you, and it can be found from anywhere. This chapter is how the pieces are meant to go together. Nothing here is required; heph runs in a bare terminal too.

heph tmux list now selects the current project's live panes, including from subdirectories. Use heph tmux list --all-projects to see all panes, or heph tmux list 'auth|login' to match names and saved branch metadata. --match is the explicit regex spelling. Unknown pane identities appear only globally, labeled as unknown projects. status and next remain global attention tools.

One pane per project

Give each project a heph of its own, in a pane or window of that project's tmux session, and leave it running. The conversation persists, so there is no cost to leaving the pane open between prompts, and a heph that is already open is one you can hand something to without ceremony.

A typical window:

┌──────────────────────────────────┬──────────────────────────────────┐
│ $ nvim src/parse.rs              │ You: the parser rejects @tests/  │
│                                  │      fixtures/odd.toml; why?     │
│                                  │                                  │
│                                  │ ok view src/parse.rs             │
│                                  │ ok grep quoted                   │
│                                  │ >> shell cargo test parse  0:04  │
│                                  │   | running 12 tests             │
├──────────────────────────────────┤                                  │
│ $ git log --oneline -5           │                                  │
│ $                                │  message ─────────────────────── │
│                                  │ >                                │
│                                  │ main | running shell 0:04 | ...  │
└──────────────────────────────────┴──────────────────────────────────┘

Your editor and a shell on the left, heph on the right. The two sides look at different trees: your editor is in your working tree; heph's model works in a worktree, a copy that mirrors your tree as it was when the turn began. Nothing the model does shows up on the left until you accept it in review, and nothing you type on the left is seen by the model until the next turn begins, when the worktree is refreshed from your tree.

That separation is what makes it safe to keep editing while a turn runs. If you change a region the model also changed, review sets the model's hunk aside and keeps yours (Rejections, Comments, and the Next Round).

Looking at the worktree

Sometimes you want to see what the model has built before review opens, or run something against it. !cmd runs a command in the worktree from inside heph (Running Commands Yourself); !pwd prints the worktree's path, and you can cd there in another pane:

$ cd ~/.local/share/heph/wt/6746d59d741e4d0e/01a0c4e6-.../
$ git diff HEAD

It is an ordinary git checkout. git diff HEAD there is the model's change so far; git log shows your project's history beneath it. Read all you like; writing there by hand is not a good idea while a turn runs, since the model will not know.

Tools that take the screen

When review opens in lazygit, or Ctrl-E opens your editor, heph suspends its screen in the pane and gives the pane to the tool, as vim does for :!. The tool runs in the same pane, at the pane's size; when it exits, heph redraws. Nothing else in the window is disturbed. A pane wide enough for lazygit's three columns is a pane wide enough for heph.

Being reached

The status bar at the bottom of heph's pane shows what the turn is doing. Once you switch to another window it is out of sight, so heph also sends a notification whenever it needs you or a wait ends: a question, a permission prompt, a review opening, a turn finishing. Under tmux without allow-passthrough, that is a tmux display-message, which appears in the status line of whichever window you are in. With passthrough, it is your terminal's own notification.

Nothing is sent while you are looking at the pane. tmux itself knows whether heph's pane is the active pane of an attached client, and heph asks it.

Several hephs

With a heph per project and several projects open, the question becomes "which one needs me?". Every heph publishes its state as tmux pane variables, and heph tmux reads them across all sessions. Publication runs on a bounded background worker; slow or unavailable tmux does not block keyboard input. Failures retry after five seconds, each subprocess has a one-second deadline, and exit cleanup waits at most two seconds. Two lines in tmux.conf put the count of waiting hephs in your status bar and bind a key that jumps to the one most in need of you.

heph tmux: finding the one that needs you

Put this in tmux.conf (heph tmux conf prints it):

set -g status-interval 5
set -g status-right '#(TMUX="#{socket_path},#{pid},0" heph tmux status) | %H:%M '
bind H run-shell 'TMUX="#{socket_path},#{pid},0" heph tmux next --client "#{client_name}" --pane "#{pane_id}"'

Now the status bar says heph 1 waiting / 1 busy (heph 0 waiting / 0 busy when no heph is running; the waiting count turns red when anything waits), and prefix H jumps to the pane most in need of you: a question outranks a permission prompt, which outranks a review, which outranks a finished turn. Press again to cycle among equals. The binding identifies the client and pane that invoked it, so detach/reattach and multiple attached clients do not confuse the jump. It switches that client, window, and pane, in any session.

Both commands explicitly select the tmux server that invoked them, rather than relying on environment changes after attaching locally or over SSH. If you used the older snippet, replace it and reload your configuration with tmux source-file ~/.tmux.conf (or your configuration's actual path). These commands also work with older heph versions; they use no new CLI flags.

Counts come from pane variables, without waiting for the session's JSON status reply. A live heph stays counted while an editor or review tool owns its pane, and differences in the running session's status schema do not affect counts. Linked windows count only once, no matter how many sessions or clients see them. If querying tmux itself fails, the segment says heph status unavailable rather than silently disappearing or reporting zero activity.

$ heph tmux list
 PANE         PROJECT     REF   STATE       WAITING  SPEND
 work:1.1     api-server  main  permission  1        $0.31
 docs:0.0     handbook    main  done        0        $0.02

heph tmux opens no database and reads no config; it runs from tmux's own status line in milliseconds. A pane whose heph has exited is ignored even if its variables were left behind.

The variables

VariableValue
@heph-stateasking, permission, review, paused, done, idle, or working
@heph-refThe conversation's human label (<unnamed> when absent)
@heph-ref-idIts full UUID, used for switching rather than the label
@heph-waiting1 when the session is waiting on you, else 0
@heph-projectThe project directory's name
@heph-sockThe session's control socket

Changed values are published on state transitions; missing options are repaired every five seconds, even while idle. Repairs leave existing options untouched. This avoids periodic tmux redraws that can clear your terminal's text selection, even in other sessions with no heph pane. Options are unset when heph exits. Read-only control-socket status queries do not redraw heph's screen. You can use the variables in your own status line: #{@heph-state} in a pane-format string.

The control socket

Each session listens on $XDG_RUNTIME_DIR/heph/<pid>.sock, mode 0600, and removes it on exit. It accepts two lines and nothing else:

  • status answers with the session's state as JSON: the active conversation, its state, its spend, whether it is waiting.

  • switch NAME asks the session to switch conversations. Switching arrives in a later phase; today the session acknowledges and says so.

heph tmux list uses status to show spend; heph tmux next uses it to check which conversation is active. There is no way to send a prompt, approve a call, or read a conversation through the socket.