Watchfire
Components

Daemon (watchfired)

The Watchfire daemon is the backend brain — managing projects, spawning agents, handling git workflows, and serving clients over gRPC.

Daemon (watchfired)

The daemon is the backend brain of Watchfire. It manages multiple projects simultaneously, watches for file changes, spawns coding agents in sandboxed PTYs with terminal emulation, handles git worktree workflows, and serves state to thin clients over gRPC.

Lifecycle

AspectBehavior
DevelopmentRun watchfired --foreground for hot reload (tray still active)
ProductionRuns in background, started automatically by CLI/TUI/GUI if not running
PersistenceStays running when thin clients close
ShutdownCtrl+C (foreground), CLI command, or system tray quit

The daemon starts automatically when you run any watchfire command. It persists in the background even after the CLI/TUI exits, so agent sessions keep running.

Network

AspectDecision
ProtocolgRPC + gRPC-Web (multiplexed on same port)
PortDynamic allocation (OS assigns free port)
DiscoveryConnection info written to ~/.watchfire/daemon.yaml
ClientsCLI/TUI use native gRPC, GUI uses gRPC-Web

Multi-Project Management

AspectBehavior
Projects index~/.watchfire/projects.yaml lists all registered projects
RegistrationProjects added via CLI (watchfire init) or GUI
ConcurrencyOne active task per project, multiple projects in parallel
Client trackingTracks which clients are watching which projects
Task cancellationTask stops only when ALL clients for that project disconnect

File Watching

The daemon uses fsnotify to watch for changes:

AspectBehavior
Mechanismfsnotify with debouncing
RobustnessHandles create-then-rename pattern (common with AI tools)
Per-project.watchfire/project.yaml, .watchfire/tasks/*.yaml
Polling fallback5s polling as safety net for missed watcher events
Re-watch on chainRe-watches directories created during earlier phases

Agent Backends

The daemon dispatches all agent-specific behavior through a pluggable Backend interface. Each supported agent — Claude Code, OpenAI Codex, opencode, Gemini CLI, and GitHub Copilot CLI — is a Backend implementation registered with a process-wide registry at startup. The daemon looks backends up by name and asks them to:

  • Resolve the agent binary (user-configured path → PATH → common install locations)
  • Build the PTY command line (binary, args, env, initial prompt handling)
  • Install the composed Watchfire system prompt into whatever form the agent expects (CLI flag, AGENTS.md, system.md, …)
  • Locate and format the JSONL transcript the session produced
  • Contribute writable paths, cache patterns, and env vars to strip to the sandbox policy

Agent Resolution

When spawning a session, the daemon walks a four-step chain to pick the backend:

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

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

Per-Session Homes

Codex, opencode, Gemini, and Copilot each run with an isolated per-session home so the Watchfire system prompt and per-session data don't leak into the user's personal config, while auth still flows from the real config directory:

BackendEnv var(s)Per-session path
CodexCODEX_HOME~/.watchfire/codex-home/<session>/
opencodeOPENCODE_CONFIG_DIR, OPENCODE_DATA_DIR~/.watchfire/opencode-home/<session>/
GeminiGEMINI_SYSTEM_MD~/.watchfire/gemini-home/<session>/system.md
CopilotCOPILOT_HOME, COPILOT_CUSTOM_INSTRUCTIONS_DIRS~/.watchfire/copilot-home/<session>/

Claude Code has no isolation — the daemon delivers the system prompt via the --append-system-prompt CLI flag and uses ~/.claude/ directly.

SettingsService.ListAgents

The daemon exposes a SettingsService.ListAgents RPC that returns the registered backends (name + display name). The GUI (and TUI settings tab) call this to populate agent-picker dropdowns, so any new backend registered in the daemon automatically appears in the UI.

Task Lifecycle

The daemon manages the reactive task lifecycle:

1. Client calls StartTask(task_id)
2. Daemon resolves the backend (task → project → global → claude-code)
3. Daemon creates git worktree for task
4. Daemon calls backend.InstallSystemPrompt (e.g. writes AGENTS.md into a per-session home)
5. Daemon spawns coding agent in sandboxed PTY (inside worktree)
6. Daemon streams screen buffer to subscribed clients
7. Agent updates task file when done (status: done)
8. Daemon detects via fsnotify OR polling fallback (5s)
9. Daemon kills agent (if still running)
10. Daemon calls backend.LocateTranscript and copies JSONL to logs
11. Daemon processes git rules (merge, delete worktree)
12. Daemon starts next task (if queued and merge succeeded)

Session Logging

The daemon captures two types of logs for each agent session, stored in ~/.watchfire/logs/<project_id>/:

FormatFilenameSource
PTY scrollback<task_number>-<session>-<timestamp>.logRaw terminal output captured during the session
JSONL transcript<task_number>-<session>-<timestamp>.jsonlStructured conversation transcript from the active backend (preferred)

Transcript Auto-Discovery

Transcript discovery is owned by each backend — the daemon calls backend.LocateTranscript on agent exit and copies (or synthesizes) the resulting JSONL into the logs directory:

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

Log Viewer

The ReadLog RPC serves session logs to clients (TUI and GUI). It prefers the .jsonl transcript when available and dispatches to the active backend's FormatTranscript to render a readable User/Assistant conversation with tool-call summaries. If no JSONL transcript exists, it falls back to the .log file (raw PTY scrollback). The same flow runs for every backend — no special-casing for Claude Code.

Phase Completion Signals

The daemon detects phase completion via signal files:

PhaseSignal FileDaemon Response
TaskTask YAML status: doneStop agent, merge worktree, start next
Refine.watchfire/refine_done.yamlStop agent, start next phase
Generate.watchfire/generate_done.yamlCheck for new tasks or end wildfire
Generate Definition.watchfire/definition_done.yamlStop agent (single-shot)
Generate Tasks.watchfire/tasks_done.yamlStop agent (single-shot)

System Tray

The daemon runs a system tray icon with a menu on macOS and Linux:

Menu ItemContent
Status header"Watchfire Daemon"
VersionDisplays the current version below the header for easy identification
Port"Running on port: port"
Running agentsList with project name
Open GUILaunches Electron GUI
QuitShuts down daemon

Desktop Notifications

The daemon sends desktop notifications when agents complete tasks or encounter errors, via the internal/daemon/notify package.

PlatformBackend
macOSNative UNUserNotificationCenter via CGo (displays Watchfire icon)
Linuxgithub.com/gen2brain/beeep
OtherNo-op (logs to stderr)

Notifications are triggered by the tray package calling notify.Send(title, message). The Watchfire icon is embedded in the notify package.

On macOS, notifications are safely handled when running outside the .app bundle (e.g., during development or when launched directly). The daemon checks for app bundle availability before sending notifications to avoid crashes. This was fixed in v0.4.0 — previously, a notification outside the .app bundle could cause a daemon crash (exit code 2) due to an unhandled NSInternalInconsistencyException.

Health Check

The daemon exposes a Ping RPC — a lightweight health check that clients can use to verify the daemon is alive and accepting connections. Unlike GetStatus, which returns detailed daemon information, Ping is a simple empty-to-empty call designed for fast connection verification.

Startup Reliability

The daemon guarantees that ~/.watchfire/daemon.yaml (the connection discovery file) is only written after the gRPC server is fully ready and accepting connections. This eliminates race conditions where clients would read the file and attempt to connect before the port was open.

CLI and GUI both verify port readiness before proceeding after launching the daemon, so commands like watchfire agent start will not fail with "connection refused" errors even when starting the daemon for the first time.

Daemon Commands

# Start the daemon (no-op if already running)
watchfire daemon start

# Show daemon status
watchfire daemon status

# Stop the daemon
watchfire daemon stop

On this page