[runtime][api] Scope long-term memory operations to the action that obtained the memory set - #1002
Open
weiqingy wants to merge 1 commit into
Open
[runtime][api] Scope long-term memory operations to the action that obtained the memory set#1002weiqingy wants to merge 1 commit into
weiqingy wants to merge 1 commit into
Conversation
…btained the set Mem0LongTermMemory holds the partition key and observation context in mutable fields that the mailbox thread overwrites on every context switch. Operations submitted through durable_execute_async run on a worker thread and read those fields themselves, so an operation can be attributed to whichever key the mailbox thread most recently switched to. The key reaches Mem0 as agent_id, which is the only isolation boundary between two keys that share a job id and a memory set name. Bind the partition key, observation id and suppression flag onto the MemorySet when it is created on the mailbox thread, and have add, get, search and delete take them from the set. The Java wrapper forwards into the same Python object and rebuilt the Python set from its name alone, so it now records the context on switch and carries it across the bridge. A set is therefore scoped to one action and must not be reused across actions. Two existing tests reused one across a context switch and expected the new key to apply; they now assert that the set keeps its own key instead. Operating on a set that carries no binding raises rather than proceeding. Mem0 ignores a falsy agent_id instead of matching on it, so an unbound set would widen an operation to every key sharing the job id and set name, which for a delete would remove another key's items. delete_memory_set takes a name rather than a MemorySet and still reads the shared field, which its docstring now records. Generated-by: Claude Code 2.1.228
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.
Linked issue: #998
Purpose of change
Mem0LongTermMemoryis shared across partition keys and holds the current key, observation id and suppression flag in mutable fields.switch_contextwrites them on the mailbox thread before each action, whileadd,get,searchanddeletecan run on a worker thread when an action passes them todurable_execute_async, and those operations read the fields themselves. The key they see is whatever the mailbox thread wrote most recently, not the key of the action that submitted the work.The key is the isolation boundary rather than observability metadata: it reaches Mem0 as
agent_id, and two keys sharing a job id and a memory set name are separated byagent_idalone. Reading the wrong one means one key's items land in, or are read from, another key's set. The observation id and suppression flag come from the same fields, so observations can be misattributed independently.This binds the partition key, observation id and suppression flag onto the
MemorySetwhen it is created, and has the four operations take them from the set. The four already receivememory_setas their first parameter, so nothing new is threaded through. A set is consequently scoped to one action and must not be held across actions.Java is covered in the same change rather than separately. Its operations all delegate into the same Python object, and the bridge rebuilt the Python set from its name alone, so once the Python side sources the key from the set, a Java-originated call would arrive without one. The Java wrapper now records the context when it switches and carries it across the bridge, and
to_python_memory_setrequires the key rather than defaulting it.Operating on a set that carries no binding raises. Mem0 tests
agent_idfor truthiness instead of matching on it, so an unbound set would widen an operation to every key sharing the job id and set name, which for a delete removes another key's items. An empty string is a legal partition key and is unaffected: the check is onNoneandnullonly.delete_memory_setis unchanged. It takes a name rather than aMemorySet, so it has no bound context and applies to the key currently in scope, which its documentation now records along with the fact that it can target a different key thanMemorySet.deleteon a same-named set. Giving it the same isolation means changing its signature, which is a public API question left open on #998.Tests
Three new tests and two adapted, plus one new Java test.
test_memory_set_stays_on_its_own_key_after_the_owner_switchestest_observations_stay_with_the_action_that_obtained_the_settest_suppression_follows_the_set_not_the_current_contexttest_unbound_memory_set_is_refused_rather_than_widenedtestForwardedSetCarriesTheContextItWasObtainedIntestUnboundSetIsRefusedRatherThanWidenedtest_switch_contextandtest_context_switch_changes_observation_owner_and_current_suppressionreused a single set across a context switch and expected the current key to apply. They now assert that a set keeps its own key, and obtain one per context.Each assertion was checked against a deliberately reverted implementation: reverting the suppression binding, the observation id binding, either guard body, or the key binding itself each leaves at least one test failing, and reverting the key binding fails five including both parameterisations of
test_switch_context.122 Python tests pass across
api/memory,runtime/memoryandruntime/tests. 75 Java tests pass acrossMemorySetTest,Mem0LongTermMemoryTest,MemoryRefTest,TestMemoryObservationFlush,ActionTaskContextManagerTestandActionExecutionOperatorTest.ruff check,ruff format --checkandspotless:checkare clean.The long-term memory e2e test is gated on
ACTION_API_KEYand needs a Flink cluster plus a local Ollama embedding model, so end-to-end coverage comes from CI.API
MemorySetgains three fields in both languages, excluded from serialization like the existing long term memory back-reference. Java addssetActionContextand three getters.BaseLongTermMemory.get_memory_set/getMemorySetkeeps its signature and gains the documented per-action scope. The runtime bridge helperto_python_memory_setnow requires the partition key; its only caller is the Java wrapper, and a version mismatch fails loudly with a missing-argument error rather than silently.Existing code that obtains a memory set inside the action using it is unaffected. Code that cached one across actions was already reading whichever key happened to be current, and now raises or stays on its original key instead.
Documentation
doc-neededdoc-not-neededdoc-included