Skip to content

feat: Add Non-polling Waiters (Deterministic Channels & Delta-Polling) #673

@tony

Description

@tony

As libtmux has evolved with the 0.57.0 "Neo" ORM parity, we've solved the N+1 problem for object hydration. However, synchronizing with terminal output still largely relies on client-side loop polling via capture_pane().

We propose introducing a two-tier waiter system modeled after the patterns battle-tested in libtmux-mcp. This brings "Wait, Don't Poll" semantics natively to libtmux.

1. Best-of-Breed: Deterministic Channel Sync (Server.wait_for_channel)

For commands where the user controls the execution, we should avoid scraping scrollback entirely and rely on tmux's native OS-level IPC blocks.

  • The Concept: Bracket shell commands with tmux wait-for -S <channel> and block the libtmux client until the signal fires.
  • libtmux-mcp Prior Art: See wait_for_tools.py which implements wait_for_channel via subprocess.run(timeout=timeout).
  • tmux Internals: This leverages tmux's cmd-wait-for.c (tmux/tmux@18ddda4), allowing the Python thread to sleep completely until tmux wakes it.
  • Proposed API:
    pane.send_keys("pytest; tmux wait-for -S tests_done")
    server.wait_for_channel("tests_done", timeout=60.0)

2. Intelligent Fallback: Delta Polling (Pane.wait_for_text)

When observing third-party output (where we can't inject a signal), we must poll. However, naive capture_pane loops often match stale screen paint. We need Absolute Grid Anchoring.

  • The Concept: At entry, snapshot the grid's absolute baseline (history_size + cursor_y). On each tick, capture only the rows below this absolute anchor to ensure we strictly match new text.
  • libtmux-mcp Prior Art: See pane_tools/wait.py for the anchor math and scrollback limit protections.
  • tmux Internals: This approach is grounded in how tmux defines the grid (see format_cb_history_bytes and format_cb_history_size in format.c at 3.2a). It ensures compatibility with grid_collect_history and clear-history shifts.
  • Proposed API:
    # Under the hood, this will use Neo-style batch hydration 
    # to fetch `#{history_size}|#{cursor_y}` in a single IPC turn.
    pane.wait_for_text("READY", timeout=8.0) 

3. Future Scope: Control Mode Listener

A long-term architectural goal could involve spawning a background tmux -C client (as currently used in libtmux testing via ControlMode) to listen for %output or %pane-mode-changed streams, offering a completely event-driven API.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions