TL;DR
The Reef Engine tags every fact, finding, and diagnostic row
with a freshness label. Tools like evidence_confidence
and verification_state expose those labels.
context_packet respects them. If you're returning
line numbers after edits, prefer live_text_search to
cross_search. If you suspect the index is wrong,
project_index_refresh with mode: "if_stale"
is the cheap fix; mode: "force" is the expensive one.
The freshness states
| State | What it means | Trust for editing? |
|---|---|---|
live | Came from a live read just now (filesystem, database). | Yes. |
fresh_indexed | Indexed and the index hasn't been invalidated. | Yes, until disk changes. |
stale | Indexed but the source file was edited since. | Read-only context only — verify before relying on line numbers. |
historical | From a prior session; no claim about current state. | Treat as background context. |
contradicted | A newer fact disagrees with this one. | No. Use the newer fact or refresh. |
unknown | Provenance is missing. | Verify before acting. |
When the index goes stale
The index is "fresh enough" until something changes. Common stale-makers:
- You edited a file the index already knew about.
- You added a new file the index hasn't seen.
- The schema changed (migration ran, Supabase types regenerated).
- A diagnostic source ran and produced new findings.
project_index_status reports indexed vs. dirty
counts. Use it before trusting indexed line numbers after a
batch of edits.
The working_tree_overlay shortcut
Sometimes you don't want a full reindex. You just want the current state of a few files reflected without re-parsing everything.
working_tree_overlay snapshots working-tree file
facts (existence, size, hash) without touching the AST/imports/
routes/schema indexes. It's the cheap "I edited a file, please
notice" call. Subsequent file_facts /
file_findings reads use the overlay.
When to force a refresh
Two refresh modes:
project_index_refresh { mode: "if_stale" }— checks the index, refreshes only what changed. Cheap. Run after a batch of edits or before relying on indexed answers.project_index_refresh { mode: "force" }— full re-index. Use only when the indexed AST/search results appear actually wrong, not just stale. Expensive on large repos.
For database facts: db_reef_refresh after
schema migrations or Supabase type regeneration.
Stable is not the same as fresh
This is the most common mistake. A stable indexed answer — one that comes back the same way every time — feels reliable. But "stable" only means the index hasn't been refreshed; it does not mean the index agrees with disk.
Two failure modes:
- You edit
lib/auth/dal.ts, then ask the agent for a line in that file. The cachedcross_searchanswer points at the old line. The agent edits the wrong line. - You add a new route file, then ask "where's the handler for /x?". The route table doesn't know about the new file yet — the agent says "no handler exists."
Defenses:
- Use
live_text_searchwhen you need exact current text. - Use
working_tree_overlaywhen you've edited files but don't need a full re-index. - Check
project_index_statusbefore trusting line numbers in indexed results. - Tell the agent (in
CLAUDE.md) to prefer live reads after edits, not stable indexed reads.
How a piece of evidence ages
Every fact in Reef has a small state machine attached.
Tools like evidence_confidence read these states
on demand:
┌──────┐ ┌──────────────┐
create → │ live │ ── refresh → fresh ─────→ │ fresh_indexed│
└──────┘ └───────┬──────┘
│
(file changes)
│
▼
┌─────────┐
│ stale │
└────┬────┘
│
┌── re-run, agrees ────┘
│
▼ │
┌──────────────┐ │
│ fresh_indexed│ re-run, disagrees
└──────────────┘ │
▼
┌─────────────┐
│ contradicted│
└─────────────┘ Two notes worth tattooing on the agent's CLAUDE.md:
- Stale is not wrong — it's "we haven't re-checked." Use it as background context, not as ground truth for line numbers.
- Contradicted is wrong — a newer fact disagrees. Treat the older one as obsolete and trust the newer fact (or refresh further).
Working with stale evidence on purpose
Stale evidence isn't useless. It's just labeled. Three patterns where stale-is-fine:
- Repo orientation. "What does this file roughly do?" — a stale symbol list is fine. The shape of the codebase doesn't change between edits.
- Cross-file impact at the type level. "Who imports this module?" — the import graph is stable across small edits. Stale is close enough.
- Historical context. "Why was this written this way?" — a stale finding from three weeks ago answers it; live reads of current code don't.
What stale evidence is not fine for:
- Line numbers in code you just edited.
- Whether a finding still applies after a refactor.
- Whether a database table still has the column you read two hours ago.
The freshnessPolicy input
Tools that read indexed evidence (context_packet,
cross_search, repo_map, etc.) accept
a freshnessPolicy argument:
"prefer_fresh"(default) — return what the index has, but mark stale results as such. Cheap."require_fresh"— block until the index is current. Triggers anif_stalerefresh under the hood. Slower but line-numbers are reliable."allow_stale"— return anything, even contradicted rows. Useful for historical queries and offline work.
For most agent workflows, "prefer_fresh" is
correct: the agent gets fast answers and decides per-tool
whether stale labels matter.
Common pitfalls
- Treating "indexed" as "checked."
Indexed means "we read this file once." Diagnostics like
lint and tsc run separately. A file with
freshness: fresh_indexedmay still have stale diagnostic rows. Useverification_statefor the diagnostic side. - Force-refreshing on every turn. Cost
scales with repo size; "force" re-parses everything.
Reach for
if_stalefirst; only force when indexed answers look wrong, not just old. - Trusting
fresh_indexedline numbers after edits. If you edited the file in the same turn, the index is now lying about line numbers without knowing. Useworking_tree_overlayorlive_text_search. - Ignoring
contradicted. Contradicted facts are the system telling you something broke. Don't render them like "stale" — they need attention, not just a refresh.