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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions