Watchfire
Concepts

Architecture

Understand Watchfire's architecture — a daemon orchestrates coding agents while thin clients connect over gRPC.

Architecture

Watchfire follows a daemon + thin client architecture. A single daemon process (watchfired) manages all orchestration, while lightweight clients (CLI/TUI and GUI) connect over gRPC to display state and accept user input.

Components

ComponentBinaryRoleTech
DaemonwatchfiredOrchestration, PTY management, terminal emulation, git workflows, gRPC server, system tray, desktop notificationsGo
CLI/TUIwatchfireCLI commands + TUI mode. Project-scoped thin clientGo + Bubbletea
GUIWatchfire.appMulti-project thin client (shows all projects)Electron

Data Flow

The daemon is the single source of truth. Clients are stateless views that subscribe to updates.

Pluggable Agent Backends

Every agent-specific detail — which binary to launch, how to assemble the command line, how to deliver the system prompt, where its transcripts live, and what the sandbox must allow for it — is encapsulated in a Backend implementation. Backends register themselves with a process-wide registry at startup, and the daemon looks them up by name when spawning a session.

AspectBehavior
InterfaceBackend in internal/daemon/agent/backend/ — one struct per agent
RegistryProcess-wide Register/Get/List keyed by name (claude-code, codex, opencode, gemini, copilot)
Shipped backendsClaude Code, OpenAI Codex, opencode, Gemini CLI, GitHub Copilot CLI
Command constructionBackends own BuildCommand — binary path, args, env, whether the initial prompt is embedded or pasted after launch
System prompt deliveryWatchfire composes one canonical prompt; each backend's InstallSystemPrompt delivers it (CLI flag for Claude Code, AGENTS.md for Codex/opencode/Copilot, system.md for Gemini)
Transcript discoveryBackends own LocateTranscript and FormatTranscript — the daemon copies and renders whatever the backend returns
Sandbox extrasSandboxExtras() contributes writable paths, cache patterns, and env vars to strip; the sandbox layer merges these with the base policy

Agent Resolution Chain

When the daemon spawns a session, it resolves the backend through a four-step chain:

task.agent → project.default_agent → settings.defaults.default_agent → claude-code

Empty string at any level defers to the next. Chat, wildfire-refine, and wildfire-generate sessions aren't scoped to a single task, so they skip the task step and start from the project default.

Per-Session Homes

Four of the five backends get an isolated per-session home so the Watchfire system prompt never contaminates the user's real configuration, while auth and global settings continue to flow from the shared location:

BackendIsolationLocationWhat's isolatedWhat's reused
Claude CodeNone needed--append-system-prompt flag delivers the prompt; user's ~/.claude/ is used directly
CodexCODEX_HOME~/.watchfire/codex-home/<session>/AGENTS.md, session transcriptsAuth + config from real ~/.codex/
opencodeOPENCODE_CONFIG_DIR + OPENCODE_DATA_DIR~/.watchfire/opencode-home/<session>/AGENTS.md, permission config, per-message JSONAuth/providers/agents symlinked from ~/.config/opencode/
GeminiGEMINI_SYSTEM_MD~/.watchfire/gemini-home/<session>/system.mdWatchfire system promptAuth, settings, hierarchical GEMINI.md from ~/.gemini/
GitHub Copilot CLICOPILOT_HOME + COPILOT_CUSTOM_INSTRUCTIONS_DIRS~/.watchfire/copilot-home/<session>/AGENTS.md, per-session stateGitHub login, MCP config, and session history symlinked from ~/.copilot/

Adding a Backend

A new backend is one file in internal/daemon/agent/backend/<name>.go — implement the Backend interface, register in init(), and contribute sandbox extras. Chat, task, start-all, and wildfire modes work against the registry generically, so no wiring changes are needed in the manager, sandbox, or UX surfaces.

PTY and Terminal Emulation

Watchfire uses a real PTY (pseudo-terminal) to run coding agents, with terminal emulation to parse escape codes:

The screen buffer is a 2D grid of cells, each with character, foreground/background color, and style attributes (bold, italic, underline, inverse). The cursor position is also tracked.

Network

AspectDecision
ProtocolgRPC + gRPC-Web (multiplexed on same port)
PortDynamic allocation (OS assigns free port)
DiscoveryConnection info written to ~/.watchfire/daemon.yaml (only after the gRPC port is accepting connections)
Health checkPing RPC — lightweight empty-to-empty call for connection verification
ClientsCLI/TUI use native gRPC, GUI uses gRPC-Web

