Skip to content

fix(memory): set updated_at on creation and preserve pre-existing created_at#4499

Merged
whysosaket merged 3 commits intomainfrom
fix/create-memory-timestamps
Mar 27, 2026
Merged

fix(memory): set updated_at on creation and preserve pre-existing created_at#4499
whysosaket merged 3 commits intomainfrom
fix/create-memory-timestamps

Conversation

@utkarsh240799
Copy link
Copy Markdown
Contributor

Description

_create_memory (both sync Memory and async AsyncMemory) had three gaps in how it handled timestamps:

Problem 1: updated_at was never set on creation

When a memory was first created, only created_at was set. updated_at remained None in the vector store payload until the memory was explicitly updated via _update_memory. This caused:

  • Inconsistent API responses — consumers had to null-check updated_at since it was sometimes a string, sometimes None
  • Broken sorting/filtering — sorting by updated_at (e.g. "most recently changed") dropped all never-updated memories
  • Wrong "last modified" queries — queries like "memories changed since X" missed memories created after X but never updated

Problem 2: created_at was unconditionally overwritten

metadata["created_at"] = datetime.now(timezone.utc).isoformat()

If any caller pre-set created_at in the metadata dict (e.g. an integration backdating a memory, or the platform API honoring a user-provided timestamp), it was silently overwritten with the current time.

Problem 3: History table missing updated_at for ADD events

_update_memory passed updated_at to db.add_history(), but _create_memory did not — leaving the updated_at column NULL for all ADD history entries, even though the value was available.

Solution

In both sync and async _create_memory:

  1. Guard created_at with if "created_at" not in metadata — only auto-generate if not already present
  2. Set updated_at = created_at — a newly created record was "last modified" at creation time
  3. Pass updated_at to db.add_history() — consistent with _update_memory behavior

Related to #3720

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

19 tests in tests/memory/test_main.py (7 new + 12 existing), all passing.

New tests:

Test What it verifies
test_create_memory_sets_updated_at updated_at is set on creation, equals created_at, is UTC, and is passed to history
test_create_memory_preserves_existing_created_at Pre-existing created_at in metadata is not overwritten; updated_at matches it
test_async_create_memory_sets_updated_at Async equivalent of the above
test_async_create_memory_preserves_existing_created_at Async equivalent of the above
test_create_then_search_and_get_all_return_same_timestamps End-to-end: create a memory, then verify search() and get_all() return identical non-null created_at and updated_at
test_update_preserves_created_at_and_updates_updated_at After update, created_at is unchanged, updated_at is refreshed
test_search_and_get_all_consistent_after_update After update, search() and get_all() still return identical timestamps

Existing tests (all still pass):

  • test_create_memory_uses_utc_timestampscreated_at is UTC
  • test_update_memory_uses_utc_timestampsupdated_at is UTC, created_at preserved and normalized
  • Async equivalents of both
  • 4 _normalize_iso_timestamp_to_utc tests
  • 4 _add_to_vector_store error handling tests

Also verified against the broader test suite:

tests/memory/test_main.py  — 19 passed
tests/test_memory.py       — 21 passed
tests/test_server_auth.py  — 74 passed
                             ─────────
                             114 passed, 0 failed
  • Unit Test

Checklist:

  • My code follows the style guidelines of this project
  • I have performed a self-review of my own code
  • My changes generate no new warnings
  • I have added tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes
  • I have checked my code and corrected any misspellings

Maintainer Checklist

…ated_at

_create_memory (sync and async) had two gaps:

1. updated_at was never set — new memories had updated_at=None until
   first update, breaking sort-by-updated queries and forcing every
   consumer to null-check the field.

2. created_at was unconditionally overwritten — any pre-existing value
   in metadata was silently discarded.

3. add_history was not passed updated_at for ADD events, leaving the
   history table column NULL for newly created memories.

Fix: initialise updated_at=created_at on creation, guard created_at
with an existence check, and forward updated_at to add_history.

Related to #3720

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Applies our timestamp fixes (updated_at on creation, created_at
preservation) on top of the deepcopy(metadata) refactor from main,
using new_metadata consistently. Preserves all TestMetadataNotMutated
tests from main.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@whysosaket whysosaket merged commit 1262455 into main Mar 27, 2026
8 checks passed
@whysosaket whysosaket deleted the fix/create-memory-timestamps branch March 27, 2026 11:29
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.

2 participants