# Getting started

Source: https://libtmux.org/en/lisp/latest/guides/getting-started/

> Getting started: libtmux documentation.

libtmux-lisp is a native Common Lisp tmux client library. Acquire a CLOS
object graph once, process its panes with ordinary sequences, and make
mutations through an explicit client.

**Implemented:** captured sessions, physical windows, window links and panes;
bounded subprocess transport; cancellation; named operations, byte buffers,
validated native queries, eager QuerySets, optional regex and bounded JSON;
[bounded declarative workspaces](https://libtmux.org/en/lisp/latest/workspace/source-guide/) with pure plans, explicit
apply and an explicit-endpoint CLI;
[decoded configuration reads](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/configuration.md),
[printer inspection](https://libtmux.org/en/lisp/latest/guides/operations/#configuration-inspection) and
[typed option, hook and environment mutations](https://libtmux.org/en/lisp/latest/guides/operations/#configuration-mutations)
for the exact reviewed tmux catalogs through 3.8; explicit entity and server
termination with retained outcome evidence; [owned daemon scopes](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/daemons.md);
optional [queued control commands, pane-output streams and explicit reconnection](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/control.md) on
tmux 3.8+.
**In development:** broader event qualification and source filtering.
MCP is intentionally out of scope. The `0.1.0`
systems are unreleased.

## Install from source

Use Linux x86-64 (including WSL), kernel 6.9+, Bash and a native tmux binary.
The supported tmux range is 3.3a through 3.8, including intermediate releases.
Plain tmux 3.3 is rejected. The pinned toolchain is SBCL 2.6.10,
ASDF/UIOP 3.3.7 and Qlot 1.8.4. The
[process requirements](https://libtmux.org/en/lisp/latest/concepts/transport/#supported-process-boundary) also apply
to WSL; its distribution name alone does not establish kernel capabilities.

Core and query loading require ASDF/UIOP only. Mise reproduces the contributor
toolchain; Qlot installs development and optional dependencies. ASDF is the
Common Lisp build system.

```console
$ git clone https://github.com/libtmux/libtmux-lisp.git && cd libtmux-lisp
```

In an existing SBCL application with ASDF loaded, register this checkout from
its root:

```lisp
(asdf:load-asd (truename "libtmux.asd"))
```

Load core and its local query API:

```lisp
(asdf:load-system "libtmux/query")
```

To reproduce the pinned contributor environment and run all optional examples,
use [mise 2026.11.3+](https://mise.jdx.dev/getting-started.html).
Review [mise.toml](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/mise.toml), then trust the checkout:

```console
$ mise trust
```

```console
$ mise install
```

Install the locked dependencies. This is the network step:

```console
$ mise run setup
```

Run the complete workspace example. It creates a private tmux socket, checks
the results and removes its daemon; it never addresses your default server.

```console
$ mise run examples
```

Success includes `EXAMPLES PASS: graph, links, native, compiled, relation,
regex and JSON selection, declarative workspace, false/presence, ownership,
refresh, bytes, capture, close, terminate`. Core and query loading need ASDF/UIOP only; optional
regex/JSON use the locked CL-PPCRE/Jzon systems. The example runner also needs
Python 3. No Java runtime is involved.

## Choose an ASDF system

All systems share this source distribution, version and [changelog](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/CHANGELOG.md).
Load only the systems your application uses. Each system has its own README:

| System | Use it for | Additional dependencies |
| --- | --- | --- |
| [`libtmux`](<https://libtmux.org/en/lisp/latest/reference/libtmux/>) | Captures, CLOS objects and explicit tmux operations | ASDF/UIOP |
| [`libtmux/query`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/query/README.md) | Native predicates, relations and eager QuerySets | Core |
| [`libtmux/regex`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/regex/README.md) | CL-PPCRE search and full-match filters | Query, CL-PPCRE |
| [`libtmux/json`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/json/README.md) | Bounded query document interchange | Query, Jzon |
| [`libtmux/control`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/control/README.md) | Queued requests, output streams and reconnection on tmux 3.8+ | Core |
| [`libtmux/workspace`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/workspace/README.md) | Native declarations, topology previews and explicit apply | Core, Jzon |
| [`libtmux/workspace/cli`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/workspace/cli/README.md) | Workspace validation, previews and explicit-endpoint apply | Workspace |

These directories provide system documentation. ASDF definitions remain in
[`libtmux.asd`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/libtmux.asd); source stays in [`src/`](<https://github.com/libtmux/libtmux-lisp/tree/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/src>). Secondary systems are
optional parts of one library release.

## Start an isolated daemon

This complete function starts an empty-config daemon, creates one session and
returns pane data after closing its client and daemon. The
[runnable program](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/examples/managed-daemon.lisp) includes loading and assertions.

<!-- example:managed -->
```lisp
(defun inspect-owned-workspace (&key (program "tmux"))
  "Start an isolated daemon; return owned pane data after closing both resources."
  (libtmux:with-daemon (daemon :program program)
    (libtmux:with-client (client :program (libtmux:daemon-program daemon)
                                 :socket-path (libtmux:daemon-socket-path daemon))
      (let ((outcome (libtmux:new-session client :name "demo" :command '("/bin/cat"))))
        (when (libtmux:outcome-observation-error outcome)
          (error (libtmux:outcome-observation-error outcome))))
      (map 'list #'libtmux:entity-data
           (libtmux:panes (libtmux:fetch-snapshot client))))))
```
<!-- /example -->

Run it from the checkout with the pinned toolchain:

```console
$ mise exec -- tools/lisp.sh examples/managed-daemon.lisp
```

It prints `MANAGED DAEMON PASS` and a pane record. Read
[daemon scopes](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/daemons.md) for environment, readiness and cleanup contracts.
Use an ordinary client alone when connecting to an existing server.

## Inspect a workspace

The [REPL tutorial](https://libtmux.org/en/lisp/latest/guides/tutorial/) loads the example package, whose local
nickname `tm` refers to [`libtmux`](<https://libtmux.org/en/lisp/latest/reference/libtmux/>). This complete function returns ordinary
owned data after closing its client:

<!-- example:inventory -->
```lisp
(defun inspect-workspace (socket-path &key (program "tmux"))
  "Acquire one graph from SOCKET-PATH; return owned session/window/pane data."
  (tm:with-client (client :program program :socket-path socket-path)
    (let ((snapshot (tm:fetch-snapshot client)))
      (loop for session across (tm:sessions snapshot)
            collect
            (list :session (tm:name session)
                  :windows
                  (loop for link across (tm:window-links session)
                        for window = (tm:window link)
                        collect (list :index (tm:index link)
                                      :name (tm:name window)
                                      :panes (map 'list #'tm:entity-data
                                                  (tm:panes window)))))))))
```
<!-- /example -->

[libtmux:fetch-snapshot](https://libtmux.org/en/lisp/latest/reference/libtmux-fetch-snapshot/) contacts tmux.
Traversal and projection inside the loops are
local. Returned strings and sequence containers belong to the caller.
Acquisition spans an interval; it is not a transaction over a changing daemon.

The [walkthrough](https://libtmux.org/en/lisp/latest/guides/tutorial/#build-a-linked-workspace) also creates two
sessions, splits a pane and links one physical window into both sessions:

```mermaid
graph LR
  work[Session: work] --> a[Window link: index 0]
  work --> b[Window link: logs]
  review[Session: review] --> c[Window link: index 7]
  review --> d[Window link: notes]
  a --> editor[Physical window: editor]
  c --> editor
  editor --> p1[Pane 1]
  editor --> p2[Pane 2]
  b --> logs[Physical window: logs]
  d --> notes[Physical window: notes]
```

Select a **window link** when session/index context matters. Rename the physical
window to affect every link. Refresh returns a new observation; earlier captures
retain their values. See [identity and ownership](https://libtmux.org/en/lisp/latest/concepts/captures/).

Process the retained capture with ordinary Lisp. This function uses only local
accessors and returns a list of owned pane data:

<!-- example:selection -->
```lisp
(defun wide-active-panes (snapshot)
  "Select captured active panes at least 40 cells wide; no tmux commands."
  (loop for pane across (tm:panes snapshot)
        when (and (tm:active-p pane) (>= (tm:width pane) 40))
          collect (tm:entity-data pane)))
```
<!-- /example -->

## Compile a local query

The tutorial's example package gives [`libtmux`](<https://libtmux.org/en/lisp/latest/reference/libtmux/>) and [`libtmux.query`](<https://libtmux.org/en/lisp/latest/reference/libtmux-query/>) the local
nicknames `tm` and `q`. This checked function compiles once, filters a captured
vector locally, and returns ordinary owned data:

<!-- example:query -->
```lisp
(defparameter *wide-active-filter*
  (q:compile-filter
   :pane
   '(:and (:= :pane/active t)
          (:>= :pane/width 40))))

(defun wide-active-pane-data (snapshot)
  "Return owned data for captured active panes at least 40 cells wide; no I/O."
  (map 'vector #'tm:entity-data
       (q:matching *wide-active-filter* (tm:panes snapshot))))
```
<!-- /example -->

[`matching`](<https://libtmux.org/en/lisp/latest/reference/libtmux-query-matching/>) returns a fresh simple vector and preserves order and duplicates.
The compiler owns mutable operands, rejects cycles and mistyped fields, and
never evaluates query data. Scalar filtering performs no tmux calls. Read the
[query guide](https://libtmux.org/en/lisp/latest/guides/queries/) for presence, limits, inspection and strict
cardinality.

Relations traverse the same captured graph. This checked query follows session
links to physical windows and panes, then uses the eager local QuerySet:

<!-- example:relation -->
```lisp
(defparameter *active-session-filter*
  (q:compile-filter
   :session
   '(:any :session/window-links
     (:is :link/window
      (:any :window/panes
       (:= :pane/active t))))))

(defun active-session-data (snapshot)
  "Return owned data for sessions with an active pane; no I/O."
  (map 'vector #'tm:entity-data
       (q:items
        (q:where (q:from (tm:sessions snapshot) :model :session)
                 *active-session-filter*))))
```
<!-- /example -->

## Declare and apply a workspace

The optional `libtmux/workspace` ASDF system reads a closed, bounded JSON
document, plans locally, and applies only through an explicit client. The
shipped [`workspace.json`](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/examples/workspace.json) creates two windows and
three panes from argv arrays. This tested function returns both the inspectable
plan and retained application evidence:

<!-- example:workspaceconfig -->
```lisp
(defun apply-workspace-document (client pathname)
  "Plan and apply one bounded workspace document through explicit CLIENT.
Return the preview steps and application result as two values. Application
shares a 30-second budget; file loading and the preview precede that budget."
  (let* ((workspace (ws:read-workspace pathname))
         (snapshot (tm:fetch-snapshot client))
         (plan (ws:plan-workspace workspace snapshot)))
    (values (ws:plan-steps plan)
            (ws:apply-workspace client workspace :total-timeout 30))))
```
<!-- /example -->

An exact second apply returns an empty plan and no command outcomes. A conflict
fails before dispatch. A partial failure retains the failed step and every core
outcome; no mutation is retried or rolled back. The CLI's `validate` and `plan`
modes need no daemon, while `apply` requires `--socket-path`. Read the
[workspace guide](https://libtmux.org/en/lisp/latest/workspace/source-guide/) and
[generated workspace API](https://libtmux.org/en/lisp/latest/workspace/reference/).

## Add regex and a versioned filter document

Optional systems keep core/query dependency-free while providing a compiled
CL-PPCRE scanner and a bounded native JSON envelope. These checked definitions
match the demo's `cat` panes, serialize the filter, read it with explicit regex
permission, and evaluate the restored predicate against the same capture:

<!-- example:regex -->
```lisp
(defparameter *cat-command-filter*
  (q:compile-filter
   :pane
   '(:regex-full-match :pane/current-command
     (:cl-ppcre "CAT" :case-insensitive-mode t))))

(defun cat-pane-data (snapshot)
  "Return owned data for captured panes whose command is exactly cat; no I/O."
  (map 'vector #'tm:entity-data
       (q:matching *cat-command-filter* (tm:panes snapshot))))
```
<!-- /example -->

<!-- example:filterdocument -->
```lisp
(defun round-trip-filter-document (snapshot)
  "Round-trip the checked regex filter and apply it to one retained snapshot."
  (let* ((document (j:write-filter *cat-command-filter*))
         (restored (j:read-filter document :allow-regex t)))
    (values document
            (map 'vector #'tm:entity-data
                 (q:matching restored (tm:panes snapshot))))))
```
<!-- /example -->

The JSON decoder rejects duplicates, unknown names, null operands and exhausted
input/depth/node/string/membership/numeric bounds. It never interns external
names. See [optional query interchange](https://libtmux.org/en/lisp/latest/topics/query-interchange/).

## Find the right guide

| Task | Start here |
| --- | --- |
| Load the library; inspect and build a workspace | [REPL tutorial](https://libtmux.org/en/lisp/latest/guides/tutorial/) |
| Start and stop a private empty-config daemon | [Daemon scopes](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/daemons.md) |
| Filter with `loop`, handle false/absence, export plists | [Local data recipes](https://libtmux.org/en/lisp/latest/guides/tutorial/#select-and-project-locally) |
| Compile typed filters, traverse relations and compose QuerySets | [Native queries](https://libtmux.org/en/lisp/latest/guides/queries/) · [Query API](https://libtmux.org/en/lisp/latest/reference/) |
| Add CL-PPCRE or exchange bounded filter documents | [Query interchange](https://libtmux.org/en/lisp/latest/topics/query-interchange/) · [JSON API](https://libtmux.org/en/lisp/latest/reference/) |
| Validate, plan or explicitly apply a declarative workspace | [Workspaces](https://libtmux.org/en/lisp/latest/workspace/source-guide/) · [Workspace API](https://libtmux.org/en/lisp/latest/workspace/reference/) |
| Rename, resize, move, send input, capture, use buffers or terminate explicit targets | [Operation contracts](https://libtmux.org/en/lisp/latest/guides/operations/) |
| Read typed options, hooks and environment | [Decoded configuration](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/configuration.md) |
| Inspect native option, hook and environment output | [Configuration inspection](https://libtmux.org/en/lisp/latest/guides/operations/#configuration-inspection) |
| Look up signatures, multiple values and conditions | [Public API](https://libtmux.org/en/lisp/latest/reference/) · [Captured fields](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/fields.md) |
| Understand deadlines, cancellation and cleanup | [Transport](https://libtmux.org/en/lisp/latest/concepts/transport/) |
| Reconnect without replaying commands | [Fresh control observations](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/control.md#reconnect-with-fresh-observations) |
| Evaluate async, streaming and performance support | [Capabilities and limits](https://libtmux.org/en/lisp/latest/guides/capabilities/) |
| Rerun FiveAM tests at the REPL or inspect CI coverage | [Testing](https://libtmux.org/en/lisp/latest/guides/testing/) · [Verification](https://libtmux.org/en/lisp/latest/guides/verification/) |
| Navigate all documentation | [Guide index](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/index.md) |

Core subprocess calls block. Each client accepts one external operation at a
time; concurrent reuse signals [`client-busy`](<https://libtmux.org/en/lisp/latest/reference/libtmux-client-busy/>). Optional control
[`submit-command`](https://libtmux.org/en/lisp/latest/reference/libtmux-control-submit-command/) queues a request
without awaiting its reply; use
[`await-request`](https://libtmux.org/en/lisp/latest/reference/libtmux-control-await-request/) or
[`next-event`](https://libtmux.org/en/lisp/latest/reference/libtmux-control-next-event/) for bounded waits.
Closing a client releases its resources and leaves tmux running.
[`kill-server`](https://libtmux.org/en/lisp/latest/reference/libtmux-kill-server/)
is a separate explicit operation that proves the daemon accepted during its
preflight exited. [Daemon scopes](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/daemons.md) separately own a new private
daemon; the tutorial also documents explicit tmux setup and cleanup.

Named mutations preserve dispatch evidence and separate successful changes
from follow-up observation failures. Inspect
[command-outcome](https://libtmux.org/en/lisp/latest/reference/libtmux-command-outcome/) or the condition's
[error-outcome](https://libtmux.org/en/lisp/latest/reference/libtmux-error-outcome/); never retry an uncertain
mutation blindly. See
[outcome handling](https://libtmux.org/en/lisp/latest/guides/tutorial/#handle-command-and-observation-outcomes).

The examples are shipped source, executed on isolated servers and checked for
excerpt drift. All tmux releases from 3.3a through 3.8 are supported. CI uses the
latest stable SBCL pin and tests only the range's endpoints, 3.3a and 3.8, to
reduce build time. No benchmark result is claimed.

For changes, read [Contributing](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/.github/CONTRIBUTING.md) and
[Writing](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/.github/WRITING.md). Licensed under [MIT](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/LICENSE).

[Branding assets](https://github.com/libtmux/libtmux-lisp/blob/c2e5fc4ed3befda21f45dfa5d5868c38fd138311/docs/branding.md) include the logo, icons, and social cards.
