Skip to content

fix(claude-code): send MemoryItem.timestamp so the extractor can resolve relative dates - #2974

Open
merlinr68 wants to merge 1 commit into
vectorize-io:mainfrom
merlinr68:fix/claude-code-retain-timestamp
Open

fix(claude-code): send MemoryItem.timestamp so the extractor can resolve relative dates#2974
merlinr68 wants to merge 1 commit into
vectorize-io:mainfrom
merlinr68:fix/claude-code-retain-timestamp

Conversation

@merlinr68

Copy link
Copy Markdown
Contributor

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:

occurred_start=None | ... | When: Today (relative to conversation)
occurred_start=None | ... | When: Last week

"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_start stays null so the memory never reaches the Timeline.

This is listed as an anti-pattern in Hindsight's own best-practices guide:

Missing timestamp on retain — Disables temporal retrieval strategies — Always set from actual content timestamps

and in the timestamp field docs:

Set whenever you have temporal context. Enables temporal retrieval strategies.

Measurement

Same transcript, same retain_mission, run through dry-run-extract on a self-hosted server with the anchor as the only variable:

transcript phrase without timestamp with timestamp
"last week we soldered the tamper pull-down" When: Last week on 2026-07-19
"yesterday the sensors phantom-cascaded" (unresolved) on 2026-07-25
"today I want to plan Phase B" When: Today (relative to conversation) on 2026-07-26

On 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_retain builds template_vars["timestamp"] (time.gmtime(), ISO 8601) and records it as retained_at metadata — it just never reached the request body. This passes that same value through as the MemoryItem reference time.

  • No new configuration.
  • No behaviour change for callers that omit it: HindsightClient.retain(timestamp=None) sends no timestamp key 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-8601 timestamp and that it matches the retained_at metadata, so both describe the same instant.
  • test_client_omits_timestamp_field_when_not_supplied — pins the omission path.

Mutation-checked: deleting the item["timestamp"] = timestamp assignment fails the first test; restoring it passes.

Scope note

This only supplies the anchor. Whether the extractor then populates occurred_start is model-dependent — a local qwen3.6 resolved relative dates into the fact text but still left occurred_start null, 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.

…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".
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant