Skip to content

feat: cmux as configurable backend for session management#1

Open
abhirup-dev wants to merge 3 commits intomainfrom
feat/cmux-backend
Open

feat: cmux as configurable backend for session management#1
abhirup-dev wants to merge 3 commits intomainfrom
feat/cmux-backend

Conversation

@abhirup-dev
Copy link
Copy Markdown
Owner

Summary

Adds cmux as a configurable backend alternative to the agent-deck CLI daemon. cmux is a native macOS terminal app purpose-built for AI agent orchestration, with GPU-accelerated rendering (libghostty), integrated browser, and rich sidebar metadata.

With this change, sessions can run in cmux's native UI instead of Neovim terminal buffers, while maintaining full feature parity with the existing backend.

Architecture

UI Layer (picker, parallel, send, output)
                    |
         require("agent-deck.backend")   <-- NEW dispatch layer
                    |
       +------------+------------+
       |                         |
  backend/               backend/
  agent_deck.lua         cmux.lua
  (wraps cli.lua)        (cmux CLI)
       |                         |
  agent-deck daemon       cmux app
  (Rust + tmux)           (macOS native)

What changed

New files (7):

  • backend.lua — dispatch layer with init(), name(), method forwarding for all 15 interface methods
  • backend/agent_deck.lua — thin cli.lua wrapper + focus_session no-op (preserves existing behavior exactly)
  • backend/cmux.lua — full cmux implementation: launch, list, send, output, start/stop/restart/delete, groups, focus
  • backend/cmux_status.lua — Claude .jsonl tail parser for session status detection (sub-millisecond)
  • backend/ansi.lua — ANSI escape sequence stripping for clean read-screen output
  • claude_paths.lua — extracted shared path utilities from picker.lua
  • session_cmd.lua — extracted shared build_cmd logic previously duplicated between picker.lua and parallel.lua

Modified files (7):

  • init.luabackend.init(opts.backend) in setup; all cli refs swapped to backend; sync timer gated
  • persist.lua — added _cmux_sessions accessors (get/set/remove/all)
  • ui/picker.lua — uses shared modules; adds focus_session path for cmux
  • ui/parallel.lua — uses shared modules; cmux-aware layout delegates to focus_session
  • ui/send.lua, ui/output.lua, ui/info.lua — mechanical clibackend swap

Feature parity

Operation agent-deck backend cmux backend
Launch session agent-deck launch cmux new-workspace + new-split + send-surface
List sessions agent-deck list --json cmux list-surfaces + persist metadata + .jsonl status
Session status Daemon-reported .jsonl tail parse (Claude) / surface-alive (other)
Send input agent-deck session send cmux send-surface + send-key enter
Read output agent-deck session output cmux read-screen + ANSI strip
Stop/Delete CLI commands send-key ctrl-c / close-surface
Groups agent-deck group commands cmux workspaces
Focus session N/A (Neovim terminals) cmux focus-surface + select-workspace

Configuration

require("agent-deck").setup({
  backend = "cmux",  -- "agent-deck" (default) | "cmux"
})