File Watching

The daemon uses fsnotify to watch for changes to task files:

AspectBehavior
Mechanismfsnotify with debouncing
Global watched~/.watchfire/projects.yaml
Per-project watched.watchfire/project.yaml, .watchfire/tasks/*.yaml
RobustnessHandles create-then-rename pattern (common with AI tools)
Re-watch on chainWhen agents chain (wildfire/start-all), re-watches to pick up new directories
Polling fallbackTask-mode agents poll task YAML every 5s as safety net
ReactionFile changes trigger real-time updates to connected clients

Crash Recovery

ScenarioBehavior
Daemon crashes mid-taskOn restart, user must manually restart task
Agent crashesDaemon detects PTY exit, stops task

JSONL Transcript Logging

Session logs capture the agent's structured JSONL transcript in addition to raw PTY scrollback. This provides a clean, formatted conversation history for reviewing what an agent did during a session, across all supported backends — not just Claude Code.

Transcript discovery: On agent exit, the daemon calls the active backend's LocateTranscript to find the session's JSONL file:

BackendSource location
Claude Code~/.claude/projects/<encoded-cwd>/<sessionId>.jsonl matched by customTitle
Codex<CODEX_HOME>/sessions/**/rollout-*.jsonl (per-session home)
opencodePer-message JSON under <OPENCODE_DATA_DIR>/storage/message/ collated into a synthesized transcript.jsonl
Gemini~/.gemini/tmp/<project_hash>/chats/session-*.jsonl (or legacy logs.json)

The backend copies or synthesizes a JSONL file into the Watchfire logs directory.

Log viewing: The ReadLog RPC prefers the .jsonl transcript and dispatches to the backend's FormatTranscript to render it as a readable User/Assistant conversation with tool-call summaries. If no JSONL transcript is available, it falls back to the .log PTY scrollback.

Both file types are stored side-by-side in ~/.watchfire/logs/<project_id>/ — see the directory structure below for details.

Restart Protection

Wildfire and start-all modes automatically stop chaining after repeated failures on the same task to prevent infinite loops caused by rate limits, crashes, or auth expiry.

AspectBehavior
TriggerSame task restarted 3+ times consecutively without reaching status: done
ActionStop wildfire/start-all chaining, start chat-mode agent instead
CounterPer-project in-memory map (reset on task progression)
ResetCounter resets when a different task is chained (successful progression) or agent is stopped by user
LoggingWarning logged with task number and restart count when limit reached

Directory Structures

Global (~/.watchfire/)

~/.watchfire/
├── daemon.yaml         # Connection info (host, port, PID)
├── agents.yaml         # Running agents state
├── projects.yaml       # Projects index
├── settings.yaml       # Global settings
├── installation_id     # Stable UUID for analytics
└── logs/               # Session logs
    └── <project_id>/
        ├── <task_number>-<session>-<timestamp>.log      # PTY scrollback (fallback)
        └── <task_number>-<session>-<timestamp>.jsonl     # Agent JSONL transcript (preferred)

Log filename examples:

  • 0001-1-2026-02-03T13-05-00.log — task 1, session 1 (PTY scrollback)
  • 0001-1-2026-02-03T13-05-00.jsonl — task 1, session 1 (agent JSONL transcript)
  • chat-1-2026-02-03T15-00-00.log — chat mode (no task)

Per-backend session homes live alongside logs/:

~/.watchfire/
├── codex-home/<session>/          # Per-session CODEX_HOME (Codex)
├── opencode-home/<session>/       # Per-session config + data dirs (opencode)
└── gemini-home/<session>/         # Per-session system.md (Gemini)

Per-Project (<project>/.watchfire/)

<project>/
├── .watchfire/
│   ├── project.yaml    # Project configuration
│   ├── tasks/          # Task YAML files
│   │   ├── 0001.yaml
│   │   ├── 0002.yaml
│   │   └── ...
│   ├── memory.md       # Persistent project knowledge across agent sessions
│   ├── secrets/        # Secrets and setup instructions
│   │   └── instructions.md
│   └── worktrees/      # Git worktrees (one per active task)
│       └── <task_number>/
└── <project files>

On this page