# Watchfire — Full Documentation > Generated from content/docs/. See /llms.txt for the index. Source: https://watchfire.io Repository: https://github.com/watchfire-io/watchfire Generated: 2026-05-04 --- ## Watchfire Source: https://watchfire.io/docs Watchfire orchestrates coding agent sessions based on task files. It supports **Claude Code**, **OpenAI Codex**, **opencode**, **Gemini CLI**, and **GitHub Copilot CLI**. The daemon runs **one agent per project** at a time, but coordinates **many projects concurrently**, spawning each session in a sandboxed PTY with git worktree isolation. Thin clients (CLI/TUI and GUI) connect over gRPC. ## Components | Component | Binary | Description | |-----------|--------|-------------| | **Daemon** | `watchfired` | Orchestration, PTY management, git workflows, gRPC server, system tray | | **CLI/TUI** | `watchfire` | Project-scoped CLI commands + interactive TUI mode | | **GUI** | `Watchfire.app` | Electron multi-project client | ## How It Works 1. **Define** your project with `watchfire init` and a project definition 2. **Create tasks** describing what you want built 3. **Start agents** that work in isolated git worktrees (one branch per task) 4. **Monitor** progress through the TUI or GUI with live terminal output 5. **Review and merge** completed work The daemon watches task files for changes. When an agent marks a task as done, Watchfire automatically stops the agent, merges the worktree (if auto-merge is enabled), and chains to the next task. ## Quick Example ```bash cd your-project watchfire init # Initialize project & define what you're building watchfire wildfire # Agents generate tasks, code, and merge — autonomously ``` ## Next Steps - [Installation](/docs/installation) — Install Watchfire from source or Homebrew - [Supported Agents](/docs/concepts/supported-agents) — Choose a backend and set up auth - [Quick Start](/docs/quickstart) — Get up and running in minutes - [Architecture](/docs/concepts/architecture) — Understand how the pieces fit together - [Tips & Best Practices](/docs/tips) — Field guide to writing tasks, sizing work, and picking modes - [Keyboard Shortcuts](/docs/keyboard-shortcuts) — Printable cheat sheet of every TUI and GUI binding - [Troubleshooting](/docs/troubleshooting) — Common errors and copy-paste fixes - [Roadmap](/docs/roadmap) — Where Watchfire is heading after Flare --- ## Installation Source: https://watchfire.io/docs/installation ## Prerequisites - **macOS, Linux, or Windows** — Watchfire runs on all three platforms with platform-specific sandboxing - **One supported agent backend** — Install and authenticate at least one of: Claude Code, OpenAI Codex, opencode, Gemini CLI, or GitHub Copilot CLI Watchfire does not bundle the coding agent itself. Install your preferred backend separately, sign in through that backend's normal CLI flow, then select it in Watchfire. See [Supported Agents](/docs/concepts/supported-agents) for backend-specific setup, auth, and executable discovery details. ## Download Download the latest installer for your platform. The desktop app includes the GUI, CLI, and daemon. [Download Watchfire](https://github.com/watchfire-io/watchfire/releases/latest) ## Install via Homebrew Works on both macOS and Linux. **Desktop app** (GUI + CLI, macOS only): ```bash brew tap watchfire-io/tap brew install --cask watchfire-io/tap/watchfire ``` **CLI & daemon only**: ```bash brew tap watchfire-io/tap brew install watchfire-io/tap/watchfire ``` ## macOS The `.dmg` installer is a Universal binary that runs natively on both Apple Silicon and Intel Macs. It includes the GUI, CLI, and daemon. Alternatively, download individual CLI or daemon binaries from [GitHub Releases](https://github.com/watchfire-io/watchfire/releases/latest). ## Linux The **AppImage** and **.deb** packages include the GUI, CLI, and daemon for x64 Linux. Alternatively, install the CLI and daemon via the install script: ```bash curl -fsSL https://raw.githubusercontent.com/watchfire-io/watchfire/main/scripts/install.sh | bash ``` Or download individual binaries for your architecture from [GitHub Releases](https://github.com/watchfire-io/watchfire/releases/latest): ```bash # Download (choose your architecture) curl -Lo watchfire https://github.com/watchfire-io/watchfire/releases/latest/download/watchfire-linux-amd64 # or: watchfire-linux-arm64 # Make executable and move to PATH chmod +x watchfire sudo mv watchfire /usr/local/bin/ ``` **Sandboxing:** Watchfire automatically detects the best sandbox backend on Linux. It uses **Landlock** (kernel 5.13+) for zero-dependency kernel-based sandboxing, or falls back to **Bubblewrap** for namespace-based isolation. No manual configuration is needed. ## Windows The **.exe installer** includes the GUI, CLI, and daemon for x64 Windows. Alternatively, install the CLI and daemon via PowerShell: ```powershell irm https://raw.githubusercontent.com/watchfire-io/watchfire/main/scripts/install.ps1 | iex ``` > Windows currently runs **without sandboxing**. Agent processes are not isolated from the rest of the system. ## Build from Source Watchfire is written in Go and builds on all three platforms: ```bash git clone https://github.com/watchfire-io/watchfire.git cd watchfire make install-tools # Dev tools (golangci-lint, air, protoc plugins) make build # Build daemon + CLI make install # Install to /usr/local/bin ``` ## Verify Installation After installation, verify everything is working: ```bash # Check CLI version watchfire version # Start the daemon watchfire daemon start # Check daemon status watchfire daemon status ``` If you plan to run tasks immediately, also verify that your chosen agent backend is installed and authenticated outside Watchfire first. If anything in this flow fails — daemon won't start, GUI shows "no daemon", agent backend reports an auth error — see [Troubleshooting](/docs/troubleshooting). --- ## Supported Agents Source: https://watchfire.io/docs/concepts/supported-agents Watchfire can orchestrate five coding agent backends: - **Claude Code** (`claude-code`) - **OpenAI Codex** (`codex`) - **opencode** (`opencode`) - **Gemini CLI** (`gemini`) - **GitHub Copilot CLI** (`copilot`) You can choose a default backend per project, change the global default in Watchfire settings, and override the backend per task when needed. ## What You Need Before starting agents in Watchfire, make sure you have: - Watchfire installed - At least one supported agent CLI installed locally - That agent authenticated in its own CLI outside Watchfire first Watchfire reuses the agent's existing local login and config. It does not replace each backend's normal authentication flow. ## Backend Overview | Backend | Binary | Typical auth/config location | Watchfire session nuance | |---------|--------|------------------------------|--------------------------| | **Claude Code** | `claude` | `~/.claude/`, `~/.claude.json` | Uses your normal Claude install directly | | **OpenAI Codex** | `codex` | `~/.codex/` | Each session gets its own `CODEX_HOME` | | **opencode** | `opencode` | `~/.config/opencode/` | Each session gets its own config + data dirs | | **Gemini CLI** | `gemini` | `~/.gemini/` | Each session gets its own `GEMINI_SYSTEM_MD` | | **GitHub Copilot CLI** | `copilot` | `~/.copilot/` | Each session gets its own `COPILOT_HOME` | ## How Watchfire Finds Agent Binaries For every backend, Watchfire resolves the executable in this order: 1. A custom path configured in Watchfire settings 2. Your shell `PATH` 3. A few common install locations for that backend If Watchfire cannot find the binary, set an explicit path in the Watchfire global settings UI or in `~/.watchfire/settings.yaml`. ## Claude Code **What to install** - Install Claude Code so the `claude` binary is available **What to authenticate** - Sign in with Claude Code normally before using it through Watchfire **How Watchfire uses it** - Watchfire looks for `claude` in settings, then on `PATH`, then in common local install locations - Claude Code uses your normal local config and auth files directly - Watchfire appends its project/task system prompt at launch time, so there is no separate per-session home directory to manage **When to configure a custom path** - If `claude` is installed somewhere unusual - If the GUI or daemon cannot see the same `PATH` as your shell ## OpenAI Codex **What to install** - Install the OpenAI Codex CLI so the `codex` binary is available **What to authenticate** - Complete Codex login once in the CLI before running it through Watchfire **How Watchfire uses it** - Watchfire looks for `codex` in settings, then on `PATH`, then in common local install locations - Each Watchfire session gets an isolated `CODEX_HOME` under `~/.watchfire/codex-home//` - Watchfire writes its generated `AGENTS.md` into that session home - Your existing `~/.codex/auth.json` and `~/.codex/config.toml` are reused, so you keep your normal login and config **What this means in practice** - Session-specific prompt files and logs stay isolated from your personal Codex history - Updating auth or config in your normal Codex setup still carries over to future Watchfire sessions ## opencode **What to install** - Install `opencode` from the upstream project so the `opencode` binary is available **What to authenticate** - Sign in to opencode normally before using it with Watchfire - Make sure the configuration you want Watchfire to reuse exists under `~/.config/opencode/` **How Watchfire uses it** - Watchfire looks for `opencode` in settings, then on `PATH`, then in common local install locations - Each Watchfire session gets its own `OPENCODE_CONFIG_DIR` and `OPENCODE_DATA_DIR` under `~/.watchfire/opencode-home//` - Watchfire reuses your existing auth and config by linking entries from `~/.config/opencode/` into the per-session config dir - Watchfire writes its own per-session `AGENTS.md` and permission config there **What this means in practice** - Your normal opencode login and provider setup are reused - Session data, prompts, and transcripts stay isolated from your personal opencode state ## Gemini CLI **What to install** - Install Gemini CLI so the `gemini` binary is available **What to authenticate** - Authenticate Gemini CLI normally before using it with Watchfire - Keep your Gemini CLI config working outside Watchfire first, since Watchfire reuses the shared global setup **How Watchfire uses it** - Watchfire looks for `gemini` in settings, then on `PATH`, then in common local install locations - Each Watchfire session writes a dedicated prompt file under `~/.watchfire/gemini-home//system.md` - Watchfire points `GEMINI_SYSTEM_MD` at that file for the session - Gemini auth and other shared CLI settings continue to live in `~/.gemini/` **What this means in practice** - The Watchfire system prompt is isolated per session - Your Gemini login, settings, and other global context still come from the shared Gemini CLI setup ## GitHub Copilot CLI **What to install** - Install [GitHub Copilot CLI](https://github.com/github/copilot-cli) so the `copilot` binary is available **What to authenticate** - Sign in with `gh` or Copilot CLI normally before running it through Watchfire, so `~/.copilot/config.json` and `~/.copilot/session-store.db` exist and are valid **How Watchfire uses it** - Watchfire looks for `copilot` in settings, then on `PATH`, then in common local install locations - Each Watchfire session gets its own `COPILOT_HOME` under `~/.watchfire/copilot-home//` - Watchfire writes its generated `AGENTS.md` into that session home and points Copilot at it via `COPILOT_CUSTOM_INSTRUCTIONS_DIRS` - Your real `~/.copilot/{config.json,mcp-config.json,session-store.db}` are symlinked in so existing GitHub login, MCP config, and session history are reused - Sessions run in yolo mode (`--allow-all`); the Watchfire sandbox is the boundary, not Copilot's prompt gate **What this means in practice** - The Watchfire system prompt stays isolated per session - Your existing Copilot login, MCP configuration, and session history continue to work unchanged ## Choosing a Backend in Watchfire You can pick a backend in several places: - During project setup - In project settings later - In global Watchfire settings for the default used by new projects - Per task, when you want one task to run on a different backend than the project default If nothing is configured, Watchfire falls back to `claude-code`. ## Setup Checklist For any backend, the shortest reliable setup flow is: 1. Install the backend CLI and confirm its binary works in your terminal 2. Authenticate in that CLI directly 3. Open Watchfire and select that backend for the project or globally 4. If Watchfire cannot find it, set the binary path explicitly in Watchfire settings ## Related Docs - [Installation](/docs/installation) - [Quick Start](/docs/quickstart) - [Projects and Tasks](/docs/concepts/projects-and-tasks) --- ## Quick Start Source: https://watchfire.io/docs/quickstart Two commands to go from zero to autonomous coding agents. ## 1. Initialize a Project Navigate to your project directory and initialize Watchfire: ```bash cd your-project watchfire init ``` This will: 1. Check for git (initialize a repo if missing) 2. Create the `.watchfire/` directory structure 3. Create an initial `project.yaml` with a generated UUID 4. Add `.watchfire/` to `.gitignore` 5. Prompt you for a project definition and settings ### Project Settings During initialization, you'll configure: - **Auto-merge** — Automatically merge completed work into your current branch - **Auto-delete branch** — Clean up task branches after merge - **Auto-start tasks** — Start the agent when a task is set to ready ## 2. Launch Wildfire Let Watchfire take it from here: ```bash watchfire wildfire ``` Wildfire mode is the fastest way to go from an empty project to working code. It autonomously: 1. **Generates a project definition** from your codebase 2. **Creates tasks** based on the definition 3. **Executes each task** in isolated git worktrees 4. **Merges completed work** back into your current branch 5. **Loops** — refining drafts and generating new tasks as needed Sit back and watch your project take shape. ## Going Further ### Add tasks manually You can also create tasks yourself: ```bash watchfire task add ``` You'll be prompted to enter: - **Title** — A short description of the task - **Prompt** — Detailed instructions for the AI agent - **Acceptance criteria** — How to verify the task is done Tasks are stored as YAML files in `.watchfire/tasks/`: ```yaml task_id: a1b2c3d4 task_number: 1 title: "Setup project structure" prompt: | Create the initial project structure with... acceptance_criteria: | - Directory structure matches spec - All config files present status: draft ``` ### Configure secrets Store API keys, environment setup, and service credentials in `.watchfire/secrets/instructions.md`. Watchfire injects these into the agent's system prompt automatically — so agents can access external services without you hardcoding secrets into task prompts. See [Secrets & Setup Instructions](/docs/concepts/secrets) for details. ### Launch the TUI Start the interactive TUI to manage tasks and monitor agents: ```bash watchfire ``` The TUI shows a split view with your task list on the left and the agent terminal on the right: ``` ┌──────────────────────────────────┬──────────────────────────────────┐ │ Task List │ Agent Terminal │ │ │ │ │ Draft (2) │ > Starting session... │ │ #0001 Setup project structure │ │ │ #0002 Implement login flow │ Claude Code v2.1 │ │ │ ~/source/my-project │ │ Ready (0) │ │ │ Done (0) │ > Working on task... │ ├──────────────────────────────────┴──────────────────────────────────┤ │ Ctrl+q quit Ctrl+h help Tab switch panel a add s start │ └─────────────────────────────────────────────────────────────────────┘ ``` ### Run tasks from the CLI ```bash # Run a single task watchfire run 1 # Run all ready tasks sequentially watchfire run all # Interactive chat mode watchfire run ``` ## Next Steps Once you've shipped your first task, see [Tips & Best Practices](/docs/tips) for how to keep going — writing tasks agents actually finish, sizing work, choosing between Wildfire and Start All, and keeping merges clean. - [Tips & Best Practices](/docs/tips) — Field guide for getting useful work out of Watchfire - [Recipes](/docs/recipes) — End-to-end walkthroughs for the workflows you'll hit first - [Secrets & Setup Instructions](/docs/concepts/secrets) — Give agents access to external services - [Agent Modes](/docs/concepts/agent-modes) — Learn about Chat, Task, Wildfire, and more - [Projects and Tasks](/docs/concepts/projects-and-tasks) — Understand the YAML schemas - [CLI Reference](/docs/components/cli) — Full command reference --- ## Architecture Source: https://watchfire.io/docs/concepts/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 | Component | Binary | Role | Tech | |-----------|--------|------|------| | **Daemon** | `watchfired` | Orchestration, PTY management, terminal emulation, git workflows, gRPC server, system tray, desktop notifications | Go | | **CLI/TUI** | `watchfire` | CLI commands + TUI mode. Project-scoped thin client | Go + Bubbletea | | **GUI** | `Watchfire.app` | Multi-project thin client (shows all projects) | Electron | ## Data Flow ```mermaid graph LR U[User] --> C[CLI/TUI] U --> G[GUI] C -->|gRPC| D[Daemon] G -->|gRPC-Web| D D -->|PTY| A[Coding Agent] D --> W[Git Worktrees] D --> F[File Watching] D --> S[Sandbox] ``` 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](/docs/glossary#sandbox) must allow for it — is encapsulated in a [`Backend`](/docs/glossary#backend) implementation. Backends register themselves with a process-wide registry at startup, and the daemon looks them up by name when spawning a session. | Aspect | Behavior | |--------|----------| | **Interface** | `Backend` in `internal/daemon/agent/backend/` — one struct per agent | | **Registry** | Process-wide `Register`/`Get`/`List` keyed by name (`claude-code`, `codex`, `opencode`, `gemini`, `copilot`) | | **Shipped backends** | Claude Code, OpenAI Codex, opencode, Gemini CLI, GitHub Copilot CLI | | **Command construction** | Backends own `BuildCommand` — binary path, args, env, whether the initial prompt is embedded or pasted after launch | | **System prompt delivery** | Watchfire 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 discovery** | Backends own `LocateTranscript` and `FormatTranscript` — the daemon copies and renders whatever the backend returns | | **Sandbox extras** | `SandboxExtras()` 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: | Backend | Isolation | Location | What's isolated | What's reused | |---------|-----------|----------|------------------|---------------| | **Claude Code** | None needed | — | — | `--append-system-prompt` flag delivers the prompt; user's `~/.claude/` is used directly | | **Codex** | `CODEX_HOME` | `~/.watchfire/codex-home//` | `AGENTS.md`, session transcripts | Auth + config from real `~/.codex/` | | **opencode** | `OPENCODE_CONFIG_DIR` + `OPENCODE_DATA_DIR` | `~/.watchfire/opencode-home//` | `AGENTS.md`, permission config, per-message JSON | Auth/providers/agents symlinked from `~/.config/opencode/` | | **Gemini** | `GEMINI_SYSTEM_MD` | `~/.watchfire/gemini-home//system.md` | Watchfire system prompt | Auth, settings, hierarchical `GEMINI.md` from `~/.gemini/` | | **GitHub Copilot CLI** | `COPILOT_HOME` + `COPILOT_CUSTOM_INSTRUCTIONS_DIRS` | `~/.watchfire/copilot-home//` | `AGENTS.md`, per-session state | GitHub login, MCP config, and session history symlinked from `~/.copilot/` | ### Adding a Backend A new backend is one file in `internal/daemon/agent/backend/.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: ```mermaid graph TD SB["Sandbox (platform-specific)"] --> AG["Coding Agent (Claude Code / Codex / opencode / Gemini / Copilot)"] AG --> PTY["PTY (creack/pty)"] PTY --> VT["vt10x Terminal Parser"] VT --> BUF["Screen Buffer (rows × cols)"] BUF -->|gRPC stream| CL[Clients] ``` 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 | Aspect | Decision | |--------|----------| | **Protocol** | gRPC + gRPC-Web (multiplexed on same port) | | **Port** | Dynamic allocation (OS assigns free port) | | **Discovery** | Connection info written to `~/.watchfire/daemon.yaml` (only after the gRPC port is accepting connections) | | **Health check** | `Ping` RPC — lightweight empty-to-empty call for connection verification | | **Clients** | CLI/TUI use native gRPC, GUI uses gRPC-Web | ## File Watching The daemon uses `fsnotify` to watch for changes to task files: | Aspect | Behavior | |--------|----------| | **Mechanism** | fsnotify with debouncing | | **Global watched** | `~/.watchfire/projects.yaml` | | **Per-project watched** | `.watchfire/project.yaml`, `.watchfire/tasks/*.yaml` | | **Robustness** | Handles create-then-rename pattern (common with AI tools) | | **Re-watch on chain** | When agents chain (wildfire/start-all), re-watches to pick up new directories | | **Polling fallback** | Task-mode agents poll task YAML every 5s as safety net | | **Reaction** | File changes trigger real-time updates to connected clients | ## Crash Recovery | Scenario | Behavior | |----------|----------| | **Daemon crashes mid-task** | On restart, user must manually restart task | | **Agent crashes** | Daemon 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: | Backend | Source location | |---------|-----------------| | **Claude Code** | `~/.claude/projects//.jsonl` matched by `customTitle` | | **Codex** | `/sessions/**/rollout-*.jsonl` (per-session home) | | **opencode** | Per-message JSON under `/storage/message/` collated into a synthesized `transcript.jsonl` | | **Gemini** | `~/.gemini/tmp//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//` — 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. | Aspect | Behavior | |--------|----------| | **Trigger** | Same task restarted 3+ times consecutively without reaching `status: done` | | **Action** | Stop wildfire/start-all chaining, start chat-mode agent instead | | **Counter** | Per-project in-memory map (reset on task progression) | | **Reset** | Counter resets when a different task is chained (successful progression) or agent is stopped by user | | **Logging** | Warning 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 └── / ├── --.log # PTY scrollback (fallback) └── --.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// # Per-session CODEX_HOME (Codex) ├── opencode-home// # Per-session config + data dirs (opencode) └── gemini-home// # Per-session system.md (Gemini) ``` ### Per-Project (`/.watchfire/`) ``` / ├── .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) │ └── / └── ``` --- ## Projects and Tasks Source: https://watchfire.io/docs/concepts/projects-and-tasks Watchfire uses YAML files to define projects and tasks. This file-based approach makes it easy to version control your task definitions and inspect state at any time. ## Project Configuration Each project has a `project.yaml` file in `.watchfire/`: ```yaml project_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890 name: my-project default_agent: codex sandbox: auto auto_merge: true auto_delete_branch: true auto_start_tasks: true definition: | This project is a web application that... next_task_number: 5 ``` ### Project Settings | Setting | Default | Description | |---------|---------|-------------| | `default_agent` | (optional) | Project-level default agent backend for new sessions | | `sandbox` | `auto` | Sandbox mechanism (auto-detects platform backend) | | `auto_merge` | `true` | Merge worktree on task completion | | `auto_delete_branch` | `true` | Delete branch after successful merge | | `auto_start_tasks` | `true` | Start agent when task set to ready | | `definition` | (empty) | Project context for the AI agent | ### Agent Selection `default_agent` is a first-class project setting. Set it when you want a project to prefer one backend by default, such as `codex`, `claude-code`, `opencode`, `gemini`, or `copilot`. The field is optional. When Watchfire needs to decide which backend to run for a task or session, it resolves the effective agent in this order: 1. `task.agent` 2. `project.default_agent` 3. Global default agent from Watchfire settings 4. `claude-code` This means you only need to set `default_agent` when the project should override the global default. If both the task and project omit an agent, Watchfire still falls back automatically. ### Project Definition The `definition` field provides context for AI agents working on your project. It should describe: - What the project does - Tech stack and conventions - Key architectural decisions - File structure overview Agents read this definition to understand the codebase before working on tasks. ## Directory Structure Each project's `.watchfire/` directory contains: ``` .watchfire/ ├── project.yaml # Project configuration ├── tasks/ # Task YAML files ├── memory.md # Persistent project knowledge across agent sessions ├── secrets/ # Secrets and setup instructions │ └── instructions.md └── worktrees/ # Git worktrees (one per active task) └── / ``` ## Agent Memory Watchfire provides a `memory.md` file in `.watchfire/` that agents can read and update to persist project-specific knowledge across sessions. Agents use this to store conventions, user preferences, recurring patterns, and workflow notes so that future sessions don't start from scratch. The file is automatically available to agents working in any task for the project. ## Task Schema Tasks are stored as individual YAML files in `.watchfire/tasks/`: ```yaml task_id: a1b2c3d4 task_number: 1 title: "Setup project structure" agent: codex prompt: | Create the initial project structure with a Next.js app, Tailwind CSS, and TypeScript configuration. acceptance_criteria: | - Next.js app boots successfully - Tailwind CSS is configured - TypeScript strict mode enabled status: draft success: null failure_reason: "" merge_failure_reason: "" position: 1 agent_sessions: 0 created_at: "2026-01-15T10:30:00Z" updated_at: "2026-01-15T10:30:00Z" ``` ### Task Fields | Field | Type | Description | |-------|------|-------------| | `task_id` | string | 8-character alphanumeric identifier | | `task_number` | int | Sequential number, used for file naming | | `title` | string | Short description of the task | | `agent` | string (optional) | Override backend for this task only | | `prompt` | string | Detailed instructions for the agent | | `acceptance_criteria` | string | How to verify the task is complete | | `status` | enum | `draft`, `ready`, or `done` | | `success` | bool/null | `null` (pending), `true`, or `false` | | `failure_reason` | string | Agent-reported failure (set by the agent when `success: false`) | | `merge_failure_reason` | string | Auto-merge failure populated by the daemon when the silent merge can't land — e.g. dirty default branch, merge conflict, post-merge hook failure (Flare). The task's own work still landed on its `watchfire/` branch; only the merge step failed | | `position` | int | Order within the task list | ### Task Agent Override Use `agent` on a task when one task should run on a different backend than the rest of the project. ```yaml title: "Refactor docs search" agent: gemini status: ready ``` If `agent` is omitted, the task inherits from the same fallback chain described above: 1. `project.default_agent` 2. Global default agent 3. `claude-code` ## Task Lifecycle Tasks flow through three statuses: ### Draft A task in `draft` status is being written or refined. It is not ready for an agent to work on. In Wildfire mode, the **Refine** phase picks up draft tasks and improves their prompts and acceptance criteria. ### Ready A task in `ready` status is queued for execution. If `auto_start_tasks` is enabled, setting a task to ready will automatically start an agent. When multiple tasks are ready, the agent picks the next one by: 1. Sort by `position` (ascending) 2. If equal, sort by `task_number` (ascending) 3. Pick first ### Done A task in `done` status has been completed (or failed). Check the `success` field: - `success: true` — Task completed successfully. If the auto-merge later failed (dirty default branch, merge conflict, post-merge hook), `merge_failure_reason` is populated and a `TASK_FAILED` notification fires; the run-all chain stops so the user can resolve the merge by hand. The agent's work itself is preserved on `watchfire/`. - `success: false` — Task failed or was blocked. Check `failure_reason` for details. ## Reviewing work Both clients expose an inline diff view of a task's changes. It works pre-merge (against the `watchfire/` branch) and post-merge (resolving the merge commit). Output is `cap at 10000 lines`. Open the [Inspect tab](/docs/components/gui#inspect) in the GUI, or press `d` in the [CLI/TUI](/docs/components/cli) for the same view. ## Multi-Project Management The daemon manages multiple projects simultaneously: | Aspect | Behavior | |--------|----------| | **Projects index** | `~/.watchfire/projects.yaml` lists all registered projects | | **Registration** | Projects added via `watchfire init` or the GUI | | **Concurrency** | One active task per project, multiple projects in parallel | --- ## Agent Modes Source: https://watchfire.io/docs/concepts/agent-modes Watchfire supports six modes for running AI coding agents, ranging from interactive chat to fully autonomous operation. ## Chat Interactive session with the coding agent. No task context — the agent starts in the project root directory and you communicate directly through the terminal. ```bash watchfire run ``` Use Chat mode for: - Exploring the codebase - Quick one-off changes - Debugging and investigation - Asking questions about the code ## Task Execute a specific task from the task list. The agent receives the task's prompt and acceptance criteria, works in an isolated git worktree, and updates the task file when done. ```bash watchfire run ``` The agent: 1. Starts in the worktree (`.watchfire/worktrees//`) 2. Reads the task prompt and acceptance criteria 3. Works autonomously to complete the task 4. Sets `status: done` and `success: true/false` when finished 5. Watchfire detects completion and handles merge/cleanup ## Start All Run all ready tasks sequentially. Each task gets its own agent session and worktree. When one task completes, the next one starts automatically. ```bash watchfire run all ``` The chain stops if: - All ready tasks are complete - A merge conflict occurs (prevents cascading failures) - You cancel with Ctrl+C ### Restart Protection If the same task is restarted 3 or more times consecutively without completing (e.g., due to rate limits, crashes, or auth errors), the daemon stops chaining and transitions to chat mode instead. This prevents infinite loops where a failing task blocks progress. The restart counter resets when a different task is chained (successful progression) or you stop the agent manually. ## Wildfire The flagship mode. A fully autonomous three-phase loop that can plan, execute, and iterate without human intervention. ```bash watchfire wildfire ``` ### Phase 1: Execute Works on all `ready` tasks, one at a time. Same as Start All mode — each task gets its own worktree and agent session. ### Phase 2: Refine When no ready tasks remain, picks up `draft` tasks. The agent: - Analyzes the codebase and project definition - Improves the draft task's prompt and acceptance criteria - Sets the task to `ready` when refined ### Phase 3: Generate When no draft tasks remain, the agent: - Analyzes the project and completed work - Creates new tasks if meaningful work remains - If new tasks are created, the loop restarts from Phase 1 ### Wildfire State Machine ```mermaid graph LR E["Phase 1: Execute\n(ready tasks)"] --> R["Phase 2: Refine\n(draft tasks)"] R --> G["Phase 3: Generate\n(no tasks left)"] G -->|new tasks created| E G -->|no new tasks| C[Chat Mode] ``` When the Generate phase creates no new tasks, Wildfire transitions to chat mode. You can also stop it at any time with Ctrl+C. ### Restart Protection If the same task is restarted 3 or more times consecutively without completing (e.g., due to rate limits, crashes, or auth errors), the daemon stops the Wildfire loop and transitions to chat mode instead. This prevents infinite loops where a failing task blocks the entire autonomous cycle. The restart counter resets when a different task is chained (successful progression) or you stop the agent manually. ## Generate Definition Auto-generate a project definition by analyzing the codebase. The agent reads your project's files and produces a comprehensive description for the `definition` field in `project.yaml`. ```bash watchfire generate ``` This is a single-shot command — the agent runs once and exits. ## Generate Tasks Auto-generate tasks from the project definition. The agent reads your project definition and creates a set of tasks to build or improve the project. ```bash watchfire plan ``` This is also single-shot. Review the generated tasks and move them to `ready` when you're satisfied. ## Mode Comparison | Mode | Autonomous | Worktree | Use Case | |------|-----------|----------|----------| | **Chat** | No | No | Interactive exploration | | **Task** | Yes | Yes | Single task execution | | **Start All** | Yes | Yes | Sequential batch execution | | **Wildfire** | Fully | Yes | End-to-end autonomous development | | **Generate Definition** | Yes | No | Bootstrap project context | | **Generate Tasks** | Yes | No | Bootstrap task list | --- ## Git Worktree Isolation Source: https://watchfire.io/docs/concepts/worktrees Watchfire uses git [worktrees](/docs/glossary#worktree) to isolate each task's work from the main working tree. Within a single project the agent runs one task at a time, but the worktree means even that single task is fully insulated from your branch, your uncommitted edits, and any other project running concurrently on the same daemon. ## How It Works When a task starts, the daemon: 1. Creates a new branch: `watchfire/` (e.g., `watchfire/0001`) 2. Creates a worktree at `.watchfire/worktrees//` 3. The agent runs inside this worktree, not the main working tree 4. On completion, the worktree is merged back to the target branch ``` your-project/ ├── .watchfire/ │ └── worktrees/ │ └── 0001/ ← Active task's agent works here │ ├── src/ │ └── ... ├── src/ ← Your main working tree (untouched) └── ... ``` A project has at most one active worktree at a time (the one belonging to the currently running task). Worktrees are created when a task starts and removed after a successful merge — so the `worktrees/` directory is usually empty between tasks, or holds one entry while a task is in flight. ## Branch Management | Aspect | Behavior | |--------|----------| | **Branch naming** | `watchfire/` (e.g., `watchfire/0001`) | | **Creation** | Created from current HEAD of the checked-out branch | | **Stale branches** | If a branch already exists, it's deleted and recreated from current HEAD | | **Cleanup** | Branch deleted after successful merge (if `auto_delete_branch` is enabled) | ## Merge Behavior When a task completes successfully: 1. The daemon merges the worktree branch into the currently checked-out branch 2. If the merge succeeds, the worktree and branch are cleaned up 3. If a merge conflict occurs: - `git merge --abort` restores a clean working directory - The chain stops (in Start All / Wildfire mode) to prevent cascading failures - You need to resolve the conflict manually To review the changes a task produced before (or after) the merge, open the [Inspect tab](/docs/components/gui#inspect) in the GUI or press `d` in the [TUI](/docs/components/cli). ## Why Worktrees? ### Isolation Each task gets a complete, independent copy of the codebase. The agent can modify any file without affecting: - Your main working tree - Other running tasks - Uncommitted changes you're working on ### Cross-project Safety The daemon runs one agent per project — within a project the queue is strictly serial — but multiple projects can run agents at the same time. Each agent works inside its own worktree, so there are no file conflicts even when the same machine is driving four repos at once. ### Clean Merges Since each task branches from the same point, merges are straightforward. Watchfire handles the merge automatically when `auto_merge` is enabled. ### Easy Rollback If a task produces bad results, you can simply discard the branch. Your main branch is never modified until a successful merge. ## Pruning The daemon periodically detects and cleans orphaned worktrees — worktrees that exist on disk but are no longer associated with an active task. --- ## Sandboxing Source: https://watchfire.io/docs/concepts/sandboxing Watchfire runs coding agents inside platform-specific sandboxes that restrict what the agent process can access. This limits the blast radius of any unintended actions — the agent has full autonomy **inside** the sandbox, but the sandbox controls what "full autonomy" actually means. ## Auto-Detection The default sandbox setting is `"auto"`. When set, Watchfire automatically selects the best available backend for the current platform: | Platform | Auto-detected backend | |----------|----------------------| | **macOS** | Seatbelt (`sandbox-exec`) | | **Linux** | Landlock (kernel 5.13+) → Bubblewrap fallback → unsandboxed | | **Windows** | Unsandboxed (no sandbox support) | On Linux, Watchfire tries Landlock first. If the kernel doesn't support it (pre-5.13), it falls back to Bubblewrap. If neither is available, the agent runs unsandboxed. ### Priority Chain Sandbox backend resolution follows a strict priority order: 1. **CLI flag** — `--sandbox ` or `--no-sandbox` (highest priority) 2. **Project setting** — `sandbox` field in `project.yaml` 3. **Global default** — `defaults.default_sandbox` in `settings.yaml` If none are set, the default is `"auto"`. ## macOS — Seatbelt On macOS, Watchfire uses `sandbox-exec` (Apple's Seatbelt framework) to enforce kernel-level restrictions. ```bash sandbox-exec -f claude --dangerously-skip-permissions --append-system-prompt "..." [--prompt "..."] ``` The sandbox profile is generated at runtime from an internal `SandboxPolicy` struct. It is not user-visible or editable. ### Why Seatbelt? - **Kernel-level enforcement** — the agent process cannot bypass the sandbox - **Child process inheritance** — even code the agent writes and executes runs within the same sandbox - **Regex pattern support** — allows fine-grained rules like blocking `.env` files and `.git/hooks` by pattern ### Allowed Operations | Operation | Details | |-----------|---------| | **Read** | Most of the filesystem (project files, system libraries, tools) | | **Write** | Project directory, temp dirs, package manager caches (`~/.npm`, `~/.yarn`, `~/.pnpm-store`, `~/.cache`, `~/Library/Caches/*`), dev tool caches (`~/.cargo`, `~/go`, `~/.rustup`), Claude config (`~/.claude`), CLI tool config (`~/Library/Application Support`) | | **Execute** | Installed tools (git, npm, make, etc.) | | **Network** | Full network access | ### Blocked Paths Read **and** write access is denied to: - `~/.ssh` — SSH keys and configuration - `~/.aws` — AWS credentials - `~/.gnupg` — GPG keys - `~/.netrc` — Network credentials - `~/.npmrc` — npm credentials - `~/Desktop`, `~/Documents`, `~/Downloads` — Personal directories - `~/Music`, `~/Movies`, `~/Pictures` — Media directories - `.env` files — Environment secrets - `.git/hooks` — Git hooks (prevents hook injection) > **Projects inside protected directories work correctly.** The sandbox profile is generated dynamically — if your project is located inside a protected folder (e.g., `~/Documents/my-project`), the deny rule for that folder is automatically skipped so the agent can access the project files. This was fixed in v1.0.0 ([#17](https://github.com/watchfire-io/watchfire/issues/17)). ## Linux — Landlock Landlock is the preferred Linux backend. It uses the kernel's Landlock LSM (available since kernel 5.13) to enforce filesystem restrictions with **zero external dependencies**. ### How It Works The daemon re-invokes itself as a helper process to apply Landlock restrictions before exec-ing the agent: ```bash watchfired --sandbox-exec # → applies Landlock restrictions → exec() claude ... ``` This approach ensures Landlock rules are applied before the agent process starts, with no way to escape them. ### Requirements - Linux kernel **5.13 or later** - No additional packages or tools required - Uses [go-landlock](https://github.com/landlock-lsm/go-landlock) — a pure Go binding ### Allowed and Blocked Paths Same policy as macOS (derived from the shared `SandboxPolicy`), except: - No regex-based pattern blocking (Landlock operates on file paths, not patterns) - Personal directories (`~/Desktop`, `~/Documents`, etc.) are not blocked (these are macOS-specific paths) ## Linux — Bubblewrap (Fallback) If Landlock is not available (kernel older than 5.13), Watchfire falls back to [Bubblewrap](https://github.com/containers/bubblewrap) (`bwrap`), a namespace-based sandbox. ### How It Works Bubblewrap creates an isolated mount namespace with controlled visibility: ```bash bwrap --ro-bind / / --bind --tmpfs ~/.ssh ... -- claude ... ``` - The root filesystem is mounted **read-only** - The project directory is mounted **read-write** - Sensitive directories (like `~/.ssh`) are replaced with empty tmpfs mounts, effectively hiding them ### Requirements - `bwrap` must be installed on the system - Available in most Linux distribution package managers (e.g., `apt install bubblewrap`, `dnf install bubblewrap`) - Requires user namespace support (enabled by default on most modern distributions) ### Limitations If Bubblewrap is not installed, the agent runs **unsandboxed** on Linux. A warning is logged when this happens. ## Windows Windows does not currently have sandbox support. Agents run **unsandboxed** with full access to the filesystem and network. ### Implications - The agent process has the same permissions as the user running the daemon - All filesystem paths are accessible (no credential or personal directory protection) - The `--dangerously-skip-permissions` flag is still used, giving the agent full autonomy without OS-level restrictions ### Recommendations When running Watchfire on Windows: - Review agent output carefully, especially for tasks that modify files outside the project directory - Consider running the daemon under a restricted user account - Use the `--no-sandbox` flag explicitly to acknowledge the unsandboxed state ## Configuration ### CLI Flags The `--sandbox` and `--no-sandbox` flags are available on `run`, `chat`, `plan`, `generate`, and `wildfire` commands: ```bash # Use a specific backend watchfire agent start --sandbox landlock # Disable sandboxing entirely watchfire agent start --no-sandbox ``` Valid backend values: `auto`, `seatbelt`, `landlock`, `bwrap`, `none`. ### Per-Project (`project.yaml`) ```yaml sandbox: "auto" # "auto" | "seatbelt" | "landlock" | "bwrap" | "none" ``` ### Global Default (`settings.yaml`) ```yaml defaults: default_sandbox: "auto" ``` Located at `~/.watchfire/settings.yaml`. ## Security Model | Aspect | macOS (Seatbelt) | Linux (Landlock) | Linux (Bubblewrap) | Windows | |--------|-------------------|-------------------|---------------------|---------| | **Enforcement** | Kernel (App Sandbox) | Kernel (LSM) | Namespace isolation | None | | **Profile source** | Runtime `SandboxPolicy` | Runtime `SandboxPolicy` | Runtime `SandboxPolicy` | N/A | | **Agent permissions** | Full within sandbox | Full within sandbox | Full within sandbox | Full (unrestricted) | | **Claude Code flag** | `--dangerously-skip-permissions` | `--dangerously-skip-permissions` | `--dangerously-skip-permissions` | `--dangerously-skip-permissions` | | **Credential dirs blocked** | Yes | Yes | Yes (hidden via tmpfs) | No | | **Personal dirs blocked** | Yes | No (Linux paths differ) | No (Linux paths differ) | No | | **`.env` / `.git/hooks`** | Blocked (regex) | Not blocked (no regex) | Not blocked | Not blocked | | **Network** | Full access | Full access | Full access | Full access | | **Child process isolation** | Inherited | Inherited | Inherited | None | | **External dependencies** | None (built-in) | None (kernel) | `bwrap` package | N/A | ## Environment Variable Isolation Across all platforms, the daemon strips internal environment variables (such as `CLAUDECODE`) from agent subprocess environments. This prevents issues like Claude Code detecting the variable and entering a nested mode. Agents always start with a clean environment, free of daemon-internal state. ## Agent Permissions Flow ```mermaid graph TD A[User starts task] --> B[Daemon resolves sandbox backend] B --> C{Platform?} C -->|macOS| D["sandbox-exec -f profile claude ..."] C -->|Linux + Landlock| E["watchfired --sandbox-exec config.json → exec claude ..."] C -->|Linux + Bubblewrap| F["bwrap --ro-bind / / ... -- claude ..."] C -->|Windows / None| G["claude ... (unsandboxed)"] D --> H["Agent runs with --dangerously-skip-permissions"] E --> H F --> H G --> H H --> I["Full autonomy within sandbox boundaries"] ``` The `--dangerously-skip-permissions` flag tells Claude Code to skip its own permission prompts. On macOS and Linux, this is safe because the OS-level sandbox enforces stricter boundaries. On Windows, the agent runs without OS-level restrictions. ## See also - [Security](/docs/security) — how the sandbox fits into the broader threat model, alongside signature verification, secret storage, and network exposure. --- ## Secrets & Setup Instructions Source: https://watchfire.io/docs/concepts/secrets Watchfire lets you provide agents with instructions for accessing external services — API keys, CLI tools, environment variables, and authentication details. These are injected into the agent's system prompt automatically. ## How It Works Secrets are stored in a plain Markdown file at: ``` .watchfire/secrets/instructions.md ``` When an agent session starts, Watchfire reads this file and injects its contents into the agent's system prompt under a **"Secrets & Setup Instructions"** section. The agent can then use this information to authenticate with services, set up environment variables, or use pre-configured CLI tools. ## Setup The secrets file is created automatically when you run `watchfire init`. Edit it with your project-specific instructions: ```markdown ## CLI Tools - Firebase CLI is authenticated. Use `firebase deploy` directly. - AWS CLI is configured with the staging profile: `aws --profile staging`. ## Environment Variables - `DATABASE_URL` is set in `.env.local` — do not commit this file. - `STRIPE_TEST_KEY` is `sk_test_abc123` — use for all payment integration. ## API Keys - OpenAI API key: `sk-proj-...` (org: my-org) - Use the test Stripe key above for checkout features. ## Notes - Always use the staging environment for testing. - Never deploy to production without explicit approval. ``` ## Security - The `.watchfire/` directory is gitignored by default — secrets never enter version control. - The sandbox blocks access to `~/.ssh`, `~/.aws`, `~/.gnupg`, and `.env` files. Use the secrets file to tell agents what they need instead of relying on system-level credentials. - Secrets are only injected into the agent's system prompt at session start. They are not written to disk inside the worktree. ## GUI Support The Watchfire GUI includes a Secrets tab where you can edit `instructions.md` directly. Changes are saved automatically. ## See also - [Security](/docs/security) — how secret storage fits into the broader threat model, alongside the sandbox, signature verification, and network exposure. --- ## Integrations Source: https://watchfire.io/docs/concepts/integrations Beacon (v4.0.0) introduced the two-way integrations layer; Flare (v5.0.0) closed the inbound loop and added OAuth, multi-host parity, per-IP rate limiting, Slack interactive components, and Discord guild auto-registration. The daemon can **deliver** notifications to external services through pluggable adapters, and **receive** signed requests from those same services through a small HTTP server. Both halves share the same secret store and the same per-provider configuration surface. The page is split into two halves that mirror the data flow: - **Outbound** — the daemon dispatches `notify.Bus` events to webhook, Slack, Discord, and GitHub auto-PR adapters. - **Inbound** — the daemon binds an HTTP server, verifies request signatures, and routes slash commands through a transport-agnostic command router. ## Outbound The outbound half lives in `internal/daemon/relay`. A single `Dispatcher` subscribes to `notify.Bus` and fans events out to every registered `Adapter`. Each adapter owns its own template rendering, retry timing, and circuit breaker. ### Architecture The `Adapter` interface is the only contract every outbound integration implements. The `Dispatcher` owns: - **Subscription** — one `notify.Bus` consumer per running daemon - **Per-adapter retry** — exponential backoff `[500ms, 2s, 8s]` - **Circuit breaker** — 3 failures inside a 5-minute rolling window opens the breaker; subsequent events skip the adapter until the window expires - **Concurrency** — adapters dispatch in parallel; one slow provider never blocks the others ### Secrets and OAuth bot tokens Adapter credentials are stored in the OS keyring through `internal/config/keyring.go`, with a file-store fallback for hosts without a keyring backend. Secrets are write-only on the gRPC wire — the GUI and TUI can save and replace, but never read existing values back. For Slack and Discord, Flare adds an OAuth install flow as the preferred path. Slack stores the workspace `xoxb-...` bot token returned from the OAuth callback and uses it for `chat.postMessage`, so slash responses can include rich attachments and DM the originator on private failures. Discord stores the bot token and authenticates inbound requests with `Authorization: Bot `. The Integrations settings UI exposes "Connect Slack" and "Connect Discord" buttons that launch the flow in the user's default browser; on success a `Connected as ` pill appears next to the integration. The legacy signing-secret + public-key path stays additive for users mid-cutover. ### Webhook adapter `WebhookAdapter` POSTs the canonical `notify.Notification` payload (plus delivery metadata — event id, attempt, timestamp) to a user-supplied URL. Each request carries an `X-Watchfire-Signature: sha256=` HMAC over the raw body so receivers can reject forged calls. ### Slack adapter `internal/daemon/relay/slack.go` renders three Block Kit envelopes through `text/template`: | Envelope | Trigger | |----------|---------| | `TASK_FAILED` | A task transitions to `done` with `success: false` | | `RUN_COMPLETE` | A run-all / wildfire batch finishes | | `WEEKLY_DIGEST` | The weekly digest fires | Each envelope is a fixed sequence of header / section / context / actions blocks. Project color is mapped to a `:large__square:` shortcode in `slack_color.go` so the project dot survives Slack's theming. The `TASK_FAILED` template carries three action buttons — `Retry`, `Cancel`, and `View in Watchfire` — that round-trip through the inbound `POST /echo/slack/interactivity` endpoint (see [Slack interactivity](#slack-interactivity)). `Retry` reruns the named task, `Cancel` opens a modal that captures a reason into `task.failure_reason`, and `View in Watchfire` deep-links into the GUI. ### Discord adapter `internal/daemon/relay/discord.go` mirrors the same three envelopes as rich embeds, tinted by the project color. Two shared `text/template` helpers — `hexToInt` (for the embed `color` int) and `rfc3339` (for the timestamp footer) — are registered once and reused. A defensive 4000-rune description trim with a single-WARN log on overflow protects against pathological payloads (Discord rejects embeds longer than 4096). ### GitHub auto-PR GitHub auto-PR is opt-in per project, gated on a single key in `project.yaml`: ```yaml github: auto_pr: enabled: true ``` Prerequisites: `gh` on `PATH` and `gh auth status` returning `0`. With both satisfied, the end-of-task lifecycle in `internal/daemon/git/pr.go::OpenPR` runs: 1. `gh auth status` — gate, so silent fallback is logged once per project lifetime 2. Parse `/` from the project's git remote 3. `git push --force-with-lease` to publish the `watchfire/` branch 4. Render the PR body via `pr_body.md.tmpl` 5. `gh api -X POST /repos/:owner/:repo/pulls` to open the PR Sentinel errors distinguish silent fallback (one WARN per project lifetime when prerequisites are missing) from per-attempt failures (logged at WARN with retry on next task). ### Reliability primitives at a glance | Primitive | Where | Behaviour | |-----------|-------|-----------| | Retry | Per adapter | `[500ms, 2s, 8s]` backoff | | Circuit breaker | Per adapter | 3 failures / 5-minute window opens the breaker | | Secrets | `internal/config/keyring.go` | OS keyring, file-store fallback | ## Inbound The inbound half lives in `internal/daemon/echo`. It binds a small HTTP server, verifies request signatures with constant-time HMAC or Ed25519, dedupes replays through an in-process LRU cache, applies a per-IP rate limit, and dispatches slash commands through a transport-agnostic router. Concrete handlers exist for GitHub (`pull_request.closed` PR-merge), GitLab (`Merge Request Hook`), Bitbucket (`pullrequest:fulfilled`), Slack slash commands and interactivity, and Discord interactions. ### HTTP server framework `internal/daemon/echo/server.go` wraps an `http.Server` with the small set of guarantees every provider handler relies on: - **Bind address** — `ListenAddr` from the inbound config; default `127.0.0.1:8765` - **Graceful shutdown** — 5-second drain on stop so in-flight requests finish - **Body cap** — 1 MiB per request via a global middleware; oversized bodies return `413` - **Panic recovery** — panicking handlers return `500` instead of crashing the daemon - **Health endpoint** — `/echo/health` is unauthenticated and returns `200 OK` for liveness probes - **Bind failure** — logged at ERROR; the daemon keeps running so a misconfigured port never wedges the rest of the system Provider handlers register themselves through a single plug-in API: ```go RegisterProvider(method, path, handler) ``` Concrete handlers return `503` until their per-provider secret has been configured. An empty `InboundConfig` (no secrets, no `ListenAddr`) means no listener — the daemon never opens a port until at least one provider is wired. ### Per-IP rate limiting Flare adds a per-IP token bucket via `golang.org/x/time/rate` in front of every `/echo/*` route. The default budget is 30 requests per minute per IP, configurable through `models.InboundConfig.RateLimitPerMin` (`0` disables the limiter entirely). Idempotent re-deliveries that hit the LRU cache do **not** count against the bucket, so retried Slack / Discord deliveries during a network blip aren't penalised. When a request is rate-limited the server returns `429`, and the daemon logs a single WARN per IP per minute to avoid log floods under sustained traffic. ### Signature verification `internal/daemon/echo/verify.go` exposes three constant-time verifiers, one per upstream: | Function | Algorithm | Signed payload | |----------|-----------|----------------| | `VerifyGitHub` | HMAC-SHA256 against `sha256=` header | Raw request body | | `VerifySlack` | HMAC-SHA256 with 5-minute drift window | `v0::` | | `VerifyDiscord` | Ed25519 with 5-minute drift window | `timestamp \|\| body` | All three use constant-time comparisons so signature checks don't leak timing. Both Slack and Discord enforce a 5-minute timestamp drift to bound replay windows. ### Idempotency cache `internal/daemon/echo/idempotency.go` is a small LRU+TTL cache that drops duplicate deliveries: - **Capacity** — 1000 entries - **TTL** — 24 hours - **Storage** — `container/list` for LRU ordering, `sync.Mutex` for concurrent access - **API** — `Seen(key)` returns whether the key was already in the cache, and refreshes its TTL on every hit so chatty deliveries don't churn The cache is process-local — a daemon restart drops state, which is acceptable for a 24-hour replay window. ### Command router Slash commands are dispatched through a single transport-agnostic function: ```go Route(ctx, cmd, subcmd, rest, CommandContext) CommandResponse ``` Three commands are wired today: | Command | Effect | |---------|--------| | `status` | Returns the current per-project status block | | `retry ` | Re-runs the named task | | `cancel ` | Cancels the named task | The response shape is intentionally transport-neutral: ```go CommandResponse{ text string blocks []Block ephemeral bool in_channel bool } ``` Each transport handler renders the same `CommandResponse` into its native envelope (Discord components, Slack Block Kit, etc.). New commands plug into `commands.Route` once and surface everywhere a transport is wired. ### Discord interactions endpoint `internal/daemon/echo/handler_discord.go` exposes `POST /echo/discord/interactions` with the full inbound pipeline: 1. **Ed25519 verification** through `VerifyDiscord` 2. **Replay window** — reject requests outside the 5-minute drift window 3. **Idempotency** — drop duplicates already in the LRU+TTL cache 4. **Dispatch** — `PING` returns `PONG`; `APPLICATION_COMMAND` calls `commands.Route` 5. **Render** — `discord_render.go::RenderInteraction` turns the `CommandResponse` into a Discord interaction response Slash-command registration is automatic on Flare: the daemon enumerates every guild the bot is in at startup and POSTs the three slash-command schemas through `internal/cli/integrations_discord.go::registerForGuild`, then subscribes to the `GUILD_CREATE` Gateway event so a freshly-added guild gets commands within ~30 seconds. The Settings UI lists every guild with a ✓ / ✗ registration pill. Discord's commands API is upsert-style, so re-running is safe; the manual CLI fallback `watchfire integrations register-discord ` stays available. ### Slack slash-command HTTP transport `internal/daemon/echo/handler_slack_commands.go` translates the URL-encoded slash-command form body (`command`, `text`, `team_id`, `channel_id`, `user_id`, `trigger_id`) into a call against the shared `commands.Route(...)` router and renders `CommandResponse` as Slack response JSON (`{response_type: "in_channel" | "ephemeral", text, blocks}`). With Flare, `/watchfire status / retry / cancel` works in Slack at parity with Discord. Slack v0 HMAC verification, the 5-minute drift window, and the LRU+TTL idempotency cache all share the inbound pipeline. ### Slack interactivity `POST /echo/slack/interactivity` handles the `block_actions` and `view_submission` payloads emitted when users click the `Retry`, `Cancel`, or `View in Watchfire` buttons on the outbound `TASK_FAILED` envelope. Verification, drift window, and idempotency match the slash-commands endpoint. Button presses route through `commands.Route` so a `Retry` click is the exact equivalent of `/watchfire retry`. `Cancel` opens a Slack modal that asks "Why are you cancelling?"; the supplied reason lands in `task.failure_reason`. ### GitHub PR-merge handler `internal/daemon/echo/handler_github.go` registered at `POST /echo/github?project=` parses `X-GitHub-Event` / `X-Hub-Signature-256` / `X-GitHub-Delivery`, resolves the per-project HMAC secret from the keyring, runs `verify.VerifyGitHub`, deduplicates against the idempotency cache, narrows on `event == "pull_request" && action == "closed" && pull_request.merged == true`, then matches the Watchfire task by `pull_request.head.ref == watchfire/` and calls `task.MarkDoneIfNotAlready`. A Pulse `RUN_COMPLETE` notification fires titled ` — PR # merged`. With Flare, the [GitHub auto-PR](#github-auto-pr) loop closes itself. ### GitHub Enterprise / GitLab / Bitbucket parity Per-project `github_host` on `models.InboundConfig` lets the existing GitHub HMAC-SHA256 verifier target arbitrary GitHub Enterprise hostnames (the same field is used by the outbound auto-PR path). New `internal/daemon/echo/handler_gitlab.go` verifies `X-Gitlab-Token` and narrows on `Merge Request Hook` events with `action: merge`. New `internal/daemon/echo/handler_bitbucket.go` verifies `X-Hub-Signature` (HMAC-SHA256) and narrows on `pullrequest:fulfilled` events. The Settings UI surfaces a "Git host" picker on inbound config. ### Inbound configuration The inbound surface is driven by a single `InboundConfig` with global fields and one block per provider: | Field | Purpose | |-------|---------| | `ListenAddr` | Bind address; default `127.0.0.1:8765` | | `PublicURL` | Used to construct the per-provider URLs the GUI offers as Copy buttons | | `RateLimitPerMin` | Per-IP rate limit across `/echo/*`; default `30`, `0` disables | | `github_host` | Per-project GitHub host (default `github.com`); also used by outbound auto-PR | | Per-provider secrets / OAuth tokens | One write-only credential per upstream; empty disables that handler | An empty `InboundConfig` is the signal to skip the listener entirely — the daemon never binds a port until at least one provider is configured. ## Migration - **Empty `InboundConfig`** = no listener. The daemon does not bind a port until you configure at least one inbound provider. - **Per-provider 503** — concrete handlers return `503 Service Unavailable` until their secret is set. The `/echo/health` endpoint is always available regardless of provider state. - **OAuth is opt-in** — existing signing-secret + public-key configs continue to work after upgrading to Flare. Use the new "Connect Slack" / "Connect Discord" buttons to migrate. - **Rate limiting** — `RateLimitPerMin` defaults to 30. Set to `0` to disable, or raise it for high-volume inbound traffic. - **Multi-host inbound** — leave `github_host` empty for github.com; set per-project for GitHub Enterprise. GitLab and Bitbucket handlers are inactive until their per-project secret is configured. For the surfaces that drive integrations from the UI side, see the [Integrations panel in the GUI Settings doc](/docs/components/gui#integrations) and the [Integrations overlay in the CLI / TUI doc](/docs/components/cli#integrations-overlay-ctrli). For CLI commands, see [`watchfire integrations`](/docs/commands/integrations). ## See also - [Security](/docs/security) — how inbound signature verification, outbound signing, and secret storage fit into the broader threat model. --- ## Insights & Metrics Source: https://watchfire.io/docs/concepts/insights Beacon (v4.0.0) turns every completed agent session into a structured metrics record, then aggregates those records into per-project and cross-project Insights views. Reports can be exported as CSV or Markdown, and a weekly digest summarises the fleet automatically. ## Per-Task Metrics Capture Every completed task gets a sibling `.metrics.yaml` file next to its task YAML in `.watchfire/tasks/`. The file is written from a non-blocking goroutine inside `handleTaskChanged`, so capture never delays the task completion path. The record carries a fixed set of fields: | Field | Type | Description | |-------|------|-------------| | `task_number` | int | Task number this metric describes | | `project_id` | string | Project the task belongs to | | `agent` | string | Backend that ran the session (`claude-code`, `codex`, `opencode`, `gemini`, `copilot`) | | `duration_ms` | int | Wall-clock duration of the session | | `tokens_in` | int (nullable) | Prompt tokens consumed, nil when unavailable | | `tokens_out` | int (nullable) | Completion tokens produced, nil when unavailable | | `cost_usd` | float (nullable) | Estimated cost, nil when unavailable | | `exit_reason` | enum | One of `completed` / `failed` / `stopped` / `timeout` | | `captured_at` | timestamp | When the record was written | Token and cost fields are pointers in the underlying `TaskMetrics` struct, so a backend that doesn't expose those numbers leaves them nil rather than emitting a zero that would skew rollups. ## Metrics Package Parsing lives in `internal/daemon/metrics`. Each backend has its own parser file alongside the shared capture goroutine: | File | Backend | Coverage | |------|---------|----------| | `claude_code.go` | Claude Code | Duration + tokens + cost | | `codex.go` | Codex | Duration + tokens + cost | | `opencode.go` | opencode | Duration + tokens + cost | | `gemini.go` | Gemini CLI | Duration + tokens + cost | | `copilot.go` | GitHub Copilot CLI | **Stub** — duration only; tokens / cost stay nil because Copilot has no transcript schema yet | | `null.go` | Fallback | Duration only, used when no backend parser matches | | `capture.go` | — | Goroutine that runs on `handleTaskChanged` and writes the file | | `parser.go` | — | Shared helpers across parsers | Copilot is explicitly a stub. Until upstream exposes per-message token usage, Copilot rows in Insights show duration but contribute nothing to token or cost rollups. The `tasks_missing_cost` caveat surfaces this in the global rollup so partial-data projects don't silently flatten the chart. ## Per-Project Insights View `internal/daemon/insights/project.go` aggregates one project's `.metrics.yaml` files into a window-scoped summary. The GUI surfaces this as a dedicated **Insights** tab on the Project View; the TUI binds the same overlay to i. The view contains: - **KPI strip** — totals for tasks, duration, tokens, and cost over the selected window - **Stacked-bar tasks-per-day** — completed tasks per day, stacked by exit reason - **Agent donut** — share of tasks per backend - **Duration histogram** — distribution of task durations - **Time-window selector** — `7d` / `30d` / `90d` / `All`. The selection persists to `localStorage[wf-insights-window]` so it sticks across reloads - **``** — header action that opens the export dialog scoped to the current project The Project View Insights tab is documented in the [GUI doc](/docs/components/gui#insights). ## Cross-Project Insights Rollup `internal/daemon/insights/global.go` aggregates the same metrics across every registered project, scoped to the same `7d` / `30d` / `90d` / `All` windows. Results are cached at `~/.watchfire/insights-cache/_global.json` so the dashboard renders without re-walking every project on each load. Surfaces: - **GUI Dashboard rollup card** — sits alongside the Beacon status bar at the top of the Dashboard. See the [GUI Dashboard doc](/docs/components/gui#dashboard) for the visual layout - **TUI fleet overlay** — bound to Ctrl+f, mirrors the GUI rollup - **Top-projects pill list** — names the projects driving fleet activity in the selected window - **`tasks_missing_cost` caveat** — banner that appears whenever a non-trivial slice of tasks is missing cost (typically Copilot sessions), so fleet-level cost numbers are never read as totals when they're really "what we could measure" ## Report Export (CSV + Markdown) Reports flow through a single RPC: `InsightsService.ExportReport`. The request carries a `oneof` `scope` — `project_id`, `global`, or `single_task` — and a `format` (`CSV` or `MARKDOWN`). The response carries `filename`, `content` (UTF-8 bytes), and `mime` (`text/csv` or `text/markdown`). | Format | Conventions | |--------|-------------| | **Markdown** | Rendered from templates under `internal/daemon/insights/templates/` (`global.md.tmpl`, `project.md.tmpl`, `single_task.md.tmpl`) | | **CSV** | Single file with `# section: ` header lines delimiting sub-tables (KPIs, per-day, per-agent, per-task) so multi-table content fits one CSV without losing structure | A single `` component is reused on the Dashboard header and the Project View header. The TUI binds export to Ctrl+e with the same scope precedence as the GUI: the active Project View if one is selected, otherwise the dashboard (global). Single-task export is reachable from the task action menu and uses the `single_task` scope. ## Weekly Digest A weekly Markdown digest is rendered to `~/.watchfire/digests/.md` regardless of toast suppression — even when notifications are muted, the file is still produced. The schedule is driven by `digestRunner`, which arms a re-armable `time.Timer` from `models.DigestSchedule.NextFire`. The runner is DST-stable and includes a 24-hour catch-up window so a daemon restart never silently skips a digest. For the full notification story (toast routing, mute schedules, channel preferences) see the [daemon notifications section](/docs/components/daemon). --- ## Daemon (watchfired) Source: https://watchfire.io/docs/components/daemon 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 | Aspect | Behavior | |--------|----------| | **Development** | Run `watchfired --foreground` for hot reload (tray still active) | | **Production** | Runs in background, started automatically by CLI/TUI/GUI if not running | | **Persistence** | Stays running when thin clients close | | **Shutdown** | Ctrl+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 | Aspect | Decision | |--------|----------| | **Protocol** | gRPC + gRPC-Web (multiplexed on same port) | | **Port** | Dynamic allocation (OS assigns free port) | | **Discovery** | Connection info written to `~/.watchfire/daemon.yaml` | | **Clients** | CLI/TUI use native gRPC, GUI uses gRPC-Web | ## Multi-Project Management | Aspect | Behavior | |--------|----------| | **Projects index** | `~/.watchfire/projects.yaml` lists all registered projects | | **Registration** | Projects added via CLI (`watchfire init`) or GUI | | **Concurrency** | One active task per project, multiple projects in parallel | | **Client tracking** | Tracks which clients are watching which projects | | **Task cancellation** | Task stops only when ALL clients for that project disconnect | ## File Watching The daemon uses `fsnotify` to watch for changes: | Aspect | Behavior | |--------|----------| | **Mechanism** | fsnotify with debouncing | | **Robustness** | Handles create-then-rename pattern (common with AI tools) | | **Per-project** | `.watchfire/project.yaml`, `.watchfire/tasks/*.yaml` | | **Polling fallback** | 5s polling as safety net for missed watcher events | | **Re-watch on chain** | Re-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: | Backend | Env var(s) | Per-session path | |---------|------------|------------------| | **Codex** | `CODEX_HOME` | `~/.watchfire/codex-home//` | | **opencode** | `OPENCODE_CONFIG_DIR`, `OPENCODE_DATA_DIR` | `~/.watchfire/opencode-home//` | | **Gemini** | `GEMINI_SYSTEM_MD` | `~/.watchfire/gemini-home//system.md` | | **Copilot** | `COPILOT_HOME`, `COPILOT_CUSTOM_INSTRUCTIONS_DIRS` | `~/.watchfire/copilot-home//` | 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//`: | Format | Filename | Source | |--------|----------|--------| | **PTY scrollback** | `--.log` | Raw terminal output captured during the session | | **JSONL transcript** | `--.jsonl` | Structured 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: | Backend | Source | |---------|--------| | **Claude Code** | `~/.claude/projects//.jsonl`, matched by `customTitle` against the session name | | **Codex** | `/sessions/**/rollout-*.jsonl` in the per-session home | | **opencode** | Per-message JSON files under `/storage/message/` collated into a synthesized `transcript.jsonl` | | **Gemini** | `~/.gemini/tmp//chats/session-*.jsonl` (or legacy `logs.json`) | | **Copilot** | `/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: | Phase | Signal File | Daemon Response | |-------|-------------|-----------------| | **Task** | Task YAML `status: done` | Stop agent, merge worktree, start next | | **Refine** | `.watchfire/refine_done.yaml` | Stop agent, start next phase | | **Generate** | `.watchfire/generate_done.yaml` | Check for new tasks or end wildfire | | **Generate Definition** | `.watchfire/definition_done.yaml` | Stop agent (single-shot) | | **Generate Tasks** | `.watchfire/tasks_done.yaml` | Stop agent (single-shot) | ## System Tray The tray menu (`internal/daemon/tray/tray.go`) is rebuilt dynamically as project state changes — instead of a flat list of running agents it groups projects into intent-driven sections: | Section | Content | |---------|---------| | **Status header** | "Watchfire Daemon" + version + "Running on port: port" | | **Needs attention** | Projects with at least one failed task or a `TASK_FAILED` event in the recent JSONL window — clicking jumps straight to that project's task list | | **Working** | Projects with an active agent — clicking opens the live Chat panel | | **Idle** | Registered projects with nothing in flight — clicking opens the project view | | **Notifications (N today) ▸** | Submenu listing today's events from the JSONL fallback (`~/.watchfire/logs//notifications.log`), newest first. Each row routes back into the GUI for the originating task. | | **Open GUI** | Launches the Electron GUI | | **Quit** | Shuts down the daemon | Click-through routing flows over the new `DaemonService.SubscribeFocusEvents` stream: when a tray entry is clicked the daemon publishes a focus event (project + task + tab), and any connected GUI/TUI subscribed to the stream brings the right view forward. The same mechanism is used by clicking an OS toast. ## Desktop Notifications The Beacon notification subsystem lives in the `internal/daemon/notify` package and exposes a typed `notify.Bus` that fans events out to every subscribed channel (slow consumers are dropped, never blocked). Every event carries a stable `MakeID` derived as `sha256(kind|project_id|task_number|emitted_at_unix)[:8]`, so the same event can be deduplicated across the OS toast, the tray submenu, the focus router, and any future subscriber. In addition to live in-process delivery, every event is appended as a JSONL line to `~/.watchfire/logs//notifications.log`. This file is the headless fallback: clients that weren't connected when an event fired (or the dynamic tray submenu rebuilding from cold) read it back to reconstruct the recent timeline. | Platform | OS toast backend | |----------|------------------| | **macOS** | Native `UNUserNotificationCenter` via CGo (displays Watchfire icon) | | **Linux** | `github.com/gen2brain/beeep` | | **Other** | No-op (logs to stderr) | 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`. ### Event Kinds | Kind | When it fires | OS toast title / body | |------|---------------|------------------------| | **`TASK_FAILED`** | Emitted from `internal/daemon/server/task_failed.go::emitTaskFailed` whenever a task transitions to `done && !success`. | Title ` — task #NNNN failed`; body is the task title plus the optional `failure_reason`. | | **`RUN_COMPLETE`** | Fires on the falling edge of every autonomous run — single-task, `run all`, and wildfire — bounded by `RunningAgent.RunStartedAt` so a run is exactly the work between a start and the moment no agent is running for that project. | Title names the project + run kind; body is `N tasks done · M failed`. | | **`WEEKLY_DIGEST`** | Scheduled by `digestRunner` using a re-armable `time.Timer` driven by `models.DigestSchedule.NextFire`. The schedule is DST-stable and includes a 24-hour catch-up window so a digest missed during sleep/shutdown still fires once on next start. The Markdown report is rendered to `~/.watchfire/digests/.md` and a toast points at it. | Defaults **off** — opt in from settings. | ### Bundled Sounds Beacon ships two short cues under `assets/sounds/`: | File | Plays for | |------|-----------| | `task-done.wav` | `RUN_COMPLETE` | | `task-failed.wav` | `TASK_FAILED` | Both are mono 22050 Hz so the renderer can decode them with no extra dependencies. The `shouldPlaySound(kind, prefs)` helper decides whether a given event should play audio at all (master toggle, per-event toggle, sounds enabled, quiet hours, per-project mute). When the GUI renderer is foregrounded it plays the sound itself at the user-set volume — and in that case the OS toast is sent **silent** so users never hear the cue twice. ### Settings & Gating Notification preferences live under `defaults.notifications` in `~/.watchfire/settings.yaml` (the same file used by the rest of the daemon defaults). Every send path funnels through the `models.ShouldNotify` helper, which combines the master toggle, per-event toggle, quiet-hours window, and per-project mute list before any toast, sound, or JSONL append happens. The schema is the single source of truth — both the GUI Settings panel and the TUI globalsettings tab read and write the same fields. ## 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 ```bash # Start the daemon (no-op if already running) watchfire daemon start # Show daemon status watchfire daemon status # Stop the daemon watchfire daemon stop ``` --- ## CLI / TUI Source: https://watchfire.io/docs/components/cli The CLI/TUI is the primary interface for developers. A single binary (`watchfire`) operates in two modes: CLI commands for scripting and automation, and TUI mode for interactive work. It's project-scoped — run from within a project directory. > Looking for a single-page reference? The [Keyboard Shortcuts](/docs/keyboard-shortcuts) cheat sheet lists every TUI binding in printable form. ## CLI Commands ### Global | Command | Alias | Description | |---------|-------|-------------| | `watchfire` | | Start TUI | | `watchfire version` | `--version`, `-v` | Show version (all components) | | `watchfire help` | `-h` | Show help | | `watchfire update` | | Update all components | | `watchfire init` | | Initialize project in current directory | ### Project Lifecycle | Command | Alias | Description | |---------|-------|-------------| | `watchfire define` | `def` | Edit project definition in $EDITOR | | `watchfire configure` | `config` | Configure project settings (interactive) | ### Task Management | Command | Alias | Description | |---------|-------|-------------| | `watchfire task list` | `task ls` | List tasks (excludes deleted) | | `watchfire task list --deleted` | | List soft-deleted tasks | | `watchfire task add` | | Add new task (interactive) | | `watchfire task ` | | Edit task (interactive) | | `watchfire task delete ` | `task rm ` | Soft delete task | | `watchfire task restore ` | | Restore soft-deleted task | ### Execution | Command | Alias | Description | |---------|-------|-------------| | `watchfire run` | | Start chat mode | | `watchfire run ` | | Run specific task | | `watchfire run all` | | Run all ready tasks sequentially | | `watchfire plan` | | Generate tasks from project definition | | `watchfire generate` | `gen` | Generate project definition | | `watchfire wildfire` | `fire` | Autonomous three-phase loop | ### Daemon | Command | Alias | Description | |---------|-------|-------------| | `watchfire daemon start` | | Start the daemon | | `watchfire daemon status` | | Show daemon info | | `watchfire daemon stop` | | Stop the daemon | ## `watchfire init` Flow ``` 1. Check for git → if missing, initialize git repo 2. Create .watchfire/ directory structure 3. Create initial project.yaml 4. Append .watchfire/ to .gitignore 5. Commit .gitignore change 6. Prompt for the coding agent (Claude Code, Codex, opencode, Gemini, Copilot) — seeded into project.default_agent 7. Prompt for project definition (optional) 8. Prompt for project settings 9. Save project.yaml 10. Register project in ~/.watchfire/projects.yaml ``` The agent prompt only appears when the global default is set to "Ask per project" (or unset). If a global default is configured, init skips the prompt and uses it. See the [Supported Agents](/docs/concepts/supported-agents) doc for backend-specific setup. ## TUI Mode Launch by running `watchfire` with no arguments. The TUI provides a split-view interface: ``` ┌──────────────────────────────────────────────────────────────────────┐ │ ● project-name Tasks | Definition | Settings Chat | Logs ● Idle│ ├────────────────────────────────┬─────────────────────────────────────┤ │ Task List │ Agent Terminal / Logs │ │ │ │ │ Draft (2) │ > Starting session... │ │ #0001 Setup structure │ │ │ #0004 Add authentication │ > Working on task... │ │ [codex] │ │ │ │ │ │ Ready (1) │ │ │ ● #0002 Implement login [▶] │ │ │ │ │ │ Done (2) │ │ │ #0003 Create schema ✓ │ │ │ #0005 Add tests ✓ │ │ ├────────────────────────────────┴─────────────────────────────────────┤ │ Ctrl+q quit Ctrl+h help Tab switch panel a add s start │ └──────────────────────────────────────────────────────────────────────┘ ``` A compact **agent badge** (e.g. `[codex]`) appears next to a task title when its `agent` field is set and differs from the project default. Tasks that defer to the project default show no badge, keeping the list visually quiet for the common case. ### Navigation | Input | Action | |-------|--------| | `Tab` | Switch between left/right panels | | `j/k` or `↓/↑` | Move up/down in lists | | `h/l` or `←/→` | Switch tabs | | `1/2/3` | Switch left panel tabs | | `Enter` | Select/edit item | | `Esc` | Close overlay / cancel | | `Ctrl+q` | Quit | | `Ctrl+h` | Help overlay | | `Ctrl+f` | Fleet (cross-project) Insights overlay | | `Ctrl+e` | Export report (CSV or Markdown) | | `Ctrl+i` | Integrations overlay (Outbound / Inbound tabs) | `Ctrl+f` opens the fleet rollup — KPI strip, stacked-bar tasks-per-day, agent donut, duration histogram, and a `7d` / `30d` / `90d` / `All` window switcher — backed by the same data the GUI Dashboard rollup card renders. `Ctrl+e` triggers `InsightsService.ExportReport` with the same scope precedence as the GUI: the active project if one is selected, otherwise the dashboard (global). Single-task export is reachable from the task action menu. ### Integrations Overlay (`Ctrl+I`) `Ctrl+I` opens the Integrations overlay — the TUI mirror of the GUI [Integrations panel](/docs/components/gui#integrations). The overlay is split into two tabs: | Tab | Content | |-----|---------| | **Outbound** | One row per configured adapter (webhook, Slack, Discord, GitHub auto-PR) with a status pill (`OK` / `Disabled` / `Breaker open` / `Misconfigured`) and a `t` shortcut to fire a synthetic test event. | | **Inbound** | Listening pill (polled every 5 s), editable `ListenAddr` (defaults to `127.0.0.1:8765`) and `PublicURL`, four write-only secret inputs (Discord, GitHub, Slack, generic webhook), and per-provider last-delivery timestamps. | Both tabs talk to the daemon through `IntegrationsService` gRPC: `List` / `Save` / `Delete` / `Test`, with secret fields write-only on the wire. See the [Integrations concept page](/docs/concepts/integrations) for the underlying model and the [`watchfire integrations`](/docs/commands/integrations) command for the scriptable equivalent. ### Task Actions When the task list is focused: | Key | Action | |-----|--------| | `a` | Add new task | | `e` / `Enter` | Edit selected task | | `s` | Start task (set to Ready + start agent) | | `r` | Move task to Ready | | `t` | Move task to Draft | | `d` | Open inline diff overlay | | `i` | Open project Insights overlay | | `x` | Delete (soft) | The diff overlay renders the same `FileDiffSet` as the GUI [Inspect tab](/docs/components/gui#inspect). The Insights overlay (`i`) is the TUI mirror of the GUI [Insights tab](/docs/components/gui#insights) — KPI strip, stacked-bar tasks-per-day, agent donut, duration histogram, and a `7d` / `30d` / `90d` / `All` window switcher. See the [Insights concept page](/docs/concepts/insights) for the underlying metrics and how the windows aggregate. ### Left Panel Tabs | Tab | Content | |-----|---------| | **Tasks** | Task list grouped by status. Tasks with a per-task `agent` override show an agent badge next to the title. | | **Definition** | Project definition (press `e` to edit in $EDITOR) | | **Settings** | Git config, automation toggles, and a **coding agent** selector that cycles through the registered backends (project default). | ### Right Panel Tabs | Tab | Content | |-----|---------| | **Chat** | Live agent terminal output | | **Logs** | Past session logs per task. Formatted transcripts render for all supported backends (Claude Code, Codex, opencode, Gemini, Copilot), falling back to PTY scrollback when no transcript is available. | ### Task Form The Add / Edit Task overlay includes an **agent** field — a cycling selector below the existing fields. The first option, `Project default ()`, maps to the empty string and keeps the task on whatever the project is configured for. The remaining options are the registered backends. Saving the form writes the chosen value to the task's `agent` field. ### TUI Features - Mouse support (click, scroll, drag divider) - Vim-like + arrow key navigation - Resizable panels (40/60 default split) - Real-time streaming from daemon - Context-sensitive keyboard shortcuts - Agent issue banners (auth errors, rate limits) - Auto-reconnect on daemon disconnection ### Global Settings (`globalsettings`) The TUI exposes app-wide preferences through a `globalsettings` overlay — the keyboard mirror of the GUI [Global Settings](/docs/components/gui#global-settings) panel. It reads and writes the same `~/.watchfire/settings.yaml` file, so a value changed in either client takes effect everywhere on the next event. Flare splits the overlay into eight category sub-pages (Appearance, Defaults, Agent Paths, Notifications, Integrations, Inbound, Updates, About). Press `/` from the overlay to open a search overlay that filters categories and surfaces individual matching controls; selecting a result jumps to the category and pulses the matching field briefly. #### Notification Preferences The Notifications section of `globalsettings` configures notification behaviour. Every control below is gated through `models.ShouldNotify` on the daemon and persisted to `defaults.notifications` in `~/.watchfire/settings.yaml`. | Control | Default | Notes | |---------|---------|-------| | **Master toggle** | **On** | Top-level kill switch — turning it off silences every event kind without losing per-event preferences. | | **Per-event toggles** | `TASK_FAILED` on, `RUN_COMPLETE` on, `WEEKLY_DIGEST` **off** | One row per event kind. The weekly digest is the only event that defaults off — opt in to start receiving the rendered Markdown report at `~/.watchfire/digests/.md`. | | **Sounds** | On | Master sounds toggle. When on, a foregrounded GUI plays `assets/sounds/task-{done,failed}.wav` and the OS toast is sent silent so each event makes one cue. | | **Volume slider** | 100% | Linear 0–100 applied to renderer-side audio when the GUI plays the cue. | | **Quiet hours** | **Off** | Optional time window (start/end, local time, DST-stable). While enabled and inside the window, notifications are suppressed. | | **Per-project mute** | None | Project list to skip regardless of other toggles. Useful for noisy long-running projects. | The TUI itself does not raise OS toasts — these settings drive the daemon's tray and any connected GUI. Clicking the tray Notifications submenu still routes back through `DaemonService.SubscribeFocusEvents`. --- ## GUI (Watchfire.app) Source: https://watchfire.io/docs/components/gui The GUI is a multi-project client built with Electron. Unlike the CLI/TUI (which is project-scoped), the GUI shows every registered project in one place and connects to the daemon over gRPC-Web. > Looking for a single-page reference? The [Keyboard Shortcuts](/docs/keyboard-shortcuts) cheat sheet lists every GUI binding in printable form. ![Watchfire dashboard showing four project cards with task counts and progress bars](/screenshots/dashboard.webp) ## Layout The window splits into three regions: - **Sidebar** — the flame mark, project list, "Add Project," and the Settings entry. Always visible. - **Main content** — either the Dashboard or the active Project View, with the integrated terminal docked as a footer (toggle with Cmd+`). - **Right panel** — Chat, Branches, and Logs. Collapsible — pop it out when you want to focus on the main area. ## Dashboard The dashboard is the home view: every registered project rendered as a card with its color dot, current branch, default agent, and a Todo / In Dev / Done task summary with a progress bar. Click any card to drop into the Project View. ### Status bar & filters A single muted line — `N working · N needs attention · N idle · N done today` — sits between the dashboard header and the project grid as a fleet-wide pulse check. Below it, a row of pill chips narrows the grid to one bucket at a time. Each chip carries a live count, and the selection persists across reloads via `localStorage[wf-dashboard-filter]`. | Chip | Selects | |------|---------| | `All` | Every registered project | | `Working` | Projects with an agent currently running | | `Needs attention` | Projects with a task in `done` + `success: false` | | `Idle` | Projects with no running agent and nothing ready | | `Has ready tasks` | Projects with at least one `ready` task waiting for an agent | The Beacon **Insights rollup card** (cross-project KPIs, top projects, and a global ``) sits under the status bar and shares the `7d` / `30d` / `90d` / `All` window selector with the Project View. See the [Insights concept page](/docs/concepts/insights#cross-project-insights-rollup) for what's aggregated and how the `tasks_missing_cost` caveat surfaces partial data. ### Sort & layout Cards are auto-bucketed by activity rather than raw `position`: needs-attention → working → has-ready-tasks → idle. When the activity order diverges from the stored position order, a muted **`Sorted by activity`** label surfaces in the header so a manual reorder is still visible. A `LayoutGrid` / `Rows3` toggle in the header switches between the default card grid and a compact list view (one ~46px row per project). The choice persists across reloads via `localStorage[wf-dashboard-layout]`. ### Project cards Each card carries: - Project name, color dot, current branch, and the active-agent badge - **Elapsed-time badge** — when the agent is running, a ticking `Ns / Nm / Nh Mm` counter sits next to the agent badge and flips to a warning color once it crosses 30 minutes - **Last-activity timestamp** — `Active now / 5m ago / 4h ago / 2mo ago` segment, derived from the most recent task `updated_at` - **Live PTY preview** — the latest non-blank terminal line, in monospace muted text, throttled to 4 Hz. A singleton subscription manager ref-counts the underlying `AgentService.SubscribeScreen` stream, so the dashboard opens at most one stream per project regardless of how many cards reference it. - **Current-task line** — replaces the previous `Next:` line with `Working: ` (with a `Flame` icon) whenever the agent is actively running - **Shell-count chip** — terminal icon + count of alive shell sessions; pulses when any session emitted output in the last 2 seconds, and clicking it expands the [integrated terminal](#integrated-terminal) bottom panel - Task counts (Todo / In Dev / Done) and a progress bar When a project has any task with `status === 'done' && success === false`, the card flips into a **needs-attention** treatment: a red-tinted border, an `AlertTriangle` chip in the header, an `N failed` segment in the counts row, and a red segment in the progress bar. "Add Project" sits at the end of the grid as the entry point for the project wizard. ## Add Project Wizard A three-step wizard for adding new projects: ### Step 1 — Project Info - Project name - Path (folder picker) - Git status (auto-detected) - Branch (auto-detected) ### Step 2 — Git Configuration - Target branch - Auto-merge on completion - Delete branch after merge - Auto-start tasks - Coding agent — picker populated from the daemon's `SettingsService.ListAgents` RPC (Claude Code, Codex, opencode, Gemini, Copilot, plus any future backends). Seeds `project.default_agent`. ### Step 3 — Project Definition - Markdown editor for project context - Skip option for later ## Project View Selecting a project opens the split layout: a tabbed main area for managing the project, and a collapsible right panel for live agent output and history. ![Project view with the Tasks tab open and the Chat panel docked on the right, ready for a new agent session](/screenshots/project-tasks.webp) ### Main Tabs #### Tasks Tasks are grouped by status (Draft, Ready, Done) with search, filters, and a "New Task" button. Each row shows a compact agent badge when its `agent` field overrides the project default. ![Task list grouped into In Development and Done sections, with status badges on each row](/screenshots/task-list.webp) Opening a task brings up the task editor — title, prompt, acceptance criteria, status, and an agent picker with a leading **Project default** option. The form preserves its contents during background polling, so edits aren't lost when the task list refreshes. ![Task edit modal with title, prompt, acceptance criteria, status toggle, and agent selector](/screenshots/task-edit.webp) #### Inspect The Inspect tab renders the diff for the selected task as an inline file-by-file viewer. - **Pre-merge** — diffs are computed as `...HEAD` on the `watchfire/` branch. - **Post-merge** — the diff is reconstructed by locating the merge commit via `git log --grep`. - **Output** — a structured `FileDiffSet`, `cap at 10000 lines`. - **Cache** — results are cached at `~/.watchfire/diff-cache//.json`. Backed by the `internal/daemon/diff` package. #### Definition A markdown editor for the project definition — the persistent context every agent reads before starting work. Use it for architecture, key files, conventions, and constraints. ![Project Definition tab showing the Watchfire definition rendered as markdown](/screenshots/project-definition.webp) #### Secrets Agent-readable instructions for accessing external services — CLI tools that need to be authenticated, environment variables, API keys, and where to find them. ![Project Secrets tab showing Markdown setup instructions for CLI tools, env vars, and credentials](/screenshots/project-secrets.webp) #### Trash Soft-deleted tasks live here until you restore or permanently delete them. ![Project Trash tab in its empty state](/screenshots/project-trash.webp) #### Settings Per-project configuration — name, color (which propagates everywhere instantly), default agent, and the automation toggles for auto-merge, auto-delete branches, and auto-start tasks. The Danger Zone unregisters the project; no files are deleted. ![Project Settings tab showing name, color picker, agent dropdown, automation toggles, and a Danger Zone](/screenshots/project-settings.webp) #### Insights A per-project view of agent activity, aggregated from the `.metrics.yaml` records under `.watchfire/tasks/`. The tab header carries an `` that opens the export dialog scoped to the current project (CSV or Markdown via `InsightsService.ExportReport`). Layout, top to bottom: - **KPI strip** — totals for tasks completed, duration, tokens, and cost in the selected window - **Stacked-bar tasks-per-day** — one bar per day, stacked by exit reason - **Agent donut** — share of tasks by backend - **Duration histogram** — distribution of task durations The header includes a window selector with `7d` / `30d` / `90d` / `All` options. The selection persists across reloads via `localStorage[wf-insights-window]`. See the [Insights concept page](/docs/concepts/insights) for the underlying metrics package, the per-task record schema, and how the rollup composes. ### Right Panel The right panel docks live agent output and history. Toggle it open or closed at any time. #### Chat The Chat tab streams the live agent terminal from the daemon. Watchfire clears the terminal before each new subscription, so switching projects or wildfire phase transitions never accumulate stale output. Tasks the agent creates during chat appear in real-time in the Tasks tab without a manual refresh. ![Project view with a live Chat session — agent output streaming on the right while the task list updates on the left](/screenshots/chat-active.webp) The toolbar above the terminal exposes the agent modes — **Generate**, **Plan**, **Run All**, **Wildfire**, and **Stop**. #### Branches A live view of every active worktree and branch with status (in development, merged, conflict) and inline actions to merge, delete, or open in your editor. ![Branches tab listing watchfire worktrees with merge status badges](/screenshots/branches.webp) #### Logs Per-task session history. The Logs tab renders formatted conversation transcripts (User/Assistant messages with tool call summaries) for every supported backend — Claude Code, Codex, opencode, Gemini, Copilot — and falls back to raw PTY scrollback when no transcript is available. ![Logs tab listing past task and chat sessions with timestamps and statuses](/screenshots/project-logs.webp) ## Integrated Terminal The GUI includes a built-in shell terminal, separate from the agent Chat terminal in the right panel. It appears as a footer bar at the bottom of the Project View. ### Opening the Terminal - Click the footer bar at the bottom of the project view, or - Press Cmd+` (backtick) to toggle The footer bar expands upward into a resizable bottom panel. Drag the top edge to adjust height. ### Features | Feature | Details | |---------|---------| | **Tabbed sessions** | Up to 5 shell tabs per project | | **Powered by node-pty** | Runs in the Electron main process, not the daemon | | **Nerd Font support** | Rich terminal rendering with icons and glyphs | | **Session cleanup** | Sessions are cleaned up on project switch or app quit | This is a general-purpose shell — use it for running builds, git commands, or anything else without leaving the GUI. The agent Chat terminal in the right panel streams the coding agent's output from the daemon and is a separate interface. ## Task Status Display | Internal Status | Display Label | Visual | |-----------------|---------------|--------| | `draft` | Todo | Default style | | `ready` | In Development | Highlighted | | `ready` + agent active | In Development | Animated indicator | | `done` (success: true) | Done | Green indicator | | `done` (success: false) | Failed | Red indicator | ## Global Settings Open Settings from the bottom of the sidebar to manage app-wide preferences — appearance, the defaults applied to every new project, and the per-backend binary paths the daemon should use to launch agents. Flare reorganises this surface into a macOS-style two-pane layout: the left sidebar lists eight categories (Appearance, Defaults, Agent Paths, Notifications, Integrations, Inbound, Updates, About), the right pane shows only the selected category. A search box at the top filters categories AND surfaces individual matching controls with category breadcrumbs — clicking a result navigates to the category and pulses the matching field for ~1.5 seconds. `Cmd/Ctrl+F` focuses search, `Esc` clears, `Up`/`Down`/`Enter` navigate. Existing deep-link routes (`#integrations` etc.) still work. ![Global Settings showing Appearance theme picker, defaults for new projects, and agent binary paths with auto-detection status](/screenshots/global-settings.webp) | Section | Content | |---------|---------| | **Appearance** | Theme (System / Light / Dark) | | **Defaults** | Automation toggles for new projects, plus the **default coding agent** used when a project hasn't chosen one. Includes an **"Ask per project"** option that forces `watchfire init` to prompt every time. | | **Agents** | Per-backend binary paths — `claude`, `codex`, `opencode`, `gemini`, `copilot`. Leave blank to fall back to `PATH` and common install locations. Also shows auto-detection status and install instructions for each backend. | | **Notifications** | Notification preferences — master toggle, per-event toggles (`TASK_FAILED`, `RUN_COMPLETE`, `WEEKLY_DIGEST`), sounds + volume, quiet hours, and a per-project mute list. See below. | | **Integrations** | Outbound adapters (webhook, Slack, Discord, GitHub auto-PR) plus the **Inbound** subsection covering the HTTP server, per-provider secrets, and per-provider URLs. See below. | | **Updates** | Check frequency, auto-download toggle | ### Notification Preferences The Notifications panel reads and writes the `defaults.notifications` section of `~/.watchfire/settings.yaml`. Every toggle below is gated through `models.ShouldNotify` on the daemon, so changes take effect on the next event without a restart. | Control | Default | Notes | |---------|---------|-------| | **Master toggle** | **On** | Top-level kill switch — turning it off silences every event kind without losing per-event preferences. | | **Per-event toggles** | `TASK_FAILED` on, `RUN_COMPLETE` on, `WEEKLY_DIGEST` **off** | One row per event kind. The weekly digest is the only event that defaults off — opt in to start receiving the rendered Markdown report at `~/.watchfire/digests/.md`. | | **Sounds** | On | Master sounds toggle. When on, the renderer plays `assets/sounds/task-{done,failed}.wav` while it has focus and the OS toast is sent silent — exactly one cue per event. | | **Volume slider** | 100% | Linear 0–100 scale applied to renderer-side audio. Has no effect on OS toast sounds when the renderer is backgrounded (those follow OS volume). | | **Quiet hours** | **Off** | Optional time window (start/end, local time, DST-stable). When enabled and inside the window, `models.ShouldNotify` returns false for everything except hard failures. | | **Per-project mute** | None | Per-project chip list — projects added here are skipped by `ShouldNotify` regardless of other toggles. Useful for noisy long-running projects. | Clicking any OS toast or tray Notifications submenu entry routes through `DaemonService.SubscribeFocusEvents` to bring this GUI to the originating project and task. ### Integrations The Integrations panel renders as `gui/src/renderer/src/views/Settings/IntegrationsSection.tsx`, with one detail panel per adapter and a separate **Inbound** subsection driven by `gui/src/renderer/src/views/Settings/InboundSection.tsx`. #### Outbound Per-adapter detail panels for the [outbound delivery framework](/docs/concepts/integrations#outbound): - **Webhook** — URL field, HMAC secret (write-only), test button - **Slack** — bot token (write-only), channel selector, test button - **Discord** — webhook URL (write-only), test button - **GitHub auto-PR** — opt-in toggle, prerequisites checklist (`gh` on `PATH`, `gh auth status`) Behind the scenes, the GUI talks to the daemon through `IntegrationsService` gRPC: `List` / `Save` / `Delete` / `Test` RPCs, with `Save` carrying a `oneof` payload. Every secret field is write-only on the wire — the GUI can save and replace, but never reads existing values back. #### Inbound The Inbound subsection drives the HTTP server documented in [Integrations → Inbound](/docs/concepts/integrations#inbound): - **Listening pill** — polled every 5 seconds; reflects whether the server is bound and the last error if not - **`ListenAddr`** — editable bind address, defaults to `127.0.0.1:8765`. A **Restart** button next to the field re-binds the server when changed. - **`PublicURL`** — used to construct the per-provider URLs the **Copy as Discord URL** / **Copy as GitHub URL** / **Copy as Slack URL** / **Copy as Webhook URL** buttons offer - **Per-provider secret inputs** — four write-only fields, one per upstream (Discord public key, GitHub HMAC secret, Slack signing secret, generic webhook HMAC secret). Empty disables the corresponding handler so it returns `503`. - **Last-delivery timestamps** — one per provider, updated whenever the daemon successfully verifies an inbound request, so it's obvious at a glance which providers are actually wired through The whole subsection mirrors the new TUI **Inbound** tab inside the [Integrations overlay](/docs/components/cli#integrations-overlay-ctrli). --- ## watchfire init Source: https://watchfire.io/docs/commands/init Initialize a new Watchfire project in the current directory. ## Usage ```bash watchfire init ``` ## Description `watchfire init` sets up a new Watchfire project by creating the necessary configuration files and directory structure. It must be run from within a git repository (or it will initialize one for you). ## What It Does 1. Checks for an existing git repository — initializes one if missing 2. Creates the `.watchfire/` directory structure (including `tasks/`) 3. Generates an initial `project.yaml` with a UUID, project name, and selected project default agent 4. Appends `.watchfire/` to `.gitignore` (creates the file if missing) 5. Commits the `.gitignore` change 6. Prompts for an optional project definition 7. Prompts for project settings, including which agent/backend the project should prefer ## Interactive Prompts During initialization, you'll be asked to configure: | Setting | Description | Default | |---------|-------------|---------| | **Agent/backend** | Project default agent written to `.watchfire/project.yaml` | Global default agent if set, otherwise `claude-code` | | **Project definition** | A description of your project used as context for every agent session | Empty | | **Auto-merge** | Automatically merge completed task branches | `true` | | **Auto-delete branches** | Delete worktree branches after merge | `true` | | **Auto-start tasks** | Start an agent when a task is set to ready | `true` | The selected backend is stored as `default_agent` in the project config. You can change it later in project settings. If you leave agent selection unset at the project level, Watchfire resolves the effective backend using: 1. `task.agent` 2. `project.default_agent` 3. Global default agent 4. `claude-code` ## Examples ### Initialise inside an existing repo ```bash cd my-project watchfire init ``` Run from the repo root. `init` detects the existing git repo, creates `.watchfire/`, appends an entry to `.gitignore`, and walks you through the interactive prompts. Example generated config: ```yaml project_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890 name: my-project default_agent: codex sandbox: auto auto_merge: true auto_delete_branch: true auto_start_tasks: true definition: "" next_task_number: 1 ``` After running, your project directory will contain: ```text my-project/ ├── .watchfire/ │ ├── project.yaml │ └── tasks/ ├── .gitignore # Updated with .watchfire/ └── ...your files ``` ### Initialise with a non-default agent ```bash mkdir new-tool && cd new-tool git init watchfire init ``` When prompted for **Agent/backend**, pick `codex`, `opencode`, `gemini`, or `copilot` — the choice is written to `default_agent` in `project.yaml` and inherited by every session in the project. You can change it later with [`watchfire configure`](/docs/commands/settings). ### Bootstrap definition and tasks immediately after ```bash watchfire init watchfire generate watchfire plan ``` A common starter sequence: initialise, draft a definition from the codebase, then generate the first task list. Refine the definition with [`watchfire define`](/docs/commands/definition) between `generate` and `plan` for sharper output. ## Common pitfalls - **Run from a parent directory** — `watchfire init` initialises whatever directory it's run from, not the project you meant. *Fix:* `cd` into the project root first; the prompt confirms the project name before writing files. - **`.gitignore` not committed** — the `.watchfire/` line is appended but the change is left unstaged. *Fix:* `init` creates a commit for the `.gitignore` change automatically, but if the repo had no commits yet the commit may be skipped — verify with `git log -1 --stat`. - **Re-running on an existing project** — `watchfire init` refuses to overwrite an existing `.watchfire/project.yaml`. *Fix:* edit settings with [`watchfire configure`](/docs/commands/settings) instead, or remove `.watchfire/` first if you really want a fresh start. --- ## watchfire task Source: https://watchfire.io/docs/commands/task Manage tasks in your Watchfire project. ## Usage ```bash watchfire task [arguments] ``` ## Subcommands ### task add Create a new task interactively. ```bash watchfire task add ``` Opens interactive prompts to set the task title, prompt, acceptance criteria, status, and (optionally) the agent backend to run this task on. The task is saved as a YAML file in `.watchfire/tasks/`. If you skip the agent prompt, the field is omitted and the task uses the project's default agent at execution time. ### task list List all tasks in the project. ```bash watchfire task list watchfire task ls # alias ``` Displays tasks grouped by status (draft, ready, done). Soft-deleted tasks are excluded by default. | Flag | Description | |------|-------------| | `--deleted` | Include soft-deleted tasks in the listing | ### task \ Edit an existing task interactively. ```bash watchfire task 1 watchfire task 3 ``` Opens the specified task (by task number) for interactive editing. You can modify the title, prompt, acceptance criteria, status, and agent override. ### task delete Soft-delete a task. ```bash watchfire task delete watchfire task rm # alias ``` Sets the `deleted_at` timestamp on the task. The task file is preserved but hidden from `task list` by default. ### task restore Restore a soft-deleted task. ```bash watchfire task restore ``` Clears the `deleted_at` timestamp, making the task visible again. ## Task Agent Override Each task YAML may include an optional `agent` field to pin the backend used for that task: ```yaml task_id: a1b2c3d4 task_number: 2 title: "Refactor docs search" agent: gemini prompt: | Replace the existing search index with a fuzzy matcher. acceptance_criteria: | - Search returns results for partial matches - Existing tests pass status: ready ``` When `agent` is omitted, Watchfire resolves the effective backend in this order: 1. `task.agent` 2. `project.default_agent` 3. Global default agent 4. `claude-code` See [Projects and Tasks](/docs/concepts/projects-and-tasks#agent-selection) for the full schema and [Supported Agents](/docs/concepts/supported-agents) for the available backends. ## Examples ### Add a focused task from the CLI ```bash watchfire task add ``` The interactive prompt produces a YAML file in `.watchfire/tasks/`: ```yaml task_number: 4 title: "Fix off-by-one in pagination cursor" prompt: | Cursor in `lib/paginate.ts` returns one fewer row than `limit` on the first page. Fix and add a regression test. acceptance_criteria: | - First page returns exactly `limit` rows - Existing tests pass status: ready ``` Setting `status: ready` triggers the agent immediately when `auto_start_tasks: true` (the default). Keep prompts focused — narrow scope is what stops the agent from drifting. ### List ready tasks ```bash watchfire task list ``` Sample output: ```text DRAFT 5 Document deploy runbook READY 4 Fix off-by-one in pagination cursor 6 Bump pino to 9.x DONE 1 Add tests for lib/parser.ts (success) 2 Refactor docs search (success) 3 Diagnose 500s on POST /api/orders (success) ``` Tasks are grouped by status and sorted by `position` then `task_number`. Add `--deleted` to include soft-deleted tasks. ### Pin a task to a different backend ```bash watchfire task add ``` When prompted for the agent, set `gemini` (or any of `claude-code`, `codex`, `opencode`, `copilot`). The resulting YAML carries the override: ```yaml task_number: 7 title: "Refactor docs search" agent: gemini status: ready ``` `task.agent` wins over `project.default_agent` and the global default. See [Projects and Tasks → Agent Selection](/docs/concepts/projects-and-tasks#agent-selection). ### Stop a runaway agent ```bash watchfire task delete 7 ``` Soft-deleting a `ready` task removes it from the queue. To stop an actively running agent, use the TUI/GUI session controls (Ctrl+C in the foreground session, or the stop button in the [Chat panel](/docs/components/gui#chat)) — `task delete` only affects the task definition, not the session. ## Common pitfalls - **Empty task list** — `watchfire task list` prints nothing because the project has no `ready` tasks (deleted ones are hidden by default). *Fix:* run `watchfire task list --deleted` to see soft-deleted tasks, or add new ones with `watchfire task add` / [`watchfire generate`](/docs/commands/generate). - **Editing a `done` task does not re-run it** — `watchfire task ` lets you change fields, but flipping `status` from `done` back to `ready` does not always re-trigger the agent. *Fix:* if you want a fresh run, add a new task instead of resurrecting a completed one. - **`agent:` value rejected** — only the five supported backends are valid. *Fix:* use `claude-code`, `codex`, `opencode`, `gemini`, or `copilot` — see [Supported Agents](/docs/concepts/supported-agents). --- ## watchfire run Source: https://watchfire.io/docs/commands/run Run an agent session — chat mode, single task, or all ready tasks. ## Usage ```bash watchfire run [taskid|all] ``` ## Modes ### Chat Mode (no arguments) ```bash watchfire run ``` Starts an interactive chat session with the coding agent. The agent has access to your project but is not assigned a specific task. Equivalent to the old `watchfire chat` command. ### Single Task ```bash watchfire run ``` Executes a specific task by its task number. The agent is spawned in an isolated git worktree on a dedicated branch (`watchfire/`), with the task prompt and acceptance criteria injected into context. When the agent marks the task as done, Watchfire automatically: - Stops the agent session - Merges the worktree branch (if auto-merge is enabled) - Cleans up the worktree (if auto-delete is enabled) ### All Ready Tasks ```bash watchfire run all ``` Runs all tasks with `status: ready` sequentially. After each task completes, the next ready task is started automatically. This is equivalent to the Start All agent mode. ## Flags | Flag | Description | |------|-------------| | `--sandbox ` | Override the sandbox backend. Valid values: `auto`, `seatbelt`, `landlock`, `bubblewrap`, `none`. | | `--no-sandbox` | Shorthand for `--sandbox none` — disables sandboxing entirely. | Sandbox priority: CLI flag → project setting (`project.yaml`) → global default (`settings.yaml`). ## Agent Selection `watchfire run` uses whichever backend Watchfire resolves for the session — it does not force a specific agent. Resolution order: 1. `task.agent` (for single-task runs, if set) 2. `project.default_agent` 3. Global default agent 4. `claude-code` In `run all` mode, each task is launched independently, so tasks pinned to different backends in their own `agent` field will each run on their pinned backend. See [Supported Agents](/docs/concepts/supported-agents) for the full list of backends and [Projects and Tasks](/docs/concepts/projects-and-tasks#agent-selection) for the resolution rules. ## Examples ### Run a single ready task ```bash watchfire run 3 ``` The agent spawns in `.watchfire/worktrees/3/` on a fresh `watchfire/3` branch off your current HEAD. With `auto_merge: true` (the default in [`project.yaml`](/docs/concepts/projects-and-tasks#project-settings)), Watchfire merges and cleans up the worktree as soon as the task flips to `done`. ### Drain the ready queue ```bash watchfire run all ``` Runs every `ready` task one at a time, merging each completed branch before starting the next. The chain stops on a merge conflict so a failing task can't poison the rest. ### Open a chat session in the project root ```bash watchfire run ``` Equivalent to [`watchfire chat`](/docs/commands/chat) — no arguments means a free-form chat session, not a task. The agent runs in the project root, not a worktree. ### Bypass the sandbox for a single task ```bash watchfire run 7 --no-sandbox ``` Useful when the task legitimately needs to touch a path the sandbox blocks. The flag applies only to this session — it does not change the project's `sandbox` setting. ## Common pitfalls - **Argument is not a task number** — `watchfire run all` is a special argument; passing a non-numeric string that isn't `all` opens chat mode silently. *Fix:* check `watchfire task list` for the correct task number first. - **Task runs against an unexpected backend** — the agent that starts isn't the one you used last week. *Fix:* the resolution order is `task.agent` → `project.default_agent` → global default → `claude-code`; pin `agent:` on the task or change the project default with [`watchfire configure`](/docs/commands/settings). - **Chat mode edits leak into your branch** — `watchfire run` (no args) writes to the project root, not a worktree. *Fix:* use a task instead when isolation matters — see [Investigate a bug without disturbing your branch](/docs/recipes#investigate-a-bug-without-disturbing-your-branch). --- ## watchfire chat Source: https://watchfire.io/docs/commands/chat Start an interactive chat session with the coding agent. ## Usage ```bash watchfire chat ``` ## Description `watchfire chat` opens a terminal-attached agent session for free-form conversation. The agent has full access to your project directory but is not assigned a specific task — it runs in the project root rather than an isolated worktree. This is useful for exploring your codebase, asking questions, making quick edits, or prototyping changes without creating a formal task. ## Flags | Flag | Description | |------|-------------| | `--sandbox ` | Override the sandbox backend. Valid values: `auto`, `seatbelt`, `landlock`, `bubblewrap`, `none`. | | `--no-sandbox` | Shorthand for `--sandbox none` — disables sandboxing entirely. | Sandbox priority: CLI flag → project setting (`project.yaml`) → global default (`settings.yaml`). ## Agent Selection Chat sessions use the project's default agent, falling back to the global default and finally to `claude-code`. To chat with a different backend, change `project.default_agent` in project settings or the global default in Watchfire settings before launching. See [Supported Agents](/docs/concepts/supported-agents) for the available backends. ## Notes - The agent runs in the project root directory (not a worktree) - No task context is injected — the agent operates in free-form mode - The daemon starts automatically if it's not already running - `watchfire run` (with no arguments) provides equivalent functionality ## Examples ### Spelunk a bug without touching your branch ```bash watchfire chat ``` Chat runs in the project root, so reads are free but any edits land on your current branch. For investigation that must not touch in-flight work, use a [task](/docs/commands/task) instead — it runs in an isolated worktree. ### Skip the sandbox for a one-off exploration ```bash watchfire chat --no-sandbox ``` Useful when you need to read paths the sandbox blocks (credentials dirs, `.env`) for diagnosis. Prefer leaving the sandbox on for normal sessions. ### Equivalent using the run command ```bash watchfire run ``` `watchfire run` with no arguments is identical to `watchfire chat` — both open a chat session against the project's resolved agent. ## Common pitfalls - **Edits land on your working branch** — chat does not use a worktree, so any file changes the agent makes are written to the currently checked-out branch. *Fix:* close the chat without saving, or run the work as a task — see [`watchfire task`](/docs/commands/task#task-add). - **Wrong backend started** — chat picks up the project default agent, not whichever CLI you last used. *Fix:* set `default_agent` in `.watchfire/project.yaml` via [`watchfire configure`](/docs/commands/settings), or pin a different default at the global level. - **Backend not authenticated** — the chat session opens but the agent prints `Please run /login` or `401`. *Fix:* re-authenticate the backend in its own CLI — see [Agent backend not authenticated](/docs/troubleshooting#agent-backend-not-authenticated). --- ## watchfire wildfire Source: https://watchfire.io/docs/commands/wildfire Start the autonomous wildfire mode. **Alias:** `watchfire fire` ## Usage ```bash watchfire wildfire ``` ## Description Wildfire is Watchfire's fully autonomous mode. It runs a continuous three-phase loop that executes ready tasks, refines draft tasks, and generates new tasks — all without human intervention. ## How It Works The daemon cycles through three phases based on the current state of your task list: ```mermaid graph LR E["Phase 1: Execute\n(ready tasks)"] --> R["Phase 2: Refine\n(draft tasks)"] R --> G["Phase 3: Generate\n(no tasks left)"] G -->|new tasks created| E G -->|no new tasks| C[Chat Mode] ``` ### Phase 1: Execute If there are tasks with `status: ready`, the agent executes them one by one (same as `watchfire run all`). ### Phase 2: Refine If there are tasks with `status: draft`, the agent reviews and refines them — improving prompts, breaking down large tasks, and setting them to `ready`. ### Phase 3: Generate If no tasks remain, the agent analyzes the project definition and completed work to generate new tasks. If new tasks are created, the loop restarts from Phase 1. If no new tasks are created, wildfire transitions to chat mode. ## Flags | Flag | Description | |------|-------------| | `--sandbox ` | Override the sandbox backend. Valid values: `auto`, `seatbelt`, `landlock`, `bubblewrap`, `none`. | | `--no-sandbox` | Shorthand for `--sandbox none` — disables sandboxing entirely. | Sandbox priority: CLI flag → project setting (`project.yaml`) → global default (`settings.yaml`). ## Agent Selection Wildfire launches a new agent session for each task it executes, refines, or generates. Each session uses Watchfire's standard resolution order: 1. `task.agent` (when executing a specific task) 2. `project.default_agent` 3. Global default agent 4. `claude-code` This means Wildfire can drive a mixed set of backends in one run — for example, Claude Code for tasks that pin `agent: claude-code` and the project default for everything else. See [Supported Agents](/docs/concepts/supported-agents) for details. ## Stopping Press **Ctrl+C** to stop wildfire at any time. The current agent session will be terminated gracefully. ## Examples ### Start Wildfire on the current project ```bash watchfire wildfire ``` Wildfire alternates Execute → Refine → Generate until none of the three phases produce work. On a brownfield project the first iteration is usually Generate; on a populated `ready` queue it's Execute. ### Stop Wildfire cleanly ```text ^C ``` There is no separate stop command. Press Ctrl+C in the session that launched Wildfire — the current agent terminates gracefully and the loop exits. Any tasks already merged stay merged. ### Run Wildfire without the sandbox ```bash watchfire fire --no-sandbox ``` Use the `fire` alias for terseness. `--no-sandbox` applies to every session Wildfire spawns during the run, so reach for it sparingly — most tasks are happier inside the sandbox. ## Common pitfalls - **Loop never exits** — Wildfire keeps drafting or regenerating the same kind of work. *Fix:* tighten `project.definition` first — see [Wildfire mode runs forever](/docs/troubleshooting#wildfire-mode-runs-forever). - **Three failures drop into chat mode** — Wildfire's [restart protection](/docs/concepts/architecture#restart-protection) bails after 3 consecutive failures on the same task. *Fix:* fix the failing task (or set `status: done` with `success: false`) before relaunching Wildfire. - **No tasks to refine, definition is empty** — Wildfire transitions to chat mode immediately. *Fix:* run [`watchfire generate`](/docs/commands/generate) and [`watchfire define`](/docs/commands/definition) first so the loop has something to anchor on. --- ## watchfire generate Source: https://watchfire.io/docs/commands/generate Generate a project definition using a coding agent. **Alias:** `watchfire gen` ## Usage ```bash watchfire generate ``` ## Description `watchfire generate` spawns an agent session that analyzes your codebase and generates a project definition for `project.yaml`. This is useful when starting with an existing codebase and you want the agent to understand and document the project structure, conventions, and architecture. The generated definition is saved to your `project.yaml` and serves as context for all future agent sessions. ## Flags | Flag | Description | |------|-------------| | `--sandbox ` | Override the sandbox backend. Valid values: `auto`, `seatbelt`, `landlock`, `bubblewrap`, `none`. | | `--no-sandbox` | Shorthand for `--sandbox none` — disables sandboxing entirely. | Sandbox priority: CLI flag → project setting (`project.yaml`) → global default (`settings.yaml`). These flags also apply to `watchfire plan`. ## Agent Selection Both `watchfire generate` and `watchfire plan` run on the project's default agent, falling back to the global default and finally to `claude-code`. Any of the supported backends (Claude Code, Codex, opencode, Gemini, Copilot) can perform definition and task generation. See [Supported Agents](/docs/concepts/supported-agents). ## Related: watchfire plan To generate tasks from an existing project definition, use: ```bash watchfire plan ``` This spawns an agent session that reads your project definition and creates a set of tasks in `.watchfire/tasks/`. It's useful for breaking down a project into actionable work items. ## Examples ### Generate a definition for a fresh project ```bash watchfire init watchfire generate watchfire define ``` After `init`, `generate` walks the codebase and drafts a `definition` field in `project.yaml`. Always follow with [`watchfire define`](/docs/commands/definition) to tighten the draft — the edited version is what every future session reads. ### Re-generate tasks after the definition changes ```bash watchfire plan ``` `plan` reads the current `project.definition` and writes new task YAML files to `.watchfire/tasks/`. Run it after a meaningful definition edit so the task list reflects the new scope. ### Skip the sandbox when bootstrapping ```bash watchfire generate --no-sandbox ``` Useful on a brand-new machine where the sandbox configuration hasn't been verified yet — keep this flag scoped to the bootstrap step and re-enable the sandbox for normal task runs. ## Common pitfalls - **Empty definition produces empty tasks** — `watchfire plan` finishes but the new tasks are vague. *Fix:* tighten `project.definition` first — see [Generate phase produces empty or low-quality tasks](/docs/troubleshooting#generate-phase-produces-empty-or-low-quality-tasks). - **Existing definition silently overwritten** — `watchfire generate` rewrites `project.definition` from scratch. *Fix:* if you have a hand-authored definition you care about, edit it with [`watchfire define`](/docs/commands/definition) instead, or copy it elsewhere before re-running `generate`. - **Drafts pile up in `.watchfire/tasks/`** — `watchfire plan` adds new draft tasks but does not remove old ones. *Fix:* delete or refine stale drafts with [`watchfire task delete`](/docs/commands/task#task-delete) or let the [Wildfire](/docs/commands/wildfire) refine phase consolidate them. --- ## watchfire daemon Source: https://watchfire.io/docs/commands/daemon Manage the Watchfire daemon (`watchfired`). ## Usage ```bash watchfire daemon ``` ## Subcommands ### daemon start Start the daemon process. ```bash watchfire daemon start ``` Starts `watchfired` if it's not already running. This is usually not needed — the daemon is started automatically when you run any project-scoped command. ### daemon status Show daemon status information. ```bash watchfire daemon status ``` Displays the daemon's host, port, PID, uptime, and number of active agent sessions. ### daemon stop Stop the daemon. ```bash watchfire daemon stop ``` Sends a SIGTERM to the daemon process, gracefully shutting it down. All active agent sessions will be terminated. ## Notes - The daemon starts automatically when you run commands like `watchfire run`, `watchfire wildfire`, or launch the TUI - Multiple CLI/TUI instances can connect to the same daemon simultaneously - If the daemon shuts down, all connected CLI/TUI instances will close - The daemon manages all projects registered in `~/.watchfire/projects.yaml` - The daemon orchestrates every supported agent backend (Claude Code, Codex, opencode, Gemini, Copilot) — it is not tied to a specific agent. See [Supported Agents](/docs/concepts/supported-agents). ## Examples ### Start the daemon manually for debugging ```bash watchfired --foreground ``` Running `watchfired` directly in the foreground prints startup output to your terminal — useful when `watchfire daemon start` exits silently and you need to see why. See [Daemon won't start](/docs/troubleshooting#daemon-wont-start). ### Quick health check ```bash watchfire daemon status ``` Sample output: ```text host: 127.0.0.1 port: 37291 pid: 54812 uptime: 4h 12m sessions: 2 active ``` The `sessions` line tells you how many agent sessions the daemon is currently driving across all registered projects. ### Stop and restart cleanly ```bash watchfire daemon stop watchfire daemon start ``` Use this pair after a Watchfire upgrade or when the connection-info file has gone stale and the CLI hangs on `connection refused`. ## Common pitfalls - **Daemon already running** — `watchfire daemon start` exits because `watchfired` is already up. *Fix:* run `watchfire daemon status` first; only stop/start if you actually want a fresh process. - **Stop terminates every active session** — `watchfire daemon stop` kills every connected agent across every project, not just the current one. *Fix:* let in-flight tasks finish first, or stop only the specific session via the TUI/GUI. - **Stale `~/.watchfire/daemon.yaml`** — CLI reports `connection refused` even though the daemon "should" be up. *Fix:* see [CLI/TUI can't connect to daemon](/docs/troubleshooting#cli-tui-cant-connect-to-daemon). --- ## watchfire definition Source: https://watchfire.io/docs/commands/definition Edit the project definition in your default editor. ## Usage ```bash watchfire define watchfire def # alias ``` ## Description `watchfire define` opens the project definition in `$EDITOR` for editing. The project definition describes your project — its structure, tech stack, goals, and conventions — and is injected into every agent session to provide context, regardless of which backend is running (Claude Code, Codex, opencode, Gemini, or Copilot). A well-written definition helps agents produce better, more consistent code by giving them the information they need about your project upfront. ## What the Definition Contains The definition is stored in the `definition` field of `.watchfire/project.yaml`. A typical definition includes: - Project name and purpose - Technical stack and frameworks - Architecture overview - Coding conventions and constraints - Goals or current priorities ## Notes - Uses the editor set in your `$EDITOR` environment variable - Changes are saved directly to `project.yaml` - The definition is included in agent context for all modes (chat, task, wildfire) - To auto-generate a definition from your codebase, use [`watchfire generate`](/docs/commands/generate) ## Examples ### Bootstrap a definition for a new project ```bash watchfire generate watchfire define ``` `watchfire generate` drafts a definition from the codebase; `watchfire define` opens it in `$EDITOR` so you can tighten it before any tasks are generated. The edited version is what every agent session reads. ### Edit the existing definition ```bash watchfire def ``` The alias is identical to `watchfire define`. A typical edit narrows scope — add explicit non-goals, name the directories that are off-limits, list the test commands the agent should run. ### Definition shape that produces useful tasks ```yaml definition: | Express API serving an internal admin tool. Goal of this cleanup pass: every route handler has a unit test, README documents local dev, eslint runs clean. Constraints: - No new runtime dependencies - Public API surface stays unchanged - Tests live next to source as *.test.ts ``` Concrete targets and explicit constraints are what keep [`watchfire generate`](/docs/commands/generate) and the [Wildfire](/docs/commands/wildfire) refine phase from drifting. ## Common pitfalls - **`$EDITOR` not set** — `watchfire define` errors out or falls back to a surprising editor. *Fix:* export `EDITOR` (e.g. `export EDITOR=vim`) in your shell profile before running. - **Empty or generic definition** — Wildfire and `watchfire generate` produce vague tasks because they have nothing concrete to anchor on. *Fix:* see [Generate phase produces empty or low-quality tasks](/docs/troubleshooting#generate-phase-produces-empty-or-low-quality-tasks). - **Definition not committed** — `.watchfire/` is gitignored by default, so the definition lives only on the machine that wrote it. *Fix:* if you want the definition shared, copy the relevant context into the project's `README` or check in a separate `definition.md` your team agrees on. --- ## watchfire settings Source: https://watchfire.io/docs/commands/settings Configure project settings interactively. ## Usage ```bash watchfire configure watchfire config # alias ``` ## Description `watchfire configure` lets you interactively update your project's settings. It presents each configurable option and saves changes to `.watchfire/project.yaml`. Global Watchfire settings (the default agent for new projects and custom binary paths) are managed from the GUI Settings panel and persisted to `~/.watchfire/settings.yaml`. The CLI configure command only edits the per-project file. ## Configurable Settings | Setting | Description | Default | |---------|-------------|---------| | **Sandbox mode** | Sandbox mechanism for agent processes | `auto` | | **Auto-merge** | Automatically merge completed task branches to the default branch | `true` | | **Auto-delete branch** | Delete worktree branches after successful merge | `true` | | **Auto-start tasks** | Start an agent automatically when a task status is set to ready | `true` | | **Default agent** | Project default backend (`claude-code`, `codex`, `opencode`, `gemini`, or `copilot`) | Inherited from global default | | **Project color** | Hex color used to identify the project in the GUI | — | The **Default agent** picker writes to `default_agent` in `.watchfire/project.yaml`. Leave it unset and Watchfire falls back to the global default agent (and ultimately to `claude-code` if no default is configured anywhere). ## Settings UI (GUI) The GUI Settings panel exposes both project and global settings, including dedicated controls for agent selection and binary discovery: - **Project default agent** — picker that selects which backend the project should prefer - **Per-task agent** — pickers in the task editor for overriding the agent on individual tasks - **Global default agent** — used by any project that does not set its own `default_agent` - **Custom binary paths** — per-backend path override for `claude`, `codex`, `opencode`, `gemini`, and `copilot` when Watchfire cannot locate the binary on `PATH` When a custom path is set, Watchfire uses it instead of searching `PATH` or known install locations. See [Supported Agents](/docs/concepts/supported-agents) for the resolution order Watchfire applies to each backend. ## Effective Agent Resolution When Watchfire launches a session it picks the backend in this order: 1. `task.agent` (if defined on the task YAML) 2. `project.default_agent` (from `.watchfire/project.yaml`) 3. Global default agent (from `~/.watchfire/settings.yaml`) 4. `claude-code` This means project and global settings only apply when a task does not pin its own agent. ## Notes - Project settings are stored in `.watchfire/project.yaml` - Global settings (default agent, custom binary paths) are stored in `~/.watchfire/settings.yaml` - Changes take effect immediately for new agent sessions - Running agent sessions are not affected by settings changes - Initial project settings are configured during `watchfire init` ## Examples ### Walk through every project setting ```bash watchfire configure ``` Opens the interactive prompt for each row in the configurable-settings table — sandbox mode, auto-merge, auto-delete-branch, auto-start-tasks, default agent, project color. Press Enter to keep the existing value. ### Toggle auto-merge for the current project ```bash watchfire config ``` When prompted for **Auto-merge**, set it to `false` if you want completed tasks to stay on their `watchfire/` branches for review (typically paired with the [GitHub auto-PR adapter](/docs/concepts/integrations#github-auto-pr)). All other settings keep their existing values. ### What `project.yaml` looks like after configuring ```yaml project_id: a1b2c3d4-e5f6-7890-abcd-ef1234567890 name: my-project default_agent: codex sandbox: auto auto_merge: false auto_delete_branch: true auto_start_tasks: true ``` The CLI rewrites these fields in place. Other project keys (like `definition` and `next_task_number`) are preserved. ## Common pitfalls - **Looking for global settings here** — `watchfire configure` only edits the per-project file. *Fix:* global default agent and custom binary paths live in `~/.watchfire/settings.yaml` and are managed from the GUI Settings panel. - **Running session not affected** — flipping `auto_merge` mid-task does not retroactively merge the running task. *Fix:* settings only take effect for new agent sessions; finish or cancel the active session first. - **Default agent picker shows unexpected backends** — only `claude-code`, `codex`, `opencode`, `gemini`, and `copilot` are valid. *Fix:* if your shell has a custom binary, set the path in [GUI Settings → Agents](/docs/components/gui#global-settings) instead of inventing a new agent name. --- ## watchfire integrations Source: https://watchfire.io/docs/commands/integrations Manage the [Integrations](/docs/concepts/integrations) layer — outbound adapters (webhook, Slack, Discord, GitHub auto-PR) and the inbound HTTP server that lets external services drive the daemon back. The parent command groups every integrations-related subcommand. ## Usage ```bash watchfire integrations ``` The CLI is the scriptable surface. The same operations are available interactively in the TUI ([Integrations overlay](/docs/components/cli#integrations-overlay-ctrli) on Ctrl+I) and in the GUI ([Settings → Integrations](/docs/components/gui#integrations)). ## Subcommands ### integrations list List configured outbound and inbound integrations. ```bash watchfire integrations list ``` Prints one row per integration with its name, type (outbound adapter or inbound provider), and a status pill — `OK`, `Disabled`, `Breaker open`, or `Misconfigured`. ### integrations test \ Fire a synthetic event through the named adapter to verify wiring end-to-end (signature, transport, render). ```bash watchfire integrations test slack watchfire integrations test webhook ``` Synthetic events are clearly labelled in the rendered output so they're easy to identify in the receiving channel. ### integrations register-discord \ Register or refresh the Watchfire slash-command set in a Discord guild. ```bash watchfire integrations register-discord 123456789012345678 ``` Calls Discord's bulk-overwrite endpoint with the current command schema (`/watchfire status`, `/watchfire retry `, `/watchfire cancel `). The command is **idempotent** — re-running upserts the schema without producing duplicates, so you can safely re-run after upgrading Watchfire to pick up new commands. This drives the inbound side of the integration. For the outbound Discord adapter (the one that posts task and digest envelopes into a channel) see the [Outbound section](/docs/concepts/integrations#discord-adapter) of the Integrations concept page. #### Prerequisites - The Discord application's **public key** has been saved as the inbound Discord secret (write-only) in [GUI Settings → Integrations → Inbound](/docs/components/gui#integrations) or via the equivalent TUI overlay. - The Watchfire daemon is reachable at `PublicURL` so Discord can call `POST /echo/discord/interactions`. - The Discord application has been added to the target guild with the `applications.commands` scope. ## Notes - All three subcommands are project-scoped — run them from inside a Watchfire-initialised project. - Outbound and inbound configuration live in `~/.watchfire/integrations.yaml`; secrets live in the OS keyring (with a file-store fallback) and never enter the YAML on disk. - For the on-the-wire shape, the `IntegrationsService` gRPC has `List` / `Save` / `Delete` / `Test` RPCs; `Save` carries a `oneof` payload and secret fields are write-only. ## Examples ### Audit what's currently wired ```bash watchfire integrations list ``` Sample output: ```text NAME TYPE STATUS slack outbound OK webhook outbound Disabled github inbound OK discord inbound Misconfigured ``` A `Misconfigured` pill is the cue to open [GUI Settings → Integrations](/docs/components/gui#integrations) and re-paste the secret — the CLI can't surface secret values. ### Verify a Slack adapter end-to-end ```bash watchfire integrations test slack ``` `test` fires a clearly-labelled synthetic event through the named adapter, exercising signature, transport, and renderer. Run this after editing the adapter URL or rotating a signing secret. ### Refresh Discord slash commands ```bash watchfire integrations register-discord 123456789012345678 ``` Idempotent — the command upserts the schema, so re-run it after every Watchfire upgrade to pick up new slash commands without producing duplicates. ## Common pitfalls - **`integrations test` fails with `Breaker open`** — the adapter has tripped its circuit breaker after repeated upstream failures. *Fix:* address the upstream issue (auth, URL), then wait for the breaker to half-open, or restart the daemon with [`watchfire daemon stop` / `start`](/docs/commands/daemon). - **Discord interactions return 401** — `register-discord` succeeded but Discord still rejects requests. *Fix:* re-paste the Discord public key into [GUI Settings → Integrations](/docs/components/gui#integrations); secrets are write-only and can be replaced but not read back. See [Webhook signature verification fails](/docs/troubleshooting#webhook-signature-verification-fails). - **Subcommand run outside a project** — `integrations` is project-scoped and exits if the cwd isn't a Watchfire project. *Fix:* `cd` into a project initialised with [`watchfire init`](/docs/commands/init). --- ## watchfire status Source: https://watchfire.io/docs/commands/status Show current project status including active agents, task progress, and daemon connection. ## Usage ```bash watchfire status ``` ## Description `watchfire status` displays a summary of the current project's state. It connects to the daemon and reports on active agent sessions, task progress, and connection details. ## Output The status output includes: | Field | Description | |-------|-------------| | **Project name** | Name of the current project | | **Daemon connection** | Whether the daemon is running and reachable | | **Active agents** | Currently running agent sessions for this project, including which backend (e.g. `claude-code`, `codex`, `opencode`, `gemini`, `copilot`) each session is using | | **Task summary** | Count of tasks by status (draft, ready, done) | ## Notes - Requires being inside a Watchfire project directory - The daemon must be running to retrieve full status information - For daemon-specific status (host, port, PID, uptime), use `watchfire daemon status` ## Examples ### Quick check on the current project ```bash watchfire status ``` Sample output: ```text project: my-project daemon: connected (127.0.0.1:37291) agents: - task 3 — claude-code (5m running) tasks: 2 draft, 4 ready, 12 done ``` The `agents` block lists the backend each active session is using — useful for confirming a task picked up the agent you expected. ### Status across all projects ```bash watchfire daemon status ``` `watchfire status` is project-scoped (current directory). For a daemon-wide view including total active sessions, fall through to [`watchfire daemon status`](/docs/commands/daemon#daemon-status) or open the [Beacon dashboard](/docs/components/gui#dashboard) in the GUI. ## Common pitfalls - **`connection refused` outside a project** — `watchfire status` errors when the cwd isn't a Watchfire-initialised project. *Fix:* `cd` into the project root, or initialise it with [`watchfire init`](/docs/commands/init). - **Daemon not running** — output reports `daemon: not connected` and the agents/tasks sections are empty. *Fix:* see [CLI/TUI can't connect to daemon](/docs/troubleshooting#cli-tui-cant-connect-to-daemon). - **Status doesn't show a "running" task** — Watchfire has no `running` task state, only `draft`/`ready`/`done`; an in-flight task still shows as `ready` until it completes. *Fix:* check the `agents` block for which task is actually being worked on, or open the TUI/GUI for live progress. --- ## watchfire metrics Source: https://watchfire.io/docs/commands/metrics Manage the per-task metrics records that feed [Insights](/docs/concepts/insights) and the weekly digest. The parent command groups metrics-related subcommands; today the only subcommand is `backfill`. ## Usage ```bash watchfire metrics ``` ## Subcommands ### metrics backfill Walk every registered project and write `.metrics.yaml` for any completed task that doesn't already have one. ```bash watchfire metrics backfill ``` #### What It Does 1. Loads the projects index from `~/.watchfire/projects.yaml` 2. For each registered project, iterates tasks with `status: done` 3. Writes a duration-only `.metrics.yaml` next to each task YAML 4. Skips tasks that already have a metrics file (idempotent) A line per project is printed during the run, and a summary at the end: ``` my-project 12 written, 3 skipped another-project 4 written, 0 skipped Backfill complete: 2 projects, 16 metrics written, 3 skipped ``` #### When To Run It - After upgrading from a pre-Beacon version (v3.x and earlier), so historical tasks show up in Insights and digests - After switching agent backends, so the new backend has a baseline to chart against The command is idempotent — re-running is safe and only touches tasks that don't yet have metrics. Use `--force` only when you want to overwrite existing records (for example, after correcting a bug in a metrics parser). #### Caveat — Backfilled Records Are Duration-Only Backfilled records are **duration-only**: `tokens_in`, `tokens_out`, and `cost_usd` remain nil because the original session logs may already be rotated by the time you run the backfill. Live captures (post-upgrade) populate those fields. This is the same partial-data signal the cross-project rollup tracks via `tasks_missing_cost` — Insights surfaces a banner when a meaningful slice of records is missing cost so the rollup is never misread as a complete total. #### Flags | Flag | Default | Description | |------|---------|-------------| | `--force` | `false` | Overwrite existing `.metrics.yaml` files | ## Examples ### Backfill after upgrading ```bash watchfire metrics backfill ``` Sample output: ```text my-project 12 written, 3 skipped another-project 4 written, 0 skipped Backfill complete: 2 projects, 16 metrics written, 3 skipped ``` Run once after upgrading from a pre-Beacon version so historical tasks show up in [Insights](/docs/concepts/insights) and the weekly digest. The "skipped" count is tasks that already had a metrics file — backfill is idempotent. ### Re-write every metrics file ```bash watchfire metrics backfill --force ``` `--force` overwrites existing `.metrics.yaml` files. Reach for it only after a metrics-parser bug fix, when the existing records are known-bad. ## Common pitfalls - **Backfilled records are duration-only** — `tokens_in`, `tokens_out`, and `cost_usd` stay nil because the original session logs may be rotated by the time you backfill. *Fix:* nothing to do — Insights surfaces a banner when too many records lack cost; live captures going forward populate the missing fields. - **No projects in the index** — backfill prints nothing because `~/.watchfire/projects.yaml` is empty. *Fix:* run [`watchfire init`](/docs/commands/init) inside each project so it registers with the daemon, then re-run backfill. - **`--force` discards good data** — re-writing a metrics file that had live token/cost data replaces it with a duration-only record. *Fix:* only use `--force` when you actually want the existing records overwritten — for routine backfills, leave it off. --- ## watchfire update Source: https://watchfire.io/docs/commands/update Self-update Watchfire by downloading the latest release from GitHub. ## Usage ```bash watchfire update ``` ## Description `watchfire update` checks for and installs the latest version of Watchfire. It queries the GitHub Releases API, downloads architecture-specific binaries, and replaces the current installation. ## What It Does 1. Queries the GitHub Releases API for the latest version 2. Downloads architecture-specific binaries (CLI and daemon) 3. Stops the daemon if it's currently running 4. Atomically replaces the CLI (`watchfire`) and daemon (`watchfired`) binaries 5. Restarts the daemon ## Notes - Requires network access to reach GitHub Releases - The daemon is automatically stopped and restarted during the update - Both the CLI and daemon binaries are updated together - The GUI application updates separately via its built-in `electron-updater` ## Examples ### Update to the latest release ```bash watchfire update ``` Stops the daemon if it's running, atomically swaps both the `watchfire` CLI and `watchfired` daemon binaries, then restarts the daemon. The GUI updates separately via its built-in `electron-updater`. ### Verify the version after updating ```bash watchfire version ``` Run before and after `watchfire update` to confirm the new release was actually applied. If the version is unchanged, fall through to [`watchfire update` failure](/docs/troubleshooting#watchfire-update-failure). ## Common pitfalls - **Permission denied on binary swap** — the update fails on a path the current process can't overwrite (typically `/usr/local/bin/`). *Fix:* re-run with the right permissions, or fall back to a source install — see [Installation → Build from Source](/docs/installation#build-from-source). - **Daemon does not restart cleanly** — the CLI is updated but `watchfire daemon status` reports the daemon is down. *Fix:* run `watchfire daemon start` manually, or `rm ~/.watchfire/daemon.yaml` if a stale connection-info file is in the way. - **GUI still shows the old version** — `watchfire update` only updates the CLI/daemon. *Fix:* the `Watchfire.app` bundle has its own auto-updater; install the latest `.dmg`/`.AppImage` from [GitHub Releases](https://github.com/watchfire-io/watchfire/releases) if it hasn't picked up the update yet. --- ## How Watchfire compares Source: https://watchfire.io/docs/compare If you are evaluating Watchfire, you are almost certainly weighing it against another way to put a coding agent to work. This page is the short, honest version: what Watchfire is for, what the alternatives are good at, and which workflow each one fits. Where Watchfire would be the wrong choice, we say so. ## Where Watchfire fits Watchfire is an **orchestrator for coding-agent CLIs** — Claude Code, OpenAI Codex, opencode, Gemini CLI, and GitHub Copilot CLI today. It is not itself an agent. It runs the agent you already trust inside a [git worktree](/docs/concepts/worktrees), in a [platform-native sandbox](/docs/concepts/sandboxing), against a structured task file. Each project runs one agent at a time; [many projects run concurrently](/docs/concepts/agent-modes) under a single daemon. The rest of this page compares Watchfire to adjacent tools across the axes that actually differ between them: - **Agent backend** — which model/agent runs the work. - **Isolation model** — what protects your repo and credentials. - **Parallelism** — can multiple tasks run at once. - **Autonomy** — does it ask before it acts, or run a loop. - **Hosting** — local machine vs. cloud. - **Licensing** — open source vs. proprietary; pricing model. ## At-a-glance | Axis | Watchfire | Raw agent CLI | Aider | Cursor agents | Copilot Workspace | Cloud autonomous (Devin / Codegen / Replit Agent) | | --- | --- | --- | --- | --- | --- | --- | | Agent backend | Pluggable: Claude Code, Codex, opencode, Gemini, Copilot CLI | Pinned to that CLI | Many models via API key | Cursor's models / your key | GitHub-hosted models | Vendor-managed | | Sandbox / isolation | Seatbelt (macOS), Landlock/Bubblewrap (Linux); per-task git worktree | Inherits your shell, full FS, full creds | Inherits your shell | Editor-scoped | GitHub-hosted VM | Vendor VM | | Git workflow | Branch + worktree per task; review-then-merge | Edits your working tree | Auto-commits in working tree | Edits your working tree | PR-based | PR-based | | Parallel tasks | One per project, many projects concurrently | One terminal at a time | One session at a time | Limited | One session per task | Yes (vendor-side) | | Local vs cloud | Local (your machine) | Local | Local | Local + cloud sync | Cloud | Cloud | | Multi-project | First-class | Per terminal | Per repo | Per workspace | Per repo | Per repo | | Open source | Yes (Apache-2.0) | Varies (Codex CLI yes; others mixed) | Yes (Apache-2.0) | No | No | No | | Pricing model | Free (you pay your model API) | Free (you pay your model API) | Free (you pay your model API) | Subscription | Subscription | Per-task / subscription | Cells are intentionally terse; the per-tool sections below add nuance. ## Running a coding-agent CLI on its own This is the baseline: open a terminal, run `claude`, `codex`, `opencode`, `gemini`, or `gh copilot` directly in your repo and start typing prompts. It is what most developers reach for first, and for many tasks it is the right tool. ### Where it shines - **Lowest possible friction** for a single, scoped change in a repo you have already opened. - **Direct access** to whatever the CLI's latest features are — nothing is wrapped or filtered. - **Free** beyond your model API costs. ### Where Watchfire is different - The raw CLI edits your **working tree directly**. Watchfire runs the same CLI inside a per-task `watchfire/` branch on its own worktree, so a failed run never touches `main`. - The raw CLI inherits your shell's `PATH`, `~/.ssh`, cloud credentials, and any `.git/hooks` you have installed. Watchfire blocks those by default via Seatbelt or Landlock. - The raw CLI is **one terminal at a time**. Watchfire runs one agent per project but coordinates many projects concurrently from the same daemon. - Scrollback is the only record. Watchfire captures clean per-task transcripts and an inline diff viewer. ### Use Watchfire if… …you want to run more than one agent at once, want platform-level isolation between the agent and your credentials, or want a written task contract you can review before merging. If you only ever run one prompt at a time on a throwaway repo, the raw CLI is fine. ## Aider Aider is an open-source pair-programming CLI that talks to many models via API key. It is one of the original "agent in your terminal" tools and remains a strong choice for interactive edits. ### Where it shines - **Tight model flexibility** — bring your own API key for many providers; switch models per session. - **Conversational editing loop** with built-in `/diff`, `/undo`, and auto-commit. Excellent for short, exploratory work. - **Open source** under Apache-2.0; mature community. ### Where Watchfire is different - Aider is itself an agent — it speaks model-API directly. Watchfire is one layer up: it drives whichever **agent CLI** you prefer (including ones that wrap multiple models themselves), and is agnostic about which model is on the other end. - Aider runs in your working tree and auto-commits. Watchfire isolates each task on its own worktree branch and waits for you to merge. - Aider is one interactive session per terminal. Watchfire runs one agent per project across many projects concurrently, with optional autonomous loops via [Wildfire mode](/docs/concepts/agent-modes). - Aider does not sandbox the host filesystem. ### Use Watchfire if… …you want to keep using a richer agent CLI (Claude Code, Codex, opencode) rather than a thin model wrapper, or you want isolation and parallelism on top of whatever agent you pick. Use Aider if you want a single tight conversational loop and direct model control. ## Cursor agents Cursor's agent mode runs inside the Cursor editor: you select a scope, describe the change, and the agent edits files directly in your editor session, with diffs and apply/reject controls. ### Where it shines - **In-editor experience** is unmatched for short, targeted edits where you want to see and accept changes inline immediately. - **Codebase-aware** retrieval and chat woven into the same surface you already use to write code. - **Low overhead** for one-off edits — no separate process, no separate review surface. ### Where Watchfire is different - Cursor agents shine for **in-editor edits**. Watchfire is built for **long-running, terminal-driven, parallel work** — tasks that may take minutes, run while you do something else, and benefit from review-after-the-fact rather than accept-as-you-go. - Cursor is a closed-source subscription editor. Watchfire is Apache-2.0 and runs with whatever editor (or no editor) you prefer. - Cursor edits your working tree. Watchfire edits a per-task worktree. - Cursor runs one agent at a time. Watchfire runs one agent per project but many projects concurrently. ### Use Watchfire if… …your work is task-shaped (a written prompt, success criteria, you want to review the result later) rather than edit-shaped (you are in the file right now and want it changed). Use Cursor if your unit of work is a single edit you are watching happen. ## GitHub Copilot Workspace Copilot Workspace is GitHub's task-driven coding agent. You describe what you want, it produces a plan and a PR, and you review and merge on github.com. ### Where it shines - **Tight GitHub integration** — issues in, PR out, no local setup. - **Hosted execution** — nothing to install or run on your machine. - **Plan-first UX** with a step before the code lands. ### Where Watchfire is different - Copilot Workspace is **cloud-hosted and GitHub-coupled**. Watchfire runs **entirely on your machine**; private repos never leave it and you do not pay for hosted compute. - Copilot Workspace runs GitHub-provided models. Watchfire runs whichever agent CLI (and therefore whichever model) you pick, including local-only ones if your CLI of choice supports them. - Copilot Workspace is **one task per session in a hosted VM**. Watchfire runs one agent per project locally with many projects active concurrently from one daemon, with per-task transcripts and an [inline diff viewer](/docs/components/gui). - Copilot Workspace is closed-source and subscription-priced. Watchfire is open source. ### Use Watchfire if… …you need to keep code on your machine, want to choose your agent and model freely, or want to drain task queues across multiple local projects concurrently. Use Copilot Workspace if your repo lives on GitHub and you want a hosted, plan-first PR loop with no local install. ## Cloud-hosted autonomous agents (Devin, Codegen, Replit Agent, …) This category covers vendor-hosted, end-to-end autonomous coders. You describe a task, the vendor runs an agent in their VM with their tools and their browser, and you receive a PR or a deployed app. ### Where they shine - **Highest level of autonomy** — they can run for long stretches, use a browser, install dependencies, and ship a PR end-to-end. - **Fully managed** — no local runtime, no agent CLI to install, no sandbox to maintain. - **Tooling included** — vendor provides the VM, the browser, the shell, and the model bundled together. ### Where Watchfire is different - Cloud autonomous agents run **on the vendor's infrastructure**. Your code, prompts, and artefacts live there. Watchfire runs on your laptop or workstation; nothing leaves it unless you ship it. - These products are typically **per-task or per-seat priced** with vendor-managed models. Watchfire has no service fee — you pay only the model API costs of whichever CLI you use. - They are closed source. Watchfire is Apache-2.0 — you can read, fork, and audit every line of the orchestration layer. - They optimise for **maximum autonomy with vendor guardrails**. Watchfire optimises for **isolation, cross-project concurrency, and review** of agents you already use, with autonomy you can opt into via [Wildfire mode](/docs/concepts/agent-modes) and turn off. ### Use Watchfire if… …you want to keep code, prompts, and credentials on your machine, or you want to keep using the agent CLI you already trust rather than a vendor-bundled one. Use a cloud autonomous agent if you want end-to-end "give it a ticket, get a PR" with no local setup and are comfortable with a hosted runtime. ## What Watchfire is not - **Not a coding agent itself.** It runs your chosen agent CLI; the intelligence is in the CLI, not in Watchfire. - **Not a hosted service.** The daemon, worktrees, and sandboxes all run on your machine. - **Not an IDE extension.** Watchfire is terminal-first (CLI/TUI) with an optional Electron GUI; it does not replace your editor. - **Not a replacement for code review.** Worktree isolation and diff transcripts make review faster and safer; they do not skip it. ## Why Watchfire - **Worktree isolation** — every task runs on its own `watchfire/` branch in a dedicated worktree, so failed or half-done runs never touch `main`. [Read more](/docs/concepts/worktrees). - **Sandboxed execution** — Seatbelt on macOS, Landlock or Bubblewrap on Linux; `~/.ssh`, credential stores, and `.git/hooks` are blocked by default. [Read more](/docs/concepts/sandboxing). - **Cross-project concurrency** — one agent per project, but many projects at once from a single daemon, with optional autonomous loops through Wildfire. [Read more](/docs/concepts/agent-modes). ## Have a correction? This page is hand-written and competitor tooling moves fast. If you spot something inaccurate or out of date — especially about a tool other than Watchfire — please open a PR. Use the **Edit this page on GitHub** link at the bottom of this page; it points straight at `content/docs/compare.mdx`. Honest, fair updates are welcome. --- ## Contributing Source: https://watchfire.io/docs/contributing Watchfire is open source under Apache-2.0, and contributions of every size are appreciated — a typo fix, a sharper sentence in the docs, a reproducible bug report, or a fully-baked feature PR. If you've used Watchfire enough to notice something off, you already have everything you need to contribute. ## Ways to contribute You don't have to write Go to help out. Pick whichever of these fits the energy you have today: - **Report a bug** — open an issue from the [bug report template](https://github.com/watchfire-io/watchfire/issues/new/choose). Steps to reproduce + what you expected to happen are enough. - **Suggest a feature** — start a thread in [GitHub Discussions](https://github.com/watchfire-io/watchfire/discussions) so the idea can be shaped before it becomes an issue. - **Improve the docs** — every page on this site has an "Edit this page on GitHub" link in the right sidebar that drops you straight into the MDX source on GitHub. The docs live in the [website repo](https://github.com/watchfire-io/website) under `content/docs/`. - **Write code** — the [`good first issue`](https://github.com/watchfire-io/watchfire/labels/good%20first%20issue) label marks issues that are scoped small enough to land without deep daemon knowledge. The [`help wanted`](https://github.com/watchfire-io/watchfire/labels/help%20wanted) label is the next step up. - **Help others** — answering a question in [Discussions](https://github.com/watchfire-io/watchfire/discussions) or triaging a stale [issue](https://github.com/watchfire-io/watchfire/issues) is just as load-bearing as code. ## Code contribution workflow Quick version of the flow — for the canonical version see [`CONTRIBUTING.md`](https://github.com/watchfire-io/watchfire/blob/main/CONTRIBUTING.md) in the repo: 1. **Fork** [`watchfire-io/watchfire`](https://github.com/watchfire-io/watchfire) and clone your fork locally. 2. **Branch** off `main`: `git checkout -b my-feature`. 3. **Change** the code, run `make test` and `make lint`, commit with a meaningful message. 4. **Push** the branch to your fork. 5. **Open a PR** against `main`. Fill out the PR template, link the issue if there is one, and request review. > This page is a starting point, not a substitute. The > [`CONTRIBUTING.md`](https://github.com/watchfire-io/watchfire/blob/main/CONTRIBUTING.md) > in the repo is the canonical reference and is kept in sync with the build > — if anything here disagrees with that file, that file wins. ## Development setup Watchfire is written in Go. To build it from source you need: - **Go 1.23+** - **Make** - **Protocol Buffers compiler** (`protoc`) The three Make targets that cover day-to-day work: ```bash make build # Build daemon + CLI into ./build/ make test # Run the test suite make lint # Run golangci-lint ``` For the rest — installing dev tools, running the daemon under `air`, generating protobuf code, packaging — see the [`CONTRIBUTING.md`](https://github.com/watchfire-io/watchfire/blob/main/CONTRIBUTING.md) and the [`Makefile`](https://github.com/watchfire-io/watchfire/blob/main/Makefile) in the repo. The [Installation](/docs/installation#build-from-source) page also has a short build-from-source block aimed at users rather than contributors. ## Code guidelines - Run `gofmt` (or `goimports`) before committing. - Keep functions focused and small; favour readability over cleverness. - Write meaningful commit messages — explain the *why*, not just the *what*. - Add tests for new functionality, especially anything touching the daemon or the agent runner. - `make lint` must pass before you open a PR. ## Website performance budget The marketing and docs site (this site, in the [`website` repo](https://github.com/watchfire-io/website)) has a small set of performance targets. They are *targets*, not promises — measured at build time on a production build, and they will drift slowly as the site grows. | Surface | Target | |---------|--------| | Landing page (`/`) initial JS | **≤ 210 KB gzipped** (≤ 700 KB raw) | | Long doc page (e.g. `/docs/changelog`) initial JS | **≤ 260 KB gzipped** (≤ 830 KB raw) | | Largest Contentful Paint on `/` | **< 2.5 s** on simulated Slow 4G | | Cumulative Layout Shift on `/`, `/docs`, `/docs/recipes` | **< 0.05** | | Public PNG screenshots | None — use WebP via `` with explicit `width` and `height` | Run `npm run perf` from the website repo to print the per-route initial JS payload from a clean production build. The script wraps `next build`, boots the production server, and tallies the unique `/_next/static/chunks/*.js` files referenced by each route's HTML — so adding a new client component, eagerly importing a heavy library, or swapping a server component for a client one will surface in the diff. When working on the site: - Keep components server-rendered by default. Reach for `"use client"` only when you need state, effects, or browser APIs. - Defer heavy client components below the fold with `next/dynamic` (`ssr: false` if no SEO content depends on the component). - New images go in `public/` as WebP, referenced via `` (or `next/image`) with explicit `width` and `height` to keep CLS at zero. - Wrap any new continuous animation in a `@media (prefers-reduced-motion: reduce)` opt-out. - The site has **no runtime dependencies** beyond what's in `package.json` today. If you genuinely need a build-time tool to measure or analyze, add it as a `devDependency` only and call it out in the PR description. ## Code of Conduct Watchfire follows the [Contributor Covenant v2.1](https://github.com/watchfire-io/watchfire/blob/main/CODE_OF_CONDUCT.md). The pledge in one sentence: > We as members, contributors, and leaders pledge to make participation in > our community a positive experience for everyone, regardless of > background or identity. Conduct concerns can be reported privately to [info@watchfire.io](mailto:info@watchfire.io). ## Community Watchfire's project conversations happen on GitHub: - **[GitHub Issues](https://github.com/watchfire-io/watchfire/issues)** — bug reports and tracked work. - **[GitHub Discussions](https://github.com/watchfire-io/watchfire/discussions)** — open-ended questions, design conversations, and Q&A. There is no Discord, Slack, or mailing list yet. If those become useful they'll be linked from this page. ## Where to find me Watchfire is primarily authored by [Nuno Coração](https://github.com/nunocoracao). Every PR opened against the repo gets a response — even a small one — so don't worry about whether your change is "big enough." If something is blocking you, ping the PR or open a discussion and it will get picked up. --- ## Security Source: https://watchfire.io/docs/security Watchfire runs arbitrary AI processes against your source tree. This page lays out the threat model the daemon is designed against, the technical mechanisms that back each guarantee, and where the limits are. It is a coordinator — the deeper details live on the [Sandboxing](/docs/concepts/sandboxing), [Secrets](/docs/concepts/secrets), and [Integrations](/docs/concepts/integrations) pages, and the canonical vulnerability disclosure policy lives in [`SECURITY.md`](https://github.com/watchfire-io/watchfire/blob/main/SECURITY.md) on GitHub. For data flow off your machine and the website's own analytics, see [/privacy](/privacy). > The agent itself is still an arbitrary AI process. The sandbox limits filesystem reach and credential exposure — it does not reason about whether a tool call is a good idea. Review what an agent has done before merging it. ## Trust boundaries Watchfire has four boundaries worth naming explicitly: | Boundary | Trust direction | Mechanism | |----------|-----------------|-----------| | **User → daemon** | Trusted | gRPC + gRPC-Web on a loopback-bound port; connection info in `~/.watchfire/daemon.yaml` | | **Daemon → agent process** | Untrusted (the agent is arbitrary code on your behalf) | PTY child process wrapped in a platform sandbox; environment scrubbed of daemon-internal vars | | **Daemon → on-disk task / project files** | Trusted (you own them) | File watcher (fsnotify) + 5-second polling fallback; gitignored `.watchfire/` directory | | **Daemon ↔ external integrations** | Untrusted both ways | Outbound: HMAC-signed POSTs and provider-specific adapters. Inbound: signature-verified HTTP server with replay protection | What is **in scope** for the daemon to defend against: - An agent reading or exfiltrating credentials in `~/.ssh`, `~/.aws`, `~/.gnupg`, `.env`, etc. - An agent installing a `.git/hooks` payload that runs on the next commit (macOS only — see below). - A forged inbound webhook impersonating Slack, Discord, or GitHub to drive the daemon. - A replayed inbound delivery executing the same slash command twice. What is **not in scope**: - The agent's reasoning. If a task prompt asks the agent to delete files inside the worktree, it will. - A locally-privileged user who can attach a debugger, inspect the daemon's memory, or read the OS keyring directly. - A network-level attacker who can intercept loopback traffic on a multi-user machine. ## Sandbox guarantees Every agent process runs inside a platform-specific sandbox. The full backend matrix and per-platform deny rules live on the [Sandboxing page](/docs/concepts/sandboxing); the short version is below. | Platform | Backend | Enforcement | Pattern-based denials (`.env`, `.git/hooks`) | |----------|---------|-------------|-----------------------------------------------| | **macOS** | Seatbelt (`sandbox-exec`) | Kernel | Yes (regex) | | **Linux 5.13+** | Landlock LSM | Kernel | No (path-only) | | **Linux (older)** | Bubblewrap (`bwrap`) | Mount namespace | No (path-only) | | **Linux (no sandbox)** | None | — | — | | **Windows** | None | — | — | Across every backend that is active, read **and** write access is denied to `~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.netrc`, and `~/.npmrc`. The daemon also strips internal environment variables (such as `CLAUDECODE`) before exec, so agents always start with a clean environment. > The sandbox story is asymmetric across platforms. Seatbelt supports regex patterns and blocks `.env` files and `.git/hooks` directly; Landlock and Bubblewrap operate on paths, so those two patterns are **not** enforced on Linux. Windows has no sandbox at all — agents run with the daemon user's full permissions. If you are evaluating Watchfire for a host where this asymmetry matters, prefer macOS or Linux 5.13+. ## Signature verification (inbound) Beacon's inbound HTTP server (`internal/daemon/echo`) authenticates every request before dispatching it. Three constant-time verifiers are shipped, one per upstream: | Verifier | Algorithm | Replay protection | |----------|-----------|-------------------| | `VerifyGitHub` | HMAC-SHA256 over the raw body, header `sha256=` | None at the verifier (GitHub uses delivery IDs) | | `VerifySlack` | HMAC-SHA256 over `v0::` | 5-minute timestamp drift window | | `VerifyDiscord` | Ed25519 over `timestamp \|\| body` | 5-minute timestamp drift window | All three use constant-time comparisons so a signature mismatch leaks no timing signal. Authenticated deliveries then pass through an in-process LRU+TTL idempotency cache (1000 entries, 24-hour TTL) that drops duplicate deliveries — the same Discord interaction id will not run a slash command twice. The full setup walkthrough — how to register webhook URLs, where to copy the signing secrets, what to put in `InboundConfig` — is on the [Integrations page](/docs/concepts/integrations#signature-verification). ## Outbound signing Watchfire authenticates itself to receivers as well. The generic webhook adapter signs every POST with: ``` X-Watchfire-Signature: sha256= ``` The signature is an HMAC-SHA256 over the raw request body using the per-webhook secret you configured. Receivers can verify the header to confirm the call came from your Watchfire daemon and not a third party. Slack, Discord, and the GitHub auto-PR adapter use their respective provider-native auth (bot tokens, webhook URLs, `gh` CLI auth) instead of `X-Watchfire-Signature`. ## Secret storage Adapter credentials and inbound signing secrets are persisted in the OS keyring through `internal/config/keyring.go`, with a file-store fallback for hosts without a keyring backend. The fallback is gated by a clear warning at startup so you know which path is in use. The `IntegrationsService.Save` gRPC has a deliberate **write-only-on-the-wire** property: the GUI and TUI can save and replace secrets, but never read existing values back over gRPC. Secrets are present in agent system prompts (via `.watchfire/secrets/instructions.md`) and on disk in the keyring or fallback store, but they do not round-trip through the daemon's RPC surface. See the [Secrets page](/docs/concepts/secrets) for details on how secrets are surfaced to agents. ## Network exposure The daemon binds two listeners. Both are loopback-only by default; opting into broader exposure is an explicit configuration step. | Listener | Default bind | How to expose | |----------|--------------|----------------| | **gRPC + gRPC-Web** | `localhost` on a dynamically-assigned port (recorded in `~/.watchfire/daemon.yaml`) | Place a reverse proxy in front of the announced port | | **Echo HTTP server** (inbound integrations) | `127.0.0.1:8765` | Override `ListenAddr` in `InboundConfig` and front it with a TLS-terminating proxy | `/echo/health` is the only unauthenticated endpoint on the inbound server; per-provider handlers return `503 Service Unavailable` until their signing secret is configured, so an empty `InboundConfig` opens no attack surface beyond a liveness probe. An empty `InboundConfig` skips the listener entirely — the daemon does not bind a port until at least one provider is wired. > Exposing either listener to the public internet is opt-in and your responsibility. The daemon's gRPC server is **not** designed to be reachable from untrusted networks — use a reverse proxy with TLS and authentication if you need remote access. ## Reporting a vulnerability > **Do not file public GitHub issues for security vulnerabilities.** Public disclosure before a fix is available puts every Watchfire user at risk. If you find a vulnerability, email **[security@watchfire.io](mailto:security@watchfire.io)** with a description, reproduction steps, and any impact you have identified. The published timeline is: - **Acknowledgment** within 48 hours of the report. - **Initial assessment** within 1 week. - **Fix and coordinated disclosure** targeted within 30 days, depending on complexity. Reporters are credited in release notes unless they prefer anonymity. The canonical version of this policy lives in [`SECURITY.md`](https://github.com/watchfire-io/watchfire/blob/main/SECURITY.md) in the repo — if anything on this page disagrees with that file, that file wins. --- ## Troubleshooting Source: https://watchfire.io/docs/troubleshooting A scannable answer to "something broke — what now?" Each section names a symptom, the most likely cause, and a fix you can paste into a terminal. For the bigger-picture model behind any of these, follow the cross-links into [Architecture](/docs/concepts/architecture), [Sandboxing](/docs/concepts/sandboxing), [Security](/docs/security), and the matching [command reference](/docs/commands/init). If your problem isn't here, jump to [Where to ask for help](#where-to-ask-for-help). ## Daemon won't start The CLI says `daemon failed to start within timeout`, or `watchfired` exits the moment you launch it. ### Cause The daemon is started silently in the background by the CLI and the GUI, so a startup failure (binary missing, port collision on the inbound HTTP server, stale state file with a live PID) is invisible until you run it yourself in the foreground. ### Fix Run the daemon directly so its stderr lands in your terminal: ```bash watchfired --foreground ``` If startup output complains that the [inbound HTTP server](/docs/concepts/integrations#inbound) can't bind, another process is already on `127.0.0.1:8765` — set a different `ListenAddr` in `InboundConfig`, or stop the conflicting process. If startup is wedged on a stale `~/.watchfire/daemon.yaml` (the file that records the running daemon's host, port, and PID — see [Architecture → Network](/docs/concepts/architecture#network)), remove it and start clean: ```bash rm ~/.watchfire/daemon.yaml watchfire daemon start ``` Watchfire normally clears this file itself on launch when the recorded PID is gone, so only delete it manually if the daemon refuses to come back up. ## CLI/TUI can't connect to daemon `watchfire status` (or any project-scoped command) fails with `connection refused` or hangs without producing output. ### Cause The daemon process exited but the connection-info file is still around, or the PID in `~/.watchfire/daemon.yaml` points at a different process on a recycled PID. ### Fix Confirm the daemon's view of itself first: ```bash watchfire daemon status ``` If it reports the daemon is not running, start it explicitly: ```bash watchfire daemon start ``` If it reports a port and PID but the CLI still can't connect, the recorded PID is stale. Stop and restart cleanly: ```bash watchfire daemon stop watchfire daemon start ``` See [`watchfire daemon`](/docs/commands/daemon) for what each subcommand does, and the [daemon command pitfalls](/docs/commands/daemon#common-pitfalls) for related quick fixes. ## GUI shows "no daemon" `Watchfire.app` opens, but the project list is empty and the header shows the daemon as unreachable. ### Cause The GUI launches `watchfired` itself, walking `PATH` first and then falling back to bundled and common install paths. If none resolve to a working binary, the GUI surfaces the connection error without launching anything. ### Fix Confirm the binary is actually on `PATH` from a normal terminal: ```bash which watchfired watchfired --version ``` If `which` finds nothing, install or reinstall Watchfire — see [Installation](/docs/installation). The GUI also checks `/opt/homebrew/bin/watchfired` and `/usr/local/bin/watchfired` as last-resort fallbacks, so a Homebrew install is enough on macOS. If the binary exists but the GUI still won't start it, launch the daemon yourself before opening the app: ```bash watchfire daemon start ``` The GUI will pick up the running daemon on its next reconnect attempt. The [Daemon component page](/docs/components/daemon#network) covers how clients discover the running daemon. ## Tasks stuck after a crash A task's `agent_sessions` counter is non-zero and `started_at` is set, but no agent is running and `status` is still `ready` (or a stuck `draft` you didn't write). ### Cause If the daemon crashes mid-task, the daemon process terminates without marking the task `done`. Watchfire's [task statuses](/docs/concepts/projects-and-tasks#task-lifecycle) are only `draft`, `ready`, or `done` — there is no `running` status to clean up, but a task that started can sit half-finished if no one restarts it. ### Fix Open the task YAML at `/.watchfire/tasks/.yaml` (the path is detailed in [Projects and Tasks → Directory Structure](/docs/concepts/projects-and-tasks#directory-structure)) and verify `status: ready`. Then re-run it: ```bash watchfire run ``` If the task should be skipped instead, set `status: done` and `success: false` with a `failure_reason`. If you want the agent to review and re-shape it first, set `status: draft` and let the [Refine phase](/docs/commands/wildfire) pick it up on the next wildfire cycle. ## Worktree won't merge A task completes with `success: true` but the changes never land on your default branch. The task YAML has a populated `merge_failure_reason`, and a `watchfire/` branch is still hanging around with the work on it. ### Cause Auto-merge runs after the agent marks the task done. It bails when the default branch has uncommitted changes, when the merge would conflict, or when the worktree was deleted out from under the daemon. The work is fine — the merge step is what failed. ### Fix First, take stock from the project root: ```bash git status git branch --list 'watchfire/*' ``` If the default branch is dirty, commit or stash, then merge the task branch by hand: ```bash git merge watchfire/ ``` If the merge conflicts, resolve them in the **default branch checkout** — not inside `.watchfire/worktrees//`. Touching files inside the worktree directly fights the daemon's bookkeeping (it owns that directory and prunes it on cleanup — see [Git Worktree Isolation](/docs/concepts/worktrees)). Once merged, the daemon's next cleanup pass will remove the orphaned worktree. If you'd rather review every merge by hand instead of fighting the auto-merge path, set `auto_merge: false` in `.watchfire/project.yaml` ([reference](/docs/concepts/projects-and-tasks#project-settings)). The agent still finishes its work in `watchfire/`; you merge when you're ready. ## Agent backend not authenticated The agent terminal shows `Please run /login`, `OAuth token has expired`, or a `401 authentication_error`. Watchfire flags the session as needing attention. ### Cause Watchfire reuses each backend's own login. It does not store API keys of its own and does not paper over expired sessions — when the backend's auth fails, the session fails. Watchfire detects this from the agent's terminal output and surfaces it to the TUI and GUI. ### Fix Re-authenticate the affected backend in its own CLI, outside Watchfire: | Backend | Re-auth flow | |---------|--------------| | **Claude Code** | Run `claude` and use the in-app `/login` command, or follow the Claude Code login flow | | **OpenAI Codex** | Run `codex` and complete the Codex login prompt | | **opencode** | Sign in via opencode's normal flow so `~/.config/opencode/` is current | | **Gemini CLI** | Re-auth Gemini CLI directly so `~/.gemini/` reflects a valid session | | **GitHub Copilot CLI** | Sign in with `gh` or Copilot CLI so `~/.copilot/config.json` is valid | Once the backend works in its own terminal, restart the failed Watchfire session — the new auth flows in automatically because Watchfire references your real config. The full per-backend setup matrix is on [Supported Agents](/docs/concepts/supported-agents). ## Sandbox denial — "Operation not permitted" The agent prints `Operation not permitted` (macOS) or generic permission errors when it tries to write outside the worktree, write to `.env`, or touch `.git/hooks`. ### Cause The agent process runs inside a platform sandbox. macOS Seatbelt blocks `.env`, `.git/hooks`, credential dirs (`~/.ssh`, `~/.aws`, `~/.gnupg`, `~/.netrc`, `~/.npmrc`), and personal dirs (`~/Desktop`, `~/Documents`, `~/Downloads`, `~/Music`, `~/Movies`, `~/Pictures`). Linux Landlock and Bubblewrap block the same credential dirs. The full matrix is on the [Sandboxing page](/docs/concepts/sandboxing). ### Fix First, decide whether the denial is **correct**. The sandbox catches real exfiltration attempts and AI hallucinations equally well — denying a write to `~/.ssh` is a feature, not a bug. The [Security threat model](/docs/security#sandbox-guarantees) lays out what the sandbox is supposed to block. If the denial is wrong (the agent legitimately needs to touch a path outside the worktree), the fastest unblock is to run the session without the sandbox: ```bash watchfire run --no-sandbox ``` `--no-sandbox` is also wired into [`watchfire wildfire`](/docs/commands/wildfire), [`watchfire generate`](/docs/commands/generate), and [`watchfire chat`](/docs/commands/chat). The [Sandboxing → Configuration section](/docs/concepts/sandboxing#configuration) lists every flag and project-level setting. The cleaner long-term fix is usually to rewrite the task prompt so the agent does its work inside the worktree — see [Tips & Best Practices](/docs/tips) for prompt patterns that keep agents in scope. ## Wildfire mode runs forever You start `watchfire wildfire` and it never stops, even though the project feels done. ### Cause [Wildfire](/docs/commands/wildfire) only exits when **all three phases yield nothing**: no `ready` tasks to execute, no `draft` tasks to refine, and the generate phase produced no new tasks. Anything that keeps drafting (an over-eager generate phase, a refine pass that churns the same task) keeps the loop alive. Wildfire and start-all also have [restart protection](/docs/concepts/architecture#restart-protection): 3 consecutive failures on the same task drop the loop into chat mode instead of looping forever on a broken task. ### Fix Stop wildfire with **Ctrl+C** in the session that launched it. The current agent terminates gracefully and the loop exits. If wildfire keeps regenerating the same kind of work, tighten the project definition before re-running: ```bash watchfire define ``` [`watchfire define`](/docs/commands/definition) opens the definition in `$EDITOR`. A specific definition produces a specific task list — see [Tips & Best Practices](/docs/tips) for what works, and the [wildfire command pitfalls](/docs/commands/wildfire#common-pitfalls) for related quick fixes. ## Generate phase produces empty or low-quality tasks [`watchfire generate`](/docs/commands/generate) finishes but no useful tasks land in `.watchfire/tasks/`, or the generated tasks are vague. ### Cause Generate uses the project definition as its primary input. An empty or generic definition produces empty or generic tasks. ### Fix Run `watchfire generate` first to bootstrap a definition from the codebase, then refine it before generating tasks: ```bash watchfire generate # populate project.yaml definition from the codebase watchfire define # tighten it in $EDITOR watchfire plan # generate a fresh task list ``` `watchfire generate` and `watchfire plan` are both documented on the [generate command page](/docs/commands/generate) (see also [generate pitfalls](/docs/commands/generate#common-pitfalls)). The [definition command page](/docs/commands/definition) covers what belongs in a good definition. ## Beacon notifications not firing You enabled OS toasts in settings, but `TASK_FAILED` and `RUN_COMPLETE` events never reach you. ### Cause Every notification path funnels through `models.ShouldNotify`, which combines the master toggle, per-event toggle, quiet-hours window, and the per-project mute list before any toast, sound, or relay fires. Any one of those gates being off silences the event. The [GUI Notifications panel](/docs/components/gui#notifications) and the TUI globalsettings tab read and write the same fields in `~/.watchfire/settings.yaml`. ### Fix Open global settings (the GUI Settings panel, or Ctrl+g in the TUI) and confirm: - **Master notifications** is on - The specific event kind (`TASK_FAILED`, `RUN_COMPLETE`, `WEEKLY_DIGEST`) is on - **Quiet hours** doesn't currently overlap the time you're testing - The project isn't on the **per-project mute** list For the deeper Beacon model — bus, JSONL fallback log, sound gating — see the [daemon notifications section](/docs/components/daemon#desktop-notifications). ## Webhook signature verification fails An [inbound integration](/docs/concepts/integrations#inbound) (Slack, Discord, GitHub) returns `401`. The Slack or Discord upstream reports the request as rejected; your slash command never runs. ### Cause Slack and Discord both enforce a 5-minute timestamp drift window on the signed payload, and every verifier uses constant-time comparison against the configured secret. Three things break this in practice: the wrong secret was pasted into the GUI, the host clock has drifted more than 5 minutes from the upstream's clock, or the upstream's signing secret was rotated and Watchfire still has the old one. ### Fix Re-paste the signing secret from the upstream's app dashboard into the [GUI Integrations panel](/docs/components/gui#integrations) — secrets are write-only on the wire, so you can replace them but not read the current value back. Then re-send the test event from the upstream. Confirm the host clock is current — Slack and Discord both reject requests outside the 5-minute drift window: ```bash date -u ``` If the clock is off, fix it through your OS time settings; the sandbox doesn't gate clock APIs. ## `watchfire update` failure [`watchfire update`](/docs/commands/update) fails to download, fails to swap binaries, or leaves you on the old version. ### Cause `watchfire update` queries the GitHub Releases API and atomically replaces both the CLI and the daemon. It fails on no network, on a GitHub rate limit, or on a binary path the current process can't overwrite (uncommon — typically a permission issue on `/usr/local/bin/`). ### Fix Confirm GitHub Releases is reachable: ```bash curl -fsSL https://api.github.com/repos/watchfire-io/watchfire/releases/latest > /dev/null ``` If that works but the update still fails, fall back to a source install: ```bash git clone https://github.com/watchfire-io/watchfire.git cd watchfire make install ``` [Installation → Build from Source](/docs/installation#build-from-source) covers prerequisites. The GUI updates separately via its built-in `electron-updater` and is unaffected. See also the [update command pitfalls](/docs/commands/update#common-pitfalls). ## Where to ask for help Once you've ruled out the cases above: - **Reproducible bug** — [open an issue](https://github.com/watchfire-io/watchfire/issues/new/choose) using the bug report template. Include the output of `watchfire daemon status` and `watchfire status`, your OS and kernel version, the relevant log file from `~/.watchfire/logs//` (the JSONL transcript is the readable one), and the exact steps to reproduce. - **Question or open-ended discussion** — start a thread in [GitHub Discussions](https://github.com/watchfire-io/watchfire/discussions). - **Security vulnerability** — do **not** file a public issue. Follow the disclosure flow on [Security → Reporting a vulnerability](/docs/security#reporting-a-vulnerability). --- ## Tips & Best Practices Source: https://watchfire.io/docs/tips A field guide for getting Watchfire to do useful work without babysitting it. The [`task`](/docs/commands/task), [`definition`](/docs/commands/definition), [`generate`](/docs/commands/generate), and [`wildfire`](/docs/commands/wildfire) pages cover the surface area; this one covers the playbook. ## 1. Writing tasks agents can complete A task is a contract. The agent reads `title`, `prompt`, and `acceptance_criteria` and works until either the criteria are met or it gives up. Vague contracts get vague work. ### Title verbs that work Lead with a concrete verb that names a deliverable: - `Add`, `Update`, `Fix`, `Refactor`, `Remove`, `Document`, `Wire up` Avoid verbs that don't have a finished state: - `Improve`, `Polish`, `Clean up`, `Look into`, `Investigate` **Before** ```yaml title: "Improve search" ``` **After** ```yaml title: "Add fuzzy matching to the docs search index" ``` The second version tells the agent what file is in scope and what "done" looks like before it has read a single line of the prompt. ### Prompt anatomy A good prompt has four parts in this order: 1. **Context** — one or two sentences on why the change is happening. 2. **What to do** — the actual change, in concrete terms (file paths, function names, behaviour). 3. **Constraints** — what not to touch, what dependencies to avoid, conventions to follow. 4. **Verification** — how the agent should know it's done (tests, build, manual check). Skip the preamble. The agent doesn't need a project tour — that's what `project.definition` is for. ### Acceptance criteria are not optional `acceptance_criteria` is the only field the agent uses to decide whether to set `success: true`. Make it testable, file-scoped, and specific. **Before** ```yaml acceptance_criteria: | - Search works better ``` **After** ```yaml acceptance_criteria: | - `lib/search.ts` exports a `fuzzyMatch(query, items)` function - Querying "instlal" returns the "Installation" page - `npm run test -- search` passes - `npm run build` and `npm run lint` pass ``` If you can't write acceptance criteria, the task isn't ready — refine it before moving it to `ready`. ## 2. Sizing tasks ### Aim for one PR-worth of change A good task is roughly **30 to 90 minutes of agent time** and produces a diff small enough that you'd be willing to review it in one sitting. If a task would need a multi-section PR description to explain, split it. ### When to bundle vs split | Situation | Recipe | |-----------|--------| | Three related edits to the same file | One task | | Adding a new route + content + nav entry | One task per layer (route, content, SEO) | | A rename touching twenty files | One task — bundle, because it has to land atomically | | Two independent bug fixes | Two tasks — split, so one failing doesn't block the other | The Wildfire scheduler benefits from smaller, independent tasks: when a task fails, the chain still drains the rest of the queue. A 4-hour mega-task that errors in hour 3 wastes the whole window. ### Cross-cutting changes For changes that touch many files but are conceptually one thing (a rename, a config bump, a dependency upgrade), keep them in one task. The diff is large, but the cognitive load on the agent is small — it knows the pattern and applies it everywhere. For changes that touch many files because they're conceptually several things (new feature + cleanup + test backfill), split. The agent will conflate the streams and ship a mess. ## 3. Writing a project definition that generates good tasks The `definition` field on `project.yaml` is injected into every agent session regardless of mode or backend. It is the single most-leveraged piece of text in your project. ### What to include - **Scope** — one paragraph on what the project is and is not. - **Tech stack** — frameworks, language, package manager, deployment target. - **Conventions** — file layout, naming, where tests live, what counts as "done" (lint passes, build passes, manual check). - **What NOT to do** — the off-limits list. "Don't add new dependencies without a reason." "Don't touch `legacy/`." "Don't introduce client components unless required." - **Pointers to source-of-truth files** — `README.md`, an architecture doc, a brand guide. Agents will read them on demand. ### What to leave out - Ephemeral context ("we're sprinting on auth this week"). It will rot. - Secrets, hostnames, internal URLs — those belong in [`.watchfire/secrets/instructions.md`](/docs/concepts/secrets). - Generic advice ("write clean code"). The agent already knows. ### Iterating on the definition ```bash watchfire generate # let the agent draft a definition watchfire define # edit it by hand ``` Run [`watchfire generate`](/docs/commands/generate) once on an existing codebase, then edit the result with [`watchfire define`](/docs/commands/definition). The generated draft is a starting point, not the finished artifact — your hand edits are where the project's actual conventions land. ## 4. Choosing an agent mode Six modes are documented on the [Agent Modes page](/docs/concepts/agent-modes). Day to day, you'll pick between four: | Mode | When to use | When not to | |------|-------------|-------------| | **Chat** | Exploring, asking questions, throwaway edits | Anything you want merged — Chat doesn't run in a worktree | | **Task** | One well-scoped change you've reviewed | Batches — start them all and walk away | | **Start All** | A handful of `ready` tasks you've reviewed | An empty queue — you need tasks first | | **Wildfire** | A trusted definition, time to step away | An empty or low-quality definition — Wildfire will generate slop | **Generate Definition** and **Generate Tasks** are bootstrap commands you run once or twice when starting a project, not modes you live in. ### Rule of thumb - Reviewed it? **Task** or **Start All**. - Haven't reviewed it but trust the definition? **Wildfire**. - Don't trust the definition yet? Refine it first — Wildfire's output is only as good as the context you give it. ## 5. Sandbox and worktree hygiene Watchfire's auto-merge path is conservative on purpose. It will refuse to proceed if the default branch is dirty, and it expects you to leave the worktrees alone. ### Keep your default branch clean Auto-merge runs on the branch you started Watchfire from. If that branch has uncommitted changes, the merge will fail and the task's branch is left unmerged. Stash or commit your in-flight work before kicking off a batch: ```bash git status # confirm clean watchfire wildfire ``` ### Don't edit files in `.watchfire/worktrees/...` directly Those directories are git worktrees the daemon owns. Editing a file inside one while an agent is running races the agent. Editing one after a task is done but before the merge interferes with the merge. If you want to fix something the agent did, edit it on your default branch after the merge lands, or open the task again and let the agent re-run. ### Flipping `auto_merge: false` `auto_merge` defaults to `true`. Set it to `false` in `project.yaml` when: - You want a code review step before changes hit your branch. - You're working on a project where merges go through a CI pipeline or a PR. - You're paired with the [GitHub auto-PR adapter](/docs/concepts/integrations#github-auto-pr) and want the PR workflow to be the merge gate. With `auto_merge: false`, completed tasks stay on their `watchfire/` branch until you merge them yourself. Use the [Inspect tab](/docs/components/gui#inspect) or the TUI's d binding to review the diff before merging. ## 6. Working with multiple agent backends Watchfire ships with adapters for Claude Code, Codex, opencode, Gemini CLI, and GitHub Copilot CLI (see [Supported Agents](/docs/concepts/supported-agents)). The same task definition can run on any of them. ### Pinning a task to a specific agent Set `agent` on a task to override the project default for that task only: ```yaml task_id: a1b2c3d4 task_number: 7 title: "Refactor docs search" agent: gemini status: ready ``` The resolution order is `task.agent` → `project.default_agent` → global default → `claude-code`. See [Projects and Tasks](/docs/concepts/projects-and-tasks#agent-selection). ### Comparing backends on the same task Insights records a per-task `.metrics.yaml` file with `agent`, `duration_ms`, `tokens_in`, `tokens_out`, and `cost_usd` (where the backend exposes it). The Project View Insights tab and the [cross-project rollup](/docs/concepts/insights#cross-project-insights-rollup) chart these by backend, so running the same task twice on different agents gives you a side-by-side read. ### Cost and latency at a glance The agent donut on the Insights tab shows distribution of completed tasks by backend; the duration histogram shows wall-clock spread. Cost is summed in the KPI strip when the backend reports it — Copilot is a [stub parser](/docs/concepts/insights#metrics-package) today and contributes duration only, surfaced via the `tasks_missing_cost` banner so you don't read the rollup as a complete total. ## 7. Beacon dashboard hygiene The dashboard is the single pane you'll stare at most. A little discipline up front keeps it readable. ### Name projects deliberately The Dashboard renders one card per registered project, keyed by the `name` field in `project.yaml`. Use names that sort sensibly and read at a glance ("`watchfire-website`", "`internal-api`") rather than throwaway directory names ("`tmp`", "`test2`"). The card grid is sorted by activity, but ties fall back to name order. ### Use filter chips to triage The [Dashboard filter chips](/docs/components/gui#status-bar--filters) — `All`, `Working`, `Needs attention`, `Idle`, `Has ready tasks` — narrow the grid to one bucket at a time. When the fleet grows past 6–8 projects, **start your day on `Needs attention`**: any project with a `done` + `success: false` task lights up red. Clear those before touching anything else. ### Wire up at least one outbound channel If you only ever look at the TUI, you'll miss things. Configure one of: - **Discord** — rich embeds, also supports inbound `/watchfire status`, `/watchfire retry `, `/watchfire cancel ` slash commands. - **Slack** — Block Kit envelopes for `TASK_FAILED`, `RUN_COMPLETE`, and the weekly digest. - **Webhook** — POST to your own URL, signed with `X-Watchfire-Signature`. Setup walkthrough on the [Integrations page](/docs/concepts/integrations). With one channel wired, you can leave Wildfire running, close the laptop lid, and trust that a `done: success: false` will reach you. ## 8. Common anti-patterns The shortlist of things that make Watchfire feel worse than it is: - **Refactor without acceptance criteria.** "Refactor `auth/` to be cleaner" has no finished state. The agent will rewrite something, declare victory, and you'll be left with a diff you can't evaluate. Spell out what observable behaviour or structure constitutes done. - **Pasting a stacktrace as the prompt.** Stacktraces are evidence, not instructions. Summarise the bug in one paragraph, then include the trace as context. The agent shouldn't have to reverse-engineer your intent from a Sentry dump. - **Mixing two unrelated changes.** "Add `/pricing` and fix the navbar bug" becomes one PR you can't cleanly revert. Two tasks, two diffs, two merges. - **Editing `next_task_number` by hand.** That field tracks the next ID Watchfire will assign. `watchfire task add` increments it for you. Manually bumping or rolling it back can collide with existing task files or skip numbers. - **Running Wildfire on an empty definition.** Wildfire's Generate phase reads `project.definition` to invent new tasks. If the definition is empty, the generated tasks are generic and the loop produces noise. Run [`watchfire generate`](/docs/commands/generate) and edit the result before flipping into Wildfire. ## 9. A worked example Suppose you want to add a `/pricing` page to a marketing site. The naive version is one task: "Add a pricing page." That's a vague title, a 2-hour agent run, and a diff you'll need to take apart by hand. Better: ### Task 1 — route ```yaml title: "Add a /pricing route with a placeholder page" prompt: | Create a new route at `app/pricing/page.tsx` that renders a placeholder heading and one paragraph of lorem ipsum. Match the layout and metadata pattern of `app/about/page.tsx`. Do not add new dependencies. acceptance_criteria: | - `/pricing` returns 200 with the placeholder heading visible - `app/pricing/page.tsx` follows the same export pattern as `about` - `npm run build` and `npm run lint` pass status: ready ``` ### Task 2 — content ```yaml title: "Add three pricing tiers and an FAQ to /pricing" prompt: | Replace the placeholder in `app/pricing/page.tsx` with three tier cards (Hobby, Pro, Enterprise) and a five-question FAQ section. Use the existing `Card` and `Accordion` components. Copy lives inline in the file — do not create a new content store. acceptance_criteria: | - Three tier cards render with name, price, feature list, CTA button - FAQ has exactly five questions, each expandable - Layout is responsive (verified in dev at 375px and 1280px) - `npm run build` and `npm run lint` pass status: draft ``` ### Task 3 — SEO ```yaml title: "Add Open Graph metadata and JSON-LD product schema to /pricing" prompt: | Export `metadata` from `app/pricing/page.tsx` with title, description, and OG image (reuse `public/og-default.png`). Add a `