Skip to content

fix(config): let semantic memory be disabled with enabled: false alone - #1651

Merged
marvinyu-memverge merged 5 commits into
mainfrom
fix/1633-semantic-memory-optional
Sep 18, 2026
Merged

marvinyu-memverge merged 5 commits into
mainfrom
fix/1633-semantic-memory-optional

Conversation

@marvinyu-memverge

Copy link
Copy Markdown
Collaborator

Purpose of the change

Let semantic memory be turned off with enabled: false alone, or by omitting the semantic_memory section. Today the configuration refuses to load unless the section is present and config_database names a database that will never be used.

Description

SemanticMemoryConf.config_database was declared required (Field(...)) regardless of enabled, and Configuration.semantic_memory had no default, so an operator disabling semantic memory still had to write a semantic section and point it at a config database.

This change:

  • makes config_database optional and folds a missing value into the existing auto-disable validator, so a config that sets enabled: true without it is auto-disabled with a warning naming the missing fields, the same way a missing storage, LLM, or embedder already is;
  • gives Configuration.semantic_memory a default of a disabled section, and treats a null or empty semantic_memory: key the same way, so the section can be omitted;
  • makes SemanticResourceManager.get_semantic_config_storage raise ResourceNotReadyError when no config database is set, matching what get_semantic_storage already does for database;
  • re-runs both load-time semantic guards (required fields, OpenAI credentials) after a runtime config API update. The API assigns fields in place without re-validating, so with config_database now optional an enable request could otherwise persist enabled: true on a config the server cannot build. An update that leaves the config incomplete is auto-disabled and the change summary says why.
  • updates the configuration docs so the four fields that are only needed when semantic memory is enabled are described the same way.

Configs that set config_database today are unaffected. The runtime request paths that ignore semantic_memory.enabled (#1600, #1601) and project deletion with semantic memory disabled (#1575) are separate issues and are not touched here.

Fixes/Closes

Fixes #1633

Type of change

  • Bug fix (non-breaking change which fixes an issue)

How Has This Been Tested?

  • Unit Test

New tests cover: SemanticMemoryConf(enabled=False) with no config database; auto-disable when config_database is missing; the sample config with the section removed, null, empty, and {enabled: false}; both disabled shapes round-tripping through to_yaml(); the config-storage getter refusing an unset database without touching the SQL engine; and the config API auto-disabling an enable request that lacks config_database or whose embedder has empty OpenAI credentials.

Each guard was mutation-checked: reverting the validator change, the section default, the null/empty mapping, the getter guard, or either API re-check fails exactly the test that names it.

Test Results: uv run pytest packages/server/server_tests — 1806 passed (the two installation/test_install_in_windows* failures reproduce on main on this machine and are unrelated); packages/client/client_tests 255 passed; ruff check / ruff format --check clean; ty check packages unchanged from main.

Checklist

  • I have signed the commit(s) within this pull request
  • My code follows the style guidelines of this project (See STYLE_GUIDE.md)
  • I have performed a self-review of my own code
  • I have made corresponding changes to the documentation
  • My changes generate no new warnings
  • I have added unit tests that prove my fix is effective or that my feature works
  • New and existing unit tests pass locally with my changes

Screenshots/Gifs

N/A

Further comments

The same declarations exist on speedkick, where #1580 made semantic memory disabled by default, so this fix is worth carrying there too; the diff is confined to the config model, the two getters, and the config service.

🤖 Generated with Claude Code

)
return missing

def auto_disable_when_incomplete(self) -> list[str]:

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The caller does not use the return data. It should be enough to log a message.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The validator does discard it, but Configuration.auto_disable_semantic_memory uses the returned names — they become the auto-disabled: missing required fields: config_database text in the config API's response, so someone disabling semantic memory through the API is told which field is missing. That came out of an earlier review round asking for the reason to name a field.

Happy to drop it if you'd rather the API just reported that it was disabled.

@malatewang

Copy link
Copy Markdown
Contributor

When semantic memory is disabled, the memmachine needs to handle the case when processing read/write request

@marvinyu-memverge

Copy link
Copy Markdown
Collaborator Author

Good catch — fixed in 3692ae7 and fcdb516. Two things were reaching semantic memory without checking whether it was enabled:

  • add_episodes / query_search / list_search defaulted target_memories to every memory type and dispatched on that alone, so with the section disabled the first request hit a backend with nothing configured and returned an unhandled 500.
  • delete_episodes resolved the semantic service before building any of its work, so POST /memories/episodic/delete — which names no semantic memory at all — returned a 500 and left the episodes undeleted.

Both now skip semantic memory when it's off, the same way startup, shutdown and session deletion already did.

