Core Concepts

Scheduled Watchers

.md

Cron-style subagents that periodically check on your deployed strategies. Built around a material-change gate so you only hear from them when something actually moved.

Creating a watcher

The schedule_subagent tool creates a watcher with:

  • Cron expression — 5-field, or a market alias like @market-open.
  • Recurring — auto-expires after 7 days unless explicitly extended.
  • Durable — survives Finny restarts.
  • Custom notes — e.g. alert if drawdown exceeds 5%.

The evaluation cycle

The scheduler ticks every 60 seconds. For each matching job:

1. Snapshot
WatcherEvaluator fetches a live market snapshot — price, account equity, position qty — and compares it to the last baseline.
2. Gate
Only triggers a full agent run if a material change is detected (≥2% price/equity move, or a position flip). First tick captures the baseline and skips.
3. Run
On material change, PromptRunner spins up the watcher agent with market context attached.
4. Deliver
Any response other than "No change." is routed back to the parent session by the Inject service.

Delivery

The Inject service watches session status on the bus and routes findings accordingly:

  • Idle session → delivered immediately via the SDK promptAsync.
  • Busy session → queued, flushed when the session goes idle.
  • Session gone → falls back to an OS notification, rate-limited to 1/hour per watcher.

Queued findings expire after 24h. A watcher that fails 5 times in a row auto-pauses.

Observability

Every state transition emits an analytics event to Convex:

watcher.fired_material
watcher.delivered
watcher.fallback_notified
watcher.auto_paused
watcher.expired
Why the material-change gate
Naive cron-driven LLM runs are expensive and noisy. A 5-minute watcher that triggers a full agent every tick burns tokens describing nothing. The gate keeps cost proportional to what actually changed in the market.

Listing and managing watchers

Use the schedule-subagent tooling to list, update, or pause existing watchers. The scheduler reloads its job table on each tick, so changes take effect within ~60 seconds.