Configuration Reference¶
Complete reference for the agentwatch configuration file.
Overview¶
agentwatch uses a YAML configuration file, typically located at ~/.config/agentwatch/config.yaml.
Generating Configuration¶
# Generate default config
agentwatch --generate-config > ~/.config/agentwatch/config.yaml
# View current config
agentwatch config show
The update channel is not in this file. The self-update channel (
stable/unstable) is a gateway-only setting in the gateway config (gateway.yaml), not this per-userconfig.yaml. Per-user daemons follow the gateway's channel and have no channel of their own. See Architecture & Installation.
Full Configuration¶
# =============================================================================
# Logging
# =============================================================================
logging:
# Log level: DEBUG, INFO, WARNING, ERROR
log_level: INFO
# Log file path (null = stderr only; when stderr is a file, as under
# launchd, the daemon uses ~/.cache/agentwatch/daemon.log instead)
log_file: ~/.cache/agentwatch/daemon.log
# Max log file size before rotation (MB)
log_rotate_max_mb: 10
# Number of rotated log files to keep
log_rotate_backup_count: 9
# =============================================================================
# Service Settings
# =============================================================================
service:
# How often to scan for new/removed tmux sessions (seconds)
session_discovery_interval: 5
# How often to capture each session's terminal (seconds)
capture_interval: 1
# tmux socket path (null = default socket)
tmux_socket: null
# Path to tmux binary
tmux_bin: tmux
# Debug settings
debug:
# Enable debug mode
enabled: false
# Directory for capture files (null = disabled)
capture_dir: null
# Auto-cleanup captures older than this (hours)
retention_hours: 4.0
# Log each capture
log_captures: false
# Log detection results
log_detections: false
# Log state changes (recommended for debugging)
log_state_changes: true
# Log hook executions
log_hooks: true
# Dry run mode - log hooks without executing
dry_run_hooks: false
# =============================================================================
# Web Viewer (HTTPS Server)
# =============================================================================
web_viewer:
# Enable/disable web viewer
enabled: true
# Host to bind (127.0.0.1 = localhost only)
host: 127.0.0.1
# Port to listen on
port: 8081
# Terminal streaming frame rate
fps: 2.0
# Max keyboard input messages per second
max_input_rate: 20
# Custom SSL certificate (optional)
ssl_cert: null
ssl_key: null
# =============================================================================
# Tunnel (agentwatch.sh)
# =============================================================================
tunnel:
# Enable auto-provisioned tunnel
# Requires device token in ~/.config/agentwatch/tokens/device
enabled: false
# =============================================================================
# Cloudflare Tunnel (Manual)
# =============================================================================
cloudflare:
# Enable manual Cloudflare tunnel
enabled: false
# Path to cloudflared config file
config_file: ~/.cloudflared/config-agentwatch.yml
# Path to cloudflared binary
cloudflared_bin: cloudflared
# =============================================================================
# Coding Sessions
# =============================================================================
sessions:
# Enable coding session management
enabled: true
# Prefix for auto-generated session names
session_prefix: coding-
# Maximum concurrent coding sessions
max_sessions: 3
codex_max_subagents: 16
# Default lines to capture for session capture
capture_lines: 50
# Gateway setting: the operator's quota policy for agents, in percent of a
# window left. Agents are told these steps at session start, in
# `agentwatch whoami`, in `swap-account --help`, in the swap notice and in
# each quota alert. A value that does not read is logged and the defaults
# hold (the gateway still starts). They are also the launch floor: no new
# session starts on an account whose 5-hour window is at or below the
# 5-hour step, or whose weekly window is at or below the no-subagents step
# counted from the account's reserve, unless forced (`session new --account
# NAME --force`, or the operator naming it in the web viewer). Automatic
# then refuses rather than use an OpenRouter key.
quota_policy:
seven_day_no_subagents: 2 # weekly: start no new subagents
seven_day_swap: 1 # weekly: finish, compact, then swap (0-99; not above the line above)
five_hour_no_subagents: 10 # 5-hour: no new subagents; never swap for it
# Gateway setting: put this sessions block in gateway.yaml for quota alerts
# to live parent sessions (including their child sessions' accounts). The
# key names keep "mail" from when the alerts were mail.
# Uses cached daemon readings; never adds provider probes. {} disables all.
# Default: one alert per policy step (five_hour [10], seven_day [2, 1]). An
# account that keeps a reserve has its weekly steps counted from it.
quota_mail_thresholds:
five_hour: [10] # Percent remaining; [] disables this window
seven_day: [2, 1]
# An alert starts an agent turn. Only a tree with ongoing work is alerted: a
# member Working or Blocked, or the recipient Idle for less than this.
quota_mail_active_seconds: 300
# Allowed working directories for coding sessions
# Sessions can only be created under these paths
allowed_workdirs:
- "~" # Home directory
- "/tmp" # Temporary files
- "/var/tmp" # Persistent temp
# =============================================================================
# Hooks
# =============================================================================
hooks:
# Webhook example
- name: my-webhook
type: webhook
trigger: state_change
programs: ["Claude Code"]
to_states: [Error, Blocked]
url: "https://example.com/webhook"
include: status
timeout_seconds: 10
retry_count: 0
retry_delay_seconds: 1.0
# Shell example
- name: my-shell
type: shell
trigger: state_change
to_states: [Idle]
command: 'echo "$SESSION_NAME finished" >> ~/agentwatch.log'
include: status
Section Reference¶
logging¶
| Key | Type | Default | Description |
|---|---|---|---|
log_level |
string | INFO |
Log level: DEBUG, INFO, WARNING, ERROR |
log_file |
string | ~/.cache/agentwatch/daemon.log |
Log file path (null = stderr only). When stderr is a regular file (launchd, a background start), nothing rotates it, so the daemon uses the default path instead |
log_rotate_max_mb |
number | 10 |
Max log file size (MB) |
log_rotate_backup_count |
number | 9 |
Rotated files to keep |
service¶
| Key | Type | Default | Description |
|---|---|---|---|
session_discovery_interval |
number | 5 |
Session scan interval (seconds) |
capture_interval |
number | 1 |
Capture interval per session (seconds) |
tmux_socket |
string | null |
tmux socket path |
tmux_bin |
string | tmux |
Path to tmux binary |
service.debug¶
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false |
Enable debug mode |
capture_dir |
string | null |
Directory for capture files |
retention_hours |
number | 4.0 |
Capture cleanup age (hours) |
log_captures |
boolean | false |
Log each capture |
log_detections |
boolean | false |
Log detection results |
log_state_changes |
boolean | true |
Log state changes |
log_hooks |
boolean | true |
Log hook executions |
dry_run_hooks |
boolean | false |
Log hooks without executing |
web_viewer¶
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Enable web viewer |
host |
string | 127.0.0.1 |
Bind address |
port |
number | 8081 |
Port number |
fps |
number | 2.0 |
Streaming frame rate |
max_input_rate |
number | 20 |
Max input messages/sec |
ssl_cert |
string | null |
Custom SSL certificate path |
ssl_key |
string | null |
Custom SSL key path |
tunnel¶
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false |
Enable auto-provisioned tunnel |
The device token is stored at ~/.config/agentwatch/tokens/device.
app¶
| Key | Type | Default | Description |
|---|---|---|---|
ios_app_ids |
list | [] |
Apple Developer team ids allowed to open this box's Universal Links, as TEAMID.sh.agentwatch.app |
Gateway-side, like tunnel and location. Feeds
GET /.well-known/apple-app-site-association, which 404s while this list is
empty — the default until Apple enrollment gives an operator a team id. See
native-app.md.
cloudflare¶
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | false |
Enable manual Cloudflare tunnel |
config_file |
string | ~/.cloudflared/config-agentwatch.yml |
Cloudflared config path |
cloudflared_bin |
string | cloudflared |
Path to cloudflared |
sessions¶
| Key | Type | Default | Description |
|---|---|---|---|
enabled |
boolean | true |
Enable coding session management |
session_prefix |
string | coding- |
Auto-generated session name prefix |
max_sessions |
number | 3 |
Max concurrent sessions |
codex_max_subagents |
integer or null | 16 |
Concurrent subagents per Codex session, excluding the main agent; positive integer, or null to use Codex's own configuration |
quota_mail_active_seconds |
number | 300 |
Quota alerts go only to a tree with ongoing work: a member Working or Blocked, or the recipient Idle for less than this many seconds (0 or more); gateway config |
sleep_wake_notices |
boolean | true |
macOS: after the Mac wakes from a sleep, mail the sessions it interrupted (when it slept and woke, for how long, what to check); gateway config |
capture_lines |
number | 50 |
Default capture lines |
allowed_workdirs |
list | ["~", "/tmp", "/var/tmp"] |
Allowed working directories |
Set sessions.codex_max_subagents in the per-user daemon configuration
(~/.config/agentwatch/config.yaml). Agentwatch passes
-c agents.max_concurrent_threads_per_session=N to both the Codex TUI and its
app server, without editing ~/.codex/config.toml. The limit applies to the
whole subagent tree in each session. Set a larger number for more parallel
workers, or null to omit the override; Codex requires a positive number and
does not support an unlimited value. Restart Agentwatch and create a new Codex
session to apply it; existing Codex sessions keep their launch configuration.
sessions.sleep_wake_notices is also gateway configuration, and only has an
effect on macOS. When the Mac wakes from a sleep of a minute or more (a closed
lid, or idle sleep), the gateway mails each session that was busy when it
slept: Working or Blocked, Idle with its last turn ending within 10 minutes, or
Idle with background shells, subagents or an active /goal. A session idle for
longer with nothing running is not mailed, so a sleep never starts a turn in
it. Set it to false to turn the notices off. Details:
agent-mail.md.
board¶
The project board (Scotty behind /board). The per-user daemon reads this
section each time it starts or reports the board, so a change needs no restart
to take effect for the next start. Details:
project-board.md.
| Key | Type | Default | Description |
|---|---|---|---|
idle_timeout_minutes |
number | 30 |
Minutes without traffic before the gateway stops the user's board process. 0 means never. The next request starts it again |
email¶
Inbound email to sessions (<session>.<box>@agentwatch.sh). This is a
gateway setting. Details: inbound-email.md.
| Key | Type | Default | Description |
|---|---|---|---|
owner_user |
string | none | The OS user who gets the email that no session name of this box matches. The provisioner holds that email for this user. |
A box with one daemon user needs no setting: that user is the owner. On Linux
the daemon users are the login users of the box (uid 1000 and up), also a user
who runs no daemon. The root gateway's own uid 0 is not one. On a box with several users and no owner_user, the
provisioner refuses that email. The gateway ignores an owner_user that is not
a listed user. Then there is no owner.
Push notification preferences¶
Web push preferences are not in the YAML file. Each user sets them in the web
viewer (GET/PUT /api/push/preferences). The flags are needs_input
(default on), error (default on), finished (default off) and needs_you
(default on). needs_you controls the "needs you" push of the project board.
The push title is the project id. The body is "1 item needs you" or "N items
need you". N counts every item that waits in the board's Needs You inbox, not
only the new ones. The board sends at most one push for each board in 10
minutes.
hooks¶
List of hook definitions. Each hook has:
| Key | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Unique identifier |
type |
string | Yes | webhook or shell |
trigger |
string | Yes | state_change, program_change, any_change |
programs |
list | No | Filter: current program must match |
states |
list | No | Filter: current state must match |
from_states |
list | No | Filter: previous state must match |
to_states |
list | No | Filter: target state must match |
from_programs |
list | No | Filter: previous program must match |
to_programs |
list | No | Filter: target program must match |
include |
string | No | Data format: status, json, text, png |
Webhook-specific:
| Key | Type | Default | Description |
|---|---|---|---|
url |
string | Required | Webhook URL |
timeout_seconds |
number | 10 |
Request timeout |
retry_count |
number | 0 |
Retry attempts |
retry_delay_seconds |
number | 1.0 |
Delay between retries |
Shell-specific:
| Key | Type | Default | Description |
|---|---|---|---|
command |
string | Required | Shell command |
accounts¶
Per-user, in your own config.yaml, and read by its own consumer (a hand edit
is live without a restart). Written by agentwatch accounts; credentials are
never stored here.
accounts:
claude_code: # Claude setup-token logins, in priority order
- name: primary
organization_id: org-…
- name: backup
organization_id: org-…
reserve_percent: 30 # optional: quota kept on this account
reserve_mode: hard # hard: never used below it; soft: others first
plan_type: max_5x # optional label: pro, max_5x, max_20x (accounts set-plan)
openrouter: # OpenRouter keys: overflow for Claude and Codex
- name: cheap
models:
claude_code: z-ai/glm-5.3-flash
codex: deepseek/deepseek-v4.1-flash
codex: # second ChatGPT logins for Codex, in priority order
- name: support
account_id: b50e5a08-… # the ChatGPT account, from the login's claims
reserve_percent: 30 # quota new sessions leave for other users
reserve_mode: hard # the default for a managed account
plan_type: 5x # optional label: plus, 5x, 20x (accounts set-plan)
shared: # this machine's own logins ("login"), shared with your terminal
codex:
reserve_percent: 2 # other accounts first below 2% ...
reserve_mode: soft # ... but still used last (the default)
plan_type: 5x # optional label, as for an account
poll_interval_minutes: 60 # 0 disables the hourly stale-account poll
handover:
window_hours: 48 # batch runs use an account this long before its weekly reset
| Key | Description |
|---|---|
claude_code |
Claude accounts; the list order is the priority order. Tokens in ~/.config/agentwatch/tokens/claude/<name> |
openrouter |
OpenRouter keys, tried after every subscription. models is optional per agent; without it the session starts on the harness default model. Keys in ~/.config/agentwatch/tokens/openrouter/<name> |
codex |
Codex accounts, tried after this box's own Codex login and before any OpenRouter key. Each login is a CODEX_HOME at ~/.config/agentwatch/tokens/codex/<name>/. reserve_percent (0–99, default 0) is the quota new sessions leave on the plan |
shared |
This machine's own logins (login), per agent: reserve_percent and reserve_mode (default soft), and an optional plan_type. agentwatch never changes these logins |
plan_type |
Optional, on a Claude account's plan block (pro, max_5x, max_20x), a Codex account (plus, 5x, 20x) or a shared login: which plan it is, as the operator records it (accounts set-plan). A label only: shown beside the account, never used to choose one. A value that is not a known type reads as unset |
reserve_percent / reserve_mode |
On a Claude or Codex account: quota kept, and how firmly. hard (default): never used at or below it, not even by name. soft: other accounts first there; below it the account is at a quota alert step (the weekly steps count from the reserve), so a new session starts there only with session new --account NAME --force or from the web viewer |
handover.window_hours |
How many hours before its weekly reset an account is handed to batch runs (accounts handover, accounts lease; default 48, 1–168; any other value is 48, with a warning). No command sets it: the operator edits it here |
poll_interval_minutes |
How often the daemon polls (default 60 minutes). A Claude account is probed only when no box holds a reading younger than the interval less 5 minutes; Codex and OpenRouter are read every time. Each box polls at its own minute. A box with no accounts: block polls nothing, and 0 turns the poll off |
Claude accounts are stored together in accounts.claude (the legacy
claude_code list above is migrated on first write). An optional API renewal
policy makes the daemon record monthly expected-credit grants at midnight UTC,
without calling a provider. It works with or without a local plan:
accounts:
claude:
- name: primary
api:
organization_id: console-org
credits: 200
expires: '2026-10-14'
granted_at: 1791504000
renewal:
credits: 100 # future monthly grant, not today's balance
renews_day: 11
next_renewal: '2026-10-11' # first grant; advanced atomically each month
Grants replace rather than accumulate; expiry is three days after the following
renewal, through that UTC day. See accounts set-credit-renewal in the CLI.
Environment Variables¶
These override configuration values:
| Variable | Overrides |
|---|---|
AGENT_WATCH_SOCKET |
service.tmux_socket |
AGENT_WATCH_TARGET |
Target pane |
AGENT_WATCH_COLS |
One-shot capture/render width |
AGENT_WATCH_ROWS |
One-shot capture/render height |
AGENT_WATCH_FORMAT |
Output format |
AGENT_WATCH_SESSION_SECRET |
Session signing key |
AGENTWATCH_TOKEN |
Device token |
SSL Certificates¶
Certificates are used in this priority:
- Tunnel certificate - Auto-provisioned for
*.agentwatch.sh - Custom certificate - From
ssl_cert/ssl_keyconfig - Auto-generated - Self-signed localhost certificate
Auto-generated Certificates¶
On first start, if no certificates are configured, a self-signed localhost certificate is generated automatically.
Custom Certificates¶
Tunnel Certificates¶
When using the auto-provisioned tunnel, Cloudflare origin certificates are fetched automatically and stored at ~/.config/agentwatch/certs/.
Hook Shell Environment¶
Shell hooks receive these environment variables:
| Variable | Description |
|---|---|
SESSION_NAME |
tmux session name |
SESSION_ID |
tmux session ID |
PROGRAM |
Current program |
PROGRAM_VERSION |
Version (if detected) |
STATE |
Current state |
DETAIL |
State detail |
PREVIOUS_PROGRAM |
Previous program |
PREVIOUS_STATE |
Previous state |
PREVIOUS_DETAIL |
Previous detail |
TIMESTAMP |
ISO timestamp |
DURATION_SECONDS |
Time in previous state |
CAPTURE_FILE |
Capture file path (if include: text/png) |
AGENTWATCH_TRIGGER |
Trigger type |
Example Configurations¶
Minimal¶
With Desktop Notifications¶
hooks:
- name: desktop-notify
type: shell
trigger: state_change
to_states: [Blocked, Error, Idle]
command: 'notify-send "agentwatch" "$SESSION_NAME: $STATE"'
With Remote Access¶
tunnel:
enabled: true
hooks:
- name: slack-notify
type: webhook
trigger: state_change
to_states: [Error]
url: "https://hooks.slack.com/services/..."