What happens
Measured on main at ad8ff24, one value at a time through each store's own code, on SQLite and PostgreSQL where a store supports both, with Qdrant 1.17.0 (REST and gRPC) and Milvus 2.6.24 and Milvus Lite. The value is a message's timestamp, which becomes the episode's creation time and its _created_at and _timestamp properties (created_at of an EpisodeEntry).
| store |
0001-01-01T00:00:00+05:00 or 9999-12-31T23:00:00-05:00 |
UTC offset with seconds (+05:30:45) |
UTC offset with a fraction of a second |
| episode store, SQLite and PostgreSQL |
refused (OverflowError: date value out of range) |
stored as its UTC instant |
stored as its UTC instant |
| segment store, SQLite and PostgreSQL |
refused (OverflowError) |
stored |
offset cut to whole seconds, instant kept |
SQLiteVectorStore, SQLiteVecVectorStore, MilvusVectorStore |
refused (OverflowError) |
stored |
offset cut to whole seconds, instant kept |
QdrantVectorStore, REST, gRPC, and local |
stored |
offset cut to +05:30 with the same wall clock, so the instant moves by 45 seconds |
offset dropped with the same wall clock, so the instant moves |
Through POST /api/v2/memories with the real stores, on SQLite and on PostgreSQL:
timestamp 0001-01-01T00:00:00+05:00: 500, nothing written;
timestamp 2026-01-01T12:00:00+05:30:45: 200, stored, so a Qdrant-backed session keeps an instant 45 seconds off.
Why
MemoryMessage.timestamp is a plain datetime (packages/common/src/memmachine_common/api/spec.py:466), and its validator takes any offset datetime.fromisoformat reads (:503), seconds and fractions included. EpisodeEntry.created_at is any aware datetime (packages/server/src/memmachine_server/common/episode_store/episode_model.py:35).
- The episode store, the type-tagged property codec, and the Milvus store convert the value to UTC (
packages/server/src/memmachine_server/common/episode_store/episode_sqlalchemy_store.py:220-222, packages/server/src/memmachine_server/common/properties_json.py:54, packages/server/src/memmachine_server/common/vector_store/milvus_vector_store.py:95), which overflows outside years 1 through 9999.
- The codec keeps the offset in whole seconds (
packages/server/src/memmachine_server/common/utils.py:77-80), and the Qdrant store sends the datetime itself (packages/server/src/memmachine_server/common/vector_store/qdrant_vector_store.py:266), whose RFC 3339 form carries the offset in minutes.
Expected
The request refuses such a timestamp with 422 before any store writes, and building an EpisodeEntry with such a created_at raises.
Fix
#1792 types timestamp as PropertyDatetime and checks EpisodeEntry.created_at with the same rule: a UTC offset of whole minutes and a UTC instant in years 1 through 9999.
🤖 Written by Claude Code (Claude Opus 5.5) on behalf of @edwinyyyu.
What happens
Measured on
mainat ad8ff24, one value at a time through each store's own code, on SQLite and PostgreSQL where a store supports both, with Qdrant 1.17.0 (REST and gRPC) and Milvus 2.6.24 and Milvus Lite. The value is a message'stimestamp, which becomes the episode's creation time and its_created_atand_timestampproperties (created_atof anEpisodeEntry).0001-01-01T00:00:00+05:00or9999-12-31T23:00:00-05:00+05:30:45)OverflowError: date value out of range)OverflowError)SQLiteVectorStore,SQLiteVecVectorStore,MilvusVectorStoreOverflowError)QdrantVectorStore, REST, gRPC, and local+05:30with the same wall clock, so the instant moves by 45 secondsThrough
POST /api/v2/memorieswith the real stores, on SQLite and on PostgreSQL:timestamp0001-01-01T00:00:00+05:00: 500, nothing written;timestamp2026-01-01T12:00:00+05:30:45: 200, stored, so a Qdrant-backed session keeps an instant 45 seconds off.Why
MemoryMessage.timestampis a plaindatetime(packages/common/src/memmachine_common/api/spec.py:466), and its validator takes any offsetdatetime.fromisoformatreads (:503), seconds and fractions included.EpisodeEntry.created_atis any aware datetime (packages/server/src/memmachine_server/common/episode_store/episode_model.py:35).packages/server/src/memmachine_server/common/episode_store/episode_sqlalchemy_store.py:220-222,packages/server/src/memmachine_server/common/properties_json.py:54,packages/server/src/memmachine_server/common/vector_store/milvus_vector_store.py:95), which overflows outside years 1 through 9999.packages/server/src/memmachine_server/common/utils.py:77-80), and the Qdrant store sends the datetime itself (packages/server/src/memmachine_server/common/vector_store/qdrant_vector_store.py:266), whose RFC 3339 form carries the offset in minutes.Expected
The request refuses such a timestamp with 422 before any store writes, and building an
EpisodeEntrywith such acreated_atraises.Fix
#1792 types
timestampasPropertyDatetimeand checksEpisodeEntry.created_atwith the same rule: a UTC offset of whole minutes and a UTC instant in years 1 through 9999.🤖 Written by Claude Code (Claude Opus 5.5) on behalf of @edwinyyyu.