Skip to content

[event memory 2/6] Rename the segment store to the event memory store (port of #1659's rename) - #1686

Draft
edwinyyyu wants to merge 22 commits into
MemMachine:feat/horizontal-scalingfrom
edwinyyyu:port/event-memory-write-transaction-main
Draft

edwinyyyu wants to merge 22 commits into
MemMachine:feat/horizontal-scalingfrom
edwinyyyu:port/event-memory-write-transaction-main

Conversation

@edwinyyyu

@edwinyyyu edwinyyyu commented Sep 17, 2026 •

Copy link
Copy Markdown
Contributor

Source and review

What changes

The rename, which the title carries because it is user-visible: the segment store is the event memory store throughout, the configuration key segment_store becomes event_memory_store in ProjectConfig, the OpenAPI document, the client, the docs, the sample configs, the Helm chart and the compose files, and its tables are event_memory_store_*, #1840's event, pending derivative and tombstone tables among them (recreated, no migration). No behavior changes. One commit.

Stack

Two stacks, one line of branches. Every PR but #1693 targets feat/horizontal-scaling, so a diff shows everything below it on that branch until that merges; #1693 is client-only, branches from main and targets it. Rebuilt on 2026-09-28: #1684 split into time bounds and sources (#1684) and sessions (#1715), the block kind moved to #1687, and each PR restacked in dependency order. Rebased on 2026-10-01 after #1713 merged, dropping the merge commit that carried it; on 2026-10-06 after #1733 was squash-merged into feat/horizontal-scaling; on 2026-10-07 after that branch took main's #1707, which makes episode uids UUIDs; and on 2026-10-09, after #1736 was squash-merged into feat/horizontal-scaling, onto #1663's head c0a2bbd, and the same day onto fb38a72, after #1628 moved beneath #1663 and #1813 was squash-merged into feat/horizontal-scaling; on 2026-10-13 onto 1982ad3, after #1702 changed how the vector store's part of a filter is chosen; on 2026-10-14 onto 251dc60, after #1702 and #1670 merged into feat/horizontal-scaling; and on 2026-10-15 onto 250f6d5, after that branch was rebased onto main d5a39a8, then onto 3f27430, the same tree with #1627's history in three commits, then onto 0cc31da, where the vector store's lookup is open_partition and the partition API changes move to #1625, then onto 8866cb8, where #1627 drops the per-partition schema record; and on 2026-10-16 onto 203be42, where the vector store's interface loses its store-level properties and validate_identifier becomes is_valid_identifier, which #1684 and #1687 now call, then onto 4134c43, where the vector store's raising checks are named validate_*, then onto 1d6e17f, which renames a configuration check; and on 2026-10-19 onto 6f42ba1, after #1824 dropped the purge queue's unread logical key, with #1715 taken out of the line: #1688 sits on #1692 and marks segments only, and the v1 API (#1690) keeps an event's session as an event property. #1715 stays open on #1692, deferred, and nothing depends on it; #1688 and the coding-agent PRs above it stay deferred with the coding-agent features. The same day onto c14c06d, which changes one word of a design document, and then onto #1628's head 0375a60, with #1663 taken out of the line at Edwin's request: event memory scores by the vector store partition's similarity metric again, eviction applies one rule under every metric, and the v1 API keeps min_cosine_similarity for a tenant that scores by cosine similarity and a higher-is-better score for every tenant. The same day onto #1628's head 35e60c9, after #1627 let DatabaseManager return the existing store for a repeated name, onto b077a30, with #1627's second-review fixes to its tests, docstrings and documents, and on 2026-10-20 onto 0bef137, where #1627's comments, docstrings and documents were corrected, then onto the event memory recovery PRs, #1839 and #1840 (head 52e1805), which give EventMemory a store's shape with a handle per partition and keep its two stores consistent through failed ingests and forgets: their write path and deletion take the place of #1686's write transaction and deletion order, so #1686 keeps the rename alone, #1685's eviction takes their write path, and the chain's changes to EventMemory's data operations land on the partition handle. On 2026-10-26 onto #1840's head 5b16f04, after #1844's SQLite write transactions came in between #1627 and #1628, #1841's upsert packing onto #1628, and #1839 and #1840 were rebuilt on them: EventMemory takes its vector store's schema in its parameters, checks the reserved keys against it once and gives each partition handle the declared keys and the similarity metric, which a handle read from its vector store partition before; every session is served from the deployment's one EventMemory; and every event session's reranker, segmenter and deriver are the deployment's. On 2026-10-27 onto #1840's head 75982b9, after #1627 was squash-merged into feat/horizontal-scaling as 805ea21 and the PRs between were rebased onto it; the tree changed only in two Milvus tests' request timeouts. The same day onto #1840's head 94b3588, after #1628 and #1844 swapped places with Edwin's approval, #1628 now directly on feat/horizontal-scaling; the tree did not change. The same day onto #1840's head cff96f2, after #1628's review commits removed the supported filter nodes and UnsupportedFilterError, folded declared_properties.py into utils.py and renamed sql_columns.py to sqlite_properties.py; nothing in this chain uses them. The same day onto #1840's head b631cd8, after #1628's second review round moved is_valid_identifier into the vector store's data_types; #1684 and #1687 import it from there. On 2026-10-28 onto #1840's head f4b4870, after #1628 took a docstring and dropped a flag that did nothing; the tree here did not change otherwise. On 2026-10-30 onto #1840's head 004e6c7, after #1831, #1628 and #1858 were squash-merged into feat/horizontal-scaling and #1844, #1841, #1839 and #1840 were rebased onto it. On 2026-11-02 onto #1840's head 96097c2, after #1844 was squash-merged, with the rename regenerated so that the event memory store carries #1840's SQLite 3.35 check and the shared validate_engine_connections. The same day onto #1840's head cda5fac, after Edwin dropped #1841's size-based upsert packing: its cancellation contract is now #1840's first commit, and #1839 sits on feat/horizontal-scaling. Then onto the key rule: Edwin moved #1828 below #1684 and set it parallel to #1839 on feat/horizontal-scaling, so the chain sits on a copy of #1828 rebuilt on #1840 (head 4fe221b, on #1840 a82fc6e and #1839 8319ec6). #1684's key-rule part moved into #1828, the filter syntax is left to the filter language and the API, and #1690 keeps an event's session as its own application property. #1839's search is query, LongTermMemory takes the session's memory or partition directly, and EventMemory takes its schema from the vector store.

Event memory, on #1828's key rule rebuilt on #1840 (on #1839):

# PR Base Content
1/6 #1684 #1828 (copy on #1840) time bounds, sources and expansion (port of #1597 without session)
2/6 #1686 (this PR) #1684 the rename to the event memory store (port of #1659's rename)
3/6 #1685 #1686 eviction at ingest (port of #1617)
4/6 #1687 #1685 the block kind column, parameter and record key; context parts; kind-keyed tables (port of #1611)
5/6 #1692 #1687 the capture block kinds: tool_call, tool_result, injected, thinking
6/6 #1688 #1692 segment id markers in rendering, lines in the store's order (port of #1632 without session blocks)

Off the line, on #1692: #1715, sessions in the event memory: the session column, index and walk, session_ids, deferred; nothing above depends on it.

Coding agents, slices of design/coding_agent_integration.md (#1579), on the event memory stack; 3/3 shares no code with the server, so its branch is on main, but it configures the endpoint 2/3 serves and writes the kinds #1692 registers, so it merges after both:

# PR Base Content
1/3 #1690 #1688 the v1 event-memory API: tenants, query, expand, events
2/3 #1691 #1690 memory_query and memory_expand served at /v1/mcp
3/3 #1693 main Claude Code and Codex in the client: the MCP entry, the Stop hook, and capture

At this head, on #1828's copy 4fe221b on #1840 a82fc6e (2026-11-03): ruff, ruff format, ty as CI runs it on packages/server, uv lock --check and the OpenAPI comparison pass. Unit and integration suites run again when this PR comes up for review; they last passed on 2026-10-07 on #1663 e0dedbd, before the chain moved onto the event memory recovery PRs and when it scored by cosine similarity only: 2140 server tests, 129 PostgreSQL store integration tests and 258 client tests.

🤖 Generated with Claude Code

https://claude.ai/code/session_01YBbQgZiCqeoLu83EkbEFHE

@edwinyyyu
edwinyyyu force-pushed the port/event-memory-write-transaction-main branch from f960290 to d507fbb Compare September 17, 2026 23:42
@edwinyyyu edwinyyyu changed the title Encode an event once, with its records inside the store's transaction (port of #1659) Encode an event once inside the store's transaction, and rename the segment store to the event memory store (port of #1659) Sep 17, 2026
@edwinyyyu
edwinyyyu force-pushed the port/event-memory-write-transaction-main branch from d507fbb to 01ebc47 Compare September 18, 2026 00:08
@edwinyyyu
edwinyyyu marked this pull request as draft September 18, 2026 16:47
@edwinyyyu
edwinyyyu force-pushed the port/event-memory-write-transaction-main branch from 01ebc47 to 90e8419 Compare September 18, 2026 19:50
@edwinyyyu
edwinyyyu force-pushed the port/event-memory-write-transaction-main branch from 90e8419 to 79c2494 Compare September 28, 2026 18:02
@edwinyyyu edwinyyyu changed the title Encode an event once inside the store's transaction, and rename the segment store to the event memory store (port of #1659) [event memory 2/7] Encode an event once inside the store's transaction, and rename the segment store to the event memory store (port of #1659) Sep 28, 2026
@edwinyyyu
edwinyyyu force-pushed the port/event-memory-write-transaction-main branch from 79c2494 to 783f776 Compare October 2, 2026 00:18
@edwinyyyu edwinyyyu added the poc Proof-of-concept implementation for a solution, feature, idea, etc. label Oct 2, 2026
@edwinyyyu
edwinyyyu force-pushed the port/event-memory-write-transaction-main branch 2 times, most recently from 9dd83d8 to 83558c5 Compare October 7, 2026 20:11
@edwinyyyu
edwinyyyu changed the base branch from main to feat/horizontal-scaling October 9, 2026 00:50
@edwinyyyu
edwinyyyu force-pushed the port/event-memory-write-transaction-main branch 6 times, most recently from c66ea7d to 2b7131f Compare October 10, 2026 00:33
@edwinyyyu
edwinyyyu force-pushed the port/event-memory-write-transaction-main branch 4 times, most recently from 952e82a to 01aed98 Compare November 2, 2026 18:05
edwinyyyu and others added 2 commits November 2, 2026 11:30
EventMemory is built from the segment store, the vector store and the
models. It opens or creates a partition by key in both stores, the vector
store's first, and deletes a partition from both. EventMemoryPartition is
the handle that encodes, queries and forgets; it holds its two store
partitions privately, so no caller can pair one key's partition in one
store with another key's partition in the other.

The service locator builds the EventMemory and opens the session's
partition through it. EventBackendParams carries the EventMemory and the
handle, and LongTermMemory deletes the session's partition through
EventMemory. The reserved timestamp field and text extraction, shared by
both classes, move to module level. No behavior changes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tion

A vector store's schema is fixed when the store is built, so EventMemory
takes it as a parameter, the same schema the composition root built the
vector store with, rather than reading it from each partition it opens.

- EventMemoryParams gains `store_schema`. EventMemory checks once, at
  construction, that the schema declares the fields EventMemory requires,
  and hands each partition handle the declared fields and the similarity
  metric. A handle's `similarity_metric` and its score direction come
  from them.
- The service locator builds the vector store and EventMemory from one
  schema, `event_backend_store_schema`.
- `VectorStorePartition` no longer has a `store_schema` property; each
  store's partitions keep the schema privately.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@edwinyyyu
edwinyyyu force-pushed the port/event-memory-write-transaction-main branch from 01aed98 to 75f65ac Compare November 2, 2026 19:40
edwinyyyu and others added 5 commits November 2, 2026 14:27
Deleting a session built its EpisodicMemory, which opened, and so could
create, the session's event-backend partitions, and then deleted them
through the session's own LongTermMemory, which held the EventMemory only
for that.

The long-term service locator now builds each backend's memory and
deletes a session's storage, both as functions that match on the
configuration's backend: the event backend deletes the session's
partitions by key through EventMemory, opening neither, and the
declarative backend deletes the episodes its DeclarativeMemory of the
session matches. The episodic service locator deletes the storage
whenever the configuration builds long-term memory. EpisodicMemoryManager
closes a cached idle instance, then deletes the stored configuration's
storage, and builds no EpisodicMemory.

LongTermMemory is the one class that routes requests on the backend: it
takes each backend's memory built, holds no EventMemory, partition key,
or deleted state, and has neither drop_session_partition nor close, whose
body did nothing. EpisodicMemory.delete_session_episodes has no caller
and goes; its short-term half only reset in-memory state. The evaluation
scripts build the DeclarativeMemory they pass in, and delete a session
through delete_long_term_memory_storage.

A session saved with `enabled: false` now has its long-term storage
deleted too, where the old path returned before deleting anything.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
LongTermMemory has had a single search since its unscored `search` went,
so `search_scored` no longer tells it apart from anything. It is `query`,
as the calls above and below it are: EpisodicMemory.query_memory and
EventMemoryPartition.query. Its two backend branches are
`_query_declarative` and `_query_event`. DeclarativeMemory keeps
`search_scored`, which it tells apart from its unscored `search`.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`_declarative_params` was one statement and `_event_params` a block with
one caller, each standing between `long_term_memory_params_from_config`'s
match and the work it routes to. The work now sits in the match arms, as
it does in `delete_long_term_memory_storage` beside it. `_declarative_memory`
and `_event_memory` stay, since building and deleting both use them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Once the service locator built each backend's memory, the two backend
params differed only in that memory: a DeclarativeMemory, or an
EventMemoryPartition with the episode storage that turns its results into
episodes and the reranker that sets its score direction. LongTermMemory
now takes the memory itself, with those two as keywords, and each request
matches on the memory's type. An EventMemoryPartition without episode
storage is refused when the LongTermMemory is built.

DeclarativeBackendParams, EventBackendParams, their union, the backend
literal, and the session id, which nothing read, go. The service locator's
`long_term_memory_from_config` builds the LongTermMemory in each match arm,
and the episodic service locator takes it from there. The evaluation
helper passes its DeclarativeMemory directly.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The episodic service locator's `delete_episodic_memory_storage` only
checked whether the stored configuration builds long-term memory and
passed it on. EpisodicMemoryManager.delete_episodic_session now makes that
check and calls `delete_long_term_memory_storage` itself, so deleting a
session goes from the manager straight to the long-term layer.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@edwinyyyu
edwinyyyu force-pushed the port/event-memory-write-transaction-main branch from 75f65ac to 7346f12 Compare November 3, 2026 01:51
edwinyyyu and others added 12 commits November 3, 2026 08:50
EventMemoryParams carried the schema its vector store was built with,
beside the store itself, so the schema had two sources that the
composition root had to keep in step. A vector store now answers its
own `store_schema`, the schema it was built with, and EventMemory reads
its required fields, declared fields, and similarity metric from it.
EventMemoryParams no longer takes a schema, and the service locator no
longer passes one. Each store returns the schema it already held.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Event memory bounds when its vector writes are sent by cancelling an
upsert at a deadline, which needs a written guarantee of what cancelling
does. VectorStorePartition.upsert now states that cancelling the call
stops it from sending further requests to the backend, and that a
request already sent may still be applied, so a cancelled upsert may
have written some of the records. RegistryBackedVectorStorePartition's
`_upsert` hook states the same obligation for its implementations.

Every implementation honors it:

- RegistryBackedVectorStorePartition awaits the registration check,
  `_upsert`, and the check again in sequence, so a cancellation stops at
  the await it lands on.
- Qdrant and Milvus await each request, and send the halves of a batch
  the server refuses as too large one after another, so a cancellation
  raised in a request ends the call. Neither client catches
  CancelledError: qdrant-client's REST path catches Exception only, and
  pymilvus's retry decorators catch grpc.RpcError, MilvusException, and
  Exception, so a cancelled RPC surfaces as CancelledError and is not
  retried.
- sqlite-vec writes in one transaction. A statement already queued to
  aiosqlite's thread finishes there; SQLAlchemy treats the cancellation
  as a disconnect and invalidates the connection, so the transaction
  never commits and nothing more is executed on it.
- The engine-backed SQLite store commits its records and pending
  operations, then applies them to the search engine, then marks them
  applied. A cancellation in the engine step leaves the operations
  pending, and startup replays them.
- The in-memory test partition has no await in its upsert.

Tests cancel an upsert mid-call and check that nothing more goes out:
for Qdrant and Milvus with a fake client that refuses the batch as too
large and holds its first half (the second half is never sent, and the
registration is not checked after it), and against the server (the
request in flight; for Milvus counted at the gRPC stub, so a pymilvus
retry would show); for both SQLite stores, cancelling at the upsert's
first statement, and for the engine-backed one also while its search
engine applies the records, no further statement executes.

Verification: ruff, ruff format, and ty clean; the vector store unit
tests, 401 passed, 5 skipped. The fake-client tests fail when the
halving swallows the cancellation and sends the second half. The
integration tests need Docker and were not run.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…forgets

The segment store and the vector store share no transaction, so an ingest
that stopped after storing its events left them unsearchable, and a forget
racing an ingest or a late vector write could leave vectors of forgotten
events behind.

The segment store now holds the events themselves, and two queues of work
the vector store still owes:

- Pending derivatives: an ingest stores its events, segments, derivative
  links, and one pending record per event in one transaction, upserts the
  vectors within a write budget, and deletes the record. A record left
  behind is claimed by any process once the budget has passed, and
  written again within a budget of its own.
- Tombstones: a forget locks its events, deletes their pending records,
  records the derivatives it unlinked in one tombstone, and deletes the
  events in one transaction. The vectors are deleted at once and again
  after the tombstone retention, which outlasts any write sent within
  its budget.

Claims hold a lease, back off doubling on failure, and stop after
repeated failures, which is logged. A search drops a hit with no link
and deletes its vector once, best effort. Partitions are deleted from
the segment store first. Each purge call stays within its bound across
segments, events, and the derivatives tombstones name.

design/event_memory_ingest_and_deletion.md gives the protocol and why
it is safe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EventMemory's background work claims pending derivatives and tombstones
from the whole segment store database, and its records name no vector
store or embedder: it writes and deletes with its own. That is right only
if one EventMemory serves every partition in the database, and only if it
runs whether or not a session is opened.

The resource manager now builds the deployment's EventMemory from the
episodic memory's long-term memory configuration, starts its background
work, and shuts it down on close. The server builds it at startup when
that configuration names a segment store. A session opens its partition
on it, and the deployment's segmenter, deriver, and reranker serve every
session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…deployment

The deployment's one EventMemory serves every event session with its own
segmenter, deriver, and reranker, so the copies a session saved no longer
apply. The session's event configuration no longer has those fields, and
rows saved earlier load with them ignored; creating a project checks a
reranker only on the declarative backend.

A project's `reranker` applies to the declarative backend only, as its
description now says, and an event project's response names none, as for
`embedder`. The OpenAPI document is regenerated for these fields only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adding events and claiming pending derivatives and tombstones depend on
RETURNING, which SQLite added in 3.35: the event insert returns the
events it stored, and each claim returns the row it took. The store's
params refuse an older SQLite runtime, after the check that each session
gets its own connection to one database, and a test raises the minimum
past the runtime to see the refusal.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…tamp under it

MemMachine and its users share one property key space. A key beginning
with `memmachine_` now belongs to MemMachine: `reserved_property_key`
builds one as `memmachine_<system>_<field>`, and every key is a valid
vector store identifier, `[a-z0-9_]` and at most 32 bytes.

EventMemory writes its timestamp under `memmachine_em_timestamp`. It
refuses an event whose property key is reserved or not a valid
identifier, through `validate_user_property_key`; a key is used exactly
as given, never rewritten.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An EventMemory filter, and a segment store's, named a property in two
ways: `m.<key>` for the key itself, and a bare name for `_<name>`, the
layout one consumer gives its own fields. The bare form tied both to
that consumer's keys.

A filter field is now `timestamp` or `m.<key>` (or `metadata.<key>`).
EventMemory refuses any other field before it searches, and the segment
stores refuse it as well. The vector search filters on the timestamp,
under its reserved key, and on the declared properties. The long-term
memory adapter names its system fields as properties when it hands a
filter to EventMemory, so its client filters are unchanged.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
MemMachine reserved two key namespaces at once. EventMemory refused a
property key beginning with `memmachine_`, while the long-term memory
adapter wrote its own fields under a leading underscore and refused a
caller key beginning with one. So a caller could not use an underscore,
and the adapter's fields sat outside the namespace meant for them.

The adapter now writes its fields under `memmachine_ltm_<field>`, and
refuses a caller key that is not a legal user property key. A filter
still names a system field by its bare name, and refuses a `m.<key>`
in the reserved namespace.

The reserved namespace is divided by the system that writes each key.
EventMemory refuses only its own `memmachine_em_` keys, in an event
and in a filter, so the component that embeds it can write its own
reserved keys. `is_reserved_property_key` takes the system to ask about
one system's keys.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An episode's metadata keys become event properties, and the event
backend refuses a key that is not a legal user property key. That
refusal came while the episode store and semantic memory were writing
the same episodes in parallel, so the request failed after they had
kept them, and a request over HTTP got a 500.

`MemMachine.add_episodes` now checks every metadata key of every entry
with `validate_user_property_key` before it writes anything, and the
add-memories route answers a ValueError with 422, as the other routes
do. The metadata field's description states the key rule.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The metadata field's description gives the key rule in words instead
of a pattern. EventMemory's filter check and the adapter's conversion
state what holds for a reserved key, and `MemMachine.add_episodes`
says its refusal comes before any store is written.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
EventMemory and the segment stores spelled out how a client names a
property in a filter, and EventMemory checked a filter's fields against
that spelling before it searched. The spelling belongs to the filter
language and to the API that accepts a client's filter.

EventMemory no longer checks a filter's fields: the long-term memory
adapter checks a client's filter, and the segment stores refuse a field
that names neither a property nor the timestamp. For the vector search,
EventMemory translates the timestamp to its reserved key and a property
to its key, and passes any other name unchanged. The docstrings describe
a filter over the segments' properties and timestamp.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@edwinyyyu
edwinyyyu force-pushed the port/event-memory-write-transaction-main branch from 7346f12 to 10c371d Compare November 3, 2026 17:32
edwinyyyu and others added 3 commits November 3, 2026 09:57
The docstrings of EventMemory's query and filter translation, the
segment stores' filter, and the adapter's filter mapping named the
timestamp as a special field. They return to their wording on the
base, changed only where it no longer holds: an event memory base
property translates to its reserved key, and a segment store refuses
an unknown field.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…hine#1597, without session)

The part of MemMachine#1597 that is not session: events and segments carry a
nullable source id; query and expand take since/until and source_ids;
expand walks outward from a seed, which is an address located whatever
the filters say, and returns a Neighborhood that excludes it; query
answers QueryHits (score, seed, neighborhood); rendering is
render_segments with a DateTimeFormat; the segment store answers
get_segments and get_segment_neighborhoods in place of
get_segment_contexts; the v2 adapter lifts timestamp/created_at bounds
and producer_id conjuncts out of the filter into the typed parameters;
the timestamp column holds a UTC instant.

On this base:
- A neighborhood is MemMachine#1713's walk: the partition's order next to the
  seed, filters applied inside its window of 1,000 segments per side.
  The ordering index keeps main's shape.
- The vector stage gets MemMachine#1684's predicates on the reserved keys and,
  joined with AND, the part of the property filter that names declared
  fields alone, as MemMachine#1702's `extract_filter` routing has it; the segment
  store still gets the whole filter, and its seed lookup leaves out a
  seed the filter rejects. A record's declared properties are its
  event's, which every segment carries unchanged, as the segmenter
  contract now states.
- On the event memory recovery PRs (MemMachine#1839, MemMachine#1840), the data operations
  and expand are EventMemoryPartition's, and rerank and render_segments
  are static on EventMemory; the reranker leaves EventMemory's
  parameters, since LongTermMemory reranks with the deployment's. A
  pending derivatives record holds its event's properties once
  (segment_store_pd), and its derivatives their source id and no
  context (segment_store_pd_dv), following Derivative, so encode and
  the background work build a record from the same properties. The
  search's cleanup of unlinked records stays a helper of its own.
- On MemMachine#1828's key rule, the source is EventMemory's own reserved key,
  `memmachine_em_source`, which `source_ids` reaches. A timestamp
  condition in the filter still reaches the vector search under its
  reserved key, beside the since/until predicates.
- Episode uids are UUIDs since MemMachine#1707: the search path reads each hit's
  episode uid back as a UUID, and the tests key their episodes by
  `_uid(name)` as main's do.
- Session and block kind are split out: the block kind column,
  parameter and record key go to the kinds PR, which is their first
  consumer, and session goes to its own PR.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBbQgZiCqeoLu83EkbEFHE
Mechanical: the store now holds events, segments, links, pending
derivatives and tombstones, so its name follows its dependent.
`SegmentStore` and every derived name become `EventMemoryStore` and
theirs, the module and test directories move with them, the tables and
indexes take the `event_memory_store_` prefix, and the configuration key
`segment_store` becomes `event_memory_store` in the API spec, the client,
the docs, the sample configs, the Helm chart and the compose files. No
behavior changes; tables are recreated. Applied by the same
substitutions to this tree rather than rebased.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YBbQgZiCqeoLu83EkbEFHE
@edwinyyyu
edwinyyyu force-pushed the port/event-memory-write-transaction-main branch from 10c371d to 386e7c4 Compare November 3, 2026 18:04

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

poc Proof-of-concept implementation for a solution, feature, idea, etc.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant