feat(event-callback): OHE-3279 : add MemoryChangeCallbackProcessor to detect MEMORY.md updates - #451
Merged
Merged
Conversation
Coverage reportClick to see where and how coverage changed
This report was generated by python-coverage-comment-action |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
… updates Add an EventCallbackProcessor that detects when an agent edits its persistent memory file (MEMORY.md) via the file_editor tool during a conversation, and records the before/after content so changes can be propagated to other conversations sharing the same memory tiers. - New MemoryChangeCallbackProcessor (event_kind=ObservationEvent) that filters for file_editor FileEditorObservation events whose path matches the project or user memory relpath, skips view/error edits, and records the change as an EventCallbackResult with a JSON detail summary (memory_tier, path, old_content, new_content). - Register the callback for new conversations when agent.agent_context.load_memory is enabled (webhook_router.py). - The callback stays ACTIVE for the conversation lifetime (memory can change multiple times); does not self-disable. - Documents the terminal-tool coverage limitation. - 11 unit tests using real ObservationEvent/FileEditorObservation instances. Co-authored-by: openhands <openhands@all-hands.dev>
…tion to primary conversation-start path Move the MemoryChangeCallbackProcessor registration from webhook_router.py (the secondary path for agent-server-created conversations) to live_status_app_conversation_service.py (the primary app-server conversation creation path), alongside the existing SetTitleCallbackProcessor registration. Why: the primary path has access to the user's agent_settings.agent_context (the source of truth for load_memory). The webhook path receives a fresh AgentContext that drops load_memory, so the callback would never fire there. - live_status_app_conversation_service.py: auto-add MemoryChangeCallbackProcessor to the processors list when user.agent_settings.agent_context.load_memory is True - webhook_router.py: revert the previous registration (back to SetTitle-only) - Add two tests: registers when load_memory=True, skips when False Co-authored-by: openhands <openhands@all-hands.dev>
…rd with callback persistence and sandbox injection Add and columns to the User model (Alembic migration 165) and corresponding fields to the Settings model. The SaasSettingsStore.store() guards from being clobbered by normal settings saves — it is written exclusively by the callback. The MemoryChangeCallbackProcessor now persists the new MEMORY.md content to the user record via a column-specific UPDATE (resolved through the conversation's created_by_user_id). The processor never self-disables. Callback registration now gates on (a top-level user setting) rather than the SDK's field, which is dropped by the fresh AgentContext built at conversation start. When is true, the app conversation service: - stamps on the AgentContext so the SDK reads MEMORY.md - writes the stored to in the sandbox during run_setup_scripts (via ) Co-authored-by: openhands <openhands@all-hands.dev>
Add an Agent Context section to the enterprise agent settings page with a SettingsSwitch for the enable_memory_context top-level user setting. The toggle uses the uncontrolled (defaultIsToggled) mode and an override pattern so the dirty state is tracked independently of the component's internal state. Co-authored-by: openhands <openhands@all-hands.dev>
…next_reset) Co-authored-by: openhands <openhands@all-hands.dev>
tofarr
force-pushed
the
feat/memory-change-callback-processor
branch
from
September 24, 2026 16:31
e65bdc0 to
1b4c63b
Compare
tofarr
marked this pull request as ready for review
September 24, 2026 17:48
tofarr
enabled auto-merge (squash)
September 24, 2026 17:55
Contributor
|
🚀 Released in 1.65.0. |
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.
HUMAN:
I have test this in a local context.
AGENT:
This PR was created by an AI agent (OpenHands) on behalf of the user.
Why
The enterprise app server needs a detection layer for persistent-memory (MEMORY.md) updates so that changes an agent makes to its memory file during a conversation can be recorded and later propagated to other conversations/sessions sharing the same memory tiers. Without it, memory edits are silent file writes with no observable signal on the app-server side.
Summary
MemoryChangeCallbackProcessor(openhands/app_server/event_callback/memory_change_callback_processor.py) — anEventCallbackProcessorwithevent_kind='ObservationEvent'that filters forfile_editorFileEditorObservationevents whosepathmatches the project (.openhands/memory/MEMORY.md) or user (memory/MEMORY.md) memory relpath, skips read-onlyviewcommands and errored edits, and records the change as anEventCallbackResult(status=SUCCESS)with a JSONdetailsummary (memory_tier,path,old_content,new_content).live_status_app_conversation_service.py— the callback is auto-added to theprocessorslist whenuser.agent_settings.agent_context.load_memoryis truthy (usesgetattrfor forward-compat), alongside the existingSetTitleCallbackProcessorregistration. This is the primary app-server conversation creation path and has access to the user'sagent_settings(the source of truth forload_memory). The webhook_router path was considered but rejected because it receives a freshAgentContextthat dropsload_memory.SetTitleCallbackProcessor, this callback never self-disables, since memory can be updated multiple times within a single conversation.file_editoredits are detected; terminal-tool writes (echo >>,sed -i, etc.) are not. This is an accepted v1 tradeoff noted in the module docstring.Issue Number
N/A — new detection layer (no tracking issue referenced).
How to Test
The processor tests construct real
ObservationEvent+FileEditorObservationinstances (no mocks) and verify:createcommand withold_content=NoneACTIVEafter firing (does not self-disable)viewcommands, error observations, non-memory paths, andNonepathsThe registration tests verify:
MemoryChangeCallbackProcessoris saved whenuser.agent_settings.agent_context.load_memory=TrueMemoryChangeCallbackProcessoris NOT saved whenload_memoryis off (default)Pre-commit passes clean (
ruff,ruff-format,mypy) on all changed files.Video/Screenshots
Turn on Persistent Memory:

Start a conversation and ask the agent to remember some facts:


Recall the facts in a later conversation:

Type
Notes
load_memoryflag lives onAgentContextin the SDK. The registration reads it fromuser.agent_settings.agent_context(the user's persisted settings) rather than from theAgentContexton the builtStartConversationRequest.agent, because_build_start_conversation_request_for_usercreates a freshAgentContextthat dropsload_memory. Usesgetattrfor forward-compat with SDK versions that predate the field.~/.openhands, a user-tier path collides with the project relpath suffix and is classified as project tier — this is the accepted ambiguity of a path-only heuristic and is noted in code comments.EventCallbackResultrecords to propagate memory updates across conversations.Enterprise server image for this PR: