Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
170 commits
Select commit Hold shift + click to select a range
7e1eee1
Ops(feat): Add typed operation and engine spine
tony Jun 21, 2026
6d448b7
Ops(feat): Add classic + concrete engines and contract suite
tony Jun 21, 2026
0615122
Ops(feat): Add async engine, lazy plans, and op catalog
tony Jun 21, 2026
040e261
Ops(feat): Add persistent control-mode engine
tony Jun 21, 2026
7e7df41
Ops(feat): Add eager + lazy pane facades over the spine
tony Jun 21, 2026
9248294
Ops(feat): Add async control-mode + concrete engines
tony Jun 21, 2026
63239e2
Ops(feat): Add AckResult for no-output operations
tony Jun 21, 2026
20876d5
Models(feat): Add pure object-graph snapshots
tony Jun 21, 2026
88d5ebc
Ops(feat): Add read-seam list operations
tony Jun 21, 2026
7f5495f
docs(experimental): Add tmuxop-catalog directive
tony Jun 21, 2026
7a78bb0
ControlMode(fix): Consume startup ACK, drain unsolicited blocks
tony Jun 21, 2026
0a2dd2d
Ops(feat): Add lazy-plan chainability (>> and ; folding)
tony Jun 21, 2026
ba1c0dd
Facade(feat): Add Server/Session/Window facades + creation ops
tony Jun 21, 2026
df893a7
Imsg(feat): Add native imsg engine + live parity test
tony Jun 21, 2026
2c1af35
Facade(feat): Complete the facade matrix (Server/Session/Client)
tony Jun 21, 2026
847be6f
chore(deps[dev]): Add ty type checker + config
tony Jun 21, 2026
88b6556
Ops(feat): Add pluggable planners + {marked} fold
tony Jun 21, 2026
f709938
Ops(feat): Add non-list read operations
tony Jun 21, 2026
045c884
Ops(feat): Add pane mutation/creation operations
tony Jun 21, 2026
30cd6a3
Ops(feat): Add window mutation/navigation operations
tony Jun 21, 2026
fb2c3b2
Ops(feat): Add server/option/environment operations
tony Jun 21, 2026
3b02295
Ops(feat): Add paste-buffer operations
tony Jun 21, 2026
5c91903
docs(experimental): Document engines and lazy plans
tony Jun 21, 2026
948c01e
docs(CHANGES): Note experimental operations and engines
tony Jun 21, 2026
4a93feb
Ops(fix): Correct move-window -k and paste-buffer -r
tony Jun 21, 2026
aeb2789
Ops(fix): Resolve SlotRef src_target in lazy plans
tony Jun 21, 2026
7980da0
Ops(fix): Skip all decorates when a marked-fold create fails
tony Jun 21, 2026
082a311
Ops(fix): Mark save-buffer readonly to match its effects
tony Jun 21, 2026
3256c2a
Ops(docs): Fix PipePane parameter name in docstring
tony Jun 21, 2026
8196831
Ops(fix): Log imsg argv as a scalar tmux_cmd field
tony Jun 21, 2026
216c8c1
Ops(docs): Add doctests to the planner plan() methods
tony Jun 21, 2026
2405a04
Ops(fix): Resolve decorate src_target in {marked} folds
tony Jun 21, 2026
48227a1
Ops(fix): Keep ; a bare separator in control-mode engines
tony Jun 21, 2026
36bf46c
Ops(fix): Treat a blank captured id as no id in marked folds
tony Jun 21, 2026
7c0693d
Ops(fix): Complete a marked fold whose creator does not capture
tony Jun 21, 2026
2b7b83d
Ops(fix): Drop create stdout when attributing marked decorates
tony Jun 21, 2026
b5d80f0
Ops(fix): Target the concrete pane in marked decorate results
tony Jun 21, 2026
bf26545
Ops(fix): Decode SubprocessEngine output as UTF-8
tony Jun 21, 2026
8d3882d
Models(refactor): Use namespaced dataclasses.replace in snapshots
tony Jun 21, 2026
19c95a9
Ops(docs): Fix PipePane -o flag description
tony Jun 21, 2026
2fcd8ee
Ops(fix): Mark save-buffer mutating (it writes a file)
tony Jun 21, 2026
905a642
Ops(fix): Correlate control-mode blocks per command and by flags
tony Jun 21, 2026
9006caa
Ops(fix): Clear pending futures on async control-mode write failure
tony Jun 21, 2026
3fba6a6
Ops(fix): Suppress ProcessLookupError on async cancel terminate
tony Jun 21, 2026
efda68b
Engines(fix): Remove the unreachable asyncio engine kind
tony Jun 21, 2026
c9b0e71
Ops(fix): Normalize tmux master version in operation gates
tony Jun 21, 2026
562e48a
Ops(fix): Reject SendKeys literal+enter combination
tony Jun 21, 2026
fbab12e
Ops(fix): Keep all lines of a display-message result
tony Jun 21, 2026
e1189ad
Ops(fix): Centralize the has-session stderr->stdout fold in the op
tony Jun 21, 2026
01e16d5
Engines(docs): Note ConcreteEngine query-simulation limits
tony Jun 22, 2026
0e16886
Engines(fix): Avoid imsg UnboundLocalError on socket() failure
tony Jun 22, 2026
7843ec8
Engines(fix): Return imsg exit result on clean close after MSG_EXIT
tony Jun 22, 2026
4efa7d5
Engines(fix): Close imsg dup'd fds if the identify send never happens
tony Jun 22, 2026
a8511b8
Imsg(fix): Send identify LONGFLAGS frame once
tony Jun 22, 2026
99a64fd
Engines(test): Widen async control-mode coverage
tony Jun 22, 2026
6fbb320
Ops(feat[send_keys]): Add suppress_history flag
tony Jun 22, 2026
2001eeb
Ops(feat): Capture implicit child ids on create
tony Jun 22, 2026
4fbe92e
Workspace(feat): Declarative WorkspaceBuilder on the typed-ops Core
tony Jun 22, 2026
3abc84b
Ops(feat): Serialize bindings + add plan preview
tony Jun 22, 2026
a740a04
Mcp(feat): Add framework-agnostic tool projection
tony Jun 23, 2026
5b92dfe
Mcp(feat): Add optional fastmcp adapter (libtmux[mcp])
tony Jun 23, 2026
54f1608
Mcp(feat): Per-op + plan tools and a stdio server
tony Jun 23, 2026
36f8d51
Mcp(feat): Port mcp_swap config-swap dev script
tony Jun 23, 2026
7d341d0
Tests(chore): Run the mcp adapter suite in the gate
tony Jun 23, 2026
761d2f4
Workspace(test): Cover analyzer, compiler, runner
tony Jun 23, 2026
bf41567
Mcp(feat): Add grok + agy CLIs to mcp_swap
tony Jun 23, 2026
667f333
Mcp(feat): Caller-aware async tmux tool surface
tony Jun 23, 2026
8299fb9
Mcp(feat): Caller discovery + self-kill guards
tony Jun 23, 2026
c787d01
Mcp(fix): Harden self-kill guards + socket scoping
tony Jun 23, 2026
0107e49
Mcp(feat): Needle-free pane-output monitor
tony Jun 23, 2026
e7dfb32
Mcp(feat): Make wait_for_output discoverable
tony Jun 24, 2026
68ef311
Mcp(fix): Close self-kill guard deferrals
tony Jun 24, 2026
9e7354a
Mcp(fix): Harden wait_for_output monitor
tony Jun 24, 2026
4253ca4
Workspace(feat): Thread env/shell/options through declarative tier
tony Jun 27, 2026
12cd0f2
Workspace(feat): Add per-command Command (enter + sleeps)
tony Jun 27, 2026
2a2ab0e
Ops(feat): Add ForwardCaptureError + ShowOptionsResult.get_int
tony Jun 27, 2026
eb16850
Workspace(feat): Add Workspace.to_dict + read suppress_history
tony Jun 27, 2026
92f142a
Workspace(feat): Add BuildEvent stream + on_event observer
tony Jun 27, 2026
18b5d68
Workspace(feat): Add opt-in wait_pane readiness (anti-race)
tony Jun 27, 2026
36ad3ef
Workspace(feat): Honor explicit Window.window_index placement
tony Jun 27, 2026
1187eb7
Mcp(feat): Expose build_workspace on the async server
tony Jun 27, 2026
97102bf
Mcp(feat[safety]): Add tier constants, resolver, ExpectedToolError
tony Jun 27, 2026
2c13b8d
Mcp(feat[middleware]): Port error-result + tail-preserving limiter
tony Jun 27, 2026
13c3528
Mcp(feat[middleware]): Port safety, audit, readonly-retry middleware
tony Jun 27, 2026
8d30536
Mcp(feat[safety]): Wire safety gate + middleware into the builders
tony Jun 27, 2026
7daa2d8
Mcp(feat[prompts]): Add recipe prompts in engine-ops vocabulary
tony Jun 27, 2026
ee3b2d8
Mcp(feat[resources]): Add tmux:// hierarchy resources over the engine
tony Jun 27, 2026
603e360
Mcp(feat[lifespan]): Add engine-probe lifespan (async server)
tony Jun 27, 2026
ab449bb
Workspace(feat[cli]): Add `load` command for .tmuxp.yaml files
tony Jun 27, 2026
9cd6d16
Workspace(feat): blank/pane empty-pane parity + cli --dry-run
tony Jun 27, 2026
ce87e86
Workspace(fix): First window's start_directory for its first pane
tony Jun 27, 2026
5143f37
Ops(feat): Per-step host hook and bounded planner
tony Jun 27, 2026
a00048b
Workspace(feat): Fold build dispatches by default
tony Jun 27, 2026
5518844
Workspace(feat): Fold --dry-run output
tony Jun 27, 2026
be2b9bd
Ops(feat[new_pane]): Add floating pane operation
tony Jun 27, 2026
ee15884
Ops(fix[break_pane]): Work around tmux 3.7 break-pane crash
tony Jun 27, 2026
0bf1c7b
Engines(feat): Resolve engine tmux version for gating
tony Jun 27, 2026
92403aa
Workspace(feat[ir]): Add floating-pane declarations
tony Jun 27, 2026
6ccf695
Workspace(feat[compiler]): Build floating panes from specs
tony Jun 27, 2026
b37dfc1
Workspace(feat[compiler]): Cross-window floats via symbol table
tony Jun 27, 2026
29a2ab0
Query(feat): Add snapshot-backed live pane query
tony Jun 27, 2026
a4a8b64
Query(feat): Add per-pane command building that folds
tony Jun 27, 2026
5409de5
Facade(feat[pane]): Add new_pane floating parity
tony Jun 27, 2026
c7ca213
Mcp(feat[pane]): Add curated new_pane floating tool
tony Jun 27, 2026
1062af2
Mcp(feat[registry]): Surface whole-op min_version in schema
tony Jun 27, 2026
20388db
Mcp(fix[prompts]): wait_for_output uses target=
tony Jun 28, 2026
b0be052
Workspace(fix[analyze]): Reject bad command items
tony Jun 28, 2026
cb28426
Workspace(fix[env]): Inherit window env in splits
tony Jun 28, 2026
6d043d6
docs(CHANGES) Note on updates
tony Jul 4, 2026
36d967c
Mcp(refactor): Drop dead is_conservative_caller
tony Jul 4, 2026
123edd4
Engines(fix): Report tmux version over control mode
tony Jul 4, 2026
6ce6e99
Ops(docs): Fix stale fold comment in chain test
tony Jul 4, 2026
d1151e6
Models(feat): Add PaneSnapshot.floating flag
tony Jul 4, 2026
600d043
Engines(fix): Reap control-mode phantom sessions
tony Jul 4, 2026
bb458c7
Engines(fix): Close subscribers on engine death
tony Jul 4, 2026
0f38537
Query(feat): Split-type forward-ref pane handles
tony Jul 4, 2026
a70fddf
Fluent(feat): Add plan() forward-ref build tier
tony Jul 4, 2026
5c78f2d
Workspace(feat): Add freeze (live server to IR)
tony Jul 4, 2026
adab176
Workspace(feat): Add variant expand + workspace sets
tony Jul 4, 2026
ce3b4fb
Ops(feat): Add plan explain() and astream()
tony Jul 4, 2026
a434cc6
Fluent(feat): Add sleep/wait host boundaries
tony Jul 4, 2026
72b45a7
Mcp(feat): Add explain_plan tool
tony Jul 4, 2026
5ed6ae1
Ops(feat): Add conditional find-or-create via ensure()
tony Jul 4, 2026
11f04ee
Fluent(feat): Add find_or_create_session
tony Jul 4, 2026
302ec17
Test(fix): Clean up sessions in fluent live tests
tony Jul 4, 2026
9e557e4
docs(experimental): Document the fluent plan() builder
tony Jul 4, 2026
5fed520
Mcp(fix): Preserve ensure across plan-tool serialization
tony Jul 4, 2026
87da678
Ops(test): Make the ensure probe test format-honest
tony Jul 4, 2026
4f9a8e9
Workspace(docs): on_event and astream caveats
tony Jul 4, 2026
39b1b7b
Workspace(fix): Stringify option/env values
tony Jul 5, 2026
f8dad20
Ops(fix): Skip marked fold for detached creators
tony Jul 5, 2026
6e5014f
Engines(fix): End subscribe() after engine death
tony Jul 5, 2026
0488b35
Query(refactor): Namespace dataclasses.replace
tony Jul 5, 2026
acddbff
docs(experimental): Drop prototype lineage
tony Jul 5, 2026
802f4d7
Workspace(fix): Honor window_shell on window 0
tony Jul 5, 2026
0a131fc
Workspace(fix): wait_pane uses effective shell
tony Jul 5, 2026
9a3eab5
docs(experimental): Add arun/aexecute doctests
tony Jul 5, 2026
843b068
Facade(fix): Add AsyncWindow.select_layout
tony Jul 5, 2026
cbc0327
Engines(fix): ImsgEngine reports tmux version
tony Jul 5, 2026
03eb764
Mcp(fix): Gate wait_for_output prompts on events
tony Jul 5, 2026
fb9f8a6
Mcp(fix): Redact non-str sensitive audit args
tony Jul 5, 2026
b83a4f8
docs(experimental): Trim more branch narrative
tony Jul 5, 2026
96af357
Mcp(docs): Fix build_workspace registration claim
tony Jul 5, 2026
fa050a3
Workspace(fix): Confirm cwd on the first pane
tony Jul 5, 2026
075149e
Engines(feat[async_control_mode]): Add supervised reconnect
tony Jul 5, 2026
2eac2dd
Mcp(fix[events]): Restart the event drain after reconnect
tony Jul 5, 2026
7940af6
Objects(refactor): Rename facade package to objects
tony Jul 5, 2026
836f922
Engines(fix[async_control_mode]): Escalate backoff on connect-then-die
tony Jul 5, 2026
b7fe0bb
Engines(fix[async_control_mode]): Terminate the prior proc on reconnect
tony Jul 5, 2026
95a96f2
Scripts(feat[bench]): Add hermetic engine build-benchmark grid
tony Jul 5, 2026
2f3e5b2
Scripts(docs[bench]): Add engine benchmark results
tony Jul 5, 2026
5a79cc7
Scripts(fix[bench]): Make bench_engines mypy-clean under `mypy .`
tony Jul 5, 2026
a89958e
Engines(refactor[mock]): Rename ConcreteEngine to MockEngine
tony Jul 5, 2026
8a3fe22
Engines(refactor[connection]): Hold a ServerConnection
tony Jul 11, 2026
363cfe0
Chain(fix[_chain]): Wire the fold guard, import the rule
tony Jul 11, 2026
2ca68dc
Experimental(refactor[dedup]): Import instead of re-inline
tony Jul 11, 2026
e144e11
Experimental(refactor[dead-code]): Delete unread surface
tony Jul 11, 2026
29ef81e
Engines(feat[control-mode]): Add unescape_control_output
tony Jul 11, 2026
f78c797
Skill(feat[bench]): Add engine-build benchmark skill
tony Jul 18, 2026
7fe107c
Skill(chore[claude]): Symlink .claude/skills to .agents/skills
tony Jul 18, 2026
4977507
Mcp(fix[mcp_swap]): Point agy at the config it actually reads
tony Jul 18, 2026
f3282ee
Scripts(feat[bench]): Add build-cost matrix + concurrency
tony Jul 18, 2026
be1bad0
Scripts(feat[bench]): Add standalone contract subcommand
tony Jul 18, 2026
08d6323
Scripts(docs[bench]): Document matrix, concurrency, contract
tony Jul 18, 2026
0c872c3
Ops(fix[plan]): Surface failed creates instead of the reference
tony Jul 18, 2026
13f27a0
Ops(feat[observability]): Report lost id captures
tony Jul 18, 2026
73c7e9c
Engines(feat[observability]): Trace dispatch and block framing
tony Jul 18, 2026
497e686
Engines(fix[control-mode]): Carry tmux stderr on connection death
tony Jul 18, 2026
7f99937
Scripts(fix[bench]): Keep cell servers alive, unblock the loop
tony Jul 18, 2026
ee6903e
Scripts(fix[bench]): Reap stale scratch dirs on startup
tony Jul 18, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 86 additions & 0 deletions .agents/skills/benchmarking-engine-builds/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
---
name: benchmarking-engine-builds
description: Use when measuring or profiling how fast libtmux.experimental engines build tmux workspaces — comparing classic vs subprocess/control_mode/imsg/mock/pipelined, chasing a build-latency regression, reading percentile grids, or finding where a control-mode build spends its time (cProfile). Runs scripts/bench_engines.py hermetically on throwaway sockets.
---

