Documentation
¶
Overview ¶
Package backend defines the mechanical layer: the vocabulary every terminal multiplexer backend speaks, and the interface it implements.
The rules this package encodes are specified in docs/terminal-behavior.md, and the shapes it marshals are specified in docs/api.md. Both are normative; this package is where their vocabulary becomes types.
Index ¶
- Constants
- Variables
- func CheckClientTag(tag string) error
- func ControlLetter(k Key) byte
- func ExitCode(code Code) int
- func FunctionNumber(k Key) int
- func IndexedPaneID(target string) bool
- func IndexedTabID(target string) bool
- func IndexedWorkspaceID(target string) bool
- func MetaLetter(k Key) byte
- func MetaSymbol(k Key) byte
- func NumericPaneID(target string) bool
- func PrefixedPaneID(target string) bool
- func PublicNumber(s string) (int, bool)
- func ResolveTarget(target string, shape PaneIDShape, panes PaneLister) (string, error)
- func TypedOf(err error) bool
- type Agent
- type AgentKind
- type AgentLister
- type AgentSession
- type AgentUsage
- type AttachSpec
- type Attachment
- type Backend
- type Capabilities
- type Capture
- type Client
- type ClientLister
- type Code
- type CreateSpec
- type Error
- type Expect
- type Focuser
- type Key
- type Liveness
- type Name
- type Outcome
- type Pane
- type PaneIDShape
- type PaneLister
- type PrefixReporter
- type Redrawer
- type Renamer
- type Role
- type ScreenMeta
- type ScreenOpts
- type Server
- type ServerLister
- type ServerStarter
- type ServerStopper
- type Session
- type State
- type View
- type ViewSpec
Constants ¶
const ( AgentWorking = "working" AgentIdle = "idle" AgentBlocked = "blocked" AgentUnknown = "unknown" )
Agent status values.
const ( // StatusSourceNative is a status the backend reported itself. StatusSourceNative = "native" // StatusSourceScreen is a status read off a capture of the pane. StatusSourceScreen = "screen" )
Agent status sources.
const ( // DetectedByNative is the backend's own agent detection: it saw the // agent as an agent, and the row carries its status and title. DetectedByNative = "herdr" // DetectedByCommand is the process-tree heuristic the ergonomic layer // applies where the backend has no detection of its own: a known // agent's name under the pane's PID, or in its foreground command where // there is no PID. DetectedByCommand = "command" )
Agent detection sources.
const SubmitSettle = 150 * time.Millisecond
SubmitSettle separates a text write from its terminator, so the terminator registers as a keypress on a paste-detecting consumer rather than becoming a literal newline inside its input box (behavior §4.5, §17.3).
It lives here because both layers that pace a terminator need the SAME gap: a backend whose spelling has no subcommand chaining paces it internally, and the run protocol paces its own injection above the interface. Two copies of one contract value is two places for it to drift.
Variables ¶
var ( ErrUsage = errors.New("usage") ErrNotFound = errors.New("not found") ErrTimeout = errors.New("timed out") ErrConflict = errors.New("conflict") ErrUnsupported = errors.New("unsupported") ErrBlocked = errors.New("agent blocked") )
The sentinels a caller matches with errors.Is (api §3). They carry no message of their own; they exist to be compared against.
There is deliberately no sentinel for CodeUnexpected. It is the catch-all — every error that carries no other code answers to it — so matching against it would say nothing a failed match against the other seven does not already say. Read CodeOf when the classification itself is the question.
Functions ¶
func CheckClientTag ¶ added in v0.26.0
CheckClientTag reports whether a caller's client tag is one a server holds: one to 128 bytes of UTF-8, with no control character. Anything else is CodeUsage, since the caller could have validated it (§12).
func ControlLetter ¶
ControlLetter reports the letter of a c-<letter> key, or 0 if the key is not one. The letter is returned lowercase.
func ExitCode ¶
ExitCode returns the process exit status for a code. An unrecognised code is CodeUnexpected's status, since a code this build does not know is precisely the "Olympus broke" case.
This mapping translates failures and nothing else. The two operations whose exit status deviates — run, which reports the command's own status, and attach, which reports its client's — handle that locally at their door (behavior §12.1). Teaching it here would leak run-specific meaning into code that every operation shares.
func FunctionNumber ¶
FunctionNumber reports the n of an f<n> key, or 0 if the key is not one.
Capped at 12: terminals encode higher function keys inconsistently, and accepting one Olympus cannot faithfully deliver would be worse than saying it is unknown.
func IndexedPaneID ¶ added in v0.2.0
IndexedPaneID is the shape used by backends whose pane ids name the window and the pane by number: "w<number>:p<number>".
Each number is spelled the way herdr spells every public id: base 32 over the alphabet 123456789ABCDEFGHJKMNPQRSTVWXYZ0, digits for the first nine allocations and letters from the tenth. The tenth pane of a workspace is "w1:pA", the tenth workspace is "wA", and the workspace counter survives a server restart, so a predicate that reads digits alone silently stops recognising ids on any server that has lived long enough. Measured.
Unlike the other two shapes this one is not structurally unambiguous — the backend that uses it will let a caller name a session anything, that spelling included. So the backend REJECTS such a name at creation rather than leaving resolution to guess, which is what turns "probably a pane" into "certainly a pane" here.
func IndexedTabID ¶ added in v0.4.0
IndexedTabID is the shape of a tab id on the same backend: "w<number>:t<number>". A tab is the backend's window, so this is the shape a window-addressing target takes there (§3.6).
func IndexedWorkspaceID ¶ added in v0.4.0
IndexedWorkspaceID is the shape of a workspace id on the same backend: "w<number>". A workspace is the backend's session, and its id is the name a workspace nothing has labelled answers to — so a session may not be CREATED with a name of this shape either, or its id and its name would name two different workspaces (§3.6).
func MetaLetter ¶ added in v0.24.0
MetaLetter reports the letter of an m-<letter> key, or 0 if the key is not one. The letter is returned lowercase.
Alt is not a bit on the byte the way control is: a terminal sends ESC and then the letter, which is how readline's M-b and M-f arrive.
func MetaSymbol ¶ added in v0.25.0
MetaSymbol reports the character of an m-<symbol> key, or 0 if the key is not one: a digit or punctuation mark, any printable ASCII character that is neither a letter nor a space.
The character is spelled as itself, however hostile it is to a shell, since no shell stands between a caller and the key. Space, the control range, DEL and anything past ASCII are not symbols: a key bar sends alt with a single printable byte, and those are either named keys already or not one keypress.
func NumericPaneID ¶
NumericPaneID is the shape used by backends whose pane ids are bare integers.
Safe only where a session name cannot be entirely numeric, or a target would be read as a pane when the caller meant a session of the same spelling. meja rejects such names outright — "session name must not be entirely numeric" — which is what makes the shape unambiguous there rather than merely unlikely.
func PrefixedPaneID ¶
PrefixedPaneID is the shape used by backends that mark pane ids with "%".
func PublicNumber ¶ added in v0.2.4
PublicNumber decodes one id segment: "1" is 1, "9" is 9, "A" is 10, "0" is 32, "11" is 33. It lives beside the shape so the alphabet has one home; a second copy is where the two would silently stop agreeing.
func ResolveTarget ¶
func ResolveTarget(target string, shape PaneIDShape, panes PaneLister) (string, error)
ResolveTarget turns a caller's target into a session name (behavior §10).
A target matching the backend's pane-id SHAPE addresses a pane, and is swapped for its owning session's name; any other target passes through unchanged. This exists in one place on purpose: every operation that compares a target against a session name — or keys a write lock on it — must resolve first, or a pane-id caller silently mismatches every name, which is the source of false "already gone" and false "died" reports.
A backend with no pane-id concept passes a nil lister or a nil shape, which makes every target ordinary. On such a backend "%7" is simply an unknown session name under the normal lookup: still not-found downstream, and never a crash here.
Types ¶
type Agent ¶ added in v0.10.0
type Agent struct {
PaneID string `json:"pane_id"`
SessionName string `json:"session_name"`
SessionID string `json:"session_id"`
// Agent is the agent's canonical name: one of the command heuristic's
// vocabulary (claude, codex, gemini, aider, opencode, goose, amp, cursor,
// pi, omp, copilot, devin, agy, cline, droid, kimi, kiro, kilo, hermes,
// qodercli, qwen, mastracode, maki, muse, grok), or whatever a
// natively-detecting backend reports.
Agent string `json:"agent"`
// Status is working, idle, blocked or unknown. Blocked is the agent
// waiting on a person — a permission prompt, a question. It is unknown
// wherever nothing showed a state: the listing MUST NOT invent one.
Status string `json:"status"`
// StatusSource is where a known status came from: native, the backend's
// own detection; or screen, read off a capture of the pane by the
// agent's manifest. Omitted when the status is unknown.
StatusSource string `json:"status_source,omitempty"`
// Title is what the agent is working on, where the backend reports it.
Title string `json:"title,omitempty"`
// Last is the one line of its own output the row stands for: what the
// agent last said, or, where the row is blocked, the question it is
// waiting on. Read off the pane by the agent's manifest regions, and
// only when the caller asked for it — it costs a capture per row.
// Omitted where it was not asked for, and where the screen had nothing
// to say.
Last string `json:"last,omitempty"`
// CWD is the directory the agent is working in.
CWD string `json:"cwd"`
// DetectedBy is how the row was found: the backend's own detection
// (herdr), which carries status, title and usage; or a known agent's
// name in the pane's process tree — its foreground command where the
// pane has no PID — which carries a status only where the pane's screen
// could be read, and never a title or usage.
DetectedBy string `json:"detected_by"`
// PID is the agent's own process, where one is known: on a
// command-detected row the process whose command named the agent; on a
// natively-detecting backend the pane's foreground process, which is
// the agent while it holds the terminal. Zero, and omitted, where no
// process is known — a pane with no pid, a match on the foreground
// command alone. A caller that wants to know how the agent was started
// or what it was given can go from here; the row itself says nothing
// about that.
PID int `json:"pid,omitempty"`
// Usage is the agent's quota readout where the backend reports one, in
// the order the backend lists it.
Usage []AgentUsage `json:"usage,omitempty"`
// AgentSession is the agent's own conversation, where the backend holds
// a reference to it: what the agent itself would take to pick the
// conversation up again. Nil, and omitted, where the backend has none —
// the row claims nothing, and a caller must not infer one.
AgentSession *AgentSession `json:"agent_session,omitempty"`
}
An Agent is a coding agent running in a pane, as far as the backend can tell (behavior §3.7). Every backend answers the listing; what a row can carry depends on how the agent was found, and DetectedBy says which.
type AgentKind ¶ added in v0.14.0
type AgentKind struct {
// Name is the canonical name an agent row carries.
Name string `json:"name"`
// Executables is every argv0 token that names this agent: the canonical
// spelling first, then the remaining aliases sorted. Matched against a
// lowercased base name with a wrapper suffix removed, so a path or a
// `.js` wrapper still matches.
Executables []string `json:"executables"`
// Packages is the package directories that identify this agent where no
// token is named after it — an npm install runs as
// `node …/@anthropic-ai/claude-code/cli.js` — and is omitted for an
// agent that has none.
Packages []string `json:"packages,omitempty"`
// Resume is the arguments that open this agent's own list of past
// conversations to pick one up again — only the picker, since which
// conversation is a choice made in the pane. Omitted for an agent
// whose way of resuming Olympus does not know.
Resume []string `json:"resume,omitempty"`
}
An AgentKind is one canonical name in the agent vocabulary the listing reports names in, with the tokens that identify it (behavior §3.7). It is a description of the detection table itself, not of anything running: it answers which agents Olympus knows, and by what executables.
type AgentLister ¶ added in v0.10.0
An AgentLister enumerates the agents a backend detects itself. It is optional in a different way from ServerLister: a backend without it is not unsupported, the layer above derives the rows from the pane listing instead (behavior §3.7). Implementing it means the backend reports status itself; its Capabilities MUST say so in AgentStatus, as MUST any backend whose panes can be captured, since the layer above reads status off the screen.
type AgentSession ¶ added in v0.15.0
type AgentSession struct {
Source string `json:"source"`
Agent string `json:"agent"`
Kind string `json:"kind"`
Value string `json:"value"`
}
An AgentSession is a reference to an agent's own conversation, as the backend stores it and exactly as it spells it: Source names who reported it, Agent the agent it belongs to, Kind whether Value is an id or a path.
type AgentUsage ¶ added in v0.10.0
An AgentUsage is one quota bar: a short label (5h, 7d, a model name) and the percent of it used, 0-100.
type AttachSpec ¶
type AttachSpec struct {
Role Role
// Supersede displaces any prior client. The backend performs its own
// supersession mechanism before returning the command — on some backends
// that needs both a guard and a sweep (behavior §8.5).
Supersede bool
Cols int
Rows int
// SessionClient asks for the multiplexer's own session client — with its
// selection, scrollback and copy — rather than a raw per-pane stream. Only
// herdr draws the distinction: its default attach is a bare terminal stream
// and its session client carries chrome, so the two are different clients.
// tmux, zmx and meja always hand their session client and ignore this
// field. When it is set the target names the backend's own session, which
// on herdr lives outside the socket-addressed panes Olympus resolves.
SessionClient bool
// Bare asks for a plain pane with no chrome. What that means is decided
// above this interface, per backend: on herdr it is the session client
// with its chrome hidden, so it arrives here together with SessionClient;
// on tmux the ergonomic layer attaches a view — already bare by
// construction (§9.3) — so a tmux backend never sees it set. A backend
// with neither reports CodeUnsupported before anything is spawned.
Bare bool
// BareView names the view a bare attach on tmux creates, instead of a
// generated name. A caller that has to drive the view while the attach
// runs — scroll it, focus a pane in it — needs to know its name, and an
// interactive attach has no channel to report one back. It MUST carry the
// reserved view prefix (behavior §17.1), or `view ls` and every sweep
// would miss it. Consumed above this interface; usage on any backend but
// tmux.
BareView string
// BareNoMouse creates the bare attach's view without mouse reporting, so
// a client that keeps its own text selection is not handed the wheel and
// the click. Consumed above this interface with BareView.
BareNoMouse bool
// ClientTag names the client a bare attach launches, instead of a
// generated tag, on a server that moves one client's view (§8.10). A
// caller that has to ask where its client is (§13.5) needs to know its
// name, and an interactive attach has no channel to report one back. It
// is CheckClientTag's shape, and usage wherever no tagged client is
// launched: any backend but herdr, an attach that is not bare, and a
// server that does not move one client's view.
ClientTag string
}
An AttachSpec is a complete attach request.
type Attachment ¶
type Attachment struct {
// Cmd is executed inside the PTY the engine owns.
Cmd *exec.Cmd
// Cleanup releases backend-side state the attach created, such as a view
// session to reap. It may be nil.
Cleanup func() error
// Notices are things the operator must be told about how this attach was
// set up, even though it succeeded — a supersession sweep that failed, for
// instance, which leaves prior clients attached. They go to the narration
// channel: a silent partial failure here is indistinguishable from a clean
// one, which is the precise thing worth reporting (behavior §8.5).
Notices []string
// Probe, when set, answers whether the thing the client was steered onto
// still exists. The engine polls it while the client runs and ends the
// attach when it answers absent: a client attached to a whole session
// rather than to its target does not end on its own when the target
// does, and would sit showing whatever the server focused next
// (behavior §8.10). Error answers are ignored — a server that cannot be
// asked is not a target that is gone, and a server that has gone away
// ends the client by itself. Nil means the client already ends with its
// target.
Probe func(ctx context.Context) State
// Settle, when set, is run by the engine once the client is up — it has
// painted and gone quiet — with the client's own input as `keys`. It is
// how a bare session client reaches its target on a herdr whose clients
// each keep their own view (0.9.0 and up): every client there follows
// the server's focus until it moves on its own, and a `workspace focus`
// on the server moves every client, so the target is reached the way a
// person reaches it, with the client's own workspace keys (measured,
// §8.10). An error ends the attach: a client left showing the wrong
// workspace is worse than none. Nil means there is nothing to do once
// the client is up.
Settle func(ctx context.Context, keys io.Writer, expect Expect) error
// SettleAfter, when set beside Settle, is a sequence the client writes
// once it is reading keys — herdr's client pushes the kitty keyboard
// protocol (`CSI > 7 u`) as it comes up, and reads the walk's keys a
// beat after — so the engine runs Settle a short beat after seeing it
// rather than waiting for the client to go quiet. A client on a
// workspace that never stops painting (an agent streaming) never goes
// quiet, and the wait ran to its cap on every such attach (measured:
// two seconds, against a quarter of one). Nil means quiet is the
// only signal.
SettleAfter []byte
// Go, when set, moves the live client onto another target on the same
// server, with the client's own keys as `keys`, the way Settle brought
// it onto its first: a caller whose stdin is a pipe asks for it with the
// in-band `go` control (§8.3, §8.10). The attachment's Probe follows the
// client, so the attach ends with the target it is ON, not the one it
// was made for. An error ends the attach as a failed Settle does. Nil
// means the client cannot be moved, and a go is ignored.
Go func(ctx context.Context, target string, keys io.Writer, expect Expect) error
// Focus, when set, puts the live client on a pane's tab with that pane
// focused and the tab not zoomed, as a click on the pane would leave it:
// a caller asks for it with the in-band `focus` control (§8.3, §8.10).
// The client stays on the tab rather than the pane, so the attach does
// not end when that pane does. An UNSUPPORTED error is dropped and said
// on stderr, as a go on an attach that cannot be moved is; any other
// error ends the attach as a failed go does. Nil means no pane can be
// focused this way, and a focus is ignored.
Focus func(ctx context.Context, target string, keys io.Writer, expect Expect) error
}
An Attachment is what to run in order to be attached, not something already running. The PTY, the signal handling and the terminal restore of behavior §8.2 belong to one shared engine; a backend contributes only the client command and whatever teardown that client leaves behind.
func (Attachment) Close ¶
func (a Attachment) Close() error
Close runs the cleanup, if there is one. It is safe on the zero Attachment so the engine can defer it unconditionally — behavior §8.8 requires a spontaneous attach exit to reap too, and a cleanup that only runs on the tidy path is the way that requirement gets lost.
type Backend ¶
type Backend interface {
// Capabilities are static facts, so this takes no context and starts no
// subprocess (behavior §13).
Capabilities() Capabilities
// Version reports the multiplexer's version, for the floors of §0.5 and
// the diagnostic of §0.6. A backend that is not installed reports
// CodeBackendUnavailable.
Version(ctx context.Context) (string, error)
// Create makes a new session. It is not ensure-semantics: deciding to
// reuse or reap an existing one is shared logic above this interface
// (behavior §2.6).
Create(ctx context.Context, spec CreateSpec) (Session, error)
// Sessions lists every session. No server running is an empty list, not an
// error (behavior §3.3).
Sessions(ctx context.Context) ([]Session, error)
// Panes lists panes. An empty target lists every pane on the server, which
// is what target resolution consumes.
Panes(ctx context.Context, target string) ([]Pane, error)
// Probe answers presence. It returns no error by design: a backend that
// cannot be reached is StateError, so the tri-state of behavior §3.5
// cannot collapse into a transport failure at the call site.
Probe(ctx context.Context, target string) State
// Kill ends a session immediately.
Kill(ctx context.Context, target string) error
// Interrupt asks the foreground process to stop. The graceful-kill
// sequence that decides when to escalate to Kill is shared logic above
// this interface (behavior §2.8); the mechanism is backend-specific and
// lives here.
Interrupt(ctx context.Context, target string) error
// Type injects literal text. It never submits, on any backend
// (behavior §4.3).
Type(ctx context.Context, target, text string) error
// Paste injects multi-line text. It never submits either (behavior §4.6);
// normalization happens above this interface.
Paste(ctx context.Context, target, text string) error
// Press sends named keys.
Press(ctx context.Context, target string, keys ...Key) error
// Submit writes the terminator alone, as a keypress rather than as part of
// a paste. The pacing that separates it from the preceding text is a
// default, so it is applied above this interface (behavior §4.5).
Submit(ctx context.Context, target string) error
// SendAtomic writes text and its terminator indivisibly, trading the
// verification of §7 for atomicity (behavior §4.7).
SendAtomic(ctx context.Context, target, text string) error
// Screen captures one target.
Screen(ctx context.Context, target string, opts ScreenOpts) (Capture, error)
// ScreenMeta reports capture metadata WITHOUT capturing.
//
// It is separate from Screen because the door needs the alt-screen flag
// before it can decide WHAT to ask for: a pane on the alternate screen has
// no scrollback, so a history request against it must be dropped rather
// than sent and silently under-answered (behavior §5.3). Deciding that
// after capturing would mean having already asked the wrong question.
ScreenMeta(ctx context.Context, target string) (ScreenMeta, error)
// Follow streams a session's output as it is produced.
//
// This is the one operation that cannot be built from Screen: polling a
// capture shows the CURRENT grid, so anything printed and scrolled away
// between two polls is simply gone, and a program that repaints in place
// has no meaningful "delta" to compute. Following taps the byte stream
// instead, which is what both backends provide a primitive for.
//
// The reader carries raw terminal output, escape sequences included: it is
// a stream, not a rendering, and a consumer that wants a picture should
// capture instead. Closing it stops the tap.
Follow(ctx context.Context, target string) (io.ReadCloser, error)
// Attach prepares a client for the engine to run inside a PTY.
Attach(ctx context.Context, target string, spec AttachSpec) (Attachment, error)
// SetStatus records an opaque label on a session, for a process inside it
// to leave for whoever drives it from outside.
//
// Olympus never interprets the value, and MUST NOT enumerate the states a
// caller may use: those describe what is driving the terminal rather than
// the terminal. UNSUPPORTED on a backend with nowhere to keep it.
SetStatus(ctx context.Context, target, status string) error
// Status reports that label, empty when the session has never been given
// one. Empty is a real answer, not an error (§3.5).
Status(ctx context.Context, target string) (string, error)
// CreateView adds a view onto base.
//
// It is not a side-effect-free read: on a backend that supports it, view
// creation defines a server-global key table (behavior §9.3), inert to
// every session that does not point at it. A view MUST NOT reconfigure
// anything else about the server.
CreateView(ctx context.Context, base string, spec ViewSpec) (View, error)
// ScrollView scrolls a view by a number of lines, negative for back into
// history.
ScrollView(ctx context.Context, view string, lines int) error
// FocusView selects the pane of the view's current window whose
// rectangle contains the cell (col, row), 0-based within the client area,
// and reports that pane's id. A cell on a border or outside every pane
// selects nothing and reports "" with no error (behavior §9.6).
FocusView(ctx context.Context, view string, col, row int) (paneID string, err error)
// Views lists the views onto a base session, or onto every session when
// base is empty.
Views(ctx context.Context, base string) ([]View, error)
// ServerEnv reads a key from the multiplexer server's global environment.
// An unset key is a real negative answer — present false, no error — and
// is not the same as a backend with no such concept, which is
// CodeUnsupported (behavior §12).
ServerEnv(ctx context.Context, key string) (value string, present bool, err error)
}
A Backend drives one multiplexer. It is the mechanical layer: explicit, complete, and free of defaults. Every rule it must satisfy is specified in docs/terminal-behavior.md, and every rule that can be observed through this interface is enforced by the conformance suite in backend/backendtest, which is exported so a third-party backend can prove itself against the same one the shipped backends run.
Targets arriving here are already resolved (ResolveTarget). Operations that a backend has no concept for return CodeUnsupported — distinct from CodeBackendUnavailable, and distinct from a real negative answer — and a consumer is expected to branch on Capabilities rather than on that error.
type Capabilities ¶
type Capabilities struct {
Backend Name `json:"-"`
NativeScrollback bool `json:"native_scrollback"`
Views bool `json:"views"`
RemainOnExit bool `json:"remain_on_exit"`
ServerEnv bool `json:"server_env"`
// ControlKeys reports whether control keys reach the session.
//
// This is the capability that decides whether a full-screen program can be
// DRIVEN, as opposed to merely started and read. An editor is left with
// Ctrl-X and saved with Ctrl-O; a pager is scrolled with Ctrl-F. Where
// these are not delivered a caller can open such a program and watch it,
// and never get out of it.
//
// Measured, rather than assumed from the backend's documentation: sending
// each byte to `cat -v` and reading back what arrived. tmux delivers the
// control range, tab and escape. zmx delivers printable text, tab and the
// terminator, but drops the control letters, a lone escape, and the arrow
// and home keys — while passing page-up and the function keys. The boundary
// is not fully characterized and is not worth characterizing: what a caller
// needs to know is that control keys cannot be relied on there.
ControlKeys bool `json:"control_keys"`
// SpawnSizing reports whether a session's size can be chosen when it is
// created.
//
// A capability rather than a warning alone because the caller's approach
// changes: with it, ask for the size you need; without it, the session
// takes the backend's own and a caller who needs a specific width has to
// resize after attaching, or accept whatever it gets. Measured — a 120x40
// request becomes 120x40 on tmux and 80x23 on meja.
SpawnSizing bool `json:"spawn_sizing"`
// SpawnCommand reports whether a session can be spawned directly onto an
// argv, executed rather than typed (behavior §2.3).
//
// It is a capability rather than an assumption because a backend can own
// the process it starts without letting a caller choose it: a multiplexer
// whose panes always run the shell its own configuration names has nowhere
// to put a per-session argv, and CreateSpec.Command is then a request it
// cannot honour at all. Measured — tmux, zmx and meja each exec the argv;
// herdr has no per-pane program, so it refuses the field.
//
// The caller's approach changes with it, which is what makes it a
// capability and not a degraded-operation warning: with it, spawn the
// program; without it, start a shell and drive the program from inside,
// accepting that the command line is echoed into the session's own output
// and that shell metacharacters are the caller's to quote.
SpawnCommand bool `json:"spawn_command"`
// SessionStatus reports whether a session can carry an opaque label a
// process inside it sets for whoever drives it from outside.
//
// It is a capability rather than an assumption because a backend needs
// somewhere to keep it that outlives the process that set it: the reporter
// is inside the session and the reader is outside, and they never run at
// the same moment. A backend without such a place MUST refuse the write
// rather than swallow it, or a caller waits forever for a state that can
// never be reported.
SessionStatus bool `json:"session_status"`
// TracksAltScreen reports whether capture metadata's alt-screen flag
// means anything on this backend. Without it a caller cannot tell "this
// pane is not on the alternate screen" from "this backend does not
// track that", and an empty capture is ambiguous in exactly the way the
// flag exists to prevent (behavior §5.3).
TracksAltScreen bool `json:"tracks_alt_screen"`
// Servers reports whether the backend can enumerate its servers — the
// level above sessions, each behind its own socket (§13.2). With it, a
// caller can discover a server by name and select it with --server;
// without it, the only way to address a server is to already know its
// socket. It says nothing about stopping one: a backend can enumerate
// servers it has no way to stop, and reports that as unsupported.
Servers bool `json:"servers"`
// SessionClient reports whether the backend has a session client that is
// distinct from its raw per-pane stream — chrome, selection, scrollback,
// copy — which an attach can ask for (behavior §8.10). Only herdr draws
// the distinction; everywhere else the ordinary attach already is the
// session client, and asking for one is refused as a caller mistake.
SessionClient bool `json:"session_client"`
// Bare reports whether an attach can show a session as a plain pane with
// no chrome: herdr's session client with its chrome hidden, or a throwaway
// view on tmux (behavior §8.9). A caller offering a "clean" attach branches
// on this rather than on the backend's name.
Bare bool `json:"bare"`
// Focus reports whether the server's focus can be steered onto a target
// without attaching (behavior §8.10). True where a server-side focus is
// what a client reads when it comes up (herdr's session client, tmux's
// plain session); false where a session is one pane and there is nothing
// to steer.
//
// It says the steering works, not that every client follows it. On herdr
// from 0.9.0 a client already attached keeps its own view, so steering
// decides what the NEXT client shows; Session.Focused is where that
// difference is reported.
Focus bool `json:"focus"`
// Rename reports whether a target can be given a new name in place
// (behavior §2.11): a session, and a window, tab or pane where the
// backend names those too. False where names are fixed at creation.
Rename bool `json:"rename"`
// AgentStatus reports whether agent rows can carry a status (behavior
// §3.7): the backend detects agents itself, or its panes can be
// captured so the ergonomic layer can read a status off the screen. A
// row says which in status_source. Without it every row's status is
// unknown.
AgentStatus bool `json:"agent_status"`
}
Capabilities are the static, subprocess-free backend facts a consumer feature-probes before hitting an unsupported error (behavior §13).
Backend is carried on the value so a Capabilities is self-describing in Go, but is not marshalled: on the wire capabilities always hang off a row that already names the backend, and repeating it there would be a second place for the two to disagree.
There is deliberately no capability for whether a session outlives its command. That is a property of the caller's own wrapper, not of backend mechanics.
type Capture ¶
type Capture struct {
Text string
Meta ScreenMeta
}
A Capture is one target's screen and the metadata the text itself cannot carry.
Meta.AltScreen set means the pane is a full-screen program's: the Text is its visible grid, and there is no scrollback behind it. Empty Text there means the program has painted nothing yet, not that anything was skipped (behavior §5.3).
type Client ¶ added in v0.26.0
type Client struct {
// ID is the server's own number for the client, as a string.
ID string `json:"id"`
// Tag is the name the client was launched with, empty for one launched
// with none.
Tag string `json:"tag,omitempty"`
// SessionID and WindowID are the session and window the client shows.
SessionID string `json:"session_id,omitempty"`
WindowID string `json:"window_id,omitempty"`
// PaneID is the focused pane of the window the client shows, which is
// where what the client sends goes.
PaneID string `json:"pane_id,omitempty"`
// Zoomed is whether that window shows PaneID alone. Nil where the server
// does not report it.
Zoomed *bool `json:"zoomed,omitempty"`
// ViewApplied is whether the client has applied the view the server holds
// for it, so that what it sends now reaches PaneID. Nil where the server
// does not report it, or the client does not acknowledge what it applies.
ViewApplied *bool `json:"view_applied,omitempty"`
}
A Client is one client attached to a server, and what it shows (behavior §13.5). It is the answer to "which session, window and pane is this person's client on right now", which a server whose clients each keep their own view holds per client and nowhere else.
On herdr a session is a workspace, a window is a tab and a pane is a pane (§3.6), and every id here is one a target takes. What the server does not report is omitted: Zoomed and ViewApplied are pointers because false is an answer, and a server that cannot give one must not be read as giving it.
type ClientLister ¶ added in v0.26.0
A ClientLister lists the clients attached to the server this handle addresses. Optional: a backend whose server cannot say which client shows what does not implement it, and the layer above reports CodeUnsupported. A backend that implements it answers CodeUnsupported itself for a server that cannot (§13.5).
type Code ¶
type Code string
A Code classifies a failure. The set of codes and their process exit statuses are a semver-bound contract (behavior §12): a shipped code is never repurposed or removed, only added to.
const ( // CodeUsage is input the caller could have validated, including an unknown // backend name. Any error a caller could have avoided by changing one // argument is this code and not CodeUnexpected. CodeUsage Code = "USAGE" // CodeSessionNotFound is a target session or pane that does not exist. CodeSessionNotFound Code = "SESSION_NOT_FOUND" // is distinct from CodeUnsupported: the concept exists, the backend does // not answer. CodeBackendUnavailable Code = "BACKEND_UNAVAILABLE" // CodeTimeout is an operation that did not complete or match before its // budget elapsed. CodeTimeout Code = "TIMEOUT" // CodeConflict is a lock or attach slot held by someone else. CodeConflict Code = "CONFLICT" // CodeUnsupported is a backend with no concept for the operation at all. // It is neither "unavailable" nor "absent": absence is a real negative // answer, unsupported means the question does not apply. CodeUnsupported Code = "UNSUPPORTED" // CodeAgentBlocked is an agent waiting on a person — a permission prompt, // a question — or showing something open over its input box (a rewind // list, a picker), refusing input that would have landed there rather // than in its composer. Nothing was submitted: either nothing was // typed, or the prompt opened after typing and the terminator was never // sent. An answer to the prompt is a deliberate keypress, never a side // effect of sending text. CodeAgentBlocked Code = "AGENT_BLOCKED" // CodeUnexpected is anything not carrying one of the above — read by a // machine consumer as "Olympus broke, retrying will not help". CodeUnexpected Code = "UNEXPECTED" )
func CodeOf ¶
CodeOf classifies any error, looking through wrapping. A nil error has the empty code — "nothing failed" is distinct from "failed for an unknown reason". Anything else that carries no classification is CodeUnexpected, per §12: a door therefore never holds an error it cannot put in the envelope.
type CreateSpec ¶
type CreateSpec struct {
Name string
// Dir is the session's starting directory.
Dir string
// Command is the argv to spawn. Empty means a plain login shell. It is
// spawned by exec, never typed into a shell (behavior §2.3).
//
// A backend whose panes always run the program its own configuration names
// has nowhere to put this. It rejects a non-empty Command as unsupported
// rather than typing it, and declares Capabilities.SpawnCommand false so a
// caller can branch before hitting the error.
Command []string
Cols int
Rows int
// RemainOnExit keeps a corpse to inspect after the session's command
// exits. It is write-only and applies on the create path only: there is no
// way to read it off a live session and no way to change one (behavior
// §2.7). A backend with no corpse concept rejects it as unsupported before
// invoking anything.
RemainOnExit bool
}
A CreateSpec is a complete session creation request. Every field is explicit: the mechanical layer never fills a blank, because defaults are decided once, in the ergonomic layer (behavior §17.3). A backend receiving a zero Cols has been handed a bug, not a request to choose a width.
type Error ¶
type Error struct {
// Code classifies the failure. It is the semver-bound vocabulary of §12.
Code Code
// Msg describes this failure in the caller's terms.
Msg string
// Cause is the underlying error, if any. It stays unwrappable so a backend
// can attach the exec or syscall failure that explains the classification.
Cause error
// Typed marks an AGENT_BLOCKED whose text was typed before the agent
// started waiting (behavior §7.5): a second send would type it again.
Typed bool
}
An Error carries a classification alongside its message, so a door can translate any failure into the structured envelope and an exit status without knowing which layer produced it.
func Wrapf ¶
Wrapf builds a classified error around an underlying cause. A nil cause is not an error-free result: it yields a classified error with no cause, since a caller that reached Wrapf has already decided something failed.
type Expect ¶ added in v0.22.0
An Expect registers interest in a sequence the client will write — the window title it paints as it lands on a workspace — BEFORE the key that provokes it is pressed, and returns the wait for it: true once the sequence has come out of the client, false when `within` passed first. It is how a walk knows a key was read rather than assuming it from a beat (§8.10): under load a bare client dropped one press in three, and the marker typed after it landed in the workspace the client was still on (measured 2026-09-13).
type Focuser ¶ added in v0.6.0
A Focuser can steer the server's focus onto a target: the workspace, tab or pane its session client shows. Only a backend whose session client follows a server-side focus rather than a per-client one has anything to steer (behavior §8.10); the rest leave this unimplemented and the ergonomic layer answers unsupported.
type Key ¶
type Key string
A Key names a keypress. The vocabulary is Olympus's own and each backend translates it, so a caller never has to know one multiplexer's spelling to press a key on another. A key outside the vocabulary is CodeUsage: it is input the caller could have validated.
const ( KeyEnter Key = "enter" KeyEscape Key = "escape" KeyTab Key = "tab" KeyBackspace Key = "backspace" KeySpace Key = "space" KeyUp Key = "up" KeyDown Key = "down" KeyLeft Key = "left" KeyRight Key = "right" KeyHome Key = "home" KeyEnd Key = "end" KeyPageUp Key = "page-up" KeyPageDown Key = "page-down" KeyCtrlA Key = "c-a" KeyCtrlC Key = "c-c" KeyCtrlD Key = "c-d" KeyCtrlE Key = "c-e" KeyCtrlL Key = "c-l" KeyCtrlU Key = "c-u" KeyCtrlZ Key = "c-z" KeyDelete Key = "delete" KeyShiftTab Key = "s-tab" KeyCtrlUp Key = "c-up" KeyCtrlDown Key = "c-down" KeyCtrlRight Key = "c-right" KeyCtrlLeft Key = "c-left" KeyMetaEnter Key = "m-enter" KeyShiftUp Key = "s-up" KeyShiftDown Key = "s-down" KeyShiftRight Key = "s-right" KeyShiftLeft Key = "s-left" KeyMetaUp Key = "m-up" KeyMetaDown Key = "m-down" KeyMetaRight Key = "m-right" KeyMetaLeft Key = "m-left" )
type Liveness ¶
type Liveness string
A Liveness classifies whether a row's session is alive, produced by the backend and never by a consumer parsing error strings (behavior §3.2).
const ( // LivenessPresent is a live session the backend vouches for. LivenessPresent Liveness = "present" // LivenessGone is positive evidence of death: safe to finalize and reap. LivenessGone Liveness = "gone" // LivenessUnknown is a row that exists but could not be confirmed this // pass. Consumers MUST treat it as present for reap purposes — never // finalize on doubt. LivenessUnknown Liveness = "unknown" )
type Name ¶
type Name string
A Name identifies a backend. It is the key sessions are scoped by: the same session name on tmux and on zmx are different sessions, which is why every envelope discloses the resolved backend (behavior §0.4).
type Outcome ¶
type Outcome string
An Outcome reports what starting a session actually did (behavior §2.6). It appears only on start.
type Pane ¶
type Pane struct {
ID string `json:"pane_id"`
SessionName string `json:"session_name"`
SessionID string `json:"session_id"`
WindowIndex int `json:"window_index"`
Dead bool `json:"dead"`
// CreatedAt is Unix seconds.
CreatedAt int64 `json:"created_at"`
CurrentPath string `json:"current_path"`
CurrentCommand string `json:"current_command"`
Liveness Liveness `json:"liveness"`
// WindowName is the name of the window (tmux) or tab (herdr) the pane
// sits in, and Title the pane's own title (tmux) or label (herdr) — the
// two names Rename can set below the session (behavior §2.11). Empty
// where the backend has no such name, or none has been given.
WindowName string `json:"window_name,omitempty"`
Title string `json:"title,omitempty"`
// PID is the pane's process id where the backend reports one: the process
// the pane was spawned on (tmux `#{pane_pid}`, zmx's `pid=` field). Zero
// where the backend does not report one (meja, herdr) — see behavior
// §3.4. It is the root the agent listing walks (§3.7).
PID int `json:"pid,omitempty"`
}
A Pane is one listed pane row.
Three of these fields mean genuinely different things per backend and MUST be documented at every door rather than reported as equivalent (behavior §3.4): CreatedAt is session-granular on both backends; CurrentPath is live on tmux and static on zmx; CurrentCommand is the live foreground process on tmux and the static spawn argv on zmx.
ID is not unique across rows once a grouped view exists, since a base session and its views share the same underlying pane. A consumer needing one row per logical session dedupes by ID, keeping the earliest CreatedAt.
type PaneIDShape ¶
A PaneIDShape reports whether a target is spelled like a pane id.
The SHAPE is what differs between backends; the rule that a pane id addresses its session does not. tmux writes "%0", meja writes a bare "1", and a backend with no pane concept has no shape at all. Passing the shape in keeps that one rule in one place instead of growing a copy per backend.
type PaneLister ¶
A PaneLister returns a full-server pane listing. Resolution takes one rather than a slice so the listing — a subprocess call — happens only for the targets that actually need it.
type PrefixReporter ¶ added in v0.9.0
A PrefixReporter reports the prefix key of the server this handle addresses, in tmux's spelling (behavior §13.3). Optional: a backend with no prefix does not implement it. A backend whose prefix is fixed by the program rather than by configuration reports the constant.
type Redrawer ¶ added in v0.32.0
A Redrawer can ask the process in a target's pane to draw its screen again, for a screen read that caught it drawn wrong (behavior §7.5). Optional: a backend that cannot ask does not implement it, and one that implements it answers CodeUnsupported for a server that cannot.
type Renamer ¶ added in v0.7.0
A Renamer can give a target a new name: a session, or a level below it where the backend has one (behavior §2.11). The name is what listings and every client show afterwards, and what the target answers to.
type ScreenMeta ¶
type ScreenMeta struct {
AltScreen bool `json:"alt_screen"`
// ScrollPosition is lines scrolled up from the live bottom, 0 when not in
// copy mode. tmux-only; zmx is always the zero value.
ScrollPosition int `json:"scroll_position"`
}
A ScreenMeta carries what a capture could not put in the text itself (behavior §5.5). AltScreen is what makes an empty capture mean "skipped by design" rather than "nothing there".
type ScreenOpts ¶
type ScreenOpts struct {
// Colors keeps escape sequences in the captured text.
Colors bool
// HistoryLines is how many lines of scrollback to include above the
// visible screen. Zero is the visible screen only.
HistoryLines int
}
ScreenOpts selects what a capture includes. Both are off by default at the mechanical layer, matching the cheapest capture.
type Server ¶ added in v0.3.0
type Server struct {
// Name is what --server selects the row by.
Name string `json:"name"`
// SocketPath is where the server is addressed: a socket file on tmux and
// herdr, the socket directory on zmx.
SocketPath string `json:"socket_path"`
// Running reports whether the server answers now. A socket file with
// nothing behind it is a known server that is not running, not an absent
// one: killing a server does not unlink its socket.
Running bool `json:"running"`
// Default marks the row the backend addresses when nothing selects one:
// tmux's "default" socket name, herdr's unnamed session, zmx's one
// directory. It is NOT necessarily the server Olympus itself defaults to —
// on tmux and herdr that is a socket of Olympus's own (§17.2).
Default bool `json:"default"`
// Dir is the directory the server keeps its state in, where the backend
// has one to report. Empty otherwise.
Dir string `json:"dir,omitempty"`
// Prefix is the key that introduces the server's own key bindings, in
// tmux's spelling (`C-b`, `C-Space`, `M-a`, `F19`), read from the
// server's configuration (behavior §13.3). Empty where the backend has
// no prefix, or where a stopped server's cannot be asked.
Prefix string `json:"prefix,omitempty"`
}
A Server is one multiplexer server: the level above sessions. Every backend can run several, each behind its own socket, and every other operation in this package addresses exactly one of them (behavior §13.2).
What a row means is backend-local, and the door discloses it rather than pretending the four are alike: a tmux server is a socket file in tmux's per-user directory, a herdr server is one of its named sessions, and a zmx server is the daemon's socket directory.
type ServerLister ¶ added in v0.3.0
A ServerLister enumerates a backend's servers. It is optional: a backend that cannot enumerate them does not implement it, and the layer above reports CodeUnsupported — distinct from an empty list, which is a real answer, and distinct from a backend that cannot be reached.
type ServerStarter ¶ added in v0.18.0
A ServerStarter brings one server up, without creating a session on it. It is optional, and independent of the other two: a backend can enumerate and stop servers it has no way to start.
It is the one operation that boots a server without being asked to make something on it, and it exists because a backend that RESTORES what it was running does that when it boots (§13.4). Without it the only way to get those panes back was to create a session nobody asked for, which is a listing's worst answer: the thing the caller was asking about, made by the asking.
It takes the row rather than a name because starting one needs more than a name does: which socket to wait on, and whether it is the backend's default server, which several backends address differently from a named one.
type ServerStopper ¶ added in v0.3.0
A ServerStopper stops one server by name, with every session on it. It is optional for the same reason ServerLister is, and independently: a backend can enumerate servers it has no way to stop.
type Session ¶
type Session struct {
Name string `json:"name"`
ID string `json:"id"`
Attached bool `json:"attached"`
Dead bool `json:"dead"`
Liveness Liveness `json:"liveness"`
CWD string `json:"cwd"`
// Focused marks the one session EVERY client attached to the server is
// showing, on a backend where clients share one view (herdr below 0.9.0:
// the focused workspace, which every session client displays). A consumer
// steering clients (§8.10) reads it to tell a client whose target is not
// what it is showing.
//
// Absent on backends whose clients each show their own session, and absent
// on herdr from 0.9.0, where a client keeps whatever it was last steered
// onto (§3.4). The server still HAS a focus there and steering still
// works; it just says where the next client will land rather than what the
// running ones display, which is not the question the flag is read for. No
// row carrying it means "cannot say", never "the focus is elsewhere".
Focused bool `json:"focused,omitempty"`
// Outcome is set by start and left empty everywhere else, so a listing row
// never implies an action was taken.
Outcome Outcome `json:"outcome,omitempty"`
}
A Session is one listed session row.
type State ¶
type State string
A State is the presence probe's tri-state answer (behavior §3.5). It is deliberately distinct from Liveness: this answers "does the target exist", where StateError means the backend could not be asked at all.
type View ¶
type View struct {
Name string `json:"name"`
Base string `json:"base"`
ID string `json:"id"`
Attached bool `json:"attached"`
}
A View is a grouped, independently-scrollable window onto an existing session (behavior §9). Its lifetime is independent of the base's, but the window and pane are shared.
type ViewSpec ¶
type ViewSpec struct {
// Name is supplied rather than generated by the backend, so the reserved
// shape of behavior §17.1 lives in one place. Enumerating views selects on
// that prefix, so a backend inventing its own would orphan every view an
// older binary created.
Name string
// Mouse enables wheel scrolling into the view's history. It is a per-view
// choice because a view is for reading: a wheel that scrolls is the point
// on one, and an unwanted mode change on another.
Mouse bool
// Window pins the view to one of the base's windows, by index or by name.
// Empty opens the view on whatever window the base is showing. A window
// the base does not have is CodeSessionNotFound, and nothing is created.
// A grouped session keeps its own current window, so pinning a view moves
// nobody else's (behavior §9.4).
Window string
}
A ViewSpec is a complete view-creation request.
Source Files
¶
Directories
¶
| Path | Synopsis |
|---|---|
|
Package backendtest is the executable definition of a correct backend.
|
Package backendtest is the executable definition of a correct backend. |
|
Package herdr drives herdr.
|
Package herdr drives herdr. |
|
Package meja drives the meja multiplexer.
|
Package meja drives the meja multiplexer. |
|
Package tmux drives tmux.
|
Package tmux drives tmux. |
|
Package zmx drives zmx.
|
Package zmx drives zmx. |