Files
Nicolò Boschi dbe0ffb989 fix(stats): drop permanently failed memories from pending_consolidation (#3362) (#3397)
`pending_consolidation` counted every fact with `consolidated_at IS NULL`,
including the ones stamped `consolidation_failed_at` that the consolidator's
own candidate query (`reads.find_unconsolidated`) excludes on purpose. The
gauge therefore had a floor no amount of work could clear: it sat above
`?consolidation_state=pending` by exactly `failed_consolidation`, and an
operator could not tell a real backlog from an abandoned residue.

`pending` now carries the consolidator's predicate, so the two buckets are
disjoint and a bank with no live backlog reaches zero. The same predicate was
missing in five more places:

- `get_bank_stats` keeps a second copy of the freshness SQL for the
  `writes_memory_rows_in_sql` path — fixing only `counts.py` would have left
  the default Postgres path wrong.
- `hindsight.consolidation.backlog` had the same floor, which made
  "backlog > 0 for N minutes" unalertable on any bank holding a residue.
- reflect's `tool_search_observations` derives `is_stale` / `freshness` from
  this count, so a residue told the model the observations were stale on every
  call, for ever (fixed transitively via `get_bank_freshness`).
- the control plane's consolidation card computed `done = total - pending`,
  which would have counted the failed rows as done once pending got strict.
- the benchmark runner waits for this count to reach 0, so one permanently
  failed fact burned the full 3000s timeout.

Everything else that answers "what is left to consolidate" already excluded
them: the memories list filter, `count_unconsolidated`, and the
`banks_needing_consolidation()` maintenance routine — so scheduling was never
spinning on the residue.
2026-08-11 18:23:33 +02:00
..
2025-11-25 19:28:26 +01:00