) when tmux reports no PID, including empty and
retained dead panes on tmux 3.8. Reported integers stay unchanged: older tmux
versions can report zero for an empty pane or a historical PID for a dead
pane. Neither a PID nor its absence proves process liveness or cleanup.
## Coverage and construction
[`Snapshot.new/2`]() validates fixture or adapter observations without I/O.
[`fields`]() declares loaded fields over the supplied entity IDs. `complete`
declares complete server entity sets; omissions remain partial. `unavailable`
declares absent capabilities separately. Declared names and values validate
before indexes or relations become observable.
Known empty strings, known nullable values, `:unloaded` and `:unavailable`
remain distinct. Required coverage checks run even for empty entity sets.
Derived attachment coverage follows its source count. A relation to many
objects needs a complete join domain, so missing children never become an
empty relation. Complete target sets reject dangling references. A partial
target set can retain unresolved foreign IDs, but traversal fails explicitly.
The [generated schema](https://libtmux.org/en/elixir/latest/topics/schema/) owns field types, explicit wire names,
tmux variables and relation cardinalities. Physical windows carry no single
session, session index or active membership flag.
## Capture consistency and bounds
Capture reads topology, then named fields, then topology again. It checks
the tmux PID against Linux boot identity and process start ticks before and
after those reads. Replacement or changing references trigger finite read
retries. A disappearing read target can retry; invalid encoding and other
decoding errors remain visible. Two attempts are the default; one to three
may be requested. All attempts share one deadline and the original budgets.
Defaults are 1024 contextual entity rows, 512 commands, 8 MiB total response
bytes, 64 KiB per response and 5000 ms. Both stdout and stderr, including
failed-command output, consume the byte budget. Overload returns an error
instead of a truncated success. Topology checks and retries also consume
work. One-shot retirement has the separate allowance documented in
[ownership](https://libtmux.org/en/elixir/latest/topics/ownership/).
Free-form fields use the qualified versioned encoder. Numeric/ID topology
columns validate their complete representation. Client rows combine one
encoded identity with restricted scalar columns; tabs and newlines cannot
escape the encoded identity. This is a fixed capture schema, not a general
raw-format row parser.
The capture interval records observation, not a transaction. Attributes can
change within it; matching topology before and after does not prove nothing
changed in between. Holding a snapshot reserves no objects. Its server
instance is distinct from a future connection epoch, and one-shot checks
retain a check/use race. Sending snapshots between BEAM processes can copy
terms; retaining one can retain its graph.
---
# API glossary
Source: https://libtmux.org/en/elixir/latest/reference/glossary/
> Kinds, modifiers and package labels in the Elixir API reference.
## Symbol kinds
Kinds identify declarations in the Elixir API reference.
### module
An Elixir module, a named collection of functions, macros and types defined with defmodule.
Module names are atoms, and a nested name such as LibTmux.Control.Request is one flat module rather than a child of LibTmux.Control.
References: [Elixir modules and functions](https://hexdocs.pm/elixir/modules-and-functions.html).
### interface
An Elixir behaviour, a module that declares callbacks other modules implement.
A module implements a behaviour with the @behaviour attribute and defines each required callback.
References: [Elixir behaviours](https://hexdocs.pm/elixir/typespecs.html#behaviours).
### struct
An Elixir module that defines a struct: a map with a fixed set of keys and optional defaults.
Fields hold values and are never reassigned. Functions on the module return a new struct rather than changing one.
References: [Elixir structs](https://hexdocs.pm/elixir/structs.html).
### function
An Elixir function identified by its module, name and arity.
Functions of one name and different arities are different functions. A name ending in ! raises on failure, and a name ending in ? returns a boolean.
References: [Elixir functions and arity](https://hexdocs.pm/elixir/modules-and-functions.html).
### method
A callback that a behaviour declares and an implementing module defines.
A callback is called through the implementing module, not through the behaviour that declares it.
References: [Elixir behaviour callbacks](https://hexdocs.pm/elixir/typespecs.html#behaviours).
### property
A field of an Elixir struct, read with dot access or pattern matching.
A field has no accessor function; structs are plain maps, so the value is stored as the field holds it.
References: [Elixir structs](https://hexdocs.pm/elixir/structs.html).
### typealias
An Elixir type declared with @type or @opaque, named and given an arity.
Types document values for readers and tools. They are not checked when a function runs.
References: [Elixir user-defined types](https://hexdocs.pm/elixir/typespecs.html#user-defined-types).
### exception
An Elixir struct declared with defexception, which can be raised and rescued.
The struct also carries the fields of the error. A bang function raises it; the plain function returns it in an error tuple.
References: [Elixir exceptions](https://hexdocs.pm/elixir/Kernel.html#defexception/1).
## Modifiers
Modifiers add information about calling, implementing, or accessing a declaration. Their precise effect follows the language and the signature.
### opaque
An Elixir type declared with @opaque, whose structure callers must not depend on.
Other modules should build and inspect the value only through the declaring module’s functions.
References: [Elixir user-defined types](https://hexdocs.pm/elixir/typespecs.html#user-defined-types).
### macro
An Elixir macro, defined with defmacro and expanded at compile time.
A caller must require or import the defining module before using the macro.
References: [Elixir macros](https://hexdocs.pm/elixir/Kernel.html#defmacro/2).
### abstract
A callback of a behaviour, declared without a body.
References: [Elixir behaviour callbacks](https://hexdocs.pm/elixir/typespecs.html#behaviours).
### deprecated
A declaration marked with the @deprecated attribute.
The compiler warns at call sites of a deprecated function. The message names the replacement when the author gave one.
References: [Elixir deprecations](https://hexdocs.pm/elixir/Module.html).
### overload
A function with several @spec clauses, each describing one accepted combination of argument types.
The clauses describe one function. Which clause applies depends on the arguments, not on a separate definition.
References: [Elixir function specifications](https://hexdocs.pm/elixir/typespecs.html#defining-a-specification).
## Package context
Package labels identify where an API is supplied.
### package
The Hex package supplying this API.
A package name and its application name are the same in a Mix dependency, and the module names it defines are independent of both.
References: [Hex packages](https://hex.pm/docs/usage).
---
# tmux MCP for Elixir is not published
Source: https://libtmux.org/en/elixir/latest/mcp/
> The Elixir repository contains an MCP package scaffold, not a usable or published server.
Elixir has no published libtmux MCP server. The repository's `packages/mcp`
directory is a scaffold: its README says the package is under implementation
and has no protocol server or executable yet.
This page records availability so cross-language navigation does not turn a
source placeholder into a product claim. There is no launch command, client
configuration, tool reference, prompt catalog, or embedding reference to use.
Use the [core Elixir library](https://libtmux.org/en/elixir/latest/guides/overview/) for direct tmux access.
Choose another language's MCP server only as a separate process with that
server's own package, policy, and compatibility requirements.
---
# tmux workspace manager for Elixir is not published
Source: https://libtmux.org/en/elixir/latest/workspace/
> The Elixir repository contains a workspace package scaffold, not a loader or supported builder API.
Elixir has no published libtmux workspace manager. The repository's
`packages/workspace` directory is a scaffold: its README says the package is
under implementation and has no loader or executable yet.
This page records availability without advertising placeholder code. There is
no Elixir equivalent of `libtmux-workspace validate`, `plan`, or `load`, and no
workspace reference tree is generated.
Use the [core Elixir library](https://libtmux.org/en/elixir/latest/guides/overview/) to observe tmux and run
commands on an existing server. A workspace tool from another language remains
a separate application with its own configuration contract; it is not an
Elixir feature.
---
# Examples
Source: https://libtmux.org/en/elixir/latest/examples/
> Examples for libtmux.
Run complete programs with their documented setup and cleanup.
- [Capture a pane and read it](https://libtmux.org/en/elixir/latest/examples/capture-and-read/): Capture a graph, select a pane and read it through one-shot and control commands.
---
# Capture a pane and read it
Source: https://libtmux.org/en/elixir/latest/examples/capture-and-read/
> Capture a graph, select a pane and read it through one-shot and control commands.
Capture once, then query the immutable result without more tmux commands.
This example works with the published `0.0.1-alpha.3` package. Install it in
an Elixir project as shown in the [README](https://libtmux.org/en/elixir/latest/guides/overview/#installation).
Save this program as `capture_and_filter.exs` in your application:
```elixir
alias LibTmux.{Selection, Server, Snapshot}
[executable, socket | _] = System.argv()
{:ok, _} = Application.ensure_all_started(:libtmux)
server =
Server.new(
executable: executable,
socket: socket,
env: Map.drop(System.get_env(), ["TMUX", "TMUX_PANE"])
)
{:ok, snapshot} = Server.snapshot(server)
active_ids =
snapshot
|> Snapshot.panes()
|> Selection.where(active: true)
|> Enum.map(& &1.id)
IO.puts(JSON.encode!(%{active_panes: active_ids}))
```
Set `LIBTMUX_TMUX` to the absolute tmux executable and `LIBTMUX_SOCKET` to
the absolute socket of the existing server you intend to observe. Run:
```console
$ mix run capture_and_filter.exs "$LIBTMUX_TMUX" "$LIBTMUX_SOCKET"
```
The JSON result lists active physical pane IDs in numeric order. A pane is
active within its physical window; this is not a choice of one session's
active window. Linked windows do not duplicate their panes.
[`Server.new/1`]() builds a descriptor without I/O. [`Server.snapshot/2`]() runs
bounded one-shot reads without attaching a control client or creating a
missing server. [`Selection.where/2`]() and `Enum.map/2` use the captured graph.
Capturing covers an interval, not an atomic tmux transaction. Capture again
explicitly when fresh state is needed.
The pattern match makes a capture error visible; production callers can
handle `{:error, %LibTmux.Error{}}`. The default capture budgets are 5000ms,
512 commands, 1024 contextual rows, 64 KiB per response and 8 MiB total.
A limit failure returns an error rather than a partial graph.
For an isolated demonstration, use the [owned-server recipe](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/docs/owned-server.md).
Next: [filter and select](https://libtmux.org/en/elixir/latest/guides/filtering/), [one-shot actions](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/docs/one-shot-actions.md),
or [managed control](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/docs/managed-control.md).
---
# Guides
Source: https://libtmux.org/en/elixir/latest/guides/
> Guides for libtmux.
Connect to tmux and work with its sessions, windows, and panes.
- [Getting started](https://libtmux.org/en/elixir/latest/guides/overview/): Add the Hex package, check the Elixir, OTP and tmux requirements, and run an example.
- [Attaching to tmux](https://libtmux.org/en/elixir/latest/guides/attaching-to-tmux/): Connect to an existing socket and leave its server running.
- [Filter captured values](https://libtmux.org/en/elixir/latest/guides/filtering/): Select observed values with portable keyword and operator filters.
---
# Getting started
Source: https://libtmux.org/en/elixir/latest/guides/overview/
> Add the Hex package, check the Elixir, OTP and tmux requirements, and run an example.
Hex.pm ·
API reference ·
HexDocs ·
Documentation
Read tmux sessions, windows and panes from Elixir. Filter a snapshot, rename
a window, split a pane, send input, or capture its screen. Queries use the
captured snapshot; actions contact tmux explicitly.
[Capture and query](https://libtmux.org/en/elixir/latest/guides/overview/#capture-and-query) ·
[Rename a window](https://libtmux.org/en/elixir/latest/guides/overview/#rename-a-window) ·
[Split and send input](https://libtmux.org/en/elixir/latest/guides/overview/#split-a-pane-and-send-input) ·
[Guides](https://libtmux.org/en/elixir/latest/guides/)
## Status and requirements
**Alpha:** the API may change between releases.
| Requirement | Version or dependency |
| --- | --- |
| Elixir / OTP | Elixir 1.20.4 or later 1.20 patch, compiled for OTP 29; OTP 29.1.2 or later 29 patch |
| Runtime platform | Linux, with a C11 compiler and headers to build the external helper |
| tmux | 3.3 minimum; tested with 3.3, 3.3a, 3.4 and 3.8 |
| Dependencies | Zero third-party core runtime packages; no NIF |
## Installation
Add the published alpha to your Mix dependencies:
```elixir
{:libtmux, "== 0.0.1-alpha.3"}
```
Fetch dependencies:
```console
$ mix deps.get
```
## Run the examples
The snippets below form one [executable program](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/examples/readme.exs). From
this checkout, run it on a private tmux server:
```console
$ python3 examples/owned_server.py \
--run "$(command -v tmux)" \
mix run examples/readme.exs
```
The [runner](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/docs/owned-server.md) creates a private server and cleans it up
afterward. It supplies the executable, socket and session ID used below,
leaving your existing tmux sessions alone. The runner needs Python 3.9+.
Already have a server? Use the [capture-only program](https://libtmux.org/en/elixir/latest/examples/capture-and-read/),
which takes its socket explicitly and leaves the server running.
## Capture and query
[`Server.snapshot!/2`][snapshot] captures an immutable graph.
[`Selection.where/2`][where] accepts predicate data; ordinary [`Enum`]() functions
work on the selection. These queries make no further tmux calls.
```elixir
alias LibTmux.{Selection, Server, Snapshot}
[executable, socket, session_id] = System.argv()
server =
Server.new(
executable: executable,
socket: socket,
env: Map.drop(System.get_env(), ["TMUX", "TMUX_PANE"])
)
snapshot = Server.snapshot!(server)
active_ids =
snapshot
|> Snapshot.panes()
|> Selection.where(active: true)
|> Enum.map(& &1.id)
```
The fixture starts with one pane, so `active_ids` is `["%0"]`. To use an
Elixir function as the predicate, choose [`Selection.filter/2`][filter].
Capture again explicitly when you need fresh observations.
## Rename a window
Continue with the same [`snapshot`][snapshot-type] and [`server`][server-type].
[`Selection.one!/2`][one] requires exactly one match, and
[`Session.active_window/2`][active-window] reads the captured relationship.
[`Ref.from_observation/1`][ref] carries the observed identity into an action.
```elixir
alias LibTmux.{Ref, Selection, Session, Snapshot, Window}
session = snapshot |> Snapshot.sessions() |> Selection.one!(id: session_id)
window = Session.active_window(snapshot, session)
{:ok, session_ref} = Ref.from_observation(session)
{:ok, window_ref} = Ref.from_observation(window)
Window.rename!(server, window_ref, "editor", session: session_ref)
```
[`Window.rename!/4`][rename] returns `:ok` when tmux acknowledges completion.
The `session` option chooses this window's session membership. The original
snapshot still contains the name captured before the action.
## Split a pane and send input
Continue with that [`window`][window-type] and [`session`][session-type].
[`Control.with_connection/2`][scope]
owns connection startup and cleanup around the callback. Its returned value
is available after connection retirement.
```elixir
alias LibTmux.{Control, Pane, Ref, Selection, Snapshot}
pane = snapshot |> Snapshot.panes() |> Selection.one!(window_id: window.id, active: true)
{:ok, pane_ref} = Ref.from_observation(pane)
{:ok, created_id} =
Control.with_connection(
[server: server, session: {:existing, session.id}, lane: :host_qualified],
fn control ->
%{id: id, ref: created} =
Pane.split!(control, pane_ref, axis: :horizontal, command: {:argv, ["/bin/cat"]})
Pane.send_text!(control, created, "hello")
Pane.send_keys!(control, created, ["Enter"])
id
end
)
```
[`Pane.split!/3`][split] returns the new pane's ID and bound reference on this
connection. [`Pane.send_text!/4`][text] sends literal text;
[`Pane.send_keys!/4`][keys] sends named keys. `{:argv, ["/bin/cat"]}` requests
a program directly; `{:shell, "command"}` requests shell interpretation.
Acknowledgement does not mean the program has finished processing its input.
The private runner qualifies this example's `lane: :host_qualified` declaration.
That option does not configure or qualify an arbitrary server. Read the
[managed-control guide](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/docs/managed-control.md) before using another host.
For one-shot screen reads, use [`Pane.capture!/3`][capture].
The complete program prints the active pane `%0`, the new pane `%1`, and the
window name `editor`. These examples run in CI. Each snippet shows its aliases;
they shorten module names and are not inferred.
## Errors and recovery
The domain actions shown above raise when they cannot complete. If a command
times out, it may still have taken effect. The [recovery guide](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/docs/recovery.md)
shows how to inspect the result before deciding what to do next.
## Concepts and reference
- [Filtering](https://libtmux.org/en/elixir/latest/guides/filtering/): predicates, relationships and finding one match.
- [One-shot actions](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/docs/one-shot-actions.md): rename, split, input and screen capture.
- [Managed control](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/docs/managed-control.md): reuse a connection for multiple commands.
- [Mental model](https://libtmux.org/en/elixir/latest/concepts/mental-model/): snapshots, references and command results.
- [API reference](https://libtmux.org/en/elixir/latest/reference/): functions and types.
## Contributing
See [Contributing](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/.github/CONTRIBUTING.md) for development setup and checks,
and [Writing](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/.github/WRITING.md) for documentation conventions.
Related projects: [libtmux for Python](https://github.com/tmux-python/libtmux),
[libtmux ports](https://github.com/libtmux), and [tmux](https://github.com/tmux/tmux).
[Artwork](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/docs/branding.md). Licensed under the [MIT License](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/LICENSE).
[snapshot]: https://libtmux.org/en/elixir/latest/reference/libtmux-server-snapshot-bang-2/
[snapshot-type]: https://libtmux.org/en/elixir/latest/reference/libtmux-snapshot/
[server-type]: https://libtmux.org/en/elixir/latest/reference/libtmux-server/
[window-type]: https://libtmux.org/en/elixir/latest/reference/libtmux-window/
[session-type]: https://libtmux.org/en/elixir/latest/reference/libtmux-session/
[where]: https://libtmux.org/en/elixir/latest/reference/libtmux-selection-where-2/
[filter]: https://libtmux.org/en/elixir/latest/reference/libtmux-selection-filter-2/
[one]: https://libtmux.org/en/elixir/latest/reference/libtmux-selection-one-bang-2/
[active-window]: https://libtmux.org/en/elixir/latest/reference/libtmux-session-active_window-2/
[ref]: https://libtmux.org/en/elixir/latest/reference/libtmux-ref-from_observation-1/
[rename]: https://libtmux.org/en/elixir/latest/reference/libtmux-window-rename-bang-4/
[scope]: https://libtmux.org/en/elixir/latest/reference/libtmux-control-with_connection-2/
[split]: https://libtmux.org/en/elixir/latest/reference/libtmux-pane-split-bang-3/
[text]: https://libtmux.org/en/elixir/latest/reference/libtmux-pane-send_text-bang-4/
[keys]: https://libtmux.org/en/elixir/latest/reference/libtmux-pane-send_keys-bang-4/
[capture]: https://libtmux.org/en/elixir/latest/reference/libtmux-pane-capture-bang-3/
---
# Attaching to tmux
Source: https://libtmux.org/en/elixir/latest/guides/attaching-to-tmux/
> Connect to an existing tmux server and find a session with Elixir.
Select an explicit socket, capture the existing server, and find the `work`
session. The program prints its name and leaves the server running. A missing
server or session ends the program with an error.
This controls tmux from Elixir. To open a session in your terminal, use
[`tmux attach-session`](https://libtmux.org/en/tmux/latest/manual/attach-session/); the [shared guide](https://libtmux.org/en/tmux/guides/attaching-to-tmux/)
covers interactive attachment and detaching.
## Connect to an existing server
Save the complete program as `connect.exs`. The environment variable
`LIBTMUX_SOCKET_PATH` selects the existing server. The launcher below sets it
to a private socket for trying the example. The first line installs the
published alpha from Hex.
```elixir title="connect.exs"
Mix.install([{:libtmux, "== 0.0.1-alpha.1"}])
alias LibTmux.{Selection, Server, Snapshot}
tmux = System.find_executable("tmux") || raise "tmux is not on PATH"
socket = System.fetch_env!("LIBTMUX_SOCKET_PATH")
server = Server.new(executable: tmux, socket: socket)
snapshot = Server.snapshot!(server)
session = snapshot |> Snapshot.sessions() |> Selection.one!(name: "work")
IO.puts(session.name)
```
[`Server.new/1`](https://libtmux.org/en/elixir/latest/reference/libtmux-server-new-1/) describes a server that
already exists and performs no I/O. [`Server.snapshot!/2`](https://libtmux.org/en/elixir/latest/reference/libtmux-server-snapshot-bang-2/)
reads tmux and returns captured state, or raises the structured capture error.
[`Snapshot.sessions/1`](https://libtmux.org/en/elixir/latest/reference/libtmux-snapshot-sessions-1/) selects
sessions from that state without contacting tmux again, and
[`Selection.one!/2`](https://libtmux.org/en/elixir/latest/reference/libtmux-selection-one-bang-2/) requires
exactly one match. Reading or filtering captured values does not refresh them.
The server value describes the daemon. It does not own a process to close, and
the program does not destroy the caller's sessions.
## Setup and run
Use an empty directory on Linux with Elixir 1.20.4 or newer, Erlang/OTP 29.1.2
or a newer 29.x release, a C11 compiler, tmux 3.3 or newer, a POSIX shell, and
access to hex.pm. Save the launcher as `run.sh`. It starts an isolated tmux
server, runs the program, checks that the session still exists, then stops only
that server. Cleanup runs after failures too. A failed shutdown keeps its socket
directory and prints its location for inspection.
```sh title="run.sh"
#!/bin/sh
set -eu
binary=$(command -v tmux)
directory=$(mktemp -d "${TMPDIR:-/tmp}/libtmux-elixir-attach.XXXXXX")
socket="$directory/tmux.sock"
cleanup() {
status=$?
trap - 0 HUP INT TERM
if [ -S "$socket" ] && ! "$binary" -S "$socket" kill-server; then
printf 'Cannot stop tmux; kept %s\n' "$directory" >&2
exit 1
fi
rm -rf "$directory" || exit 1
exit "$status"
}
trap cleanup 0
trap 'exit 1' HUP INT TERM
unset TMUX TMUX_PANE
export LIBTMUX_SOCKET_PATH="$socket"
"$binary" -S "$socket" -f /dev/null new-session -d -s work /bin/cat
"$@"
"$binary" -S "$socket" has-session -t '=work'
```
```console
$ sh run.sh elixir connect.exs
```
The program prints `work`. To use your own existing server, set
`LIBTMUX_SOCKET_PATH` to its socket and run the program without the launcher.
The launcher owns only the demonstration server.
## Find or create a session
Reading a session does not reserve its name. If your application creates a
session after an unsuccessful lookup, another client may create the same name
between those operations. Handle the creation error as well as the lookup.
[Mental model](https://libtmux.org/en/elixir/latest/concepts/mental-model/) and
[Observations](https://libtmux.org/en/elixir/latest/concepts/observations/) explain what a snapshot holds.
[Capture a pane and read it](https://libtmux.org/en/elixir/latest/examples/capture-and-read/) is a complete
program that runs against an isolated server.
The [library source revision](https://github.com/libtmux/libtmux-elixir/tree/b72452c198917433229bdc266a68c163541b6127)
records the implementation used by this example.
---
# Filter captured values
Source: https://libtmux.org/en/elixir/latest/guides/filtering/
> Select observed values with portable keyword and operator filters.
Obtain one [snapshot](https://libtmux.org/en/elixir/latest/concepts/observations/), then select its observed panes:
```elixir
alias LibTmux.{Selection, Snapshot}
editors =
snapshot
|> Snapshot.panes()
|> Selection.where(active: true, current_command: [in: ["nvim", "vim"]])
Enum.map(editors, & &1.id)
```
Selections retain their immutable graph and order. Enumerable delegates to
the materialized list, including suspension and early halt. `size/1` uses
the cached count; ordinary [`Enum.member?/2`]() compares elements. Use
[`Selection.member_id?/2`]() for native ID membership. Enumeration performs no I/O.
[`Selection.filter/2`]() accepts only unary functions and uses Enum truthiness.
Predicate exceptions propagate. `where/2` accepts validated data: keywords
are shorthand for maps, with duplicate keys rejected at every nesting level.
Both validate before scanning, including when no items remain.
`one/2` returns `{:ok, item}` or a structured `:no_match` / `:multiple_matches`
error. `one_or_nil/2` permits zero matches. Their bang variants raise the
same error; both stop at the second match. `exists?/2` stops at its first
match. `count/2` counts matching items; `size/1` returns the cached total.
## Predicate grammar
A bare scalar means equality. Explicit `%{equals: value}` is equivalent.
The [generated schema](https://libtmux.org/en/elixir/latest/topics/schema/) lists each field's type and operators.
Numbers compare numerically, strings compare case-sensitively without
normalization, and nullable numeric ordering against known nil is false.
`not` negates equality; `not_in` negates membership, including known nil.
Portable regex is not supported. Native predicates may use [`Regex`]() directly.
Fields in one object conjoin. Boolean `and` and `or` accept lists of
criteria; `not` accepts one criteria object. Empty `and` is true and empty
`or` is false. Normalization never combines separate relation quantifiers:
```elixir
alias LibTmux.{Selection, Snapshot}
Selection.where(Snapshot.sessions(snapshot), %{
panes: %{some: %{active: true, current_command: "nvim"}}
})
```
That predicate requires one pane to satisfy both conditions. Two separate
`some` clauses can match different panes. `every` and [`none`]() are true for
a known-empty relation; `some` is false. [`count`]() counts that relation's
identities: physical windows/panes deduplicate, window links retain memberships.
To-one `is: criteria` is false for known absence and `is_not` negates it.
`is: nil` means absent; `is_not: nil` means present. Session [`active_window`]()
is derived from a complete link domain and loaded active flags. Zero active
links means known absence; multiple active links raise `:ambiguous_relation`.
An unresolved active target remains unloaded. Coverage checks run before
evaluation, including fields behind a Boolean branch that cannot match.
## External filters
[`Filter.new/3`]() returns a validated portable value, and `new!/3` raises its
structured error. Applying that value retains its constructor validation;
explicit new limits validate the normalized tree without generating extra
criteria objects. Prepared predicates may contain
sets and closures; portable filters contain data only.
[`Filter.from_json/3`]() uses built-in JSON and rejects duplicate keys before
map conversion, trailing data and unknown names. `from_wire/3` accepts an
already decoded map; that map cannot reveal discarded duplicate keys.
Neither decoder creates atoms from external keys. Encode through
[`Filter.to_wire/1`](), then [`JSON.encode!/1`]().
```json
{"version":1,"entity":"pane","where":{"active":true,"currentCommand":{"in":["nvim","vim"]}}}
```
Default ceilings are 65,536 bytes, depth 16, 1024 value nodes, 256 array
elements and 8192 bytes per string. Wire depth starts at root zero and
counts every member/element; the ceiling is inclusive. Positive integer
options `max_bytes`, `max_depth`, `max_nodes`, `max_list` and
`max_string_bytes` can narrow or widen the constructor budget. JSON object
members have a fixed ceiling of 256. Native input depth counts criteria
objects and also bounds the normalized tree. A normalized conjunction does
not add relation/Boolean nesting; disjunctions, negations and relation
children do. Wire budgets additionally cover the full versioned envelope.
Errors carry the affected path, including Boolean and membership indexes;
early parser-limit errors can identify the root before a path is available.
Selections and filters operate on captured values. Server query planning
and pushdown remain under implementation.
---
# Third-party notices
Source: https://libtmux.org/en/elixir/latest/meta/third-party-notices/
> Thanks to tmux's creator and contributors, with licences and attribution for the software behind libtmux.org.
## tmux
First and foremost, thank you to **[Nicholas Marriott (nicm)](https://github.com/nicm)**,
the creator of tmux, and to the many
[tmux contributors](https://github.com/tmux/tmux/graphs/contributors).
We are grateful for the work that makes tmux possible.
libtmux is a separate project. Our libraries and supporting tools control a
real tmux server; tmux itself provides the terminal multiplexer. This is the
libtmux website. Visit the [official tmux website](https://github.com/tmux/tmux/wiki)
for the upstream project and its documentation.
See tmux's [COPYING file](https://github.com/tmux/tmux/blob/master/COPYING)
and the copyright and permission notices in its source files for its licensing.
### tmux artwork
The tmux logomark is by Jason Long. The site uses the unmodified
[upstream SVG](https://github.com/tmux/tmux/blob/8f25579c5aef8d93924a20681f394e2a582fd3ad/logo/tmux-logomark.svg)
under its [copyright and permission notice](https://libtmux.org/en/brand/tmux/LICENSE.txt).
## Programming-language artwork
The homepage language selector uses local copies of the following artwork
to identify the selected language. Each source record includes the download
URL, retrieval time, file hash, copyright information and usage terms.
These marks identify their respective languages and do not imply endorsement
of libtmux.
The SVG files are copied unchanged except for Scala, whose empty surrounding
canvas is cropped. Its paths, gradients and colors are preserved, and the
original SVG is retained beside the cropped copy. Rust's supplied SVG includes
its own dark-theme colors.
| Language | Credit and terms | Local source record |
|---|---|---|
| Python | Python Software Foundation. The [PSF logo terms](https://www.python.org/psf/trademarks/) permit the unaltered mark to identify Python. | [Python provenance](https://libtmux.org/en/brand/languages/py/provenance.json) |
| Ruby | Copyright © 2006, Yukihiro Matsumoto. The [Ruby logo](https://www.ruby-lang.org/en/about/logo/) is licensed under [CC BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/). | [Ruby provenance](https://libtmux.org/en/brand/languages/ruby/provenance.json) |
| Lua | Copyright © 1998 Lua.org; graphic design by Alexandre Nakonechnyj. The Devicon copy carries its [MIT notice](https://libtmux.org/en/brand/languages/lua/LICENSE.txt); [Lua's logo terms](https://www.lua.org/images/) also apply. Visit [Lua.org](https://www.lua.org/). | [Lua provenance](https://libtmux.org/en/brand/languages/lua/provenance.json) |
| TypeScript | Microsoft. The [official branding terms](https://www.typescriptlang.org/branding/) govern the mark; the website repository licenses exclude logo and trademark rights. | [TypeScript provenance](https://libtmux.org/en/brand/languages/ts/provenance.json) |
| Rust | The Rust Foundation. [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/) and the [Rust trademark policy](https://rustfoundation.org/policy/rust-trademark-policy/) apply. | [Rust provenance](https://libtmux.org/en/brand/languages/rs/provenance.json) |
| Go | The [Go gopher](https://go.dev/blog/gopher) is by Renee French, licensed under [CC BY 4.0](https://creativecommons.org/licenses/by/4.0/). | [Go provenance](https://libtmux.org/en/brand/languages/go/provenance.json) |
| C++ | Created by Jeremy Kratz and licensed by the Standard C++ Foundation under its [logo-use terms](https://isocpp.org/home/terms-of-use). | [C++ provenance](https://libtmux.org/en/brand/languages/cxx/provenance.json) |
| Swift | Apple Inc., under the [Swift Logo Guidelines](https://developer.apple.com/swift/downloads/swift-logo.zip). Swift and the Swift logo are trademarks of Apple Inc. | [Swift provenance](https://libtmux.org/en/brand/languages/swift/provenance.json) |
| Java | Devicon collection copyright (c) 2015 konpa, with its [MIT notice](https://libtmux.org/en/brand/languages/java/LICENSE.txt). This does not establish unrestricted rights to the underlying Java logo; [Oracle's logo terms](https://www.oracle.com/legal/logos/) apply. | [Java provenance](https://libtmux.org/en/brand/languages/java/provenance.json) |
| Kotlin | Kotlin Foundation brand guidelines preserve JetBrains copyrights. The [icon-use terms](https://kotlinfoundation.org/guidelines/) permit identifying Kotlin alongside other programming-language icons. | [Kotlin provenance](https://libtmux.org/en/brand/languages/kotlin/provenance.json) |
| Scala | Copyright EPFL. [Historical permission](https://groups.google.com/g/scala-user/c/bCC-R0FQn1w) covers noncommercial Scala promotion; the [general artwork-license question](https://github.com/scala/scala-lang/issues/1040) remains unresolved. | [Scala provenance](https://libtmux.org/en/brand/languages/scala/provenance.json) |
| C# | Copyright the .NET authors. [Brand-use permission](https://github.com/dotnet/brand/issues/10#issuecomment-669465301) allows the unmodified logo to represent .NET. The repository's CC0 statement covers illustrations; no blanket CC0 claim is made for the logo. | [.NET provenance](https://libtmux.org/en/brand/languages/csharp/provenance.json) |
| F# | The F# Software Foundation. Its [logo terms](https://foundation.fsharp.org/logo) require unchanged shape, colors and proportions, without implying Foundation representation. | [F# provenance](https://libtmux.org/en/brand/languages/fsharp/provenance.json) |
## Terminal artwork
The tmux CLI selector displays the Windows Terminal artwork by Microsoft
Corporation, licensed under [CC BY-ND 4.0](https://creativecommons.org/licenses/by-nd/4.0/).
The SVG is unmodified. See the [source and provenance](https://libtmux.org/en/brand/tools/terminal/provenance.json)
and [copyright and license](https://libtmux.org/en/brand/tools/terminal/LICENSE.txt).
## Documentation toolchain
libtmux and this site are built with open-source software. The following
tools have their own licences and attribution requirements.
| Tool | Licence | Role |
|---|---|---|
| [Astro](https://astro.build/) | MIT | The site shell |
| [Tailwind CSS](https://tailwindcss.com/) | MIT | Styling |
| [Pagefind](https://pagefind.app/) | MIT | Site-wide search |
| [Expressive Code](https://expressive-code.com/) | MIT | Code blocks |
| [Sphinx](https://www.sphinx-doc.org/) | BSD-2-Clause | Python and C++ reference |
| [Furo](https://github.com/pradyunsg/furo) | MIT | Sphinx theme |
| [Breathe](https://github.com/breathe-doc/breathe) | BSD-3-Clause | Doxygen XML into Sphinx |
| [Doxygen](https://www.doxygen.nl/) | GPL-2.0-only | Parses C++ headers to XML |
| [API Extractor](https://api-extractor.com/) | MIT | TypeScript API model |
| [IBM Plex](https://github.com/IBM/plex) | OFL-1.1 | Typeface |
### A note on Doxygen
Doxygen is licensed GPL-2.0-only. It runs as a build step that reads libtmux's
own headers and emits XML; that XML is rendered by Breathe and Sphinx, and no
Doxygen-generated HTML is published. Running a GPL program over your own input
does not place its licence on the output, and libtmux does not distribute
Doxygen or any modified version of it.
## Reference hosting
The [PyPI blocks logo](https://pypi.org/trademarks/) is a trademark of the
Python Software Foundation and identifies links to the Python Package Index.
Other package-host icons use [Simple Icons](https://simpleicons.org/)
(CC0-1.0). The docs.rs mark is its
[`docsdotrs` icon](https://github.com/simple-icons/simple-icons/blob/16.35.0/icons/docsdotrs.svg),
inlined unchanged. The book beside javadoc.io links is original artwork for
this site: the host publishes no vector logo, and the mark does not imitate
its favicon.
Some ports deep-link to the canonical host their ecosystem already uses,
rather than duplicating it here:
- Rust: [docs.rs](https://docs.rs/libtmux), for each published crate
- Go: [pkg.go.dev](https://pkg.go.dev/github.com/libtmux/libtmux-go/tmux)
- Java, Kotlin and Scala: [javadoc.io](https://javadoc.io/doc/io.github.libtmux/libtmux)
- Swift: the [Swift Package Index](https://swiftpackageindex.com/libtmux/libtmux-swift/documentation/libtmux)
copy of the DocC reference
Those sites are operated independently of this project and carry their own
terms.
## libtmux itself
Each port is MIT licensed. See the `LICENSE` file in that port's repository
for the authoritative text.
---
# Topics
Source: https://libtmux.org/en/elixir/latest/topics/
> Topics for libtmux.
Understand object ownership, state, and operation behavior.
- [Execution ownership](https://libtmux.org/en/elixir/latest/topics/ownership/): Run an executable without a shell, with exit status kept apart from output.
- [Command and field encoding](https://libtmux.org/en/elixir/latest/topics/encoding/): Render commands and decode fields without a shell or a raw newline.
- [Observation and filter schema](https://libtmux.org/en/elixir/latest/topics/schema/): Fields, wire names, value categories and operators of each observed entity.
---
# Execution ownership
Source: https://libtmux.org/en/elixir/latest/topics/ownership/
> Run an executable without a shell, with exit status kept apart from output.
[`LibTmux.Transport.OneShot.run/3`]() runs an absolute executable and argv on
Linux without a shell. It returns bytes, process exit status and cleanup
evidence separately. A nonzero process exit is still a completed transport
result; the tmux command layer interprets that status. Public domain actions
and persistent control use these ownership boundaries. Start with
[one-shot actions](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/docs/one-shot-actions.md) or [managed control](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/docs/managed-control.md);
this guide explains the lower-level contracts.
The native helper supplies receive credits that an ordinary OTP Port
lacks. It reads each stdout/stderr pipe only against one bounded credit;
its control pipe stays responsive while output or stdin is blocked. A
capture limit after delivery cannot bound an upstream mailbox while its
owner is descheduled.
## Limits and results
Default ceilings are 1 MiB stdout, 64 KiB stderr and 4096 retained chunks.
Byte limits may be 0–16 MiB per stream; chunk limits may be 1–65,536.
`max_total_bytes` limits both streams together to 0–32 MiB; its default is
the sum of the stream limits. The retained prefix obeys both ceilings.
Encoded cwd, argv and environment together fit a 32 KiB initialization
payload. Argv and environment each have at most 256 entries, including
argv's executable. Target environment values preserve empty strings and
non-NUL bytes; inherited tmux context is removed. Requested variables apply
to child `execve`; the helper inherits the trusted host environment.
The timeout defaults to 5000 ms and is at most 600,000 ms. A caller can
pass an existing [`LibTmux.Deadline`]() instead. Admission, startup and capture
share that budget. Cleanup has a separate allowance up to 750 ms. Expiry
never turns partial output into success.
Overload stops the owned producer and returns a structured error with a
bounded prefix. `cleanup: :retired` requires private child-retirement evidence
and helper exit. Missing proof stays `:unknown`. An exit status or retryable
error never authorizes replay.
The worker acknowledges terminal child-retirement proof before the helper
exits. The helper keeps its input open while earlier cancellation or read
credits drain; after acknowledgement the worker writes nothing further.
This preserves the helper's separate exit-status evidence. Missing
acknowledgement uses the existing 750 ms delivery allowance.
The worker monitors its caller. Caller death initiates cleanup; worker/VM
death closes the helper's parent pipe. Caller settlement and retirement
remain separate. Closing the reply alias also removes an already-delivered
result, releasing captured bytes from the caller's mailbox.
## Accounting and scope
Per-call bounds cover output bytes, chunk metadata, encoded input and
private frames. Captured chunks can coexist with an assembled result;
account for both copies. The helper has fixed input, queue and write
buffers and bounded initialization allocations. It requests 8 KiB child
pipes and a 4 KiB signal pipe. Kernel page rounding can increase the actual
capacities; a resize failure rejects startup. Parent pipes and Port delivery add
buffers outside the capture byte count. Aggregate accounting must measure
actual pipe capacities. See [the protocol](https://github.com/libtmux/libtmux-elixir/blob/66b9b1c3f3f3186428abdfda2c76f5cf87446710/c_src/README.md).
This primitive has one request per worker and no shared request queue.
Applications creating many independent callers need admission limits at
their host/server boundary. A per-call ceiling is not a global memory
ceiling. Ordinary finite descendants are killed and reaped. Uninterruptible
tasks, helper SIGKILL, hostile escape and host compromise are outside that
guarantee; incomplete cleanup retains uncertainty and ownership.
Use explicit sockets and `-N` for borrowed tmux servers. Retiring a client
must not stop a borrowed server. Intentional server creation and server-owned
jobs need their own ownership boundaries.
## Building and distribution
Mix builds the shipped C source with a C11 compiler and Linux headers.
`CC` may name one compiler executable. Generated binaries are excluded from
Hex archives and rebuilt during dependency compilation. No third-party
runtime package or NIF is added.
Packed-core checks must compile and execute outside the checkout.
Workspace/MCP escript extraction and helper discovery still need their
installed-consumer checks; the current path expects an unpacked application
`priv` directory. Other platforms remain unqualified.
## Persistent connection components
The internal Guardian owns one native helper and monitors its Manager and
router independently. Startup grants one stdout credit after router demand.
After activation it keeps one native read in flight and buffers at most
64 KiB or 128 stdout chunks while Router consumes its previous chunk. Each
native read and delivered chunk fits 8 KiB. Exceeding either queue ceiling
closes the epoch with `resource_limit`; it does not indefinitely stop reading
from a still-attached client. These limits do not cap the borrowed tmux
server's allocations or total process memory.
Guardian caps stderr at 8 KiB and 64 chunks, and acknowledges one write
fragment at a time. A fragment fits 8 KiB; a complete rendered command fits
64 KiB. The writer rechecks the consumed publication lease, original deadline
and epoch. A partial line keeps its writer until the newline is acknowledged
or the epoch closes. Completed slots retain their generation to reject replay.
After orderly EOF or native exit, Guardian blocks further writes and drains
already-received stdout through the existing Router credits. An end token
follows pending native write acknowledgements; Router acknowledges it after
processing earlier data and write messages. Only then does ordinary closure
finalize remaining operations. Explicit closure, owner loss or protocol failure
can abort that drain. Established results survive; unfinished effects remain
uncertain. Drain and closure share the original 750 ms Guardian and 800 ms
Manager cleanup allowances. Neither clock restarts after data delivery.
Helper terminal acknowledgement remains independent of Router progress.
Deadline accounting uses one 25 ms timer, rescheduled only after consumption.
Busy delivery closes the epoch instead of suspending the owner or retrying.
Retirement needs native terminal proof, its acknowledgement and actual helper
exit; missing proof or Manager loss leaves the shared lifetime unclean.
Supervisor shutdown without those proofs also leaves it unclean.
Reserved closure revokes the exact lifetime generation without consuming a
command or metadata slot. Guardian observes that word on its accounting
timer even when Manager or Router makes no progress. A concurrent
starting-to-ready transition cannot escape revocation; retries follow only
the same generation. Revocation requests closure and blocks later checks.
It does not prove cleanup or prevent a write that already passed its final
check. Native and actor retirement remain separate required evidence.
Startup uses [`attach-session -E`](https://libtmux.org/en/tmux/latest/manual/attach-session/) to preserve the borrowed session environment.
Attachment still changes client counts and session activity. After reading
the server PID and Linux process identity, a fresh same-connection challenge
must return the exact nonce/PID and receive a complete native write
acknowledgement before activation. It shares the original startup deadline.
The server and inspected `/proc` must share a PID namespace.
This requires host authority for completed, clean configuration throughout
the epoch: no aliases overriding admitted command names, no executable
effective hooks, and no unqualified messages or subscriptions that can write
to the control stream. Other clients can inject multiline text through
client-directed messages; parser limits cannot authenticate such output.
Absence of a configuration-error notification cannot certify configuration.
The library never silently disables hooks or changes configuration.
The internal epoch router registers operation identity and FIFO position
before sending fragments. A qualified reply after final-fragment dispatch
can establish an immutable logical result before the native acknowledgement
arrives. The writer still waits for native acknowledgement before advancing;
wire retirement also requires it. Completed reply events survive a later
parser error in the same chunk. Expired unsent work removes its exact queued
token before capacity reuse. A retained reply observation and its logical
result are separate copies in the eventual connection byte accounting.
The lane admits encoded field reads, literal server user-option writes, split,
rename and literal/named input. Option names are bounded ASCII and values do
not expand. It rejects arbitrary command lines and screen capture output.
Host qualification includes pane-mode key bindings, which can enqueue other
commands in response to input.
Reply numbers must increase when observed; gaps are normal because other
clients and callbacks use the same server counter. This does not detect
every possible counter wrap or authenticate an unqualified stream.
Metadata requests use a separate fixed ETS inbox before any wake message.
Its context binds the real table owner, capacity, allowed request kinds and
atomics references. Publication creates the caller's reply alias and token;
only a small ready notification enters its mailbox. Owner completion and
caller receipt remain separate: a finished row stays occupied until alias
closure and receipt acknowledgement, or caller death. A monotone receipt
word cannot regress when a delayed old close meets a replacement generation.
Wake hints also coalesce across slot reuse; accounting sweeps never clear a
hint that a paused publisher still owns.
Inbox outcomes copy every binary leaf, including map keys. Copying charges
bytes and nodes before allocation, caps depth at 64 and nodes at 8192, and
rejects functions, ports and unqualified references. Known context references
come from explicitly identified, separately bounded resources. Metadata keeps
the default 64 KiB outcome allowance; an independently configured Inbox may
reserve 1–128 KiB so a 64 KiB reply plus its envelope fits. The 1 KiB minimum
covers fixed fallback errors. Copying shares the original deadline, checked
again before result publication. These limits do
not yet establish complete connection-wide heap/work accounting.
The selected waiter design retains bounded outcome copies in receipt-controlled
Inbox rows, superseding the earlier record-reference-only proposal. This avoids
cross-table pin/reclamation races at the cost of separately charged copies.
The internal waiter owner settles accepted requests from the exact immutable
terminal record before releasing it. A prior pending read cannot justify
forgetting a later completed record. Closed or expired waiters affect no
operation state; pre-acceptance await failures retain unknown effect certainty.
Manager services its separate waiter Inbox before reconnect admission and
startup continuations, and again after actor events. Both explicit forget
and automatic record expiry use the exact-record release barrier. A finished
waiter copy survives record reclamation and epoch replacement until receipt.
Lost waiter storage closes an active epoch and blocks this Manager's reuse.
If native retirement was already proved, that lifetime remains retired;
the Manager's fault does not erase cleanup knowledge or preserve lost tables
across its replacement.
### Managed execution
[`Control.execute/3`]() uses the same operation slots and a collection request in
the existing waiter Inbox. It waits for wire retirement, detaches the result
for every accepted collector, releases the exact operation lease, then makes
collector copies readable. Ordinary [`Operation.await/2`]() may still return an
acknowledged result before wire retirement. It does not reclaim the record.
Expired or closed collectors settle independently. Failure to prepare the
remaining bounded result copies preserves the record. Expiry after release
can still prevent delivery: [`Control.ExecutionError`]() then reports lost
context. No timeout establishes no effects or authorizes replay. Already
completed waiter copies stay in their fixed Inbox slots until receipt or
caller death. An acknowledged completed row may be reused by a publisher
through an exact-generation compare-and-swap, without waiting for a sweep;
unfinished work cannot be recycled this way.
A managed lease names its original publisher at admission. The Manager keeps
at most one monitor per managed lease and reclaims a dead publisher's record
only after wire retirement, including death before collection registration.
Existing actor events and publisher DOWN signals service this work; no
per-command cleanup process is created. Existing unpublished-lease accounting
still fences a publication that never arrives before its deadline.
After an execution error, a live caller can inspect its retained Operation
until forget, expiry or Manager loss. Death of that original caller also
permits reclamation, even if it passed the handle elsewhere. Use
[`Control.submit/3`]() for retention independent of the submitting process.
Accounting finalizes retained records for all observed dead destinations in
one lease scan, matching each epoch and PID together. It preserves acknowledged
results and dispatched uncertainty, and releases no live destination's record.
[`LibTmux.Operation`]() pins its original record, lease and Inboxes. Retained
status and result reads are local. Each await claims a bounded independent
slot and releases its exact alias and monitor on return; timeout does not
stop the operation. Metadata admission errors cannot label existing effects
as unwritten. Canonical failed, skipped and unknown outcomes remain values,
including in bang functions. Forget uses the original lease epoch after
reconnect. Stop validates the authoritative Births owner, epoch and Router
before revoking its generation through reserved capacity; an already-retired
record needs no surviving Births table. Stop closes the whole epoch and may
affect siblings. It is a request, never rollback or native retirement proof.
The retained-record registry finalizes an epoch only after its exact Router
PID has exited. It scans current admission leases, including a consumed
lease whose Router died before storing its first record. Router stores its
record and writing phase before sending, so an absent matching record proves
no write under that ordering. Existing acknowledged results remain intact;
dispatched work without evidence remains unknown. These outcomes retain
their slots until explicit release. Clearing a released record uses its
exact old value, preserving any replacement already published in that slot.
Logical finalization does not prove Guardian/helper cleanup or authorize a
new epoch; Manager must separately establish complete resource retirement.
Startup registration uses one fixed row for three epoch actor roles and its gate.
Each actor records its own PID before side effects. Closing the gate captures
all accepted PIDs in the same CAS; a queued late startup cannot be omitted
between a separate read and close. Router and Guardian bind the supplied
registration to their manager, epoch and original deadline. Guardian also
rechecks actor aliveness and its starting lifetime before opening the driver.
An accepted PID remains owned through actual exit: registration or a pre-open
check can race later closure. Gate closure alone is not native retirement.
Manager keeps one asynchronous request to an ordinary Supervisor outstanding
while it accounts for deadlines and metadata. A suspended Supervisor cannot
park that accounting. Closing the reply alias does not cancel a queued
Supervisor action; an abandoned unresolved start leaves cleanup unknown and
blocks replacement. Captured actors remain monitored independently of the
request alias. A Router born without a Guardian exits after the startup gate
closes. A captured pair binds even during closure, including buffered native
retirement events.
Explicit reconnect carries its original deadline through old retirement and
new bootstrap. Replacement requires native proof, actual Guardian and Router
exit, the host's child-finalization acknowledgment and EpochSupervisor exit.
Manager restarts preserve the external lifetime latch and do not reconnect
automatically. The internal qualified cases retain outcomes across reconnect,
forget exact completed leases, and expire retained records. Accepted metadata
work remains owned after caller timeout or alias closure; neither cancels it.
The internal connection Supervisor owns the view and starts EpochHost before
Manager. Its external child-spec factory pins the lifetime across root
restarts. Manager replacement invalidates its old tables; root replacement
also invalidates the view. A surviving view never recovers old operation
evidence. Finite Supervisor shutdown can leave descendants alive; only the
shared unknown/unretired lifetime prevents a replacement helper in that case.
An unsuccessful descriptor handoff after root startup kills the known root
and reports cleanup uncertainty, even after observing root exit.
The internal startup adapter passes the remaining initialization budget to
OTP's generic server entry with its source-qualified Supervisor init shape.
OTP stores the adapter as its callback; the adapter delegates the original
module and arguments. A private alias captures the exact Root PID before
delegation so cleanup drains only that Root's racing startup ACK or NACK.
Descriptor publication rechecks the original deadline, and failed handoff
cleanup uses only its remaining budget. OTP still waits for actual Root DOWN
after an initialization timeout; this primitive alone does not prove a strict
caller-return deadline or complete descendant retirement.
The adapter is maintained against
[OTP 29.1.2 supervisor startup](https://github.com/erlang/otp/blob/d199a49a01a2079afb6f2c5c9ae047cd4555e68d/lib/stdlib/src/supervisor.erl#L547-L548)
and [proc_lib timeout cleanup](https://github.com/erlang/otp/blob/d199a49a01a2079afb6f2c5c9ae047cd4555e68d/lib/stdlib/src/proc_lib.erl#L438-L453).
The public Supervisor API has no initialization timeout option. The adapter
rejects runtime OTP majors other than 29, or a library build made with an
Elixir compiled for another OTP major, as `unsupported_runtime` with no-write
certainty before Root startup. Build identity is captured at compilation so
this check cannot demand-load version parsing during a cold startup.
This is a major-version guard, not patch qualification: the support manifest
and reference/tracking runtime checks enforce the declared patch minima and
verify the running Elixir's compiled OTP identity. Passing the guard alone
does not qualify every OTP 29 patch.
When updating OTP support, review the pinned Supervisor initialization shape,
parent handling and `proc_lib` timeout/ACK cleanup. Run the direct adapter,
connection-Supervisor, cold-loader and native startup/ownership checks on the
reference and resolved tracking toolchains before changing the guard. Record
their actual versions; identical resolutions qualify one toolchain pair.
Public [`Supervisor.start_link`]() has no initialization timeout. A temporary
`proc_lib` bridge changes the Root's recorded parent; transferring a link does
not adopt a new parent. An external watchdog that kills a still-linked Root
can kill a non-trapping caller. Replacing the adapter requires a qualified
handoff design as well as a timer. The current isolated dependency remains
private OTP coupling despite these compatibility checks.
The initialization timer belongs to the caller. If that caller dies while
the Supervisor callback is blocked, the Root can outlive its original
deadline: the Supervisor traps the parent's exit until initialization returns.
The adapter does not promise cleanup after untrappable startup-caller death.
Both startup wrappers close their private reply aliases through local BIFs
and zero-time receives. Timeout cleanup does not demand-load the shared reply
helper after Root exit. Isolated loader-gate checks qualify this dependency;
they do not bound OTP's actual-DOWN wait or explain earlier timing failures.
Failed initial startup fences the fresh lifetime even before its first epoch
reservation. If that reservation wins the CAS race, the fence closes only the
first generation. Proven retirement remains intact, and later generations
cannot be revoked by an old failure. Root cleanup uses its generation captured
at startup entry. Manager can remain inert at generation zero with unknown
cleanup through normal shutdown or Host loss, without creating epoch actors.
This fence handles observed startup failures; it does not establish recovery
from untrappable caller or VM death. One lifetime belongs to one connection
Root; concurrent starts sharing that lifetime intentionally fail closed.
[`Control.start_link/1`]() requires an explicit Server, exact existing session ID
and host-qualified lane. It returns `{:ok, root, control}` only after fresh
bootstrap and a bounded ready exchange. Its 1..600000ms timeout includes
preparation, Root startup and handoff; every successful handoff rechecks the
original deadline. The provisional handle stays internal. Accepted ready
replies bind their exact endpoint without a later Births or latest-view lookup.
If startup failed before ready registration, the error retains the original
failure kind. Cleanup fences the lifetime, requests exact-Root termination,
and reports actual Root exit only when observed. Native cleanup can remain
unknown. Ordinary linked-process exits retain their OTP semantics.
[`Control.child_spec/1`]() prepares detached bounded process bytes without I/O.
One lifetime survives its permanent factory's invocations. Each invocation
starts a new startup budget, but a nonfresh lifetime returns inert instead of
automatically attaching. Explicit reconnect requires proven retirement;
unknown cleanup, including generation zero, returns a structured error.
[`Supervisor.start_child/2`]() and [`Supervisor.restart_child/2`]() return fresh handle
info. After automatic restart, terminate and restart that exact child to obtain
the new info; this recovery does not clear unknown cleanup. Retain a distinct
child spec/lifetime per Root, and never use an old handle as a locator.
[`Control.submit/3`]() pins a ready Root view, Manager and epoch. It detaches the
request's binary leaves under a separate 20,000-byte owned-term limit before
claiming a fixed operation slot. One deadline includes validation, admission,
queueing, writing and replies. After claim, pure Operation binding preserves
the handle even if context disappears. A final local gate check can suppress
publication; the reserved lease stays owned. Its original deadline requests
epoch closure; capacity still follows native/destination retirement and
record retention or explicit forget rules. Router and Guardian independently
validate before writing.
[`Control.request_close/1`]() requests whole-epoch closure without claiming a
slot, including when operation and metadata capacity are full. It does not
acknowledge native retirement or roll back siblings. Repeated requests for
the same closing or proven-retired generation succeed; old handles cannot
follow replacement Managers or epochs. Status errors report unknown
certainty about existing work; a rejected new, unadmitted submission reports
no write.
[`Control.close/2`]() waits for native and epoch-actor retirement plus the Host's
epoch-child finalization acknowledgement under its original budget. An
admitted metadata watcher retains its fixed Inbox cell
through settlement, including after caller closure. Metadata settlement
failure blocks replacement. Ready watches never start a failed or closed
epoch. Accepted retirement watches settle before reconnect in the same batch,
independent of slot order. Epoch reservation rechecks metadata and waiter
storage; lost storage cannot authorize replacement.
[`Control.reconnect/2`]() returns the acknowledged endpoint directly. A delayed
caller receives that epoch even if a newer one already exists; its handle
then reports stale. It does not reread the latest view or replay operations.
The Root and retained records outlive ordinary epoch closure. Public lifecycle
errors preserve unknown certainty about existing effects; direct retirement
proof must still be observed within the original close budget.
Strict caller-return startup bounds, connection-wide accounting and
authoritative subscriptions remain unqualified. These component checks do
not qualify those contracts.
---
# Command and field encoding
Source: https://libtmux.org/en/elixir/latest/topics/encoding/
> Render commands and decode fields without a shell or a raw newline.
[`LibTmux.Command`]() is a value. Constructing it or rendering its bytes performs
no I/O. Both one-shot execution and the qualified persistent control lane
use these encoders.
## Separate parsers
[`Command.argv/2`]() returns process arguments and protects terminal semicolons
from tmux's argument-vector parser. It does not invoke a shell.
[`Command.render/2`]() produces one tmux command line with every argument byte
octal-escaped inside double quotes. The result contains no raw newline or
carriage return. It excludes the framing newline the control writer adds.
Both renderers validate the encoded byte budget before dispatch.
Command quoting does not protect a value that a command interprets as a
tmux format. [`LibTmux.Format.literal/1`]() constructs a literal format operand
without evaluating embedded `#{...}`, shell jobs or shorthand variables.
It accepts non-NUL bytes. The command still needs its own appropriate
format flags; arbitrary commands have different expansion behavior.
## One field per response
[`Format.field/1`]() encodes a named field with a `v1:` marker. It replaces
backslashes first, then dollars and ASCII control bytes, using fixed tmux
substitutions. [`LibTmux.Codec.decode_field/3`]() with `encoding: :escaped_v1`
requires that marker, removes one framing newline and decodes escapes once.
The conservative field format performs several substitutions.
This encoding avoids an ambiguity in tmux 3.4: its command print path
escapes some bytes while preserving literal backslashes. Removing arbitrary
backslashes from that output would corrupt legitimate values. A versioned
field encoding protects those values before printing. A separator byte
alone cannot do that, because titles and paths may contain it.
The default raw decoder removes only the final framing newline. Its source
must independently guarantee unmodified bytes. It must not be applied to
an unqualified tmux print path. Text decoding requires UTF-8; `:bytes`
retains arbitrary bytes. Integer parsing consumes the whole value and can
enforce a range; Boolean parsing accepts only `0` or `1`.
## Capture rows
Snapshot capture batches each physical entity's free-form fields in one
response. A row starts with `r1:N`, followed by N tab-separated `v1:` fields
and one final newline. Each field uses the same conservative encoder above:
tabs and newlines inside values become escapes, so they cannot become row
delimiters. The decoder requires the expected version and field count,
rejects malformed or truncated data, then validates each decoded field
against its schema. Invalid UTF-8 remains an error for text fields.
The response ceiling applies to the complete encoded row, including markers
and separators. Multiple fields that each fit the ceiling individually can
exceed it together; capture returns a resource-limit error, with no partial
graph or alternate decoder. The default ceiling remains 64 KiB per response.
One successful attempt uses `10 + S + W + P` commands, where S, W and P are
the physical session, window and pane counts. Each command still launches
one owned helper and one tmux client. Linked windows share their text read.
The two topology scans, incarnation checks, contextual-row accounting and
capture-wide deadline and byte budgets remain in force. This encoding does
not qualify arbitrary pane output or hook output for a shared control lane.
## Bounded framing
[`LibTmux.Protocol`]() is a pure control-mode parser with separate chunk, line,
frame, line-count and emitted-event ceilings. It retains reply guard IDs;
notification-looking lines inside a reply remain output. A rejected suffix
preserves completed prefix events in the structured error. Retire a lane
after a parse error.
Lexical parsing cannot authenticate output that forges a matching guard.
Commands, aliases, hooks and subscriptions require separate admission
rules before sharing a persistent lane. Parser ceilings also do not bound
upstream Port delivery or a connection owner's mailbox. Those transport
contracts require independent qualification.
---
# Observation and filter schema
Source: https://libtmux.org/en/elixir/latest/topics/schema/
> Fields, wire names, value categories and operators of each observed entity.
Generated from [`LibTmux.Schema`](); regenerate with `mix run scripts/generate_schema.exs`.
All current fields use the tmux 3.3 capture baseline. Coverage must still be loaded.
Only `nullable_integer` admits [`nil`](); other unavailable or unloaded fields raise.
Link IDs are `{session_id, index, window_id}` locally and three-element arrays on the wire.
See [filtering](https://libtmux.org/en/elixir/latest/guides/filtering/) for Boolean, relation and error semantics.
## `session`
| Elixir field | Wire field | Value category | tmux format | Operators |
| --- | --- | --- | --- | --- |
| [`attached`]() | [`attached`]() | `:boolean` | Derived | `equals`, `not`, `in`, `not_in` |
| [`attached_count`]() | `attachedCount` | `:integer` | [`session_attached`](https://libtmux.org/en/tmux/latest/catalog/formats/session_attached/) | `equals`, `not`, `in`, `not_in`, `lt`, `lte`, `gt`, `gte` |
| [`id`]() | [`id`]() | `{:id, :session}` | [`session_id`](https://libtmux.org/en/tmux/latest/catalog/formats/session_id/) | `equals`, `not`, `in`, `not_in` |
| `name` | `name` | `:string` | [`session_name`](https://libtmux.org/en/tmux/latest/catalog/formats/session_name/) | `equals`, `not`, `in`, `not_in`, `contains`, `starts_with`, `ends_with` |
| Relation | Wire relation | Cardinality | Target | Operators |
| --- | --- | --- | --- | --- |
| [`active_window`]() | `activeWindow` | [`one`]() | `window` | `is`, `is_not` |
| `clients` | `clients` | `many` | `client` | `some`, `every`, [`none`](), [`count`]() |
| `panes` | `panes` | `many` | `pane` | `some`, `every`, [`none`](), [`count`]() |
| `window_links` | `windowLinks` | `many` | `window_link` | `some`, `every`, [`none`](), [`count`]() |
| `windows` | `windows` | `many` | `window` | `some`, `every`, [`none`](), [`count`]() |
## `window`
| Elixir field | Wire field | Value category | tmux format | Operators |
| --- | --- | --- | --- | --- |
| `height` | `height` | `:integer` | [`window_height`](https://libtmux.org/en/tmux/latest/catalog/formats/window_height/) | `equals`, `not`, `in`, `not_in`, `lt`, `lte`, `gt`, `gte` |
| [`id`]() | [`id`]() | `{:id, :window}` | [`window_id`](https://libtmux.org/en/tmux/latest/catalog/formats/window_id/) | `equals`, `not`, `in`, `not_in` |
| [`layout`]() | [`layout`]() | `:string` | [`window_layout`](https://libtmux.org/en/tmux/latest/catalog/formats/window_layout/) | `equals`, `not`, `in`, `not_in`, `contains`, `starts_with`, `ends_with` |
| `name` | `name` | `:string` | [`window_name`](https://libtmux.org/en/tmux/latest/catalog/formats/window_name/) | `equals`, `not`, `in`, `not_in`, `contains`, `starts_with`, `ends_with` |
| `width` | `width` | `:integer` | [`window_width`](https://libtmux.org/en/tmux/latest/catalog/formats/window_width/) | `equals`, `not`, `in`, `not_in`, `lt`, `lte`, `gt`, `gte` |
| [`zoomed`]() | [`zoomed`]() | `:boolean` | [`window_zoomed_flag`](https://libtmux.org/en/tmux/latest/catalog/formats/window_zoomed_flag/) | `equals`, `not`, `in`, `not_in` |
| Relation | Wire relation | Cardinality | Target | Operators |
| --- | --- | --- | --- | --- |
| `panes` | `panes` | `many` | `pane` | `some`, `every`, [`none`](), [`count`]() |
| `sessions` | `sessions` | `many` | `session` | `some`, `every`, [`none`](), [`count`]() |
| `window_links` | `windowLinks` | `many` | `window_link` | `some`, `every`, [`none`](), [`count`]() |
## `window_link`
| Elixir field | Wire field | Value category | tmux format | Operators |
| --- | --- | --- | --- | --- |
| `active` | `active` | `:boolean` | [`window_active`](https://libtmux.org/en/tmux/latest/catalog/formats/window_active/) | `equals`, `not`, `in`, `not_in` |
| [`id`]() | [`id`]() | `{:id, :window_link}` | Derived | `equals`, `not`, `in`, `not_in` |
| `index` | `index` | `:integer` | [`window_index`](https://libtmux.org/en/tmux/latest/catalog/formats/window_index/) | `equals`, `not`, `in`, `not_in`, `lt`, `lte`, `gt`, `gte` |
| [`session_id`](https://libtmux.org/en/tmux/latest/catalog/formats/session_id/) | `sessionId` | `{:id, :session}` | [`session_id`](https://libtmux.org/en/tmux/latest/catalog/formats/session_id/) | `equals`, `not`, `in`, `not_in` |
| [`window_id`](https://libtmux.org/en/tmux/latest/catalog/formats/window_id/) | `windowId` | `{:id, :window}` | [`window_id`](https://libtmux.org/en/tmux/latest/catalog/formats/window_id/) | `equals`, `not`, `in`, `not_in` |
| Relation | Wire relation | Cardinality | Target | Operators |
| --- | --- | --- | --- | --- |
| `session` | `session` | [`one`]() | `session` | `is`, `is_not` |
| `window` | `window` | [`one`]() | `window` | `is`, `is_not` |
## `pane`
| Elixir field | Wire field | Value category | tmux format | Operators |
| --- | --- | --- | --- | --- |
| `active` | `active` | `:boolean` | [`pane_active`](https://libtmux.org/en/tmux/latest/catalog/formats/pane_active/) | `equals`, `not`, `in`, `not_in` |
| [`current_command`]() | `currentCommand` | `:string` | [`pane_current_command`](https://libtmux.org/en/tmux/latest/catalog/formats/pane_current_command/) | `equals`, `not`, `in`, `not_in`, `contains`, `starts_with`, `ends_with` |
| [`current_path`]() | `currentPath` | `:string` | [`pane_current_path`](https://libtmux.org/en/tmux/latest/catalog/formats/pane_current_path/) | `equals`, `not`, `in`, `not_in`, `contains`, `starts_with`, `ends_with` |
| [`dead`]() | [`dead`]() | `:boolean` | [`pane_dead`](https://libtmux.org/en/tmux/latest/catalog/formats/pane_dead/) | `equals`, `not`, `in`, `not_in` |
| [`exit_status`]() | `exitStatus` | `:nullable_integer` | [`pane_dead_status`](https://libtmux.org/en/tmux/latest/catalog/formats/pane_dead_status/) | `equals`, `not`, `in`, `not_in`, `lt`, `lte`, `gt`, `gte` |
| `height` | `height` | `:integer` | [`pane_height`](https://libtmux.org/en/tmux/latest/catalog/formats/pane_height/) | `equals`, `not`, `in`, `not_in`, `lt`, `lte`, `gt`, `gte` |
| [`id`]() | [`id`]() | `{:id, :pane}` | [`pane_id`](https://libtmux.org/en/tmux/latest/catalog/formats/pane_id/) | `equals`, `not`, `in`, `not_in` |
| `index` | `index` | `:integer` | [`pane_index`](https://libtmux.org/en/tmux/latest/catalog/formats/pane_index/) | `equals`, `not`, `in`, `not_in`, `lt`, `lte`, `gt`, `gte` |
| [`pid`]() | [`pid`]() | `:nullable_integer` | [`pane_pid`](https://libtmux.org/en/tmux/latest/catalog/formats/pane_pid/) | `equals`, `not`, `in`, `not_in`, `lt`, `lte`, `gt`, `gte` |
| [`title`]() | [`title`]() | `:string` | [`pane_title`](https://libtmux.org/en/tmux/latest/catalog/formats/pane_title/) | `equals`, `not`, `in`, `not_in`, `contains`, `starts_with`, `ends_with` |
| `width` | `width` | `:integer` | [`pane_width`](https://libtmux.org/en/tmux/latest/catalog/formats/pane_width/) | `equals`, `not`, `in`, `not_in`, `lt`, `lte`, `gt`, `gte` |
| [`window_id`](https://libtmux.org/en/tmux/latest/catalog/formats/window_id/) | `windowId` | `{:id, :window}` | [`window_id`](https://libtmux.org/en/tmux/latest/catalog/formats/window_id/) | `equals`, `not`, `in`, `not_in` |
| Relation | Wire relation | Cardinality | Target | Operators |
| --- | --- | --- | --- | --- |
| `window` | `window` | [`one`]() | `window` | `is`, `is_not` |
## `client`
| Elixir field | Wire field | Value category | tmux format | Operators |
| --- | --- | --- | --- | --- |
| [`control_mode`]() | `controlMode` | `:boolean` | [`client_control_mode`](https://libtmux.org/en/tmux/latest/catalog/formats/client_control_mode/) | `equals`, `not`, `in`, `not_in` |
| `height` | `height` | `:nullable_integer` | [`client_height`](https://libtmux.org/en/tmux/latest/catalog/formats/client_height/) | `equals`, `not`, `in`, `not_in`, `lt`, `lte`, `gt`, `gte` |
| [`id`]() | [`id`]() | `{:id, :client}` | [`client_name`](https://libtmux.org/en/tmux/latest/catalog/formats/client_name/) | `equals`, `not`, `in`, `not_in` |
| [`pid`]() | [`pid`]() | `:integer` | [`client_pid`](https://libtmux.org/en/tmux/latest/catalog/formats/client_pid/) | `equals`, `not`, `in`, `not_in`, `lt`, `lte`, `gt`, `gte` |
| [`session_id`](https://libtmux.org/en/tmux/latest/catalog/formats/session_id/) | `sessionId` | `{:id, :session}` | [`session_id`](https://libtmux.org/en/tmux/latest/catalog/formats/session_id/) | `equals`, `not`, `in`, `not_in` |
| `width` | `width` | `:integer` | [`client_width`](https://libtmux.org/en/tmux/latest/catalog/formats/client_width/) | `equals`, `not`, `in`, `not_in`, `lt`, `lte`, `gt`, `gte` |
| Relation | Wire relation | Cardinality | Target | Operators |
| --- | --- | --- | --- | --- |
| `session` | `session` | [`one`]() | `session` | `is`, `is_not` |