# Benchmarking engine builds

## Overview

`scripts/bench_engines.py` times how long each experimental engine takes to
build a tmux session structure (`W` windows × `P` panes-per-window), sweeping
shapes × engines × wait-modes and reporting min/avg/median/p90/p95/p99/max.

**Hermetic and safe to run beside a live tmux session:** every server gets its
own socket under a throwaway `mkdtemp` dir, `TMUX` is unset before libtmux is
imported, and an `atexit` hook kills every spawned server. The default tmux
server is never contacted.

It is a PEP 723 script — **always launch it with `uv run`**, never `python`, or
its inline deps (`rich`, `typer`, editable `libtmux`) won't resolve.

## When to use

- Comparing engine build cost (which engine is fastest for a given shape).
- Checking whether a change to the ops/plan/engine layer moved build latency.
- Reading percentile spread (is p99 blowing out?) rather than a single number.
- Locating the hot path inside one engine's build (`profile` → cProfile cumtime).

## Quick reference

Run from the repo root.

| Command | What it does |
|---|---|
| `uv run scripts/bench_engines.py run` | full engine grid (the clean signal) |
| `uv run scripts/bench_engines.py matrix --shapes 1x4,3x3,5x4` | 4-axis factorial: which choice drives build cost |
| `uv run scripts/bench_engines.py concurrency --transport control_mode --k 4` | K builds sync-serial vs async-`gather` |
| `uv run scripts/bench_engines.py contract` | mock-parity ops-language check only (for CI) |
| `uv run scripts/bench_engines.py profile --engine control_mode --shape 8x4` | cProfile one engine, print slowest by cumtime |
| `uv run scripts/bench_engines.py cell control_mode 8x4` | one isolated build (for wrapping in hyperfine) |

`run` flags: `--shapes 1x1,1x4,3x3,5x4,8x4`, `--engines classic,subprocess,control_mode,imsg,mock,pipelined`,
`--wait` (ALSO measure with shell-readiness wait), `--runs 20`, `--warmup 3`,
`--json-out grid.json`. Shape is `windows x panes-per-window`.

`matrix` sweeps five expression layers (`imperative`, `plan-seq`, `plan-fold`,
`ws-seq`, `ws-fold`) × transport {subprocess, control_mode} × mode {sync, async}
against a `classic` reference. `mock` is the offline correctness **oracle**, not
a results row: `matrix --check` (default on) and `contract` assert every layer ×
mode renders identical tmux argv to it, so the benchmark doubles as an
ops-language contract test.

Engines: `classic` (Server/Session/Window/Pane API) · `subprocess` (one fork
per op) · `control_mode` (one persistent `tmux -C`) · `imsg` (AF_UNIX one-shot) ·
`mock` (offline, in-memory Python floor) · `pipelined` (prototype: batch
independent creates via `run_batch`).

## Reading the results

- **`control_mode` is the fastest shipped engine** (~21× classic at 32 panes)
because it avoids a per-op `tmux` fork. `pipelined` edges it (~1.4×) by
batching independent creates into ~3 round-trips.
- **Builds are tmux-server-bound, not round-trip-bound** — one shell fork per
pane dominates, so cutting round-trips helps less than the count implies.
`mock` (~1–2 ms) is the pure-Python floor: the plan/compile layer is
negligible; the time is tmux.
- **`profile` shows ~68% in `select.epoll.poll`** inside `_read_blocks`: each
created id is read back before the next op targets it. Latency-bound.

