Appearance
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 byRecencyPhaseService. - 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 whoseupdated_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.