Skip to content

Recency phase

Sonar has a single global recency phase — one forward-only cutoff timestamp that decides what counts as "recent." It's a small idea with a big effect on which targets a scan and the AI stages consider.

What it is

  • One row, one timestamp (recency_phase), managed by RecencyPhaseService.
  • It only ever moves forward. Advancing the phase raises the cutoff.
  • "Recent" queries (the targets Recent toggle, and the AiResolveWorker — the loop that runs AI resolution) only look at rows whose updated_at >= cutoff.

The heartbeat

How does a still-relevant program stay "recent" as the cutoff advances? Every time it's crawled, BulkUpsertService stamps updated_at = NOW() on it. That crawl is its heartbeat — active targets keep bubbling above the cutoff, while stale ones naturally age out.

This is why the seeded self-hosted VDP programs age out of "Recent" once the phase advances past their seed date: they have no crawl heartbeat. That's intentional.

Using it in queries

Recency-gated queries for programs/scopes use the WhereUpdatedAfter extension so the cutoff is applied consistently. When you pass updatedAfter to a read tool (e.g. list_domains), you're doing the same thing manually — focusing on recently-changed assets.