Repository navigation
Conversation
edwinyyyu
force-pushed
the
fix/semantic-enabled-gate-speedkick
branch
from
September 9, 2026 21:35
4f5cb71 to
07f8b13
Compare
…tup (speedkick) `semantic_memory.enabled: false` only skipped the semantic service in start(). Every request path that could name the semantic type still resolved the semantic stack lazily and used it: add_episodes, query_search and list_search branched on target_memories alone, and the SemanticManager getters built storage, config store, embedder and language model on first use without reading the flag (running Alembic migrations on the request path for pgvector). The flag changed who paid to bring the stack up, not whether it came up. With a complete config the request succeeded and did real work; with the incomplete config that MemMachine#1580 now produces by default, get_semantic_storage raised from inside the request. Deletion had the same gap with a twist. memmachine.py defined _cleanup_semantic_history twice; both arrived in MemMachine#1151. The later definition, which requests the semantic service unconditionally, shadowed the earlier one that caught ResourceNotReadyError, so with semantic memory disabled every project deletion raised in the worker, dropped the job, and left the session in `delete` status for good (MemMachine#1575). delete_episodes requested the service unconditionally too. Three changes: - ResourceManagerImpl.get_semantic_manager raises ResourceNotReadyError("Semantic memory is disabled.") when the flag is off, so nothing semantic is constructed; get_semantic_service and get_semantic_session_manager go through it. The flag is read only on the server side: MemMachine routes requests, and the composition root refuses to build a component the deployment does not have. SemanticResourceManager and everything below it never see it. The semantic-only MemMachine operations (features, set types, ...) now fail with that message instead of building a stack the operator turned off. - MemMachine gains enabled_memory_types, read from the two configuration flags, and add_episodes, query_search and list_search filter target_memories through it. A request naming a disabled type gets None for that field, exactly as when it did not name the type. Skipping rather than rejecting is forced by the client: memmachine_client sends both types on every add and search, so rejecting would fail every client call against the default configuration. Episodic gets the same treatment, so a semantic-only deployment no longer stores the episode and then raises "Episodic memory is disabled" on the same request. The MCP tools keep passing ALL_MEMORY_TYPES, which now means every type the server has. - The shadowed _cleanup_semantic_history is removed; the surviving one is called only when semantic memory is enabled, matching the guard _delete_queued_session already applied to the semantic delete itself, and delete_episodes resolves the semantic service under the same guard. - Two more mixed paths reached the semantic stack unconditionally and would have started failing on a disabled deployment once the composition root refused: the MCP delete tool's _delete_memories called delete_features even with no semantic uids named, and delete_all asked for the semantic manager before touching the episode store. The first now calls delete_features only when semantic uids were named; the second touches the semantic stores only when semantic memory is enabled. The integration conftest built a complete SemanticMemoryConf without saying enabled=True and relied on the lazy build; it now says it. The batched deletion test flips semantic memory on so its per-batch cleanup assertion keeps meaning something. Fixes MemMachine#1575 Fixes MemMachine#1600 Co-Authored-By: Claude Fable 5.1 <[email protected]> Claude-Session: https://claude.ai/code/session_018nSC7JKXByD1WADJukvupg
edwinyyyu
force-pushed
the
fix/semantic-enabled-gate-speedkick
branch
from
September 9, 2026 21:48
07f8b13 to
304619e
Compare
9 tasks done
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.
Fixes #1600 and #1575. Both are the same missing gate:
semantic_memory.enabledwas consulted once, instart(), and nowhere on the request-serving side.Purpose of the change
With
semantic_memory.enabled: false, every request path that could name the semantic type still resolved the semantic stack lazily and used it.add_episodes,query_searchandlist_searchbranched ontarget_memoriesalone, and theSemanticResourceManagergetters built storage, config store, embedder and language model on first use without reading the flag, running Alembic migrations on the request path for pgvector. The flag changed who paid to bring the stack up, not whether it came up:enabled: false: the request succeeded and did real work. Measured on 2026-08-27 at231ce171: a search naming both types cost 2.00 embedder calls per request at steady state, 4 on the first request after a start.enabled: false, which is what feat(config): default to no short-term or semantic memory, and infer the backend #1580 now produces by default:get_semantic_storageraisedResourceNotReadyError("No database configured for semantic storage.")from inside the request.The REST router has defaulted
typesto episodic since #1555, so an untyped REST call was shielded. An explicittypes, the Python client (which sends both types on every add and search), and both MCP tools (which hardcodeALL_MEMORY_TYPES) were not.Deletion had the same gap with a twist.
memmachine.pydefined_cleanup_semantic_historytwice; both arrived in #1151. The later definition, which requests the semantic service unconditionally, shadowed the earlier one that caughtResourceNotReadyError. So with semantic memory disabled, every project deletion raised in the worker, dropped the job, and left the session indeletestatus until a restart re-queued it and failed the same way (#1575).delete_episodesrequested the service unconditionally too.Description
ResourceManagerImpl.get_semantic_manager(common/resource_manager/resource_manager.py): raisesResourceNotReadyError("Semantic memory is disabled.", "semantic_memory")when the flag is off, so nothing semantic is constructed.get_semantic_serviceandget_semantic_session_managergo through it. The semantic-onlyMemMachineoperations (features, set types, and so on) now fail with that message instead of building a stack the operator turned off; their routes already map unexpected exceptions to 500, and I have left the status mapping alone.Where the flag is read. Two places, both on the server side:
MemMachineroutes requests (the filter below, and the delete guards), and the composition root refuses to build a component the deployment does not have, the same contract it has for an embedder name that is not configured.SemanticResourceManagerand everything below it never see the flag. The first revision of this PR had the semantic manager's own getters read it; that was the wrong layer, and the episodic side already shows the right one, with "Episodic memory is disabled" raised inMemMachine.MemMachine(main/memmachine.py):enabled_memory_typesproperty, read fromepisodic_memory.enabledandsemantic_memory.enabledat call time.add_episodes,query_searchandlist_searchfiltertarget_memoriesthrough it. A request naming a disabled type getsNonefor that field, exactly as when it did not name the type. The docstring ofquery_searchalready promised "Search across enabled memory types"; now it does._cleanup_semantic_historyis removed. The surviving one is called only when semantic memory is enabled, matching the guard_delete_queued_sessionalready applied to the semantic delete itself.delete_episodesresolves the semantic service under the same guard, in the same position as before so an enabled-but-broken semantic store still fails before anything is deleted.Two more mixed paths. With the composition root refusing, anything that reached the semantic stack unconditionally on a non-semantic request would have started failing on a disabled deployment. Two did. The MCP
delete_memorytool goes through_delete_memoriesinserver/api_v2/service.py, which calleddelete_featureseven when no semantic uids were named, so an episodic-only delete over MCP would have failed. It now callsdelete_featuresonly when semantic uids were named.MemMachine.delete_allasked for the semantic manager before touching the episode store; it now touches the semantic stores only when semantic memory is enabled. Both are routing decisions in the server layer, and both have tests.Skip, not reject. #1600 left the choice open. The client settles it:
memmachine_clientsends[Episodic, Semantic]on every add and search, so rejecting a request that names a disabled type would fail every client call against the default configuration. Episodic gets the same treatment, so a semantic-only deployment no longer stores the episode and then raises "Episodic memory is disabled" on the same request. The MCP tools are unchanged;ALL_MEMORY_TYPESnow means every type the server has.Tests. The
main/conftest.pyintegration configuration built a completeSemanticMemoryConfwithout sayingenabled=Trueand relied on the lazy build; it now says it. The batched-deletion test flips semantic memory on so its per-batch cleanup assertion keeps meaning something.Not in scope: the second half of #1575, that a failed deletion leaves a session no API call can revisit. That is #1577.
Type of change
Behavior change worth stating: a request that names a disabled memory type used to either do hidden work or fail; it now returns
Nonefor that type. The semantic-only endpoints now fail with "Semantic memory is disabled." where they previously failed with a storage error or silently built the stack.How Has This Been Tested?
Thirteen new tests. The seven
MemMachineones from the first revision were run against the unfixed code first; all failed there, and the deletion one failed with the exact log line from #1575 (Failed to delete session s1 ... No database configured for semantic storage.).test_memmachine_mock.py:enabled_memory_typesfollows the flags;query_searchskips semantic when disabled (episodic still searched, semantic session manager never requested,semantic_memory is None);query_searchskips episodic when disabled;add_episodesskips semantic;list_searchskips semantic;delete_episodesskips semantic history; anddelete_sessioncompletes with semantic disabled (episodes deleted, session row deleted, semantic service never requested, with the getter mocked to raise the #1575 error).test_resource_manager.py:get_semantic_manager,get_semantic_serviceandget_semantic_session_manageron aResourceManagerImplwhose config has semantic memory disabled raiseResourceNotReadyErrormatching "disabled". No other code path produces that message, so these fail without the gate.test_service.py:_delete_memoriesnever callsdelete_featureswhen no semantic uids are named, and calls it with the uids when they are.test_memmachine_mock.py:delete_allwith semantic memory disabled deletes the episode store and never requests the semantic manager (mocked to raise the disabled error).Test Results:
Checklist
🤖 Generated with Claude Code
https://claude.ai/code/session_018nSC7JKXByD1WADJukvupg