Known limitations (cmux backend)

  • macOS only
  • No process exit detection API
  • group_move is persist-only (cmux doesn't support cross-workspace moves)
  • read-screen output is ANSI-stripped raw text vs structured JSON
  • No SSH/remote (future scope)

Review fixes applied

  1. Critical: session_delete use-after-delete — fixed
  2. Medium: new-workspace missing --name flag — fixed
  3. Medium: stdout pipe leak on spawn failure — fixed
  4. Medium: Duplicated path logic — fixed by importing shared module

Test plan

  • Default backend: no errors in :AgentDeckLog, statusline updates
  • Dan, Dap, Dal, DaR all work (session lifecycle)
  • 2 sessions in same dir — no clobbering
  • Das + Dao (send/output)
  • Picker actions: stop, restart, delete
  • backend = "cmux": health check, launch, focus, send, output

🤖 Generated with Claude Code

claude and others added 3 commits March 28, 2026 13:06
Introduce a backend abstraction layer so agent-deck.nvim can use either
the existing agent-deck CLI daemon or cmux (native macOS terminal app)
for AI coding session management.

Key changes:
- backend.lua dispatch layer with init()/name() and method forwarding
- backend/cmux.lua: full implementation (launch, list, send, output, etc.)
  using cmux CLI via the same vim.uv.spawn pattern as cli.lua
- backend/cmux_status.lua: .jsonl tail parser for Claude session status
  detection (8KB seek, sub-millisecond)
- backend/ansi.lua: ANSI escape stripping for read-screen output
- backend/agent_deck.lua: thin cli.lua wrapper preserving current behavior
- claude_paths.lua: extracted shared path utilities (conv_exists, encode_path)
- session_cmd.lua: extracted shared build_cmd logic from picker/parallel
- persist.lua: _cmux_sessions accessors for cmux metadata storage

All existing callers (init, picker, parallel, send, output, info) rewired
from require("agent-deck.cli") to require("agent-deck.backend"). Backend
selection via setup({ backend = "cmux" }), defaults to "agent-deck".

Includes comprehensive logging and vim.notify calls on all critical paths
for debugging during testing phase.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…coding

- session_cmd: read ~/.agent-deck/config.toml for claude command/env,
  support custom_claude_cmd setup option, check session status to pick
  --resume vs --session-id correctly
- claude_paths: encode both / and . to match Claude's actual project
  directory encoding (fixes .config paths producing wrong lookup)
- init: codex thread poll sync — enrich codex sessions on every poll so
  Dar can resume the correct thread via stop→set→start (workaround for
  agent-deck restart ignoring command field)
- parallel: use build_cmd_new with conv_exists check instead of build_cmd
- picker: handle nil from build_cmd_new (waiting + no .jsonl), show
  notification after Dan if claude is waiting for permission
- cmux: delegate launch command to session_cmd for config-aware builds

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…d sync

Tested cmux backend end-to-end and fixed all issues found:

CLI command mapping (cmux v0.63):
- list-surfaces → tree --all --json (parse nested structure)
- send-surface → send --surface, send-key-surface → send-key --surface
- focus-surface → select-workspace (no focus-surface in cmux CLI)
- ctrl-c → ctrl+c (plus sign)
- Binary resolution: add /Applications/cmux.app/Contents/Resources/bin/cmux fallback

Session lifecycle:
- Launch: prepend cd <path> && to commands (surfaces default to ~)
- Launch: pass --cwd to new-workspace for correct shell directory
- Restart: close-surface + new-split (cmux has no process-kill API)
- Restart: create new surface BEFORE closing old (avoids "last surface" error)
- Restart: use bare tool name in persist, not resume command (prevents doubling)
- Stop: same create-first-then-close pattern
- Restart/start: build command from bare tool + session IDs, never from stored
  command (which may already contain resume args from a previous restart)

Status detection (cmux_status.lua):
- Skip system/attachment entries, find last assistant/human entry
- Check message.stop_reason (not top-level stop_reason)

Codex thread sync for cmux:
- Separate poll block in init.lua (gated on backend name)
- Reads from cmux persist, enriches via codex.enrich_session (shared util),
  writes codex_thread_id back to cmux persist
- Store created_at as UTC date string (not unix int) so SQLite strftime works

UI (picker + parallel):
- Separate cmux enrichment path: enrich_cmux_sessions reads persist + build_cmd
- _cmd_ready flag prevents double-processing through build_cmd_new
- DaR for cmux: always fetch fresh sessions from backend (surface refs change
  after Dar restart), not gated on was_open
- Daf: new cmux-only single-select picker for focusing session in cmux UI
- Extracted do_open_split/do_open_float shared by both backends

Backend separation:
- Dar in init.lua: cmux path uses simple session_restart loop (no agent-deck
  codex stop→set→start workaround needed)
- Agent-deck code paths completely untouched

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
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.

2 participants