## Common mistakes

- Running with `python` instead of `uv run` — PEP 723 deps don't resolve.
- **Comparing `--wait` against no-wait across engines.** Shell startup
(~0.8–2.1 s) dwarfs a fast build, so the ~20× engine win collapses to ~1.5×
once both sides wait. A classic-that-waits vs a builder-that-doesn't is
apples-to-oranges (this is the bogus "~79×" trap). Compare like with like.
- Trusting hyperfine whole-process wall time over the in-process grid — Python
startup + import dwarfs a 3 ms build and understates the builder. The
in-process `run` grid is the clean signal.
- Expecting `mock` under `--wait`: it has no real panes and is skipped.

## Results & reproduction

Committed results live in `scripts/bench-results/`: `RESULTS.md` (narrative +
tables), `grid.json` (no-wait grid), `wait.json` (wait comparison). Regenerate
the raw JSON with `--json-out`.
1 change: 1 addition & 0 deletions .claude/skills
81 changes: 81 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,33 @@ $ uvx --from 'libtmux' --prerelease allow python
_Notes on the upcoming release will go here._
<!-- END PLACEHOLDER - ADD NEW CHANGELOG ENTRIES BELOW THIS LINE -->

### What's new

#### Experimental operations and engines (#690)

Operations describe tmux commands as data. Each renders its argv against a tmux
version (dropping flags an older tmux cannot accept), adapts raw output into a
typed result, and serializes to and from plain dicts -- all without a running
tmux server. The set spans the read seam (``list-*``, ``has-session``,
``display-message``, ``show-options``, ``show-buffer``) and the
mutating/creating surface for panes, windows, the server, options, environment,
hooks, and paste buffers. A registry-generated catalog on the {ref}`experimental`
page always matches the code.

