Skip to content

Debugging

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.

Terminal window
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.

  1. 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.

  2. Force the run:

    Terminal window
    pnpm run-due:force
  3. 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.

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.

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.

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.

Terminal window
curl -s https://<your-host>/config/status

Booleans for every integration. It resolves “broken” versus “never configured” in one request, and needs no token.