What I didn't touch: a request that explicitly names semantic memory on a disabled server. {"types": ["semantic"]} now comes back 200 with an empty result instead of erroring, and the /memories/semantic/* routes still go straight to the backend. I think those want a 4xx rather than a silent skip, but that's an API contract call rather than a bug fix, so I left it out of this PR — it belongs with #1575, which is the same area. That one also has _cleanup_semantic_history defined twice in MemMachine, so the guarded copy is dead code and the unguarded one runs.

marvinyu-memverge and others added 5 commits September 17, 2026 16:59
Turning semantic memory off required more than `enabled: false`: the
`semantic_memory` section had to be present and `config_database` had
to name a database that would never be used, or the configuration
failed to load.

- `SemanticMemoryConf.config_database` is optional; a missing value now
  auto-disables semantic memory the same way a missing storage, LLM, or
  embedder does, and is named in the warning.
- `Configuration.semantic_memory` defaults to a disabled section, so the
  section can be omitted entirely.
- `SemanticResourceManager.get_semantic_config_storage` raises
  `ResourceNotReadyError` when no config database is set, matching the
  storage getter, instead of passing `None` to the SQL engine lookup.

Fixes #1633

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Signed-off-by: Marvin Yu <[email protected]>
…null section

Follow-ups from review of the #1633 fix:

- The runtime config API assigns SemanticMemoryConf fields in place, so an
  enable request on a config with no config_database (now a legal state)
  could persist enabled=True and make the next lazy semantic-service build
  fail. The completeness check is now a public method the API re-runs after
  every semantic update; a request that leaves the config incomplete is
  auto-disabled and says so in the change summary.
- A bare `semantic_memory:` key (YAML null) now reads as a disabled section,
  matching the omitted-section behaviour.
- The config-storage getter refuses an empty config_database the same way
  the validator does, not only None.
- `enabled` field description and the two doc sites that marked
  `config_database` required (configuration.mdx, retrieval_agent README)
  updated to the new contract.
- Round-trip test now pins that a complete-but-disabled section keeps
  `enabled: false` across save and reload.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Signed-off-by: Marvin Yu <[email protected]>
Second review round on the #1633 fix:

- The API re-check covered only the required-fields guard; the sibling
  OpenAI-credential guard (`Configuration._maybe_disable_semantic_memory`)
  was still bypassed, so an enable request against an embedder with an
  empty key and no base_url could persist enabled=True. A new
  `Configuration.auto_disable_semantic_memory()` runs both guards and
  returns the reason; both API entry points call it and report the reason
  in the change summary, naming the missing fields.
- `auto_disable_when_incomplete()` returns the missing field names; a new
  `missing_required_fields()` computes them.
- `semantic_memory: {}` is treated like a null or omitted section
  (disabled, no warning).
- `get_semantic_storage` refuses an empty database id the same way the
  config-storage getter does.
- Docs: the `llm_model`, `embedding_model`, and `database` rows are
  described the same way as `config_database` (required only when
  semantic memory is enabled).
- Tests: the API re-validation tests load the real sample Configuration;
  both disabled-section shapes are pinned through the YAML round trip; the
  null/empty-section test asserts no auto-disable warning.

Co-Authored-By: Claude Fable 5.1 <[email protected]>
Signed-off-by: Marvin Yu <[email protected]>
Requests default target_memories to every memory type, so add/search/list
dispatched into semantic memory on the type list alone, without consulting
semantic_memory.enabled. With the section disabled its required fields are
legally unset, so the dispatch reached get_semantic_config_storage and raised
ResourceNotReadyError; no route handles it, so a first POST /memories after
disabling semantic memory returned an unhandled 500.

Route the three dispatch sites through _semantic_memory_targeted, which
conjoins the enabled flag, matching what the startup, shutdown, and
session-deletion paths already do.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Signed-off-by: Marvin Yu <[email protected]>
…sabled

delete_episodes resolved the semantic service before building any of its
work, so with semantic memory disabled the call raised ResourceNotReadyError
and never reached episode_storage.delete_episodes. POST /memories/episodic/
delete, which names no semantic memory, returned a 500 and left the episodes
in place.

Resolve the semantic service only when semantic memory is enabled, and pin
both directions. Also rename the dispatch predicate to
_should_dispatch_to_semantic_memory, since the call sites gave no sign that
it consults configuration as well as its argument, and drop its per-call log
so it stays a pure predicate.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
Signed-off-by: Marvin Yu <[email protected]>
@marvinyu-memverge
marvinyu-memverge force-pushed the fix/1633-semantic-memory-optional branch from aad98ec to 305a0e1 Compare September 18, 2026 00:22
@marvinyu-memverge
marvinyu-memverge merged commit d7a391e into main Sep 18, 2026
53 of 54 checks passed
@marvinyu-memverge
marvinyu-memverge deleted the fix/1633-semantic-memory-optional branch September 18, 2026 16:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

semantic_memory cannot be disabled with enabled: false alone: the section and config_database are required even when semantic memory is off

3 participants