Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
3235e93
Agents(feat[state]): Add AgentState enum + Agent record
tony Jun 27, 2026
ada4bbf
Agents(feat[merge]): Add latest-wins Stamp ordering
tony Jun 27, 2026
6e3985a
Agents(feat[store]): Add AgentStore + pure apply reducer + atomic Jso…
tony Jun 27, 2026
95521fb
test_store.py(fix): Remove unused typing import
tony Jun 27, 2026
eb80aca
Agents(fix[store]): Add doctests on JsonFile.load/save
tony Jun 27, 2026
a97273f
Agents(feat[signals]): Add OptionSignal + fragmented-OSC OscSignal pa…
tony Jun 27, 2026
3ecc371
Agents(feat[health]): Add is_alive process probe (PID-less remote safe)
tony Jun 27, 2026
39b180d
Agents(feat[tree]): Add derived pane tree + reconcile diff helpers
tony Jun 27, 2026
d7edec0
Ops(feat[refresh_client]): Add typed -B subscription + -C size
tony Jun 27, 2026
48793f1
Engines(fix[async_control_mode]): Reset sticky attach on reconnect
tony Jun 27, 2026
399d85a
Agents(feat[monitor]): Add AgentMonitor ingest + store wiring
tony Jun 27, 2026
84b82d4
Agents(feat[hooks]): Add shared emitter (local set-option / remote OS…
tony Jun 27, 2026
93da8ab
Agents(feat[hooks]): Add AgentHook protocol + registry + event→state map
tony Jun 27, 2026
a40f511
Agents(feat[hooks]): Add Claude Code hook installer (non-clobbering)
tony Jun 27, 2026
1a685c5
Agents(feat[hooks]): Add Codex hook installer
tony Jun 27, 2026
8a54e6e
Mcp(feat[agents]): Add list_agents/watch_agents/install_agent_hooks t…
tony Jun 27, 2026
58d3de6
Agents(feat): Monitor self-attach + live tests
tony Jun 27, 2026
8534160
Agents(docs): Document the agent-state monitor + CHANGES entry
tony Jun 27, 2026
5c5427f
Agents(fix): Monitor self-heals across reconnects + wires health
tony Jun 27, 2026
d0d98ce
Agents(docs): Correct remote-pane expiry wording (no v1 TTL)
tony Jun 27, 2026
92d3334
Mcp(fix[lifespan]): Nest monitor start in try/finally
tony Jun 27, 2026
31a9d06
Mcp(fix[agents]): Skip agent tools when lifespan won't start monitor
tony Jun 27, 2026
76d0192
Agents(fix[hooks]): Collapse duplicate Codex marker blocks on install
tony Jun 27, 2026
903d09b
Agents(test[signals]): Cover BEL-terminated OSC 3008
tony Jun 27, 2026
c7b92c6
Agents(test[hooks]): Cover Codex outdated status
tony Jun 27, 2026
6f1f1f0
Agents(refactor[signals]): Drop duplicate OptionSignal doctest
tony Jun 27, 2026
45ae1a1
Agents(refactor[monitor]): Drop duplicate AgentStore import
tony Jun 27, 2026
3c2eec3
Agents(fix[monitor]): Skip attach when own-session probe fails
tony Jun 27, 2026
7aba09c
Agents(fix): Verify attach-session succeeded before marking attached
tony Jun 27, 2026
3939303
Agents(feat[tree]): Read pane_floating_flag into snapshots
tony Jun 27, 2026
45edca8
Agents(feat[hud]): Add floating agent-state HUD
tony Jun 27, 2026
fb60b53
Mcp(fix[agents]): watch_agents observes the store
tony Jun 28, 2026
9fb1aa4
Agents(fix[monitor]): Recover the floating HUD after a restart
tony Jun 28, 2026
dfb5af7
Agents(docs[hud]): Correct the repaint-cadence docstring
tony Jun 28, 2026
3f26066
Agents(fix[hooks]): Isolate the install/uninstall doctests
tony Jun 28, 2026
53eb2ec
docs(CHANGES): Correct the reconcile cadence wording
tony Jun 28, 2026
d27ed79
Mcp(test): Type monitor test fakes for strict mypy
tony Jun 28, 2026
021c19d
Agents(fix[store]): Tolerate unknown saved state
tony Jun 28, 2026
4283945
Agents(fix[emit]): Guard --name with no value
tony Jun 28, 2026
d01c714
Mcp(fix[agents]): Offload hook I/O off event loop
tony Jun 28, 2026
fbc0ee9
docs(CHANGES): Drop version from monitor entry
tony Jun 28, 2026
6f3ea01
Agents(feat[sync]): Add wait/send/lock verbs
tony Jul 4, 2026
17c9a88
Wrappers(refactor): Rename facade package to wrappers
tony Jul 4, 2026
99bc9d9
Agents(feat[statusline]): Render fleet in one call
tony Jul 4, 2026
fb0cbaf
Engines(fix[control-mode]): Reap the sync engine's phantom too
tony Jul 4, 2026
78b0fae
Mcp(feat[settle]): Resolve wait_for_output on a needle
tony Jul 4, 2026
ecb3221
Objects(refactor): Rename package
tony Jul 4, 2026
732ccdb
MCP(feat[pane]): Add run_in_pane
tony Jul 4, 2026
1c7ecfe
Agents(feat[state]): Add done
tony Jul 4, 2026
d9c65e1
Workspace(feat[set]): Build workspace sets
tony Jul 4, 2026
8dc9587
Mcp(fix[events]): Attach raw output streams
tony Jul 4, 2026
8c143a8
Agents(feat[cli]): Add tmux console
tony Jul 4, 2026
43f794d
Agents(fix[monitor]): Seed console state
tony Jul 4, 2026
9274621
Agents(fix[monitor]): Discover agent panes
tony Jul 4, 2026
66712f5
Rebase(fixup): Reconcile supatui onto the fluent engine-ops
tony Jul 4, 2026
ca13dfd
Agents(refactor[mock]): Follow the MockEngine rename
tony Jul 5, 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
Loading