The prefetch recorded the rows its searches returned immediately, then went
on to check for an empty result, stamp telemetry and format two markdown
sections. Any failure in that tail is swallowed by the surrounding handler,
which returns None and leaves `_prepare_query` building a prompt with no
prefetch block at all -- so evidence could name conclusions the agent was
never shown.
Move the recording below the formatting, so what is recorded is what the
returned context actually contains.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Add an opt-in `include_evidence` to both chat endpoints. The response then
carries the conclusions and messages the agent read while answering, plus the
tools it called.
Evidence is collated from what the agent accessed rather than reported by the
model. That over-reports -- a conclusion appears because the agent saw it, not
as proof the answer used it -- but it is deterministic, costs no model tokens,
and behaves the same at every reasoning level. Asking the model to cite its
sources fails quietly instead: weaker models produce incomplete or invented
citations, and a sparse citation list is indistinguishable from a sparse
answer. The prefetch already makes the point, feeding explicit conclusions
into the prompt without their IDs, so the model could not cite them if asked.
An accumulator is threaded from the router through the agent into ToolContext,
and read handlers hand it the rows they already loaded. Nothing is re-queried
when the response is built, so evidence inherits the scoping of the reads that
produced it and cannot become a way around a session allowlist.
Three details worth knowing:
- Prefetched conclusions never pass through the tool executor, so they are
captured via a new `documents_out` sink on `search_memory`. On a query that
answers without a tool call they are the whole of what was read.
- Conclusions dedupe by ID. `Representation`'s own deduplication keys on
content and timestamp and ignores IDs, which would collapse distinct
conclusions that happen to read alike.
- Conclusion timestamps are re-stamped UTC. `Representation` strips tzinfo so
observations render compactly into prompts, which would otherwise put naive
timestamps in the API beside timezone-aware message ones.
The two chat routes had byte-identical nested SSE formatters; they now share
one helper, so the terminal event carries evidence on both.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>