recall retrieves. reflect answers.

The difference is that reflect gives a model tools and lets it search memory itself, repeatedly, deciding what to look for next based on what it just found.

What the loop actually does

The model is handed four tools:

  • recall — the ordinary ranked search
  • search_observations — search consolidated knowledge rather than raw facts
  • search_mental_models — search curated, longer-lived structures
  • expand — pull the surrounding context of something already found

A question like "why did the payments migration slip?" typically produces a first search that surfaces the slip, an entity name the model had not been given, and a second search on that name. One-shot retrieval cannot do that, because the second query does not exist until the first result comes back.

This is worth what it costs:

accuracy
fixed reader (recall → top-8 → answer)0.467
reflect0.833

McNemar exact p = 0.001 over the same memories with the same model. The delta is the loop, not the data.

Observations, and why they exist

retain stores facts. Consolidation merges related facts into observations — durable statements that outlive any single conversation. Twenty scattered mentions of someone's schedule become one observation about how they work.

search_observations reads that layer. It is often the difference between an answer assembled from twenty fragments and one drawn from a single settled statement.

Consolidation runs on write, in sub-batches, with a drain loop. Fixing that loop recovered +52% more observations (594 → 901) that were previously left unconsolidated. Notably, it did not move end-to-end accuracy — 0.711 → 0.708, which is nothing. It shipped as a correctness fix with no benefit claimed, and that is exactly how it is recorded.

Checking that it actually ran

A model that cannot call tools cannot run the loop, and Foresight falls back to a fixed structured pipeline rather than failing. Silently getting the fallback while believing you have the agentic loop is the most likely way to be disappointed by reflect.

The trace tells you which happened:

{:ok, %{"trace" => %{"mode" => mode}}} = Foresight.reflect(ctx, %{"query" => q, "trace" => true})
mode  #=> "agentic" or "fixed"

If you see "fixed" unexpectedly, the LLM you configured does not do native tool calling. Reasoning-style models are a common cause of a subtler version: they spend the token budget on reasoning and return empty content, which zeroes the answer without erroring. A direct instruct model is the safer choice for this role.

Grounding

Reflect records which memories an answer rests on, and can be required to meet thresholds before answering — minimum grounding count, minimum diversity of sources, minimum provenance clusters. Configure under engine::

config :foresight,
  engine: [
    reflect_max_iterations: 10,
    reflect_min_grounding_count: 1,
    reflect_min_diverse_grounding_count: 1,
    reflect_max_context_tokens: 100_000,
    reflect_timeout_ms: 120_000
  ]

Raising the grounding minimums makes the system decline to answer more often. For an assistant that is usually the wrong trade; for anything that must not confabulate, it is the point.

A limitation worth stating

Hindsight opens its loop with a forced sequence of tool calls. Foresight cannot currently express that: tool_choice is absent from llm_core entirely, so forced-tool retrieval is not available. Foresight seeds the opening context instead, which is a different mechanism reaching a similar place, and the measured parity above was obtained with that difference in place.