Inside a Running Herdr Session

Herdr's central architectural question is not how to draw a split terminal; it is how to keep a live terminal process owned by a background server while one or more clients view and control it. This chapter follows one keystroke and one screen update through that system, then explains how sessions, workspaces, tabs, panes, and scoped IDs turn the live runtime into something a human or script can safely address. The payoff is a precise answer to a common operational confusion: reorganizing a pane can change its place in the workspace without changing the process doing the work.

A keystroke crosses an ownership boundary

Imagine an agent is running in a pane and a person types a short instruction. The person is looking at a Herdr client, but the client is not the owner of the agent process. It captures the interaction allowed by the current mode, sends the intended input toward the Herdr server, and renders the server's view of the terminal. The server resolves the target pane, writes input to the pane's terminal runtime, and lets the process inside that runtime receive it.

A pseudo-terminal, or PTY, is the operating-system interface that makes a child process believe it is connected to a terminal. It carries both directions of the interaction: bytes written by the program become terminal output, and input written by Herdr becomes what the program reads from its terminal. Herdr's pane runtime owns that live terminal, its I/O channels, the child-process identity, terminal parsing, and background tasks such as detection. The exact implementation is deliberately below the user-facing workspace model, but the ownership consequence is direct: the agent is not attached to the pixels of the client window.

The return path is just as important. The agent writes output to the PTY. Herdr's runtime consumes that output and updates its terminal representation, including the visible screen and available recent content. The server's event loop drains internal events and API requests, schedules work, and renders the current application state into a surface that a client can receive. A client may render that surface in a physical terminal, while a headless server can keep maintaining the runtime without reading a person's keyboard or drawing directly into a physical screen.

This creates two different kinds of state. The first is live runtime state: the PTY, child process, parser, output buffers, detection tasks, and channels that must remain active for the agent to keep working. The second is application state: which workspaces and tabs exist, which pane is focused, what labels are attached, and how a layout is arranged. Herdr keeps the live terminal runtime outside its pure workspace state. That is not needless separation. A running shell cannot be reduced to the path it started in, and a terminal parser cannot be recreated merely by remembering that a pane once occupied the right side of a split.

The separation also clarifies what a stale client means. If a network connection or client fails, the visible surface may stop updating even though the server and its PTY continue. Reconnecting can restore the view of the live runtime. The reverse failure is possible too: a client can remain open while the server or child process has stopped. A familiar window is therefore not proof that work is still running. The reliable question is which server owns the target and what that server reports about its runtime.

Herdr's default headless server is built for exactly this distinction. It listens for its API and client connections, initializes or restores PTYs, runs its event loop, renders into memory, streams frames to attached clients, and routes input back through the same pipeline. It is designed to continue after client disconnect. This is why the same project can be approached from a local terminal, from an SSH session on the machine where the work lives, or through a local UI bridged to a remote server. The attachment point changes; the process owner does not automatically change with it.

The model is easier to reason about as a set of interfaces rather than as a list of source files:

classDiagram direction LR class Client class Server class Session class Workspace class Tab class Layout class Pane class TerminalRuntime class Agent Client --> Server : attaches and sends input Server --> Client : renders terminal surface Server *-- Session : owns namespace Session *-- Workspace : contains Workspace *-- Tab : contains Tab *-- Layout : describes topology Layout --> Pane : positions Pane --> TerminalRuntime : attaches TerminalRuntime --> Agent : may contain

The arrows are not all the same kind of relationship. A client attaches to a server and exchanges views and input. The server owns the session namespace and the live terminal runtimes. A workspace, tab, and layout organize where panes appear; the terminal runtime is what keeps an actual process connected. An agent is an occupant that Herdr may recognize inside that terminal. Keeping those relationships distinct prevents a UI concept such as “the selected pane” from being mistaken for a second process owner.

It also explains why “detaching” is a narrow operation. When a client detaches, the client-side surface disappears, but the server still owns the PTY and child process. A later client can attach and receive the current state. When the server is explicitly stopped, that ownership ends and the pane processes are normally gone. The difference is not a user-interface convention; it follows from which component still holds the live runtime.

Topology is a control surface, not a file tree

