---
name: Watchers
description: Use when the operator wants to be told about something WHEN IT HAPPENS rather than on a clock — "let me know if the log fills with errors", "watch the queue", "tell me when that endpoint changes", "keep an eye on my app's errors". Teaches you to write a watcher file (00 watchers new), what the coalescing rule means for how often you may interrupt them, and how to answer when one wakes you.
icon: eye
tagline: Have your agent tell you the moment something happens — a file changes, a log fills with errors, a page moves.
version: 1
situations: watch the log, tell me when it breaks, let me know if errors spike, keep an eye on, alert me when, monitor the endpoint, notify me if the queue backs up, wake me when it changes, watch this file, app errors digest, event-driven, when it happens
requires-secrets:
requires-packages:
---

# Watchers

A schedule fires because the clock said so. A **watcher** fires because something happened.

One file per watcher, in your own workspace: `watchers/<name>.md`. The frontmatter says what to
watch and when it counts; the body is the prompt you are woken with. The engine reconciles the
folder on every tick, so a file you write is armed within seconds, an edited one is re-armed, and a
deleted one is dropped. The folder travels with you — a full move of your agent folder to another
machine takes your watchers along, already armed.

## When to make one

Make a watcher when the operator describes a TRIGGER rather than a time: "if the log fills with
errors", "when that endpoint goes down", "if orders stop coming in". Make a **schedule** instead
(`schedules/<name>.md`) when they describe a time: "every morning", "on Fridays".

Do not make one for something you can just check inside a task you are already running. A watcher is
a standing promise to interrupt a person; it should earn that.

## How to make one

Either way is fine, and they do the same thing — the second one just gets the validation errors
back before you walk away:

```bash
00 watchers new log-errors --file logs/app.log --match "/ERROR|Traceback/i" \
  --window 5m --threshold 1 --cooldown 10m --budget 6/h --context 3 \
  --title "Log errors" \
  --prompt "The log matched {{count}} time(s) in the last {{window}}:

{{matches}}

Look for a pattern, check the last deploy, and tell me what you find."
```

It prints the path it wrote (`watchers/log-errors.md`) — or every reason the engine refused it, in
the engine's own words. **Writing the file yourself with your `write` tool is equally correct**; the
file is the interface. The command exists because a file with a bad regex is skipped silently until
someone reads a debug log, and the command tells you at the moment you write it.

Then, always: `00 watchers test log-errors` — it evaluates the source once and shows what it would
have caught, waking nothing. Say what it found before you tell the operator the watcher is up.

Other verbs: `00 watchers` (list), `00 watchers wake <name>`, `00 watchers pause|resume <name>`,
`00 watchers rm <name>` (deletes the file).

## The coalescing rule — three sentences

A watcher has at most one live run: while it is running, new hits do not wake you again — they
collect in `pending` and are delivered into that same run as one follow-up, once they reach
`threshold` again or 60 s after the first of them. The cooldown starts when that run **ends**, not
when it started, and the budget counts wakes, not hits. A `digest` watcher never starts anything at
all: its hits are folded into your next turn in any session as a short "N hits since …" block.

The practical consequence: `cooldown` and `budget` are how loud this watcher is allowed to be. A
noisy source with `cooldown: 10m` and `budget: 6/h` can interrupt six times an hour at worst. If
the operator would not want six interruptions, put `mode: digest` on it instead.

## Three worked examples

### A log file

```bash
00 watchers new log-errors --file logs/app.log --match "/ERROR|Traceback/i" \
  --window 5m --threshold 1 --cooldown 10m --budget 6/h --context 3 \
  --title "Log errors" --prompt "…"
```

`--file` is tailed, not polled — rotation- and truncation-aware, so a log that rolls over at
midnight keeps working. `--context 3` keeps three lines either side of each match, which is usually
the difference between a traceback you can read and one line of it.

### An endpoint

```bash
00 watchers new provider-status --http https://status.example.com/api --every 60s \
  --json "path=status,changed" --window 5m --cooldown 15m \
  --title "Provider status" \
  --prompt "The provider's status changed to: {{last}}

If it is not \"operational\", tell the user what is affected and what you are doing about it."
```

`--json "path=…,changed"` matches on a VALUE changing rather than on text — the right tool for a
status endpoint, which says the same word until the day it doesn't. `path=queue.depth,gt=100` is
the other common shape. HTTP is polled every `--every` (30 s minimum). If the endpoint needs a
token, the file may only NAME an environment variable you already hold — never the value.

### Your own app's errors

```bash
00 watchers new app-errors --stream app-errors --match "/./" \
  --window 10m --threshold 3 --cooldown 30m --budget 2/h --mode digest \
  --title "App errors" \
  --prompt "My app page threw {{count}} error(s) in the last {{window}}:

{{matches}}

Read the page's code under skills/, find the cause, and tell me what you'd change."
```

`app-errors` is one of the engine's own streams (`timeline`, `app-errors`, `sessions`,
`channel:<name>`). `--mode digest` is deliberate here: a page that throws does not need to wake
anybody at 3am, and three errors in ten minutes is a thing to mention, not an emergency.

## The body — what you get woken with

The body is a prompt, and these placeholders are filled in before it reaches you:
`{{count}}`, `{{window}}`, `{{matches}}` (each hit with its context, capped at 8 KB), `{{first}}`,
`{{last}}`, `{{name}}`, `{{pending}}` (hits that arrived during the previous run).

Write it as an instruction to yourself, not as an alert to forward. "Look for a pattern, check the
last deploy, and tell me what you find" is a good body; "ERROR in log" is not — you would just be
relaying a line the operator could have read themselves.

## Answering a wake

You are woken in a fresh task session (or the main chat, with `session: main`), holding the matches
and nothing else. So:

1. **Read the matches before saying anything.** `{{count}}` is not the story; the pattern in them is.
2. **Go and look.** Open the file, run the command, read the last deploy, check the related code —
   the watcher's job was to tell you where to point your attention, not to do the work.
3. **Say what happened, what it means, and what you did or propose to do**, in that order, in a few
   lines. If it is the same thing as last time, say so — "third time today, same endpoint".
4. **If it was noise, fix the watcher, not just the message.** Tighten the regex, raise the
   threshold, lengthen the cooldown, or switch it to `mode: digest` — then say you did, and why. A
   watcher that cries wolf is worse than no watcher, and you are the one who can tell.
5. **If nothing is wrong, say so briefly and stop.** A wake does not oblige you to produce a report.

## Limits

At most 20 enabled watchers per agent. The regex must compile, stay under 200 characters and carry
no nested quantifiers (`(a+)+` is refused — it can hang on a long line). A command gets 30 s and
1 MB; an HTTP read gets 15 s and 1 MB. `--every` is at least 10 s for a command, 30 s for HTTP.

The full frontmatter table is in your workspace docs: `docs/authoring.md`, "Watchers —
`watchers/<name>.md`".
