Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
62 commits
Select commit Hold shift + click to select a range
bfb5e4e
Agents(feat[state]): Add AgentState enum + Agent record
tony Jun 27, 2026
e695d2c
Agents(feat[merge]): Add latest-wins Stamp ordering
tony Jun 27, 2026
3b24d6e
Agents(feat[store]): Add AgentStore + pure apply reducer + atomic Jso…
tony Jun 27, 2026
ccd18fb
test_store.py(fix): Remove unused typing import
tony Jun 27, 2026
fdedeb0
Agents(fix[store]): Add doctests on JsonFile.load/save
tony Jun 27, 2026
c3f66a0
Agents(feat[signals]): Add OptionSignal + fragmented-OSC OscSignal pa…
tony Jun 27, 2026
431bc1e
Agents(feat[health]): Add is_alive process probe (PID-less remote safe)
tony Jun 27, 2026
e893f1a
Agents(feat[tree]): Add derived pane tree + reconcile diff helpers
tony Jun 27, 2026
ddab918
Ops(feat[refresh_client]): Add typed -B subscription + -C size
tony Jun 27, 2026
5b2a2e9
Engines(fix[async_control_mode]): Reset sticky attach on reconnect
tony Jun 27, 2026
1313125
Agents(feat[monitor]): Add AgentMonitor ingest + store wiring
tony Jun 27, 2026
effe918
Agents(feat[hooks]): Add shared emitter (local set-option / remote OS…
tony Jun 27, 2026
35f0aab
Agents(feat[hooks]): Add AgentHook protocol + registry + event→state map
tony Jun 27, 2026
f0dbc9e
Agents(feat[hooks]): Add Claude Code hook installer (non-clobbering)
tony Jun 27, 2026
072e338
Agents(feat[hooks]): Add Codex hook installer
tony Jun 27, 2026
1966971
Mcp(feat[agents]): Add list_agents/watch_agents/install_agent_hooks t…
tony Jun 27, 2026
b359d0c
Agents(feat): Monitor self-attach + live tests
tony Jun 27, 2026
3d03d27
Agents(docs): Document the agent-state monitor + CHANGES entry
tony Jun 27, 2026
b707aee
Agents(fix): Monitor self-heals across reconnects + wires health
tony Jun 27, 2026
1e093f9
Agents(docs): Correct remote-pane expiry wording (no v1 TTL)
tony Jun 27, 2026
ff5b4e9
Mcp(fix[lifespan]): Nest monitor start in try/finally
tony Jun 27, 2026
72d6705
Mcp(fix[agents]): Skip agent tools when lifespan won't start monitor
tony Jun 27, 2026
341fda1
Agents(fix[hooks]): Collapse duplicate Codex marker blocks on install
tony Jun 27, 2026
dde3bbb
Agents(test[signals]): Cover BEL-terminated OSC 3008
tony Jun 27, 2026
49012df
Agents(test[hooks]): Cover Codex outdated status
tony Jun 27, 2026
4be6975
Agents(refactor[signals]): Drop duplicate OptionSignal doctest
tony Jun 27, 2026
697694c
Agents(refactor[monitor]): Drop duplicate AgentStore import
tony Jun 27, 2026
e3bddc5
Agents(fix[monitor]): Skip attach when own-session probe fails
tony Jun 27, 2026
aea3626
Agents(fix): Verify attach-session succeeded before marking attached
tony Jun 27, 2026
807fd75
Agents(feat[tree]): Read pane_floating_flag into snapshots
tony Jun 27, 2026
06755fe
Agents(feat[hud]): Add floating agent-state HUD
tony Jun 27, 2026
6609e63
Mcp(fix[agents]): watch_agents observes the store
tony Jun 28, 2026
e9092b1
Agents(fix[monitor]): Recover the floating HUD after a restart
tony Jun 28, 2026
ee45d9b
Agents(docs[hud]): Correct the repaint-cadence docstring
tony Jun 28, 2026
78261dc
Agents(fix[hooks]): Isolate the install/uninstall doctests
tony Jun 28, 2026
f0eb631
docs(CHANGES): Correct the reconcile cadence wording
tony Jun 28, 2026
6052250
Mcp(test): Type monitor test fakes for strict mypy
tony Jun 28, 2026
35847f7
Agents(fix[store]): Tolerate unknown saved state
tony Jun 28, 2026
f57931d
Agents(fix[emit]): Guard --name with no value
tony Jun 28, 2026
2e2cbfc
Mcp(fix[agents]): Offload hook I/O off event loop
tony Jun 28, 2026
4bec9f0
docs(CHANGES): Drop version from monitor entry
tony Jun 28, 2026
a4d3d74
Agents(feat[sync]): Add wait/send/lock verbs
tony Jul 4, 2026
bccc498
Wrappers(refactor): Rename facade package to wrappers
tony Jul 4, 2026
eab49cd
Agents(feat[statusline]): Render fleet in one call
tony Jul 4, 2026
824738b
Engines(fix[control-mode]): Reap the sync engine's phantom too
tony Jul 4, 2026
f7b08d1
Mcp(feat[settle]): Resolve wait_for_output on a needle
tony Jul 4, 2026
9253e6a
Objects(refactor): Rename package
tony Jul 4, 2026
1551878
MCP(feat[pane]): Add run_in_pane
tony Jul 4, 2026
821e508
Agents(feat[state]): Add done
tony Jul 4, 2026
4402ea8
Workspace(feat[set]): Build workspace sets
tony Jul 4, 2026
65d0b70
Mcp(fix[events]): Attach raw output streams
tony Jul 4, 2026
f2c7150
Agents(feat[cli]): Add tmux console
tony Jul 4, 2026
84a267d
Agents(fix[monitor]): Seed console state
tony Jul 4, 2026
1aab806
Agents(fix[monitor]): Discover agent panes
tony Jul 4, 2026
34dcbfa
Rebase(fixup): Reconcile supatui onto the fluent engine-ops
tony Jul 4, 2026
3fe7932
Agents(refactor[mock]): Follow the MockEngine rename
tony Jul 5, 2026
44b9716
Agents(fix[monitor]): Unescape tmux %output bytes
tony Jul 11, 2026
d06f5f6
Agents(refactor[protocol]): Own the wire grammar in one codec
tony Jul 11, 2026
7bc68eb
Agents(fix[protocol]): Escape payload values on the wire
tony Jul 11, 2026
4bf07f0
Agents(refactor[dedup]): Import the helper, don't re-inline it
tony Jul 11, 2026
f05dafe
Agents(refactor[dead]): Delete the unread EVENT_STATE map
tony Jul 11, 2026
19d7617
Mcp(refactor[dedup]): Fold the workspace-set outcome to_dict
tony Jul 11, 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
42 changes: 42 additions & 0 deletions CHANGES
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,48 @@ _Notes on the upcoming release will go here._

### What's new

#### Agent console session (#692)

`python -m libtmux.agents` now boots a tmux-native agent console. Running the
module with no subcommand creates the managed `libtmux-agents` session when it
does not exist, then attaches or switches the current tmux client to it; `start`
is the explicit form, and `attach` or `att` reconnect to an already-running
console. The session itself is declared with
{class}`~libtmux.experimental.workspace.ir.Workspace`, so creation uses the same
chainable workspace compiler and folded tmux dispatch path as other declarative
workspace builds.

The console includes a long-lived `monitor` pane backed by
{class}`~libtmux.experimental.agents.monitor.AgentMonitor` and an interactive
shell pane for orchestration work. `status` and `list --json` read the
monitor's persisted JSON store directly, so a caller can inspect the latest
agent snapshot without making a live tmux call. The command also exposes hook
installation plus `wait` and `send` wrappers over the existing in-process agent
synchronization primitives.

#### Agent-state monitor (#692)

`libtmux.experimental.agents` is a live agent-state monitor
that lets you see, at a glance, which coding agent in which pane is running,
waiting for your input, or idle — all without polling or scraping pane output.
A {class}`~libtmux.experimental.agents.monitor.AgentMonitor` subscribes to a
control-mode engine and coalesces incoming tmux notifications into per-pane
{class}`~libtmux.experimental.agents.state.Agent` records carrying the agent's
name, its current {class}`~libtmux.experimental.agents.state.AgentState`
(`RUNNING`, `AWAITING_INPUT`, `IDLE`, `EXITED`, or `UNKNOWN`), the timestamp of
the last transition, and a liveness flag refreshed on each reconciliation.

Agents publish their state through tmux option subscriptions or OSC escape
sequences; when both signals arrive for the same pane the monitor applies a
last-writer-wins merge so the in-memory store stays consistent without locks.
A reconciliation sweep runs at startup and on each engine reconnect to catch
any pane the stream missed, compare it against the stored snapshot, and emit
the minimal diff —
so the monitor self-heals across reconnects and supervisor restarts. Shell
hooks for Claude Code and Codex are included; install them into a running
session at any time via `install_agent_hooks` or the bundled
{class}`~libtmux.experimental.agents.hooks.base.AgentHook` installer API.

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

Operations describe tmux commands as data. Each renders its argv against a tmux
Expand Down
5 changes: 5 additions & 0 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -59,3 +59,8 @@
exclude_patterns=["_build", "AGENTS.md", "CLAUDE.md"],
)
globals().update(conf)

