tmuxicate
tmuxicate is a Go CLI for running multiple AI coding agents side by side in tmux with a durable, file-backed coordination layer. It gives each agent a pane, mailbox, and task workflow so a human operator can watch work happen, intervene when needed, and keep coordination reliable rather than implicit. The next project scope extends that foundation with coordinator-driven automation for task decomposition, routing, review flow, and blocker handling.
Core Value: A human can coordinate multiple terminal agents through a reliable, observable workflow where the coordinator keeps work moving without hiding what happened.
- Tech stack: Stay within the existing Go CLI architecture and current tmux/mailbox runtime — the new work should extend current packages rather than introduce a second orchestration system
- Product philosophy: Reliability and operator visibility come before autonomy — automated behavior must remain inspectable and explicit
- Compatibility: Preserve the existing mailbox protocol and multi-vendor adapter model — current Codex/Claude/generic flows must not be broken by coordinator features
- Operational model: Human operator remains the final escalation point — coordinator automation should surface blocked or risky situations instead of hiding them
- Quality: New orchestration flows need direct test coverage in the currently under-tested session/runtime areas — otherwise automation will amplify regressions
- Go 1.26.1 - application code, CLI entrypoint, runtime daemon, adapters, mailbox store, and tests in
cmd/tmuxicate/main.go,internal/runtime/daemon.go,internal/session/*.go, andinternal/*/*_test.go. - Bash - generated agent launcher scripts and test helper shell automation in
internal/session/up.goandtest-agents/fake-agent.sh. - YAML - operator configuration and persisted mailbox/config artifacts in
tmuxicate.yaml,internal/config/config.go,internal/config/loader.go, andinternal/mailbox/store.go. - JSON - runtime heartbeat, ready-state, observed-state, and log/event payloads in
internal/runtime/daemon.goandinternal/session/up.go. - Markdown - operator docs and message bodies via
README.md,DESIGN.md, and markdown mailbox payloads referenced byinternal/session/send.go.
- Native Go CLI binary built from
cmd/tmuxicate/main.go. - Go toolchain requirement is declared as
go 1.26.1ingo.mod. - Shell execution assumes
bashfor generatedrun.shscripts ininternal/session/up.go. - Go modules via
go.modandgo.sum. - Lockfile: present in
go.sum.
github.com/spf13/cobrav1.10.2 - CLI command tree, flags, and argument parsing incmd/tmuxicate/main.go.github.com/knadh/koanf/v2v2.3.4 - declared dependency for configuration layering support ingo.mod; current loader code ininternal/config/loader.goreads YAML directly and does not invoke Koanf.gopkg.in/yaml.v3v3.0.1 - config, envelope, and receipt serialization ininternal/config/loader.go,internal/mailbox/store.go, andinternal/session/up.go.github.com/fsnotify/fsnotifyv1.9.0 - inbox and log file watching ininternal/runtime/daemon.goandinternal/session/log_view.go.- Go
testingpackage - unit and integration tests throughoutinternal/*/*_test.go. - Race detector enabled in
Makefileand.github/workflows/ci.ymlviago test ./... -count=1 -race. go build/go install- local build and install flows inMakefile,README.md, and.github/workflows/ci.yml.golangci-lint- lint runner configured in.golangci.ymland executed inMakefileplus.github/workflows/ci.yml.gofumptandgoimports- formatting tools invoked fromMakefile.
github.com/spf13/cobrav1.10.2 - all user-facing commands are registered incmd/tmuxicate/main.go.github.com/fsnotify/fsnotifyv1.9.0 - delivery daemon and log follower depend on filesystem notifications ininternal/runtime/daemon.goandinternal/session/log_view.go.gopkg.in/yaml.v3v3.0.1 - configuration and mailbox persistence format ininternal/config/loader.goandinternal/mailbox/store.go.golang.org/x/sysv0.42.0 - filesystem locking for receipt/message sequencing ininternal/mailbox/store.go.github.com/go-viper/mapstructure/v2v2.4.0 - indirect dependency viago.mod.github.com/knadh/koanf/mapsv0.1.2 - indirect dependency viago.mod.github.com/mitchellh/copystructurev1.2.0 andgithub.com/mitchellh/reflectwalkv1.0.2 - indirect dependencies viago.mod.github.com/inconshreveable/mousetrapv1.1.0 andgithub.com/spf13/pflagv1.0.9 - Cobra support dependencies viago.mod.
- Primary operator config file is
tmuxicate.yaml, parsed ininternal/config/loader.go. - Resolved session config is written to
config.resolved.yamlunder the session state dir byinternal/session/up.go. - Runtime environment variables injected into agent panes are
TMUXICATE_SESSION,TMUXICATE_AGENT,TMUXICATE_ALIAS, andTMUXICATE_STATE_DIRfrominternal/session/up.go. - CLI fallback resolution also reads
TMUXICATE_STATE_DIRandTMUXICATE_AGENTincmd/tmuxicate/main.go. - Picker behavior reads
TMUXICATE_PICK_TARGETandTMUX_PANEininternal/session/pick.go. - Build/test/lint/format tasks live in
Makefile. - CI automation lives in
.github/workflows/ci.yml. - Linter configuration lives in
.golangci.yml.
tmuxis a hard runtime dependency used through the process-backed client ininternal/tmux/real.go.fzfis an optional local dependency fortmuxicate pick, validated ininternal/session/pick.go.- Agent CLIs are user-supplied commands configured per agent in
tmuxicate.yamland auto-detected duringtmuxicate initininternal/session/init_cmd.go. - Transcript capture relies on
tmux pipe-panewriting raw ANSI logs to per-agent files created ininternal/session/up.go.
- Go toolchain compatible with
go.mod. tmuxavailable onPATHfor runtime and integration tests ininternal/tmux/real_test.go.bashavailable for generated launcher scripts ininternal/session/up.go.golangci-lint,gofumpt, andgoimportsare expected byMakefilebut their versions are not pinned in-repo.- No hosted deployment target is defined.
- Runtime target is a local or remote POSIX-like machine with filesystem access,
tmux, configured agent CLIs, and permission to create the session state tree under.tmuxicate/or another configured state dir.
- Use lowercase package directories with short domain names under
internal/, for exampleinternal/config,internal/mailbox,internal/runtime, andinternal/tmux. - Use snake_case filenames for multiword Go files, especially command-oriented session files such as
internal/session/read_msg.go,internal/session/init_cmd.go, andinternal/session/log_view.go. - Keep test files co-located and named
*_test.go, for exampleinternal/mailbox/store_test.goandinternal/runtime/daemon_test.go. - Exported entry points use PascalCase and map to package responsibilities, such as
config.LoadResolvedininternal/config/loader.go,session.Upininternal/session/up.go, andruntime.NewDaemonininternal/runtime/daemon.go. - Internal helpers use lowerCamelCase and are usually narrow, for example
resolveTargetAgentininternal/session/send.go,createStateTreeininternal/session/up.go, andvalidateBodyininternal/mailbox/store.go. - Constructors follow
NewXnaming, for exampletmux.NewRealClientininternal/tmux/real.go,tmux.NewFakeClientininternal/tmux/fake.go, andadapter.NewGenericAdapterininternal/adapter/generic.go. - Use explicit domain names over abbreviations. Common examples are
stateDir,agentName,paneID,receipt,resolvedStateDir, andbootstrapPathacrossinternal/session/*.goandinternal/runtime/daemon.go. - Use
cfgfor configuration values andctxforcontext.Context, consistently incmd/tmuxicate/main.go,internal/session/up.go, andinternal/tmux/real.go. - Data structs are noun-based and package-scoped to the domain, for example
Configininternal/config/config.go,EnvelopeandReceiptininternal/protocol/*.go,Daemonininternal/runtime/daemon.go, andFakeClientininternal/tmux/fake.go. - Interfaces stay small and capability-oriented.
tmux.Clientininternal/tmux/client.goandadapter.Adapterininternal/adapter/adapter.goare the main examples.
- Format code with
gofumptandgoimportsviamake fmtinMakefile. - Keep imports grouped by standard library first, then internal/external packages as produced by
goimports; representative files arecmd/tmuxicate/main.goandinternal/session/up.go. - Favor early returns and guard clauses instead of deep nesting. This is the dominant shape in
internal/config/loader.go,internal/tmux/real.go, andinternal/session/reply.go. - Lint with
golangci-lint run ./...fromMakefile. - The enabled rules in
.golangci.ymlenforce practical correctness over stylistic churn:govet,staticcheck,errcheck,ineffassign,unused,gocritic,misspell,revive,unconvert, andprealloc. reviveexplicitly enforcescontext-as-argument,error-return, anderror-namingin.golangci.yml. Follow that pattern when adding new APIs.
- There are no custom path aliases. Import packages by full Go module path, as in
cmd/tmuxicate/main.go. - When package names would collide with common identifiers, use a local alias only where necessary, such as
tmuxruntimeforinternal/runtimeincmd/tmuxicate/main.go.
- Validate inputs first and return direct errors for missing requirements. Examples:
internal/tmux/real.go,internal/session/send.go, andinternal/session/reply.go. - Wrap downstream failures with context using
fmt.Errorf("context: %w", err). This is the standard pattern acrossinternal/config/loader.go,internal/mailbox/store.go,internal/runtime/daemon.go, andinternal/session/up.go. - Use
errors.New(...)for package-level sentinel or simple invariant failures, for exampleErrNoUnreadMessagesininternal/session/next.go. - Keep domain validation close to the struct being validated.
(*Envelope).Validateand(*Receipt).Validateininternal/protocol/validation.goare the canonical examples. - Do not silently coerce invalid values except for defaulting in config resolution. Validation failures are explicit and specific.
- CLI commands print human-readable output directly with
fmt.Println,fmt.Printf, and tabwriters incmd/tmuxicate/main.go. - Runtime diagnostics are persisted as JSON/JSONL files instead of going through a shared logger. See
logEventininternal/runtime/daemon.go,appendStateEventininternal/session/task_cmd.go, and status/heartbeat writers ininternal/session/up.goandinternal/session/down.go. - No active shared logging package is used.
internal/logx/exists as a directory but currently contains no files.
- Comments are sparse and used only when the code needs behavioral justification, not narration.
- The main example is the invariant note in
internal/protocol/validation.goexplaining why active receipts may temporarily holddone_atbefore the folder move completes. - Not applicable. This codebase is Go-only.
- Go doc comments are not broadly used for internal functions. Follow the current style unless a new exported package API needs package-level documentation.
- Keep low-level helpers small and focused, for example
replyKindininternal/session/reply.goandpriorityRankininternal/session/inbox.go. - Larger orchestration functions are acceptable in boundary packages when they sequence multiple side effects. Examples include
Upininternal/session/up.go,Statusininternal/session/status.go, andRunininternal/runtime/daemon.go. - Pass infrastructure dependencies explicitly instead of relying on globals. Examples:
session.Up(cfg, tmuxClient)ininternal/session/up.goandNewDaemon(stateDir, tmuxClient, cfg)ininternal/runtime/daemon.go. - Keep config/state paths explicit. Many session functions take
stateDirand derive additional dependencies locally, such asReadMsgininternal/session/read_msg.goandTaskDoneininternal/session/task_cmd.go. - Return domain results plus
errorwhen state is being queried, such as(*ResolvedConfig, error)ininternal/config/loader.go,(*ReadResult, error)ininternal/session/read_msg.go, and(*StatusReport, error)ininternal/session/status.go. - Return only
errorfor command-like mutations unless a stable identifier is produced, such asSendandReplyreturningprotocol.MessageID.
- Keep most implementation details behind package-local helpers. Export only the package surface needed by the CLI and neighboring layers.
- Boundary split is consistent:
cmd/tmuxicate/main.goowns Cobra command wiring and console formatting.internal/session/*.goowns user-visible workflows.internal/runtime/daemon.goowns background delivery behavior.internal/mailbox/*.goowns durable filesystem state.internal/tmux/*.goowns tmux process interaction and fakes.internal/protocol/*.goowns message/receipt schemas and validation.- Not used. Packages are composed through normal Go files, not re-export aggregators.
- Treat
internal/config/loader.goas the single place for config parsing, defaulting, path resolution, and structural validation. New config fields should be added there and tointernal/config/config.go. - Persist operational state under
cfg.Session.StateDirand not the repo root. Session writers consistently usemailbox.*Dir(...)helpers frominternal/mailbox/paths.go. - Environment reads are narrow and explicit:
TMUXICATE_AGENTininternal/session/send.goandcmd/tmuxicate/main.go,TMUXICATE_STATE_DIRincmd/tmuxicate/main.go, and picker-related tmux vars ininternal/session/pick.go. - External process access is isolated to
internal/tmux/real.go, CLI detection ininternal/session/init_cmd.go, and daemon spawning ininternal/session/up.go. Keep new shelling-out logic behind those boundaries.
- Filesystem writes are usually followed by validation or atomic-move semantics.
internal/mailbox/store.gois the model: stage, sync, rename, then sync parent directories. - JSON files are written pretty-printed with a trailing newline for operator readability in
internal/session/up.go,internal/session/down.go,internal/session/task_cmd.go, andinternal/runtime/daemon.go. - YAML is the persistence format for durable mailbox/config records. Follow
yaml.Marshalandyaml.Unmarshalusage ininternal/config/loader.goandinternal/mailbox/store.go. - Fake implementations are preferred over mocking frameworks for boundary tests.
internal/tmux/fake.gois the reference fake. - No
TODO,FIXME,HACK, orXXXmarkers were detected undercmd/orinternal/; new work should either be implemented or filed externally instead of leaving inline debt markers.
cmd/tmuxicate/main.gois the single executable entrypoint and wires every subcommand withcobra.internal/session/*.gois the application layer: each file maps closely to one user action such asup,send,read,reply,status, orpick.- Durable state lives on disk under the session state directory, while
tmuxis used as the operator-facing pane/process layer rather than the message bus.
- Purpose: Parse flags, resolve defaults, print user-facing output, and delegate to internal services.
- Location:
cmd/tmuxicate/main.go - Contains: Cobra command constructors such as
newUpCmd,newSendCmd,newServeCmd,newStatusCmd, and hidden picker helpers. - Depends on:
internal/config,internal/session,internal/runtime,internal/mailbox,internal/protocol,internal/tmux. - Used by: The compiled binary launched from
./cmd/tmuxicate. - Purpose: Implement session lifecycle and mailbox workflows as plain functions.
- Location:
internal/session/up.go,internal/session/down.go,internal/session/send.go,internal/session/read_msg.go,internal/session/reply.go,internal/session/task_cmd.go,internal/session/status.go,internal/session/log_view.go,internal/session/pick.go,internal/session/init_cmd.go - Contains: Orchestration logic, file writes for runtime artifacts, state transitions, dashboard aggregation, and picker UX.
- Depends on:
internal/config,internal/mailbox,internal/protocol,internal/tmux,internal/runtime. - Used by:
cmd/tmuxicate/main.go. - Purpose: Load YAML config, apply defaults, resolve relative paths, and validate agent/session definitions.
- Location:
internal/config/config.go,internal/config/loader.go - Contains:
Config,ResolvedConfig, duration parsing, and validation helpers for layouts, adapters, and task kinds. - Depends on:
internal/protocolandgopkg.in/yaml.v3. - Used by: CLI commands, session functions, and daemon startup.
- Purpose: Define canonical message and receipt schemas that all other packages exchange.
- Location:
internal/protocol/envelope.go,internal/protocol/receipt.go,internal/protocol/ids.go,internal/protocol/validation.go - Contains:
Envelope,Receipt, message/thread IDs, folder states, kinds, priorities, and validation rules. - Depends on: Standard library only.
- Used by:
internal/session,internal/mailbox, andinternal/runtime. - Purpose: Persist immutable messages and mutable per-agent receipts using atomic filesystem operations.
- Location:
internal/mailbox/store.go,internal/mailbox/paths.go - Contains: Sequence allocation, receipt moves, receipt locking, body hash verification, and path helpers.
- Depends on:
internal/protocolandgolang.org/x/sys/unixforflock. - Used by:
internal/sessionandinternal/runtime. - Purpose: Watch unread inboxes, probe panes, inject notifications, and publish heartbeat/observed state.
- Location:
internal/runtime/daemon.go - Contains:
Daemon, fsnotify watcher loop, periodic sweep, retry bookkeeping, and JSON event logging. - Depends on:
internal/adapter,internal/config,internal/mailbox,internal/protocol,internal/tmux,github.com/fsnotify/fsnotify. - Used by:
tmuxicate serveand background daemon startup ininternal/session/up.go. - Purpose: Hide agent-specific notification behavior and tmux command execution behind interfaces.
- Location:
internal/adapter/*.go,internal/tmux/*.go - Contains:
adapter.Adapter,tmux.Client,GenericAdapter,CodexAdapter,ClaudeCodeAdapter,RealClient, andFakeClient. - Depends on: Standard library plus internal protocol/session state where needed.
- Used by:
internal/runtime/daemon.go,internal/session/up.go,internal/session/down.go,internal/session/status.go,internal/session/pick.go.
- The filesystem under the configured session state directory is authoritative.
- Core paths are built by
internal/mailbox/paths.go. tmuxpane metadata in@tmuxicate-*options is auxiliary and used for discovery/reconciliation, not as the primary source of message truth.tmuxicate upstarts the main tmux session and then spawns a detached background process that runstmuxicate serve; seeinternal/session/up.go.tmuxicate serveruns a long-lived event loop ininternal/runtime/daemon.gowith three concurrent concerns multiplexed in one select loop: fsnotify events, periodic health/heartbeat ticks, and periodic full sweeps.- Command handlers are otherwise synchronous and short-lived: each CLI subcommand loads config/state, performs one operation, prints output, and exits.
- Purpose: Freeze config defaults and absolute paths before session logic runs.
- Examples:
internal/config/loader.go,internal/session/up.go,internal/runtime/daemon.go - Pattern: Parse once near the edge, then pass a resolved struct through the call chain.
- Purpose: Separate immutable message content from per-recipient mutable delivery state.
- Examples:
internal/protocol/envelope.go,internal/protocol/receipt.go,internal/mailbox/store.go - Pattern: One message directory plus one receipt file per recipient/folder state.
- Purpose: Isolate shelling out to
tmuxfrom application logic. - Examples:
internal/tmux/client.go,internal/tmux/real.go,internal/tmux/fake.go - Pattern: Interface-driven infrastructure with a real implementation and an in-memory fake for tests.
- Purpose: Encapsulate readiness probing and notification phrasing for each agent CLI.
- Examples:
internal/adapter/adapter.go,internal/adapter/generic.go,internal/adapter/codex.go,internal/adapter/claude_code.go - Pattern: Generic adapter core with thin vendor-specific wrappers.
- Location:
cmd/tmuxicate/main.go - Triggers: User invokes
tmuxicate. - Responsibilities: Build the root command tree and dispatch subcommands.
- Location:
cmd/tmuxicate/main.govianewServeCmd, implemented byinternal/runtime/daemon.go - Triggers:
tmuxicate serveor the background process spawned byinternal/session/up.go - Responsibilities: Delivery retries, observed-state updates, heartbeat emission, and runtime JSONL logging.
- Location:
internal/session/up.go - Triggers:
tmuxicate up - Responsibilities: Prepare state directories, generate agent bootstrap artifacts, create tmux panes, and start the daemon.
- Location:
internal/session/status.go,internal/session/log_view.go,internal/session/pick.go - Triggers:
tmuxicate status,tmuxicate log,tmuxicate pick,__list-panes,__preview-pane - Responsibilities: Aggregate runtime state into dashboards, logs, and popup picker data.
- Packages mostly return
fmt.Errorf("context: %w", err)rather than defining custom error types. - Validation happens at boundaries: config in
internal/config/loader.go, protocol schema ininternal/protocol/validation.go, and filesystem invariants ininternal/mailbox/store.go. - Runtime failures in the daemon are logged to
logs/serve.jsonland usually converted into retryable receipt metadata rather than crashing the process.
No project skills found. Add skills to any of: .claude/skills/, .agents/skills/, .cursor/skills/, or .github/skills/ with a SKILL.md index file.