Engines run those operations behind one protocol, so the same operation returns
the same typed result whether it goes through a subprocess (the classic path
that reproduces today's libtmux behavior), an in-memory simulator for tests and
dry runs, a persistent ``tmux -C`` control connection, an async transport, or
tmux's native binary peer protocol. Results never raise on construction;
raising is opt-in via ``raise_for_status()``, and how a failed result is handled
is each engine's policy.

A {class}`~libtmux.experimental.ops.plan.LazyPlan` records operations and yields
forward references so a later operation can target an object that does not exist
yet, resolved against captured ids at execution time. How a plan becomes tmux
dispatches is a pluggable {class}`~libtmux.experimental.ops.planner.Planner`
(sequential, ``;``-folding, or ``{marked}``-folding), so dispatch strategies can
be A/B tested against the same plan with identical results.
### Documentation

#### Cleaner `from_env` examples (#719)
Expand All @@ -57,6 +84,60 @@ environment-setup plumbing, so each example leads with the constructor call it
demonstrates instead of the socket-path and `$TMUX` boilerplate needed to run
it.

#### Declarative workspace builds fold to a few tmux calls (#690)

A {class}`~libtmux.experimental.workspace.ir.Workspace` declares a session as a
tree of windows and panes and lowers to a Core
{class}`~libtmux.experimental.ops.plan.LazyPlan`, so a tmuxp-style spec can be
analyzed, inspected, and built over any engine.
{meth}`~libtmux.experimental.workspace.ir.Workspace.build` and its async twin
{meth}`~libtmux.experimental.workspace.ir.Workspace.abuild` fold the build's
dispatches by default: a multi-pane window collapses from one tmux call per
operation into a handful of ``;``-chained and ``{marked}`` dispatches, so a
session renders in a few round-trips instead of dozens.

The resulting {class}`~libtmux.experimental.ops.plan.PlanResult` is identical to
an unfolded build -- only the dispatch count changes -- because host-side steps
(per-command sleeps, the ``wait_pane`` anti-race, ``before_script``) stay hard
fold boundaries that a fold never crosses. Pass a
{class}`~libtmux.experimental.ops.planner.SequentialPlanner` to ``build`` for one
legible tmux call per operation when debugging.

#### Floating panes on tmux 3.7 (#690)

On tmux 3.7, the operations create floating panes -- overlays with an absolute
size, position, and optional zoom. A ``new-pane`` operation, ``new_pane()`` on
the pane wrappers, and a curated MCP tool each open one, and a
{class}`~libtmux.experimental.workspace.ir.Workspace` can declare floating panes
on a window, including a pane that overlays a different window.

#### Query and command live panes with `panes()` (#690)

{func}`~libtmux.experimental.query.panes` is a lazy, chainable query over the
panes a running server has: ``filter``, ``order_by``, ``limit``, and ``map``
compose and read nothing until a terminal call. The same query commands what it
selects -- ``commands()`` attaches per-pane actions (send keys, resize, select,
respawn, clear history, kill) that run as one folded tmux dispatch. A query
resolves against a live engine or a plain list of pane snapshots, so the same
code runs offline in tests.

#### Drive tmux from an MCP server (#690)

An optional Model Context Protocol server exposes tmux as tools an AI agent can
call, installed with the ``libtmux[mcp]`` extra and launched as
``libtmux-engine-mcp``. It offers a curated vocabulary of intuitive verbs
(``send_input``, ``wait_for_output``, ``split_pane``, ``capture_pane``,
``new_pane``, and the session, window, and pane lifecycle), a tool for every
individual operation, and plan tools that preview or build a whole workspace in
one call.

The server is caller-aware: because a control-mode agent shares the server with
the panes it drives, it knows which pane launched it and refuses to kill or
respawn its own pane, window, or session. A safety level (``LIBTMUX_SAFETY``)
keeps mutating and destructive tools hidden until an operator opts in, and a
needle-free ``wait_for_output`` reports when a pane goes quiet after a command --
no sentinel string -- and whether its process exited.

## libtmux 0.62.0 (2026-07-12)

libtmux 0.62.0 teaches libtmux objects to locate themselves and to resolve
Expand Down
120 changes: 120 additions & 0 deletions docs/_ext/tmuxop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,120 @@
"""Sphinx directive that renders the experimental operation catalog.

``.. tmuxop-catalog::`` (or the MyST fenced form) walks
:func:`libtmux.experimental.ops.catalog` and emits a table of operations with
their scope, safety tier, result type, minimum tmux version, and summary. The
operation registry is the single source of truth, so the rendered reference
cannot drift from the code.

Options
-------
``:scope:`` / ``:safety:``
Filter to one scope (``pane``/``window``/``session``/``server``/``client``)
or safety tier (``readonly``/``mutating``/``destructive``).
``:primitive-only:``
Show only operations that wrap a single tmux command.

This is the in-repo renderer; a full gp-sphinx ``tmuxop`` domain (cross-reference
roles + an operations index) can later replace it under the same directive name.
"""

from __future__ import annotations

import typing as t

from docutils import nodes
from docutils.parsers.rst import directives
from sphinx.util import logging
from sphinx.util.docutils import SphinxDirective

if t.TYPE_CHECKING:
from collections.abc import Sequence

from sphinx.application import Sphinx

logger = logging.getLogger(__name__)

_HEADERS = ("Operation", "Command", "Scope", "Safety", "Result", "Min tmux", "Summary")


def _row(cells: Sequence[str]) -> nodes.row:
"""Build a docutils table row from string cells."""
row = nodes.row()
for cell in cells:
entry = nodes.entry()
entry += nodes.paragraph(text=cell)
row += entry
return row


def _table(headers: Sequence[str], rows: Sequence[Sequence[str]]) -> nodes.table:
"""Build a simple docutils table."""
table = nodes.table()
tgroup = nodes.tgroup(cols=len(headers))
table += tgroup
for _ in headers:
tgroup += nodes.colspec(colwidth=1)
thead = nodes.thead()
thead += _row(headers)
tgroup += thead
tbody = nodes.tbody()
for row in rows:
tbody += _row(row)
tgroup += tbody
return table


class TmuxopCatalogDirective(SphinxDirective):
"""Render the operation catalog as a table."""

has_content = False
option_spec: t.ClassVar[dict[str, t.Any]] = {
"scope": directives.unchanged,
"safety": directives.unchanged,
"primitive-only": directives.flag,
}

def run(self) -> list[nodes.Node]:
"""Build the catalog table from the operation registry."""
from libtmux.experimental.ops import catalog

entries = catalog()
scope = self.options.get("scope")
safety = self.options.get("safety")
if scope:
entries = [entry for entry in entries if entry.scope == scope]
if safety:
entries = [entry for entry in entries if entry.safety == safety]
if "primitive-only" in self.options:
entries = [entry for entry in entries if entry.primitive]

if not entries:
logger.warning(
"tmuxop-catalog: no operations matched the given filters",
location=self.get_location(),
)
return []

rows = [
(
entry.kind,
entry.command,
entry.scope,
entry.safety,
entry.result_type,
entry.min_version or "-",
entry.summary,
)
for entry in entries
]
return [_table(_HEADERS, rows)]


def setup(app: Sphinx) -> dict[str, t.Any]:
"""Register the directive."""
app.add_directive("tmuxop-catalog", TmuxopCatalogDirective)
return {
"version": "0.1",
"parallel_read_safe": True,
"parallel_write_safe": True,
}
2 changes: 2 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
project_src = project_root / "src"

sys.path.insert(0, str(project_src))
sys.path.insert(0, str(cwd / "_ext"))

# package data
about: dict[str, str] = {}
Expand All @@ -34,6 +35,7 @@
"sphinx_autodoc_api_style",
"sphinx_autodoc_pytest_fixtures",
"sphinx.ext.todo",
"tmuxop",
],
intersphinx_mapping={
"python": ("https://docs.python.org/", None),
Expand Down
Loading
Loading