# Exclude design specs and implementation plans from the Sphinx build so that
# docs/superpowers/** files are tracked in git without generating toctree
# warnings.
exclude_patterns += ["superpowers/**"] # type: ignore[name-defined] # noqa: F821
126 changes: 126 additions & 0 deletions docs/experimental.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,3 +173,129 @@ the code.
```{tmuxop-catalog}
:safety: destructive
```

## Agents

```{warning}
The agent-state monitor is experimental and subject to change without notice.
```

The `libtmux.experimental.agents` package gives you a live, server-side view
of every coding agent running across your tmux sessions. A
{class}`~libtmux.experimental.agents.monitor.AgentMonitor` subscribes to a
control-mode engine, classifies incoming tmux notifications, and coalesces them
into a per-pane {class}`~libtmux.experimental.agents.state.Agent` record —
carrying the agent's name, its current
{class}`~libtmux.experimental.agents.state.AgentState` (`RUNNING`,
`AWAITING_INPUT`, `DONE`, `IDLE`, `EXITED`, or `UNKNOWN`), the timestamp of the
last transition, and a liveness flag refreshed from the pane tree on each
reconcile. `DONE` means a turn completed and needs review, distinct from an
idle shell. A *local* pane whose process has exited is marked `EXITED`. Remote
(SSH) panes have no local pid to probe, so they are left at their last-known
state and only become `EXITED` when their tmux pane disappears (no keepalive/TTL
in v1).

