Debugging
Start with the run record
Section titled “Start with the run record”Watcher writes one record per run, and it is far more informative than a log line. It holds the outcome, the summary and evidence, the model’s own reasoning, per-source errors, collection counts, which models were consulted, and the corroborating trust tier.
pnpm watches history <id>curl -s https://<your-host>/watches/<id>/runs -H "authorization: Bearer $TOKEN"Read the outcome first: it names the stage the run stopped at. See Run outcomes.
Reproducing locally
Section titled “Reproducing locally”-
Point at the same database as the instance you are debugging, or a copy of it. Run state is what makes a run behave the way it did.
-
Force the run:
Terminal window pnpm run-due:force -
If it says
nothing-new, you are fighting content memory — the content you want re-examined has already been consumed. Reproduce with a watch that has a new id, which has no memory.
Isolating the layer
Section titled “Isolating the layer”The architecture makes this quick, because each layer can be exercised alone.
Is it collection? Check collection counts on the run record. collected
high but extracted low means fetching works and extraction does not.
Is it the model? Read reasoning. If it explains a defensible decision, the
condition is the problem. If it is nonsense, look at what was actually sent —
thin or wrong text is the usual cause.
Is it the gate? A triggered verdict that produced no alert was demoted. Check
in this order: is there an evidence quote at all; does it appear in the page text;
what is corroboratingTier; were two models consulted and did they disagree.
Is it delivery? Outcome notified means Watcher handed it to a channel. The
problem is then between Watcher and the provider.
Writing a focused test instead
Section titled “Writing a focused test instead”Usually faster than another live run. Every layer takes injected dependencies:
const evaluator = new FakeEvaluator(theVerdictYouSaw)const result = await runWatch(theWatch, { collectors, evaluator, notifiers, repository })expect(result.outcome).toBe('below-threshold')Pin the verdict you observed and assert the outcome. If the test reproduces the behaviour, you can iterate in milliseconds; if it does not, the difference is in the data, not the logic.
Where bugs actually hide
Section titled “Where bugs actually hide”| Symptom | Look at |
|---|---|
| Works locally, not in production | Persistence, or configuration differences |
| Works once, then never again | Content memory — the content is now “seen” |
| Works for one watch, not another | Trust grading, or source-specific extraction |
| Intermittent | Key rotation, provider rate limits, or a second scheduler |
| Alert arrives twice | Two schedulers, or the embedder was unavailable |
| Alert never arrives | Delivery — check the outcome, not the model |
The CLI prints one line per watch, plus skipped sources underneath. The API logs structured records including scheduler ticks. Neither logs a secret: provider failures are reported by status code.
For deeper tracing, add temporary logging in an adapter — never in the domain layer, which must stay pure.
Checking configuration fast
Section titled “Checking configuration fast”curl -s https://<your-host>/config/statusBooleans for every integration. It resolves “broken” versus “never configured” in one request, and needs no token.