Skip to content

feat(tmux): tmux agent sessions — operator UX, attach, status, prerequisites, E2E - #25

Open
devguyio wants to merge 40 commits into
mainfrom
feature/issue-5-tmux-module
Open

devguyio wants to merge 40 commits into
mainfrom
feature/issue-5-tmux-module

Conversation

@devguyio

@devguyio devguyio commented May 15, 2026

Copy link
Copy Markdown

Summary

Implements the Tmux Agent Sessions epic (stories #5#8). Adds complete tmux integration for BotMinter:

  • Tmux module (formation/local/tmux/): Config management, version detection, session/window lifecycle with botminter socket isolation
  • Window lifecycle (TmuxSession): create/query/kill/remove/attach windows with pane capture support
  • Start integration: prepare_tmux_session() creates session before member loop, start_member_tmux_window() launches each member in a named window with single-start guard
  • bm attach [member]: Attaches to tmux session, optional member window targeting, nested-session warning, cheat sheet on stderr
  • bm status tmux info: Shows session name, window count, bm attach and raw tmux attach commands
  • Prerequisites: check_prerequisites() validates tmux presence and version (≥3.0) with actionable install suggestions
  • Branding: Custom tmux.conf with BotMinter status bar, keybinding hints (C-b n/p/[/d), remain-on-exit for dead pane inspection

Stories

Story Title Sub-issues
#5 Tmux module with config and session lifecycle #17, #18, #19
#6 Window lifecycle and TmuxSession methods #20, #21, #22
#7 Start integration and member window management #23, #24, #25 (team)
#8 Operator UX — attach, status, prerequisites, E2E #27, #28, #29, #30

Test Plan

  • 974 lib tests passing (tmux module, attach, status, prerequisites)
  • 125 integration tests passing (branding, keybinding hints, window names, cheat sheet)
  • E2E scenarios: tmux_verify_after_start, tmux_attach_succeeds, tmux_windows_preserved_after_stop
  • Exploratory tests: H25b-e (tmux after start), H37b-c (windows after stop)
  • Thread-safe env mutation (PATH_MUTEX, ENV_MUTEX with RAII helpers)
  • Clippy clean

Closes devguyio-bot-squad/may-team-team#5
Closes devguyio-bot-squad/may-team-team#6
Closes devguyio-bot-squad/may-team-team#7
Closes devguyio-bot-squad/may-team-team#8

devguyio and others added 30 commits May 11, 2026 14:01
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
… session constructor

TDD Red phase: 11 failing tests covering all Task 01 acceptance criteria:
- TmuxSession::new() name validation (valid and invalid team names)
- TmuxConfig::config_content() branding, keybindings, remain-on-exit
- TmuxConfig::path() resolves to ~/.config/botminter/tmux.conf
- TmuxConfig::ensure_written() file creation, permissions, atomic write

Ref: #5

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…structor

Implement TmuxConfig (include_str tmux.conf, atomic write via tempfile,
0600 permissions) and TmuxSession::new() with team name validation.
All 17 CT-01 tests pass. Uses NamedTempFile for race-safe atomic writes.

Ref: #5

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract write_to(path) from ensure_written() for testability, write
directly to the temp file handle instead of re-opening via fs::write,
and isolate tests with temp directories to fix a race condition where
concurrent ensure_written() calls left .tmp files visible to other tests.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…sing

12 new tests covering: standard version parsing ("tmux 3.4"),
letter suffix ("tmux 3.3a"), dev prefix ("tmux next-3.4"),
release candidate ("tmux 3.2-rc"), minimum version enforcement,
garbage/empty input rejection, TmuxVersion Display,
tmux_cmd() TMUX_TMPDIR removal, and check_tmux_available() integration.

7 tests fail (awaiting green phase implementation), 5 pass from stubs.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement parse_tmux_version() and check_tmux_available() for CT-02 Green.
Handles standard ("tmux 3.4"), letter suffix ("3.3a"), dev prefix ("next-3.4"),
and release candidate ("3.2-rc") formats. Enforces minimum version 3.0.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract MINIMUM_MAJOR_VERSION constant from hardcoded version check
- Reduce parse_tmux_version visibility to fn (only used internally)
- Consolidate two impl TmuxSession blocks into one
- Move TmuxSession struct before its impl block (was forward-referenced)

Ref: #5
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement create(), exists(), destroy(), destroy_if_exists() for
TmuxSession. All commands use tmux_cmd() helper for TMUX_TMPDIR
isolation and -L socket_name for socket isolation. create() ensures
config is written before spawning the session.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Extract run_session_cmd() helper to deduplicate create/destroy command execution and error handling
- Remove #[allow(dead_code)] from TmuxSession — all fields are actively used
- Use tmux_cmd() consistently in tests and TmuxGuard instead of raw Command::new with manual env_remove
- Remove redundant TmuxConfig::ensure_written() calls in CT-03 tests — create() handles this internally
Story #6 CT-01: 7 failing tests covering window creation with command,
env var security (secrets not in pane_start_command or ps), name
validation (valid/invalid), immediate exit detection (non-zero exit
and binary not found), and no-session error handling.

Scaffolding: create_window() stub (bail!("not yet implemented")),
validate_name() shared helper extracted from TmuxSession::new().

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implements TmuxSession::create_window() with env var passing via tmux -e
flags (secrets never appear in pane_start_command or ps output), name
validation via shared validate_name() helper, PID retrieval via
display-message #{pane_pid}, and immediate exit detection via
#{pane_dead}/#{pane_dead_status} inspection.

Sources the tmux config file on the socket before window creation to
ensure remain-on-exit is active for dead pane retention.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract three helpers from create_window() to reduce duplication:
- config_str(): shared config path conversion (was duplicated in create() and create_window())
- source_config(): separates config sourcing concern from window creation
- query_pane_format(): deduplicates PID query and dead-pane check display-message pattern

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract require_window() helper to deduplicate the window existence
guard and target formatting pattern shared by is_pane_dead() and
pane_pid().
9 tests for kill_window_process, remove_window, remove_dead_window,
session_info, attach, and full lifecycle integration. 8 fail against
stubs, 1 passes (remove non-existent expects Err). Ref: #23

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract allocate_pty() helper from attach() to isolate unsafe
libc::openpty block. Reorder create_window() to logical position
after session lifecycle methods (before window queries).
6 failing tests covering all acceptance criteria for story #7 CT-01:
- launch_ralph creates tmux window with valid PID
- launch_brain creates window with stderr log
- agent output visible in tmux pane (polling)
- daemon PID not in any tmux window
- credentials not in pane_start_command or ps
- TMUX_TMPDIR isolation for launched agents

Scaffolding: launch_ralph() and launch_brain() gain tmux/member_name
params with bail!("not yet implemented") stubs. TmuxSession gets
public session_name() accessor. Callers updated to compile.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Rewrite launch_ralph() and launch_brain() to use TmuxSession::create_window()
instead of Command::spawn(). Credentials are written to a temporary file and
sourced by a bash wrapper (setenv, not execve), keeping secrets out of
/proc/pid/environ and ps auxe. Brain stderr is teed to brain-stderr.log via
bash process substitution while remaining visible in the tmux pane.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…start_local_members

Red phase for CT-02 Story #7: 4 tests covering session creation on full
start, destroy-and-recreate of previous sessions, single-member start
preserving existing sessions, and stop preserving tmux windows with
scrollback. All fail via prepare_tmux_session() stub.

Ref: #7

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement prepare_tmux_session() to manage tmux session lifecycle:
- Full start: checks prerequisites, destroys existing session, creates fresh
- Single-member start: checks prerequisites, creates only if not existing

Fix flaky scrollback test by polling for echo output and using full
history capture (-S -) instead of visible-area-only capture-pane.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The tmux session lifecycle (create/destroy) is a one-time setup concern,
not a per-member concern. Hoisting it before the loop ensures destroy-on-
full-start runs exactly once and prevents re-destroying the session (and
its windows) on each subsequent member iteration.

Ref: #7
6 tests for CT-03 single-start guard logic:
- fresh_single_start: no session → create + launch
- additive_single_start: add window, preserve existing
- skip_if_live: live window → Skip with PID
- dead_window_restart: dead pane → remove + Launch
- stop_then_start_cycle: kill → dead → remove + Launch
- toctou_race: concurrent starts → clear error, not corruption

Stubs: SingleStartAction enum and single_start_guard() with
bail!("not yet implemented").

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Implement single_start_guard() which checks tmux window state before
launching a member: returns Launch when no window exists, Skip{pid}
when the window is alive, and Launch after cleaning up dead windows.
Fix CT-03 Red test command to survive create_window's 100ms pane-dead check.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Import TmuxSession directly to eliminate verbose
formation::local::tmux::TmuxSession paths in
prepare_tmux_session and single_start_guard. Remove
unused launches counter from TOCTOU race test.
CT-01 Red (Story #8): 3 tests for check_prerequisites() tmux detection.
2 failing as expected (current impl doesn't check tmux), 1 passing (success case).

Ref: #27

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
devguyio and others added 10 commits May 16, 2026 00:55
Extract with_path() helper that serializes PATH mutations via a static
mutex, preventing parallel test interference between prerequisite check
tests and other tests that depend on a stable PATH (e.g., config tests).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CT-02 Red phase for Story #8. Breaking trait change:
Formation::shell() now accepts member: Option<String> for per-member
window targeting. All implementors updated with stubs. CLI Attach
command gains optional positional member argument.

6 failing tests:
- shell_no_session_returns_start_hint (no session → error with "bm start" hint)
- shell_attaches_to_existing_session (session exists → attach, not stub)
- shell_with_member_targets_window (bob window → target it)
- shell_with_nonexistent_member_lists_available_windows (alice → list bob, cos)
- shell_nested_tmux_warns_but_proceeds ($TMUX set → warn then proceed)
- attach_cheat_sheet_displayed_on_stderr (Ctrl-b hints on stderr)

Plus 1 compile-time test (shell_trait_compatibility_all_implementors_compile).

Ref: #8

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ref: Story #8 CT-03

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…_info

Replace hardcoded "botminter" socket name with info.socket_name and use
info.session_name consistently instead of mixing info and session sources.
Extract raw_attach_command computation before struct init for clarity.

Ref: #29

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
CT-04 Red phase — write failing tests for branded config verification,
E2E tmux lifecycle, and exploratory test updates.

Integration tests (3):
- tmux_branding_status_left_contains_botminter (PASS — scaffolding)
- tmux_branding_list_windows_shows_member_names (PASS — scaffolding)
- tmux_keybinding_hints_all_four_visible_in_status_right (FAIL — C-b d:detach missing)

E2E tests (3 case functions, registered in both journey passes):
- tmux_verify_after_start: AC1/AC2/AC3/AC5 (session, branding, keybindings, status)
- tmux_attach_succeeds_while_running: AC4/AC7 (attach works, raw command functional)
- tmux_windows_preserved_after_stop: AC6 (remain-on-exit preserves windows)

Exploratory tests (5 assertions in phase-h.sh):
- H25b: has-session after start
- H25c: list-windows shows member names
- H25d: bm status shows tmux info
- H25e: bm attach shows cheat sheet
- H37b/H37c: windows preserved + capture-pane after stop

Ref: #8 #30

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Ref: #30

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Extract shared TmuxTestGuard and setup_tmux_session() helper in
integration tests, replacing 4 inline Guard struct definitions.
Remove duplicate SESSION_NAME assignment in phase-h.sh.
@devguyio devguyio changed the title feat(tmux): add formation/local/tmux/ module with config and session lifecycle feat(tmux): tmux agent sessions — operator UX, attach, status, prerequisites, E2E May 16, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant