# libtmux for Clojure > The Clojure library (io.github.libtmux:libtmux-clojure). These pages include its guides, tested examples and API documentation. - [Clojure API reference](https://libtmux.org/en/clojure/latest/reference/): every public symbol, generated from the source. Hosted on libtmux.org. --- # API glossary Source: https://libtmux.org/en/clojure/latest/reference/glossary/ > Kinds, modifiers and package labels in the Clojure API reference. ## Symbol kinds Kinds identify declarations in the Clojure API reference. ### module A Clojure namespace containing public vars. Require the namespace with an alias, then call a var as alias/name. A namespace and an installable artifact are different boundaries. References: [Clojure namespaces](https://clojure.org/reference/namespaces). ### function A function stored in a public Clojure var. The signature lists argument vectors. A trailing ! marks external I/O in this library; captured data operations remain local. References: [Clojure vars](https://clojure.org/reference/vars). ## Modifiers Modifiers add information about calling, implementing, or accessing a declaration. Their precise effect follows the language and the signature. ### overload Alternative argument counts accepted by one Clojure function. Each argument vector is an arity. Selection uses the number of arguments; it does not dispatch on Java parameter types. References: [Clojure function arities](https://clojure.org/reference/special_forms#fn). ## Package context Package labels identify where an API is supplied. ### package The Maven artifact supplying this namespace. Use the installation instructions for artifact coordinates. The optional core.async adapter is a separate artifact. References: [Clojure namespaces](https://clojure.org/reference/namespaces). --- # Concepts Source: https://libtmux.org/en/clojure/latest/concepts/ > Understand Clojure captures, explicit references and resource ownership. Start with the [object graph](https://libtmux.org/en/clojure/latest/concepts/server-session-window-pane/), then use the [task examples](https://libtmux.org/en/clojure/latest/examples/) to apply these contracts. - [Server, session, window, pane](https://libtmux.org/en/clojure/latest/concepts/server-session-window-pane/): Traverse captured maps and choose the reference used by an effect. - [Captures, effects and streams](https://libtmux.org/en/clojure/latest/guides/captures-and-effects/): Own clients, workers and subscriptions; inspect failures before retrying. - [Captured values](https://libtmux.org/en/clojure/latest/guides/captured-values/): Look up namespaced fields and distinguish unavailable from absent data. --- # Server, session, window, pane Source: https://libtmux.org/en/clojure/latest/concepts/server-session-window-pane/ > Traverse one captured graph and keep effects explicit through Java references. [`open!`][open] returns a closeable Java server client for an explicit socket. [`snapshot!`][snapshot] acquires a graph of persistent maps. A snapshot contains sessions; sessions contain window placements; windows contain panes. [`data/windows`][windows] and [`data/panes`][panes] traverse that capture without another tmux command. Ordinary keyword lookup reads its scalar fields. Maps retain an opaque Java handle at `:tmux/ref`. Pass that reference to an effect such as [`rename!`][rename]. Editing an ID in a map changes the map; it does not change the target authenticated by its reference. The old map keeps its earlier name after a rename. [`refresh!`][refresh] acquires a new observation. A linked window can occur in several sessions. [`same-entity?`][entity] compares physical identity; [`same-link?`][link] also compares its placement. Keep input order and repeated placements when processing the graph. Use native collection functions to filter captured values. When a Java [`FilterExpr`]() already exists, [`matching`][matching] adapts it to a Clojure transducer or lazy sequence. [`exactly-one`][one] checks cardinality before an effect. [`data`][data] removes operational references for detached EDN export. The [quickstart](https://libtmux.org/en/clojure/latest/) demonstrates these operations. The [captured-value reference](https://libtmux.org/en/clojure/latest/guides/captured-values/) lists the keys; [captures and effects](https://libtmux.org/en/clojure/latest/guides/captures-and-effects/) explains ownership, lazy processing and recovery. [open]: https://libtmux.org/en/clojure/latest/reference/libtmux-core-open/ [snapshot]: https://libtmux.org/en/clojure/latest/reference/libtmux-core-snapshot/ [windows]: https://libtmux.org/en/clojure/latest/reference/libtmux-data-windows/ [panes]: https://libtmux.org/en/clojure/latest/reference/libtmux-data-panes/ [rename]: https://libtmux.org/en/clojure/latest/reference/libtmux-core-rename/ [refresh]: https://libtmux.org/en/clojure/latest/reference/libtmux-core-refresh/ [entity]: https://libtmux.org/en/clojure/latest/reference/libtmux-data-same-entity/ [link]: https://libtmux.org/en/clojure/latest/reference/libtmux-data-same-link/ [matching]: https://libtmux.org/en/clojure/latest/reference/libtmux-data-matching/ [one]: https://libtmux.org/en/clojure/latest/reference/libtmux-data-exactly-one/ [data]: https://libtmux.org/en/clojure/latest/reference/libtmux-data-data/ --- # tmux client library for Clojure Source: https://libtmux.org/en/clojure/latest/core/ > tmux client library for Clojure. Control tmux, connect to and create servers, navigate sessions, windows, and panes, send keys, watch for changes via control mode, capture pane contents The Clojure packages have not been published. Follow the source installation steps in [Getting started](https://libtmux.org/en/clojure/latest/guides/getting-started/).Control tmux, connect to and create servers, navigate sessions, windows, and panes, send keys, watch for changes via control mode, capture pane contents. ## At a glance | Function | Wraps | | ---- | ----- | | [`libtmux.core/open!`](https://libtmux.org/en/clojure/latest/reference/libtmux-core-open/) | A tmux server | | [`libtmux.core/sessions!`](https://libtmux.org/en/clojure/latest/reference/libtmux-core-sessions/) | A session, ID `$n` | | [`libtmux.data/windows`](https://libtmux.org/en/clojure/latest/reference/libtmux-data-windows/) | A window in a session, ID `@n` | | [`libtmux.data/panes`](https://libtmux.org/en/clojure/latest/reference/libtmux-data-panes/) | A pane in a window, ID `%n` | ## Typed API - Typed objects instead of parsed format strings: [`libtmux.data`](https://libtmux.org/en/clojure/latest/reference/libtmux-data/) - Typed errors instead of exit codes and stderr: [`libtmux.core/error-summary`](https://libtmux.org/en/clojure/latest/reference/libtmux-core-error-summary/) ## What you get over the tmux CLI - Connect to or start a tmux server: [`libtmux.core/open!`](https://libtmux.org/en/clojure/latest/reference/libtmux-core-open/) - Traversal from a server down to its panes: [`libtmux.data/panes`](https://libtmux.org/en/clojure/latest/reference/libtmux-data-panes/) - Send keys to a pane: [`libtmux.core/send-keys!`](https://libtmux.org/en/clojure/latest/reference/libtmux-core-send-keys/) - Watch for changes through tmux control mode: [`libtmux.control/subscribe!`](https://libtmux.org/en/clojure/latest/reference/libtmux-control-subscribe/) - Capture pane contents: [`libtmux.core/capture!`](https://libtmux.org/en/clojure/latest/reference/libtmux-core-capture/) - Async operations: [`libtmux.async`](https://libtmux.org/en/clojure/latest/reference/libtmux-async/) - [Guides](https://libtmux.org/en/clojure/latest/guides/): Install from source and work with captures, effects and streams. - [API reference](https://libtmux.org/en/clojure/latest/reference/): Public namespaces, function arities and ownership contracts. - [core.async adapter](https://libtmux.org/en/clojure/latest/guides/core-async/): Optional event channels with explicit cancellation and closure. --- # Examples Source: https://libtmux.org/en/clojure/latest/examples/ > Run complete Clojure programs against private tmux servers. The [quickstart](https://libtmux.org/en/clojure/latest/) creates a session, renames and splits its window, sends a shell command, and prints the captured graph and output. Its setup installs the documented source into an isolated Maven repository. Every view closes its client and stops the server it created. The API reference includes complete programs with imports, Tools.deps setup, an owned-server launcher and expected output: - [Capture output](https://libtmux.org/en/clojure/latest/reference/libtmux-core-capture/): send literal input, wait for an application output marker, and read the screen. - [Build a layout](https://libtmux.org/en/clojure/latest/reference/libtmux-core-split-pane/): split panes, rename a window, move its placement and compare physical identity with link identity. - [Capture concurrently](https://libtmux.org/en/clojure/latest/reference/libtmux-async-submit/): submit bounded work and cancel a wait while checking cleanup. - [Observe UTF-8 output](https://libtmux.org/en/clojure/latest/reference/libtmux-control-subscribe/): consume a bounded stream and inspect its terminal state. - [Use core.async](https://libtmux.org/en/clojure/latest/reference/libtmux-core-async-observe/): stop the adapter and continue using its borrowed control connection. The stream examples require tmux 3.7 or newer. On an older supported tmux, they print the capability refusal shown beside the expected output. --- # Guides Source: https://libtmux.org/en/clojure/latest/guides/ > Guides for io.github.libtmux:libtmux-clojure. Connect to tmux and work with its sessions, windows, and panes. - [Getting started](https://libtmux.org/en/clojure/latest/guides/getting-started/): Read about libtmux-clojure. - [Captures, effects and streams](https://libtmux.org/en/clojure/latest/guides/captures-and-effects/): Read about clojure. - [Captured values](https://libtmux.org/en/clojure/latest/guides/captured-values/): Read about clojure captured-value reference. --- # Getting started Source: https://libtmux.org/en/clojure/latest/guides/getting-started/ > Getting started: io.github.libtmux:libtmux-clojure documentation. **libtmux for Clojure:** a data-first façade over [libtmux-java][java-core]. It acquires tmux state as persistent maps and vectors, retains a Java handle at `:tmux/ref` for an explicit operation, and leaves ordinary Clojure collection processing local and unconstrained. ## Install The Clojure façade has not shipped. Its Maven coordinate is `io.github.libtmux:libtmux-clojure`; there is no public release to install yet. Build and verify it from this checkout: ```console $ ./gradlew :libtmux-clojure:check :integration-tests:clojureIntegrationTest ``` The façade requires JDK 25 or newer. The project tests tmux 3.3a through 3.7c; [control streaming][guide-streaming] requires tmux 3.7 or newer. ## Quick start [`open!`]() connects to an explicit socket and owns only the local client. [`snapshot!`]() acquires one captured graph. The `data/windows` and `data/panes` walks below read that graph; they do not issue another tmux command. The example runner supplies `libtmux.docs.socket` for its private server. In a REPL, replace that property lookup with the path to your server's socket. The [complete programs][programs] include a launcher that sets up and removes an isolated server for you. ```clojure (require '[libtmux.core :as tm] '[libtmux.data :as data]) (let [socket-path (java.nio.file.Path/of (System/getProperty "libtmux.docs.socket") (make-array String 0))] (with-open [server (tm/open! {:socket-path socket-path})] (tm/new-session! server {:name "docs-quick-start"}) (let [snapshot (tm/snapshot! server) session (first (filter #(= "docs-quick-start" (:session/name %)) (:tmux/sessions snapshot)))] [(:session/name session) (count (data/windows session)) (count (data/panes session))]))) ;; => ["docs-quick-start" 1 1] ``` Use `with-open` for every client, runtime, control connection, and subscription that this program owns. Closing a [`Server`]() releases client resources; it does not kill the tmux server. ## Namespaces - [`libtmux.core`][core-api] — acquisition, effects, options, and command groups. - [`libtmux.data`][data-api] — captured traversal, projections, identity, cardinality, and Java predicate adapters. - [`libtmux.async`][async-api] — bounded tasks, deadlines, cancellation, and CompletionStage interop. - [`libtmux.control`][control-api] — explicit control replies and owned output subscriptions. ## Run complete programs The [Clojure API programs][programs] include their imports, entry points, consumer dependencies, and private-server launcher. Start with captured session/window/pane traversal, then compare native and Java predicates, create a detached window, or send literal input and verify its captured output. The capability programs add layout/movement, bounded capture and cancellation, control output, and Java command exit results. CI executes every program and checks its displayed result. ## What it leaves out - **No hidden refreshes.** Acquisition and mutation end in `!`; captured traversal and native `filter`, `map`, `into`, and transducers stay local. - **No second query engine.** Use Clojure predicates for local logic, or adapt a canonical Java [`FilterExpr`]() when its model and serialization semantics matter. The façade does not yet accept an EDN query language. - **No editable remote maps.** `assoc` changes ordinary data, not tmux. Keep `:tmux/ref` for an explicit effect, or use `data/data` for a detached EDN projection. - **No implicit asynchronous runtime.** [`libtmux.async`]() is bounded and closeable. The [`core.async`]() bridge is a separate optional artifact. - **No Clojure MCP server.** Model-facing tools belong to [libtmux-mcp][mcp-guide]; this façade remains a JVM library. - **No ClojureScript, Babashka, or native-image target.** The operational API depends on Java handles and the Java transport. ## Support and status Releases are numbered `0.1.N`, and each release increments `N`. The API is not settled: any release may change or remove exported identifiers, and the changelog marks each break. Pin an exact version to avoid one. Only the newest release is supported, with no backports. The Clojure façade is currently pre-release; CI tests Clojure 1.12.6 on JDK 25 and 27 against the supported tmux endpoints, 3.3a and 3.7c. [Report a defect][contributing] with the tmux version, endpoint choice, and the smallest reproducer. ## Documentation - [Clojure guide][guide] — captures, links, filtering, effects, control streams, [`core.async`](), cancellation, and Java interop. - [Captured-data reference][reference] — every map key, relation, identity rule, and Java filter boundary. - [Native API reference][api] — public namespaces, arities, and docstrings, generated from the Clojure source and linked to it. - [Optional `core.async` adapter][core-async] — ownership, capacity, loss, and terminal outcomes. - [MCP guide][mcp-guide] — the model-facing server and its explicit boundary from this JVM façade. - [Benchmarking guide][benchmarks] — how the Java operation measurements are run. It does not yet claim a façade-specific benchmark. - [Changelog](https://github.com/libtmux/libtmux-java/blob/5f88402151ff08fc0390dd438b90963692ec3fe4/libtmux-clojure/CHANGELOG.md) — module-visible changes once this façade ships. Every Clojure snippet in this README and the guide runs against an isolated tmux server in `:integration-tests:clojureIntegrationTest`; no snippet is illustrative pseudocode. The module's `javadoc` classifier contains native Clojure reference HTML and an offline source browser. Build it with `:libtmux-clojure:clojureApiJar`; the generated site starts at `build/docs/clojure/index.html` in this module. [benchmarks]: https://github.com/libtmux/libtmux-java/blob/5f88402151ff08fc0390dd438b90963692ec3fe4/benchmarks/README.md [api]: https://libtmux.org/en/clojure/latest/reference/ [async-api]: https://libtmux.org/en/clojure/latest/reference/libtmux-async/ [contributing]: https://github.com/libtmux/libtmux-java/blob/5f88402151ff08fc0390dd438b90963692ec3fe4/.github/CONTRIBUTING.md [control-api]: https://libtmux.org/en/clojure/latest/reference/libtmux-control/ [core-api]: https://libtmux.org/en/clojure/latest/reference/libtmux-core/ [core-async]: https://libtmux.org/en/clojure/latest/guides/core-async/ [data-api]: https://libtmux.org/en/clojure/latest/reference/libtmux-data/ [guide]: https://libtmux.org/en/clojure/latest/guides/captures-and-effects/ [guide-streaming]: https://libtmux.org/en/clojure/latest/guides/captures-and-effects/ [java-core]: https://github.com/libtmux/libtmux-java/blob/5f88402151ff08fc0390dd438b90963692ec3fe4/libtmux/README.md [mcp-guide]: https://github.com/libtmux/libtmux-java/blob/5f88402151ff08fc0390dd438b90963692ec3fe4/docs/guide/mcp.md [programs]: https://github.com/libtmux/libtmux-java/blob/5f88402151ff08fc0390dd438b90963692ec3fe4/examples/api/README.md#clojure-programs [reference]: https://libtmux.org/en/clojure/latest/guides/captured-values/ --- # Captures, effects and streams Source: https://libtmux.org/en/clojure/latest/guides/captures-and-effects/ > Captures, effects and streams: io.github.libtmux:libtmux-clojure documentation. `libtmux-clojure` is a JVM façade over the Java library. It keeps tmux command encoding, transport, server identity and compatibility in Java, while exposing captured persistent Clojure values and ordinary collection operations. Start with the package [README](https://libtmux.org/en/clojure/latest/guides/getting-started/) for the install boundary and one capture. This guide explains the choices that matter after the first program: what is captured, what can perform I/O, who owns workers and streams, and where model-facing MCP tooling begins. The native API references list every documented public var in [the core façade](https://libtmux.org/en/clojure/latest/reference/) and [the optional adapter](https://libtmux.org/en/clojure/latest/reference/libtmux-core-async/). They derive arities and docstrings from source; module checks reject stale output. [Captured values](https://libtmux.org/en/clojure/latest/guides/captured-values/) documents the maps separately. For complete files with their own entry points and a private-server launcher, use the [Clojure API programs](https://github.com/libtmux/libtmux-java/blob/5f88402151ff08fc0390dd438b90963692ec3fe4/examples/api/README.md#clojure-programs). Every Clojure fence in reader-facing documentation is discovered and run against an owned tmux server. A fence must open and close its own client with `with-open` and end with one `;; =>` EDN result. The test fixture supplies `libtmux.docs.socket` and `libtmux.docs.binary`; applications supply their socket path and use their selected binary or the default `tmux` command. ## Values, links and projection [`sessions!`](), [`windows!`](), [`panes!`]() and [`clients!`]() acquire a graph. `data/windows` and `data/panes` traverse that graph without I/O. A window occurrence has a contextual `:tmux/link`; `data/same-link?` includes the session, window and index, while `data/same-entity?` compares only the physical identity. `nil` means an optional relationship or lookup was absent in a successful capture. Failed acquisition throws an [`ExceptionInfo`](); it is never represented as an empty collection. `data/data` returns a detached EDN projection without the Java refs required for effects or Java filter evaluation. ## Resource scopes and laziness Acquisition returns eager captured values. A lazy `filter` or `map` over those values reads only Clojure data, so it can escape the `with-open` scope. An I/O operation such as [`capture!`]() is different: realize it inside the scope that owns its server. Do not return `(map #(tm/capture! (:tmux/ref %)) panes)` from `with-open`; use `mapv`, `into`, or another eager reduction before closing. ```clojure (require '[libtmux.core :as tm] '[libtmux.data :as data]) (let [socket-path (java.nio.file.Path/of (System/getProperty "libtmux.docs.socket") (make-array String 0)) {:keys [lazy-names captures]} (with-open [server (tm/open! {:socket-path socket-path})] (let [session (tm/new-session! server {:name "docs-lazy"}) pane (first (data/panes (first (data/windows session))))] {:lazy-names (->> (tm/sessions! server) (filter #(= "docs-lazy" (:session/name %))) (map :session/name)) :captures (mapv #(tm/capture! (:tmux/ref %) {:from :history}) [pane])}))] [(vec lazy-names) (count captures)]) ;; => [["docs-lazy"] 1] ``` ```clojure (require '[libtmux.core :as tm] '[libtmux.data :as data]) (let [socket-path (java.nio.file.Path/of (System/getProperty "libtmux.docs.socket") (make-array String 0))] (with-open [server (tm/open! {:socket-path socket-path})] (let [session (tm/new-session! server {:name "docs-projection"})] (data/data (select-keys session [:tmux/ref :session/name]))))) ;; => {:session/name "docs-projection"} ``` ## Native filtering and Java expressions Use `filter`, `map`, `into` and transducers for unrestricted local Clojure logic. Java [`FilterExpr`]() remains available when an expression must use its canonical Java fields and relationship semantics. `data/predicate` returns an IFn. `data/matching` returns a transducer with one argument and a lazy sequence with an expression and collection. Both evaluate preserved `:tmux/ref` values, not fields edited in a Clojure map. ```clojure (require '[libtmux.core :as tm] '[libtmux.data :as data]) (import '[io.github.libtmux Session_]) (let [socket-path (java.nio.file.Path/of (System/getProperty "libtmux.docs.socket") (make-array String 0))] (with-open [server (tm/open! {:socket-path socket-path})] (tm/new-session! server {:name "docs-query"}) (->> (tm/sessions! server) (data/matching (.is (Session_/name) "docs-query")) (mapv :session/name)))) ;; => ["docs-query"] ``` Use `data/exactly-one` when zero or many candidates is an error, and `data/one-or-none` when absence is allowed. Establish cardinality before any effect. ## Effects, errors and execution modes Every effect in [`libtmux.core`]() takes a Java ref from `:tmux/ref`; a captured map is not an editable remote object. [`new-session!`](https://libtmux.org/en/tmux/latest/manual/new-session/), [`rename!`](), [`split-pane!`]() and [`refresh!`]() return new captured values when they can recapture the result. Void operations return `nil`. Validation errors include `:tmux/phase :validation` and `:tmux/dispatch :not-dispatched`. Transport and mutation failures retain the operation, dispatch certainty and whether effects are possible. Inspect the stable subset with `core/error-summary`; do not parse exception messages. [`batch!`]() and [`chain!`]() use fresh Java groups. Their per-position results can be `:complete`, `:failed`, `:skipped` or `:unknown`; neither operation promises a transaction or rollback. A group that can wait needs `{:kind :wait}` so its release can reserve capacity. ### Bounded capture work and cancellation [`libtmux.async/runtime!`]() bounds asynchronous work. Submit each capture as a task, then dereference its result after the runtime has admitted it. Task submission does not add another tmux transport or alter the captured values. ```clojure (require '[libtmux.async :as async] '[libtmux.core :as tm]) (let [socket-path (java.nio.file.Path/of (System/getProperty "libtmux.docs.socket") (make-array String 0))] (with-open [server (tm/open! {:socket-path socket-path}) runtime (async/runtime! {:max-running 3})] (tm/new-session! server {:name "docs-parallel"}) (let [tasks (mapv (fn [_] (async/submit! runtime #(tm/snapshot! server))) (range 2))] (mapv #(boolean (some (fn [session] (= "docs-parallel" (:session/name session))) (:tmux/sessions @%))) tasks)))) ;; => [true true] ``` [`cancel!`]() is explicit. A cancellation accepted after a task starts reports unknown dispatch certainty, because the enclosing tmux operation might have started. Use a separate `:wait` task for operations that wait for a release. ```clojure (require '[libtmux.async :as async] '[libtmux.core :as tm]) (import '[java.time Duration]) (let [socket-path (java.nio.file.Path/of (System/getProperty "libtmux.docs.socket") (make-array String 0))] (with-open [server (tm/open! {:socket-path socket-path}) runtime (async/runtime! {})] (let [started (promise) task (async/submit! runtime {:kind :wait} #(do (deliver started true) (tm/await-channel! server "docs-cancel" (Duration/ofSeconds 20))))] @started (async/cancel! task) (try @task (catch clojure.lang.ExceptionInfo failure [(:tmux/error (ex-data failure)) (:tmux/dispatch (ex-data failure))]))))) ;; => [:cancelled :unknown] ``` ### Layouts and literal input Creation and layout functions modify tmux and return fresh observations when they create a target. [`apply-layout!`]() accepts tmux's explicit layout text. [`send-literal!`]() writes characters without treating them as tmux key names; [`send-keys!`](https://libtmux.org/en/tmux/latest/manual/send-keys/) takes an explicit key vector. Their complete return does not mean the program in the pane has finished processing input. ```clojure (require '[libtmux.core :as tm] '[libtmux.data :as data]) (let [socket-path (java.nio.file.Path/of (System/getProperty "libtmux.docs.socket") (make-array String 0))] (with-open [server (tm/open! {:socket-path socket-path})] (let [session (tm/new-session! server {:name "docs-layout"}) window (tm/new-window! (:tmux/ref session) {:name "docs-layout-window" :detached? true}) pane (first (data/panes window)) _ (tm/apply-layout! (:tmux/ref window) (:window/layout window)) split (tm/split-pane! (:tmux/ref pane) {:direction :right :percent 30})] [(:window/name window) (= (:tmux/link pane) (:tmux/link split))]))) ;; => ["docs-layout-window" true] ``` ```clojure (require '[clojure.string :as str] '[libtmux.core :as tm] '[libtmux.data :as data]) (let [socket-path (java.nio.file.Path/of (System/getProperty "libtmux.docs.socket") (make-array String 0))] (with-open [server (tm/open! {:socket-path socket-path})] (let [session (tm/new-session! server {:name "docs-literal"}) pane (first (data/panes (first (data/windows session))))] (tm/send-literal! (:tmux/ref pane) "printf docs-literal") (tm/send-keys! (:tmux/ref pane) ["Enter"]) (boolean (some #(str/includes? % "docs-literal") (tm/capture! (:tmux/ref pane) {:from :history})))))) ;; => true ``` ### Contextual targeting A linked window keeps the same physical window identity but has another occurrence context. Effects use its preserved ref, so an unlinked occurrence does not silently target a remaining link in another session. ```clojure (require '[libtmux.core :as tm] '[libtmux.data :as data]) (let [socket-path (java.nio.file.Path/of (System/getProperty "libtmux.docs.socket") (make-array String 0))] (with-open [server (tm/open! {:socket-path socket-path})] (let [first-session (tm/new-session! server {:name "docs-link-first"}) second-session (tm/new-session! server {:name "docs-link-second"}) window (first (data/windows first-session))] (tm/link-window! (:tmux/ref window) (:tmux/ref second-session)) (let [linked (first (filter #(= (:window/id window) (:window/id %)) (data/windows (tm/refresh! (:tmux/ref second-session)))))] [(data/same-entity? window linked) (data/same-link? window linked)])))) ;; => [true false] ``` ## Lifecycle, streaming and optional adapters [`libtmux.async/runtime!`]() owns bounded JVM workers. Close it, or use `with-open`; closing a borrowed server or executor is never an adapter side effect. Tasks have explicit admission, cancellation and deadline results, and expose a Java [`CompletionStage`]() for Manifold or other JVM integration. Control needs tmux 3.7 or newer. On an older daemon [`attach!`]() is refused before any client exists, with `:tmux/error` `:tmux/unsupported-feature` and `:tmux/dispatch` `:not-dispatched`; the rest of the façade keeps the 3.3a minimum. Control observations have an explicit owner and terminal result. A bounded consumer can lose events: the next read is a `:gap` naming how many were dropped, ahead of the events that remain, and an adapter loss stays distinct from it. `:capacity` bounds queued events; `:max-bytes` bounds their unescaped payload, defaulting to 16 MiB. An oversized event drops the buffered prefix and itself. `:loss` reports cumulative upstream event and byte counts. Text subscriptions decode preserved bytes independently per pane and reset after their own gaps. Malformed bytes become `\\xHH`; partial characters at termination appear in `:trailing-text`, keyed by pane ID. `:decoder-capacity` bounds panes with partial characters, defaulting to 1024. A reset reports `:decoder/reset` as `:gap` or `:capacity` and increments `:decoder/resets`. The terminal `:reason` distinguishes `:closed`, `:completed`, `:control-exit`, `:transport-failure`, `:protocol-failure`, and `:unknown`. A `%exit` notification ends the client; it does not prove daemon death. An unexplained EOF stays unknown. `:cause` retains the original exception, including any sensitive text. Do not model a subscription as an unbounded lazy sequence, and do not perform blocking observation work in a core.async `go` block. Add the optional `io.github.libtmux/libtmux-clojure-core-async` dependency only for [`libtmux.core-async`](). Stopping the channel adapter preserves the owned subscription's final outcome under `:upstream/terminal`, including loss, cause, and trailing partial text. Its `:adapter/events` loss counter stays separate from upstream counters. An empty slow stream reports a timeout without becoming terminal. A later event remains readable from the same owned stream. ```clojure (require '[libtmux.control :as control] '[libtmux.core :as tm]) (import '[java.lang AutoCloseable] '[java.time Duration]) (let [socket-path (java.nio.file.Path/of (System/getProperty "libtmux.docs.socket") (make-array String 0))] (with-open [server (tm/open! {:socket-path socket-path :binary (System/getProperty "libtmux.docs.binary")}) connection (control/attach! (:tmux/ref (first (tm/sessions! server))) (Duration/ofSeconds 2)) stream (control/subscribe! connection {:capacity 64 :max-bytes 65536})] (let [initial (control/next! stream (Duration/ofMillis 25)) pane (first (:reply/lines (control/send! connection ["new-window" "-P" "-F" "#{pane_id}" (str "stty -echo; printf docs-slow;" " read stop")]))) event (control/next! stream (Duration/ofMillis 900))] [(= :timeout (:stream/status initial)) (= pane (:pane/id event))]))) ;; => [true true] ``` ```clojure (require '[clojure.core.async :as async] '[libtmux.control :as control] '[libtmux.core :as tm] '[libtmux.core-async :as adapter]) (import '[java.time Duration]) (let [socket-path (java.nio.file.Path/of (System/getProperty "libtmux.docs.socket") (make-array String 0))] (with-open [server (tm/open! {:socket-path socket-path :binary (System/getProperty "libtmux.docs.binary")}) connection (control/attach! (:tmux/ref (first (tm/sessions! server))) (Duration/ofSeconds 2))] (let [owner (adapter/observe! connection) pane (first (:reply/lines (control/send! connection ["new-window" "-P" "-F" "#{pane_id}" (str "stty -echo; printf docs-async;" " read stop")]))) timeout (async/timeout 900) [event port] (async/alts!! [(adapter/events owner) timeout] :priority true)] (async/close! (adapter/events owner)) [(and (identical? port (adapter/events owner)) (= pane (:pane/id event))) (:reason (async/ [true :adapter-stopped] ``` ### Manifold CompletionStage recipe Manifold remains a consumer dependency. Add `manifold/manifold {:mvn/version "0.5.0"}` to the application, then adapt the public [`CompletionStage`](); it does not become a `libtmux` dependency. [`then!`]() uses the runtime's bounded callback executor. A Manifold deferred observes the stage; cancelling that deferred does not cancel the task, so call [`cancel!`]() explicitly when ownership requires it. ```clojure (require '[libtmux.async :as async] '[libtmux.core :as tm] '[manifold.deferred :as deferred]) (import '[java.util.concurrent CompletionStage]) (let [socket-path (java.nio.file.Path/of (System/getProperty "libtmux.docs.socket") (make-array String 0))] (with-open [server (tm/open! {:socket-path socket-path}) runtime (async/runtime! {})] (tm/new-session! server {:name "docs-manifold"}) (let [task (async/submit! runtime #(tm/snapshot! server)) stage (async/then! runtime task #(boolean (some (fn [session] (= "docs-manifold" (:session/name session))) (:tmux/sessions %)))) observed (deferred/->deferred stage)] [(instance? CompletionStage stage) @observed]))) ;; => [true true] ``` ## Compatibility and Java interop The façade supports JDK 25 or newer, the floor of the Java core it wraps, and the repository's tmux 3.3a through 3.7c matrix; control needs tmux 3.7 or newer. It is JVM-only: it does not run in ClojureScript, Babashka, or a native image. Existing Java handles, [`FilterExpr`](), the optional Jackson codec, and Java async types remain directly usable when the Clojure functions do not cover an operation. [`clojure-reference.md`](https://libtmux.org/en/clojure/latest/guides/captured-values/) lists every captured key and relation, its availability, identity meaning and Java filter support. ## MCP boundary and measurement This façade is for a Clojure process that owns its own tmux decisions. It does not turn arbitrary persistent maps, predicates, or Clojure functions into MCP tools. For an MCP client such as Codex or Claude Code, use [libtmux-mcp](https://github.com/libtmux/libtmux-java/blob/5f88402151ff08fc0390dd438b90963692ec3fe4/docs/guide/mcp.md): it has named tools, endpoint selection, toolsets, and bounded reads. A Clojure application can use both libraries, but their contracts remain separate. The [operation benchmark](https://github.com/libtmux/libtmux-java/blob/5f88402151ff08fc0390dd438b90963692ec3fe4/docs/benchmarks/operation-costs.md) measures Java operations under documented conditions. It is useful when a Clojure program changes its acquisition pattern, but it is not a Clojure façade benchmark. There is no published façade throughput or allocation claim. Measure the application's capture count, filtering/projection allocation, and any owned runtime queue under its real tmux workload before setting a capacity. --- # Captured values Source: https://libtmux.org/en/clojure/latest/guides/captured-values/ > Captured values: io.github.libtmux:libtmux-clojure documentation. [`libtmux.data/from-capture`]() creates every value in this reference. [`snapshot!`]() and the `*!` listing functions acquire one captured graph; `data/windows`, `data/panes`, `data/active-window` and `data/active-pane` traverse it without I/O. Scalar access is ordinary Clojure keyword lookup and is also local. Every owning map has its documented key, even when the value is `nil` because tmux did not report an optional value. A missing relationship means the parent or target was absent from an otherwise successful capture. Failed acquisition throws; it is never an empty graph. `data/data` removes every `:tmux/ref`. The ref is the only effect authority; editing `:session/id`, `:window/id` or `:pane/id` changes a map, not tmux. `data/same-entity?` compares `:tmux/identity`. `data/same-link?` also compares the window placement in `:tmux/link`; it does not establish a daemon generation or detect an unseen identical unlink-and-relink replacement. ## Shared keys - `:tmux/ref` is the Java handle. `data/data` removes it; effects and [`FilterExpr`]() need it, while identity does not. - `:tmux/identity` is always-present endpoint, server-PID, kind and physical-ID data. `same-entity?` compares it. - `:tmux/link` exists only on windows and panes. It holds session, window and index occurrence context; `same-link?` compares it. - `:tmux/realm` and `:tmux/server` are always-present identity text. Neither has a Java filter field. - `:tmux/pid` is an integer or `nil`; the key stays present when tmux does not report a daemon or pane PID. It has no Java filter field. - `:tmux/kind` is always-present `:server`, `:session`, `:window`, `:pane` or `:client` identity data. It has no Java filter field. - `:tmux/id` is always-present physical identity text. Editing it cannot retarget an effect, and it has no Java filter field. - `:tmux/captured-at` is a root-only [`Instant`](); `data/data` writes ISO-8601 text. `:tmux/version` is root-only text or `nil` when unavailable. Neither has a Java filter field. ## Root and sessions - `:tmux/sessions`, `:tmux/windows`, `:tmux/panes` and `:tmux/clients` are root-only captured vectors. They have no root Java relation. - `:session/id` and `:session/name` are always-present text. `Session_/id` and `Session_/name` support Java filtering. - `:session/attached?` is an always-present Boolean. `Session_/attached` supports Java filtering. - `:session/windows` is an always-present local vector relation. `data/windows` traverses it and `Session_/windows` is the canonical Java relation. ## Windows and panes - `:window/id` is always-present underlying-window text across links. `Window_/id` supports Java filtering. - `:window/index` is the always-present index within `:tmux/link`. `Window_/index` supports Java filtering. - `:window/name` is always-present text. `Window_/name` supports Java filtering. - `:window/active?` is an always-present Boolean for this link's session. `Window_/active` supports Java filtering. - `:window/linked?` is an always-present Boolean stating that the underlying window has another link. `Window_/linked` supports Java filtering. - `:window/width` and `:window/height` are always-present captured cell sizes; `:window/layout` is always-present layout text. They have no Java fields. - `:window/panes` is an always-present local vector relation. `data/panes` traverses it and `Window_/panes` is the canonical Java relation. - `:pane/id` is always-present physical-pane text; `Pane_/id` supports Java filtering. `:pane/index` is an always-present window index; `Pane_/index` supports Java filtering. - `:pane/active?` is an always-present Boolean for the captured window. `Pane_/active` supports Java filtering. - `:pane/current-command` and `:pane/current-path` are always-present tmux text. `Pane_/command` and `Pane_/path` support Java filtering. - `:pane/floating?` is Boolean or `nil` when tmux does not report it. The key stays present and it has no Java field. - `:pane/width` and `:pane/height` are always-present cell sizes; `Pane_/width` and `Pane_/height` support Java filtering. - `:pane/left` and `:pane/top` are always-present captured positions; `Pane_/left` and `Pane_/top` support Java filtering. - `:pane/title` is always-present text; `Pane_/title` supports Java filtering. - `:pane/pid` is an integer or `nil` when tmux does not report it. The key stays present and it has no Java field. - `:pane/edges` is an always-present map with unqualified Boolean edge keys. It has no Java field. ## Clients - `:client/name` is always-present tmux client text; `Client_/name` supports Java filtering. - `:client/session` is a session map or `nil` when no session is attached. The key stays present and `Client_/session` is the canonical Java relation. - `:client/active-window` and `:client/active-pane` are a captured map or `nil` when no attached session supplies them. Their keys stay present and they have no Java fields. Java expressions always evaluate the preserved Java ref through `data/predicate` or `data/matching`; they never evaluate edited or detached maps. [`Pane_`]() has no registered pane-to-window relation, so use captured traversal for that direction instead of inventing a canonical Java relation. --- # core.async adapter Source: https://libtmux.org/en/clojure/latest/guides/core-async/ > core.async adapter: io.github.libtmux:libtmux-clojure documentation. Optional [core.async][core-async] integration for [libtmux-clojure](https://libtmux.org/en/clojure/latest/guides/getting-started/). The base façade has no core.async dependency. ## Install This adapter has not shipped. Its Maven coordinate is `io.github.libtmux:libtmux-clojure-core-async`; there is no public release to install yet. Build and verify it from this checkout: ```console $ ./gradlew :libtmux-clojure-core-async:check ``` ## Ownership and backpressure [`observe!`]() borrows a control connection and owns its subscription, virtual worker, events channel, and terminal channel. Closing the adapter or its events channel stops that owned work; it never closes the borrowed connection. The adapter defaults to an unbuffered events channel. Choose `:event-capacity` deliberately, account for its one pending event, and inspect `:gap` values from the upstream subscription separately from adapter loss. Do not run blocking control I/O in a `go` block. The [Clojure guide][guide] contains executable control and core.async examples, including terminal outcomes and shutdown. See this module's [native API reference][api] for function arities and ownership contracts, and its [changelog](https://github.com/libtmux/libtmux-java/blob/5f88402151ff08fc0390dd438b90963692ec3fe4/libtmux-clojure-core-async/CHANGELOG.md) for release history. The [complete core.async program][program] includes imports, a CLI entry point, the optional dependency alias, private-server setup and cleanup. It closes its events consumer, reads the terminal outcome, and reuses the borrowed connection. The `javadoc` classifier contains native reference HTML and an offline source browser, generated from this module's var metadata. [api]: https://libtmux.org/en/clojure/latest/reference/libtmux-core-async/ [core-async]: https://clojure.github.io/core.async/clojure.core.async.html [guide]: https://libtmux.org/en/clojure/latest/guides/captures-and-effects/ [program]: https://github.com/libtmux/libtmux-java/blob/5f88402151ff08fc0390dd438b90963692ec3fe4/examples/api/README.md#clojure-programs