The server needs more than a bag of PTYs. A person must be able to say which work belongs together, which arrangement they want to view, and which terminal should receive an action. Herdr expresses those decisions through nested scopes. A session is an independent Herdr server namespace. A workspace is a project, task, or investigation container within that session. A workspace contains tabs, and each tab contains a layout of panes. A pane is the addressable location of a real terminal.

The scopes are related, but they are not interchangeable. A session is the boundary between independent Herdr runtimes. A workspace gives a project a place in the sidebar and a stable identity. A tab lets that project maintain separate arrangements—for example, one view for agents, another for logs, and another for a server or review. A pane is where a shell, test runner, editor, server, or coding agent actually runs. The same pane can be visually moved or resized without turning its occupant into a different process.

A new workspace automatically receives a first tab and root pane. That default is more than a convenience for first launch. It means a workspace is immediately usable and gives both people and automation a concrete terminal from which to continue. Herdr can then split that pane right or down, create another tab, or move a pane into a different tab or workspace as the work changes. The layout is a binary-space-partition tree: a split records two child regions and a ratio, while a pane occupies a leaf. The tree describes arrangement; it does not contain the shell's execution state.

That distinction produces useful operations. Swapping two panes changes their positions in the same tab while preserving the split shape, ratios, pane identities, and running processes. Moving a pane to another tab or workspace changes its topology and may change its workspace-qualified public ID, but the underlying terminal and process remain alive. Zooming changes which portion of a tab is presented without requiring the hidden panes to stop. These are not merely visual effects. They let a person reorganize the workspace around a process that is still compiling, waiting, or conversing with an agent.

There is a limit to what topology can promise. A layout operation that creates a new pane must also create a new terminal, and that spawn can fail; Herdr's tab logic is designed to roll back the layout change rather than leave a half-created split. A layout exported and applied later can reproduce structure, labels, working directories, and optional commands, but it does not preserve live PTYs, scrollback, or running processes. The same fact appears in the persistent session snapshot: it can record the shape and metadata of the workspace, but it is not a serialized copy of every program inside it.

Addressability is the other half of topology. Herdr exposes public handles such as a workspace, tab, or pane ID, and the CLI and socket API use them to select objects. Those handles are stable within one server and are not derived from whatever row happens to be visible. A script should capture the ID returned by a creation or move operation rather than guess it from an example. This matters when the workspace is reordered, when a pane is moved, or when another client changes focus.

Scope is a safety property here. Two separate servers can both contain a pane that looks like w1:p1, and two remote machines can both have an agent named reviewer. The string is not globally unique. Selecting a remote machine in a TUI does not retarget a CLI command already running inside an existing pane; that command keeps its inherited session and socket context. A remote command must use the intended machine's route and rediscover IDs there.

Cross-workspace moves make this visible. The moved process stays alive, but its public pane ID is assigned in the destination workspace. Herdr reports both the previous and current handles so a caller can update its target. The process's inherited caller context can keep --current safe for commands issued from inside that terminal, and a live agent name can continue to resolve to the occupant. That does not make the old public ID a universal alias. It means Herdr preserves enough context for the process that moved, while asking other callers to use the authoritative new result.

Multiple clients add another layer without changing ownership. Each client can view a workspace or tab and interact with the server. Presentation choices such as focus and local theme belong to the view that is attached, while the server remains responsible for the live pane and process. If two clients look at the same tab, their interactions can affect shared layout decisions; if they look at different tabs, each surface can follow the relevant view. None of this creates a second shell or a second agent conversation. It creates additional eyes and control paths over one runtime.

Named sessions are the point at which the separation becomes a deliberate namespace choice. If all a person needs is another project, another workspace is usually the lighter unit. A named session is for a completely separate runtime with its own panes, workspaces, sockets, and persisted state. The choice affects where the server listens and where its session data lives, so it is also part of target routing. Treating a named session as a mere label would make it too easy to send an action to the wrong server.

The running-session model therefore has a simple diagnostic sequence. First identify the server or session that owns the work. Then identify the workspace and tab that contain the target pane. Finally resolve the pane or live agent handle from that server and ask whether the intended operation changes topology, sends input, reads output, or waits on a lifecycle state. This sequence is more reliable than acting on whichever terminal is currently visible.

With that ownership and addressability model in place, Herdr can add meaning to a terminal without hiding it. The next problem is not where an agent lives, but how the system decides whether the occupant is working, blocked, idle, or finished—and how much confidence each of those labels deserves.

References