fix(claude-code): send MemoryItem.timestamp so the extractor can resolve relative dates - #2974
Open
merlinr68 wants to merge 1 commit into
Open
fix(claude-code): send MemoryItem.timestamp so the extractor can resolve relative dates#2974merlinr68 wants to merge 1 commit into
merlinr68 wants to merge 1 commit into
Conversation
…lve relative dates The retain hook never sent `timestamp`, so the extractor had no reference time for relative expressions. Transcripts saying "yesterday" or "last week" were stored with the phrase unresolved in the fact text (literally "When: Today (relative to conversation)"), which conveys nothing once the fact is recalled weeks later. The hook already computes the instant it needs — `template_vars["timestamp"]`, recorded as `retained_at` metadata — so this passes that same value through as the MemoryItem reference time. No new config, no behaviour change when a caller omits it. Measured against a self-hosted server, same transcript and mission, anchor the only variable: "last week we soldered the tamper pull-down" became a fact dated 2026-07-19, and "yesterday" became 2026-07-25. Hindsight's own best-practices guide lists this omission as an anti-pattern: "Missing `timestamp` on retain — disables temporal retrieval strategies".
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
The Claude Code retain hook never sends
MemoryItem.timestamp, so the extractor has no reference time to resolve relative expressions in a transcript.The result is facts that are stored but not usable later. A session saying "yesterday the sensors phantom-cascaded" is extracted as:
"Last week" relative to what? The anchor is exactly the information that was dropped. Once that fact is recalled weeks later it conveys nothing, and
occurred_startstays null so the memory never reaches the Timeline.This is listed as an anti-pattern in Hindsight's own best-practices guide:
and in the
timestampfield docs:Measurement
Same transcript, same
retain_mission, run throughdry-run-extracton a self-hosted server with the anchor as the only variable:timestamptimestampWhen: Last weekon 2026-07-19on 2026-07-25When: Today (relative to conversation)on 2026-07-26On a bank of ~1,400 facts accumulated without the anchor, exactly 1 carried an
occurred_start.Fix
The hook already computes the instant it needs.
run_retainbuildstemplate_vars["timestamp"](time.gmtime(), ISO 8601) and records it asretained_atmetadata — it just never reached the request body. This passes that same value through as the MemoryItem reference time.HindsightClient.retain(timestamp=None)sends notimestampkey at all, so the server keeps its existing ingestion-time fallback rather than receiving an explicit null.Tests
Two added to
TestRetainHook(203 pass, up from 201):test_retain_sends_timestamp_for_relative_date_resolution— asserts the posted item carries an ISO-8601timestampand that it matches theretained_atmetadata, so both describe the same instant.test_client_omits_timestamp_field_when_not_supplied— pins the omission path.Mutation-checked: deleting the
item["timestamp"] = timestampassignment fails the first test; restoring it passes.Scope note
This only supplies the anchor. Whether the extractor then populates
occurred_startis model-dependent — a local qwen3.6 resolved relative dates into the fact text but still leftoccurred_startnull, while a hosted model populated it 3/5 of the time on the same input. Sending the timestamp is a precondition for either, and improves the stored text regardless of which model is behind the extractor.