How the OMC Team CLI Spawns Real tmux Workers for Codex, Gemini, and Claude
The OMC team CLI spawns isolated tmux sessions for each AI agent by creating pane topologies, resolving binary paths via model contracts, and sending launch commands through tmux send-keys to run Codex, Gemini, and Claude in separate terminal environments.
The omc team command turns your terminal into a multi-agent control center by orchestrating real tmux workers for OpenAI's Codex, Google's Gemini, and Anthropic's Claude. Unlike background process wrappers, the OMC team CLI creates tangible tmux sessions you can attach to, inspect, and debug. This architecture ensures each AI worker operates in an isolated terminal environment while remaining fully manageable through the orchestration layer.
Creating the tmux Session Topology
The spawn process begins in src/team/tmux-session.ts with createTeamSession(), which establishes the tmux infrastructure for the entire team. This function determines whether to split the current tmux window, open a dedicated window, or fall back to a detached session based on your current terminal state.
The function sanitizes session names through sessionName() to ensure tmux compatibility, then executes raw tmux commands including new-session, split-window, and select-layout to construct the pane grid. Each worker receives its own tmux pane within this topology, creating physical isolation between agents.
Resolving Agent Binaries and Launch Arguments
Before spawning, the CLI resolves the correct binary and launch configuration through src/team/model-contract.ts. The buildWorkerArgv() function validates team and worker names, then calls resolveValidatedBinaryPath() to locate the agent-specific CLI—codex, gemini, or claude—on the host system.
This layer injects worker-specific environment variables including OMC_TEAM_WORKER and model-specific configurations. It also assembles launch flags such as model selection parameters and --prompt-mode flags when supported, creating a complete argument vector for each agent type.
Spawning Workers in tmux Panes
The actual spawning occurs in spawnWorkerInPane() within src/team/tmux-session.ts. This function receives the pane ID and a WorkerPaneConfig object, then orchestrates the launch through three precise steps:
- Validation: Calls
validateTeamName()to ensure the session identifier is safe for tmux - Command Construction: Uses
buildWorkerStartCommand()to generate a shell-safe command that sources the appropriate rc file, sets environment variables, and executes the binary withexec - Pane Injection: Sends the command to the specific tmux pane using
tmux send-keys -t {paneId} -l {command}, followed by anEnterkeystroke
For interactive agents, the CLI then invokes waitForPaneReady() to monitor the pane output until the agent's prompt appears (such as › for Codex or ❯ for Claude), ensuring the worker is fully initialized before sending the inbox trigger message via sendToWorker().
Agent-Specific Spawn Flows
Each agent exhibits distinct spawn characteristics handled by the model-contract layer:
Codex (Full TUI Only)
Codex does not support headless prompt-mode operation. The CLI resolves the binary path through resolveValidatedBinaryPath('codex') and launches it without --prompt-mode arguments. After spawning, waitForPaneReady() watches for the Codex prompt (›), then sends the inbox trigger (new-task:<id> or Read … inbox.md) through sendToWorker().
Gemini (Prompt-Mode Optimization)
Gemini supports headless operation via --prompt-mode. When isPromptModeAgent('gemini') returns true, getPromptModeArgs() appends flags like --prompt-mode --instruction 'Read … inbox.md' to the launch command. Because the task instruction is baked into the launch arguments, the CLI skips waitForPaneReady() and allows Gemini to execute and exit autonomously.
Claude (Model-Specific Flags)
Claude spawning follows the Gemini pattern when using prompt-mode, but includes additional model flags such as --model $CLAUDE_MODEL. When running in interactive mode, the CLI waits for the Claude prompt (❯) before injecting the inbox trigger, identical to the Codex flow.
Orchestration and Runtime Management
Higher-level coordination happens in src/team/runtime.ts (and runtime-v2.ts for newer CLI entry points). The runtime ties the components together by:
- Calling
createTeamSession()to establish the tmux infrastructure - Iterating over the required worker count and calling
createWorkerPane()for each instance - Invoking
spawnWorkerInPane()with the appropriateWorkerPaneConfig - Recording pane IDs and writing a panes-tracking file for session persistence
- Registering workers in the in-memory runtime state for health monitoring and message routing
Code Examples
Starting a Team with Three Workers
# Create a new team called "my-project"
omc team create my-project \
--agents codex,gemini,claude \
--workers 3 \
--cwd /path/to/repo
Internal execution flow:
// runtime.ts (simplified)
const session = await createTeamSession(teamName, workerCount, cwd);
for (let i = 0; i < workerCount; i++) {
const paneId = await createWorkerPane(session, i); // tmux split-window
const config = await buildWorkerConfig(i); // env + launch args
await spawnWorkerInPane(session.sessionName, paneId, config);
}
Low-Level Pane Spawning Implementation
The core spawning logic in src/team/tmux-session.ts:
export async function spawnWorkerInPane(
sessionName: string,
paneId: string,
config: WorkerPaneConfig
): Promise<void> {
// 1️⃣ Validate the team name (sanitises for tmux)
validateTeamName(config.teamName);
// 2️⃣ Build the safe shell command that tmux will execute
const startCmd = buildWorkerStartCommand(config);
// 3️⃣ Send the command to the pane (literal mode, no extra quoting)
await execFileAsync('tmux', ['send-keys', '-t', paneId, '-l', startCmd]);
await execFileAsync('tmux', ['send-keys', '-t', paneId, 'Enter']);
}
Constructing Launch Arguments for Claude
Binary resolution and argument building in src/team/model-contract.ts:
const argv = buildWorkerArgv('claude', {
teamName: 'my-project',
workerName: 'worker-0',
cwd: '/path/to/repo',
resolvedBinaryPath: undefined, // resolve from contract
model: 'claude-3-opus-20240229', // derived from env or default
extraFlags: [], // can include --prompt-mode
});
// argv => ['/usr/local/bin/claude', '--model', 'claude-3-opus-20240229']
Prompt-Mode Optimization for Gemini
Headless execution bypasses interactive waiting:
// src/team/runtime.ts – after building launch args
if (isPromptModeAgent('gemini')) {
const promptArgs = getPromptModeArgs('gemini', generateTriggerMessage(teamName, workerName));
launchArgs.push(...promptArgs); // e.g. ['--prompt-mode', '--instruction', 'Read … inbox.md']
}
When isPromptModeAgent returns true, the CLI does not call waitForPaneReady() because the agent exits after processing the supplied instruction.
Summary
- The OMC team CLI creates physical tmux sessions through
createTeamSession()insrc/team/tmux-session.ts, ensuring each agent runs in an isolated terminal environment - Binary resolution and argument construction happen via
buildWorkerArgv()andresolveValidatedBinaryPath()insrc/team/model-contract.ts - Workers spawn through
spawnWorkerInPane()usingtmux send-keysto inject shell commands that source rc files and execute the agent binary withexec - Codex always runs in full TUI mode with prompt-waiting logic, while Gemini and Claude support
--prompt-modefor headless execution - Runtime orchestration in
src/team/runtime.tsmanages pane creation, worker registration, and session persistence through panes-tracking files
Frequently Asked Questions
How does the CLI handle tmux session conflicts?
The sessionName() utility sanitizes team names to remove characters that tmux interprets specially, ensuring valid session identifiers. If a session already exists, the CLI either attaches to it or creates a uniquely named detached session, preventing naming collisions while maintaining the requested worker topology.
Can I attach to a running worker pane manually?
Yes. Because the OMC team CLI spawns real tmux sessions rather than background processes, you can use standard tmux commands like tmux attach-session -t {team-name} or tmux select-pane -t {paneId} to interact directly with Codex, Gemini, or Claude workers. The session topology is tracked in the panes-tracking file written by runtime.ts.
What environment variables are injected into each worker?
Each worker receives OMC_TEAM_WORKER identifying its role, along with model-specific variables such as CODEX_MODEL, GEMINI_MODEL, or CLAUDE_MODEL depending on the agent type. These are set through the WorkerPaneConfig and sourced into the shell environment before the agent binary executes via buildWorkerStartCommand().
Why does Codex require different handling than Gemini or Claude?
Codex lacks a --prompt-mode flag for headless operation, requiring the CLI to maintain an interactive TUI session. This necessitates calling waitForPaneReady() to detect the Codex prompt (›) before sending task instructions, whereas Gemini and Claude can receive instructions via launch arguments and exit immediately without interactive monitoring.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →