Skip to content

Architecture

Overview

agentic-team uses tmux as the execution layer and TOML files for durable state.

team CLI
  ├── cli.py         Click entry point and command orchestration
  ├── config.py      Team config, worker state, log/session paths
  ├── agents.py      Provider-specific command builders and system prompts
  ├── tmux.py        All tmux subprocess interaction
  ├── status.py      Worker completion detection and state refresh
  ├── taskfile.py    Markdown task-file parsing and writeback
  ├── models.py      Provider registry and ProviderHealth checks
  └── names.py       Worker naming and prefix matching

State model

All state lives under ~/.agentic-team/:

Path Purpose
teams/<name>.toml Saved TeamConfig
state/<name>/workers.toml Saved WorkerState list
state/<name>/pending_prompts/ Interactive worker prompts waiting for the pane to become ready
state/<name>/multi_targets Pane join order for team attach --multi
state/<name>/standup.md Standup report written by the lead
logs/<name>/<timestamp>/ Session log files
logs/<name>/current Symlink to the active session log directory
active Symlink to the active team's state directory

State serialization uses tomllib for reads and tomli_w for writes.

Tmux layer

TmuxOrchestrator centralizes all tmux calls:

  • session lifecycle: new-session, has-session, kill-session
  • window lifecycle: new-window, kill-window, list-windows
  • input/output: send-keys, capture-pane
  • dashboard mode: join-pane, break-pane, select-layout tiled
  • terminal handoff: os.execvp("tmux", ...) for attach

TmuxSnapshot is a per-refresh-cycle cache that stores window listings, pane-dead flags, resolved targets, and pane captures. Hot-path commands such as team status and team wait create a snapshot once per poll cycle and pass it through all status-checking functions so that repeated tmux subprocess calls are avoided.

capture_pane_safe(...) is the preferred way to read pane content. It gracefully handles dead panes and missing windows, returning an empty string instead of raising.

Two implementation details matter for runtime behavior:

  1. Window renaming is disabled so status tracking can rely on stable names.
  2. window-size is forced to smallest so attached clients resize all windows consistently.

Logging model

The current code does not use pipe-pane.

Instead, provider commands are built with native logging flags and shell redirection in agents._build_command_with_logging(...) inside src/agentic_team/agents.py:

  • interactive commands append stderr to <session-log-dir>/<worker>.log
  • oneshot commands redirect stdout and stderr to the log file
  • providers can also contribute environment variables such as Codex's RUST_LOG

User-facing log inspection happens in team logs inside src/agentic_team/cli.py, which prefers the log file and falls back to tmux capture-pane.

Interactive prompt delivery

Interactive workers are not given their task immediately. The flow is:

  1. spawn_worker(...) starts the provider CLI in a new tmux window.
  2. If the worker is interactive, the task is written to state/<team>/pending_prompts/<worker>.
  3. deliver_pending_prompts(...) checks capture-pane for provider-specific readiness strings:
  4. Claude Code
  5. OpenAI Codex
  6. Gemini CLI
  7. Type your message
  8. Once ready, tmux sends the task text and removes the pending file.

This is why team status can change a newly launched worker from "waiting to start" into active work: status refresh calls deliver_pending_prompts(...).

Completion detection

Oneshot workers

status._is_oneshot_done(...) uses pane capture, not just process exit:

  1. capture the last 80 lines
  2. find the last provider command invocation in scrollback
  3. inspect only the lines after that command
  4. mark the worker done when either:
  5. Claude emits "type":"result" after the last command, or
  6. a shell prompt appears after the command output

Interactive workers

status._is_interactive_idle(...) is provider-specific:

  • Claude: idle when the tail no longer shows esc to inter and the pane has enough non-empty content
  • Codex: running while Working ( is visible; done when Worked for or an idle prompt is visible
  • Gemini: done when the tail shows Type your message and the pane has enough prior output

Provider abstraction

src/agentic_team/models.py defines a ProviderConfig with:

  • CLI binary name
  • model aliases
  • interactive and oneshot argument lists
  • optional resume flag
  • optional system-prompt flags
  • provider logging flags and logging environment variables

Provider health checking

ProviderHealth is a frozen dataclass that records whether a provider CLI is installed, authenticated, and viable (both installed and authenticated). get_provider_health(name) resolves the CLI binary via shutil.which, then runs a provider-specific auth probe.

get_viable_providers() returns only providers that pass both checks. team init uses this list for auto-detection when --provider is omitted: if exactly one provider is viable, it is selected automatically; if zero or more than one are viable, the CLI raises a clear error.

team doctor surfaces the same health information interactively so users can diagnose missing installs or expired auth before creating a team.

Current provider-specific behavior is summarized in Providers.

CLI routing

TeamGroup extends click.Group so a bare prompt such as:

team "review the workers and summarize progress"

is rewritten to:

team send "review the workers and summarize progress"

The router checks for close subcommand typos first, so team stats raises a usage error instead of silently sending "stats" to the lead.

User-facing operations

Architecture explains how the pieces fit together. For actual runtime inspection and recovery commands, use Operations.