feat(recall): let callers supply the temporal window instead of parsing it (#3678)
* feat(recall): let callers supply the temporal window instead of parsing it
Recall derives the temporal arm's window by parsing dates out of the query
text. A caller that already knows the range it means — a date picker, an agent
that resolved "last quarter" itself — had no way to say so, and had to phrase
it in English and hope dateparser agreed.
Add `temporal_window: {start, end}` to RecallRequest. When set it is used
verbatim and the extraction is skipped entirely, which is also the point: that
work is pure CPU serialised through a single worker and costs up to ~1.3s on
document-sized query text, which is exactly what consolidation and reflect
recall with.
Naming and wording carry weight here, because the obvious reading of a date
range on a search API is "restrict results to this period" and that is not
what this does. The temporal arm is one of four retrieval arms: it surfaces
memories whose own dates fall in the window so fusion ranks them higher, and
the other three arms are untouched, so memories outside the window are still
returned. Every description — model docstring, OpenAPI field, MCP tool, both
wrappers, control plane, docs — says so explicitly.
It does not override `enable_temporal_retrieval`. That per-bank flag gates the
arm itself and stays the single switch for it, so a supplied window cannot
re-enable an arm a bank turned off.
Bounds are inclusive and naive datetimes are read as UTC at parse time, so
both ends are unambiguous before they reach a query; a reversed window is
rejected at the boundary rather than silently returning nothing.
The Rust struct literals in the CLI and the client's doctest have to name the
new field or progenitor's generated RecallRequest stops compiling.
* feat(control-plane): add the temporal window to the Recall Analyzer
The recall UI could not reach the window it now proxies. Adds a Time window
row to the Recall Analyzer: two datetime inputs, a Clear button, and a hint
that states what the window actually does — ranks memories dated in the range
higher, does not hide the ones outside it — since "date range on a search
form" reads as a filter otherwise.
The two rules live in lib/temporal-window.ts rather than the component so they
are testable: a window needs both ends (one alone is an incomplete range, not
a half-open filter), and a reversed range is withheld and flagged inline with
the Recall button disabled, instead of being sent for the API to reject with a
422.
Comparing the raw `datetime-local` strings is exact — they are already
YYYY-MM-DDTHH:mm, which sorts chronologically — so there is no Date parsing
and no local-timezone reinterpretation between the input and the request. The
value is sent with no offset, which the API reads as UTC, matching what
query_timestamp already does from this same form; the hint says so.
Strings added to all ten locales.
* fix(control-plane): reject a reversed window on Enter, not just on the button
Disabling the Recall button left the Enter-key handler on the query input
calling runSearch() directly. With a reversed range that ran the search anyway
and silently dropped the window, which is the failure the inline warning
exists to prevent. Guard in runSearch so every entry point agrees, and toast
the same message rather than doing nothing visible.
* feat(cli): expose the recall temporal window as --window-start/--window-end
check-cli-coverage caught that recall_memories gained a request-body field the
CLI neither exposes nor exempts. The exemption list is for genuinely complex
nested bodies — min_scores' four calibrated floats, tag_groups' boolean tree —
and two datetimes is not that, so expose it rather than write it off.
Flattened into two flags and recorded as such in the coverage manifest, the
same shape `include` already uses.
Both ends are required: one alone is an incomplete range, not a half-open
filter, and running a recall without the window the caller asked for is worse
than refusing. A reversed range is rejected before the request rather than
sent for the API to 422, and a datetime with no offset is read as UTC, which
is how the API reads one. N
Nicolò Boschi committed
3de41af867582c810309d6ea4c1b1de9d0ed9b7e
Parent: 9ef1f1b
Committed by GitHub <noreply@github.com>
on 8/21/2026, 11:02:46 AM