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:

  1. Validation: Calls validateTeamName() to ensure the session identifier is safe for tmux
  2. Command Construction: Uses buildWorkerStartCommand() to generate a shell-safe command that sources the appropriate rc file, sets environment variables, and executes the binary with exec
  3. Pane Injection: Sends the command to the specific tmux pane using tmux send-keys -t {paneId} -l {command}, followed by an Enter keystroke

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 appropriate WorkerPaneConfig
  • 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() in src/team/tmux-session.ts, ensuring each agent runs in an isolated terminal environment
  • Binary resolution and argument construction happen via buildWorkerArgv() and resolveValidatedBinaryPath() in src/team/model-contract.ts
  • Workers spawn through spawnWorkerInPane() using tmux send-keys to inject shell commands that source rc files and execute the agent binary with exec
  • Codex always runs in full TUI mode with prompt-waiting logic, while Gemini and Claude support --prompt-mode for headless execution
  • Runtime orchestration in src/team/runtime.ts manages 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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →