Background automation daemon for Pi — workflow discovery, file watchers, schedule rules, and task queue.
- Workflow discovery: Auto-discovers workflows from
packages/*/workflows/,.pi/workflows/, andworkspace/workflows/ - File watchers: Watch glob patterns for changes, record triggers
- Rules engine: Evaluate trigger-based and schedule-based rules on heartbeat
- Task queue: Sequential execution with retry, backoff, and crash recovery
- Agent runner: Spawns Pi agents (
pi --print) or runs scripts for each workflow - Live reload:
daemon({ action: "reload" })rediscovers workflows and rebuilds watchers/rules without a daemon restart - Control interface: Filesystem IPC — agents write JSON commands, daemon writes responses
- Pi extension:
daemon()tool + auto-start in tmux onsession_start
pi install npm:pi-daemonCreate .pi/daemon.json (typically generated by just init from config.toml):
{
"enabled": true,
"heartbeat_interval_seconds": 60,
"state_dir": ".bosun-daemon",
"log_level": "info"
}Workflows are auto-discovered — no manual rules or watchers needed in the config.
Each workflow is a directory containing:
my-workflow/
├── config.toml # Trigger, model, prompt, retry settings
├── agent.md # Agent system prompt (for agent workflows)
├── validate-input.ts # Optional: skip if conditions not met (exit 1)
└── validate-output.ts # Optional: retry if output invalid (exit 1)
[workflow]
name = "my-workflow"
description = "What this workflow does"
type = "agent" # "agent" or "script"
[trigger]
schedule = "hourly" # "hourly", "daily:HH", or omit
# watcher = "path/glob/**/*.json" # File watcher pattern
# startup = true # Run on daemon startup
# debounce_ms = 5000 # Watcher debounce
[agent]
model = "lite" # Model tier from config.toml
prompt = "Task description for the agent"
[validators]
input = "validate-input.ts"
output = "validate-output.ts"
[retry]
max_attempts = 2
[timeout]
minutes = 10packages/*/workflows/*/— packaged defaults.pi/workflows/*/— repo-level customizationsworkspace/workflows/*/— user-level overrides (gitignored)
The Pi extension registers a daemon() tool:
daemon({ action: "status" }) — show status, watchers, queue
daemon({ action: "trigger", handler: "my-workflow" }) — manually trigger a workflow
daemon({ action: "logs", lines: 100 }) — view recent log lines
daemon({ action: "reload" }) — rediscover workflows/watchers/rules without restart
daemon({ action: "stop" }) — stop daemon
When Pi starts inside tmux and .pi/daemon.json has enabled: true, the extension automatically spawns the daemon in a background tmux window named "daemon".
reload command
│
└─► rediscover workflows → rebuild watchers/rules → continue heartbeats without restart
heartbeat (every N seconds)
│
├─► evaluate rules (derived from currently loaded workflows)
│ ├─ trigger rules: check triggers.json for pending watcher events
│ └─ schedule rules: check time since last run
│
├─► enqueue matching tasks (dedup by rule name)
│
└─► process next task in queue
├─ validate input (optional, exit 1 to skip)
├─ spawn agent (pi --print) or run script
├─ validate output (optional, exit 1 to retry)
├─ on success: move to history, clear triggers
└─ on failure: retry with backoff
file change → watcher → triggers.json → heartbeat → rule matches → workflow runs
All stored in state_dir (default .bosun-daemon/):
| File | Purpose |
|---|---|
status.json |
Running state, PID, heartbeat, watchers |
triggers.json |
Pending trigger records from watchers |
rules-state.json |
Per-rule last run time and result |
queue.json |
Task queue and history |
daemon.log |
Log file |
control/ |
Incoming command files (IPC) |
responses/ |
Outgoing response files (IPC) |
MIT