Agents report their state via tmux option subscriptions or OSC escape sequences.
When both signals arrive for the same pane the monitor applies a
last-writer-wins merge so the store stays consistent without locks. On every
engine (re)connect the monitor runs a full-pane reconciliation — it lists all
panes, compares them against the stored snapshot, emits the minimal diff for
panes that vanished, and refreshes liveness — then re-subscribes to the
notification stream. Because this runs on each reconnect (not on a fixed
timer), the monitor self-heals across a tmux restart or socket blip: a dropped
connection never leaves the store serving a stale snapshot.

```python
import asyncio

from libtmux import Server
from libtmux.experimental.agents.monitor import AgentMonitor
from libtmux.experimental.engines.async_control_mode import AsyncControlModeEngine


async def main() -> None:
engine = AsyncControlModeEngine.for_server(Server())
monitor = AgentMonitor(engine)
await monitor.start()
try:
for agent in monitor.agents:
print(agent.pane_id, agent.state, "awaiting" if agent.is_awaiting else "")
finally:
await monitor.stop()


asyncio.run(main())
```

### Agent console

For an interactive tmux-native view, run the top-level module:

```console
$ python -m libtmux.agents
```

With no subcommand, the module behaves like tmux: it creates the managed
`libtmux-agents` session if needed, then attaches to it (or switches the current
tmux client when already inside tmux). The explicit `start` command has the
same behavior, while `attach` and `att` reconnect to an existing console without
creating one.

```console
$ python -m libtmux.agents attach
```

The managed session contains a monitor pane and an interactive shell pane. The
monitor writes a JSON snapshot under `$XDG_STATE_HOME/libtmux/agents/` (or
`~/.local/state/libtmux/agents/`), so status commands can render the latest
known state without contacting tmux:

```console
$ python -m libtmux.agents status
```

Machine-readable callers can use the `list` alias:

```console
$ python -m libtmux.agents list --json
```

Socket and session selectors mirror tmux's own flags. For example, this starts
or attaches a console on a named tmux socket:

```console
$ python -m libtmux.agents -L work-agents -s libtmux-agents
```

The same module also exposes operational shortcuts over the in-process monitor:
`hooks status`, `hooks install`, `wait <pane-id>`, and `send <pane-id> <text>`.

### Installing agent hooks

Before a coding agent can report state, its lifecycle hooks must be installed.
The {class}`~libtmux.experimental.agents.hooks.base.AgentHook` subclasses do not
touch tmux: `ClaudeCodeHook` merges hook entries into `~/.claude/settings.json`
and `CodexHook` into `~/.codex/config.toml`, leaving the rest of each file
untouched. Every installed hook runs the `libtmux-agent-emit` console script on
the agent's lifecycle events, and that script is what writes the agent's state
to tmux — a per-pane `@agent_state` option locally, or an OSC 3008 escape
sequence over SSH — exactly the signals the monitor subscribes to. The MCP tool
`install_agent_hooks` runs the matching installer on demand — pass `"claude"` or
`"codex"` as the agent name.

### MCP tools

When `libtmux-mcp` is running with the agent monitor wired in, these tools are
exposed to LLM clients:

- **`list_agents`** — returns a snapshot of every currently tracked agent:
pane id, name, state string, seconds since last transition, and liveness.
- **`watch_agents`** — collects state-change events for a bounded window (default
5 s) and returns them as a list, useful for agents that need to wait for a
peer to reach `AWAITING_INPUT` before sending a message.
- **`wait_for_agent`** — blocks on the monitor's in-process store until a pane
reaches a target state such as `AWAITING_INPUT` or `DONE`.
- **`send_to_agent`** — waits for `AWAITING_INPUT`, `DONE`, or `IDLE`, then sends
a prompt through one folded tmux dispatch.
- **`install_agent_hooks`** — installs the named agent's shell hooks into the
session so the monitor can begin receiving state signals.
Loading