{"record":{"id":"def0abe4a595291c","repo":"headroomlabs-ai/headroom","slug":"field-name-is-required-when-backend-is-external","errorCode":null,"errorMessage":"{field_name} is required when backend is EXTERNAL; set it to the entry-point name registered under '{group}'.","messagePattern":"(.+?) is required when backend is EXTERNAL; set it to the entry-point name registered under '(.+?)'\\.","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"headroom/memory/factory.py","lineNumber":53,"sourceCode":"# BackendRouter. Without this cache, opening N project DBs would load\n# the sentence-transformers / ONNX model N times.\n_EMBEDDER_CACHE: dict[tuple[str, str, str], Embedder] = {}\n_EMBEDDER_CACHE_LOCK = threading.Lock()\n\n\ndef _load_external_backend(\n    group: str,\n    name: str | None,\n    field_name: str,\n    config: MemoryConfig,\n) -> Any:\n    \"\"\"Load a memory backend registered via setuptools entry points.\n\n    Mirrors the pattern used by\n    `headroom.cache.compression_store._create_default_ccr_backend`.\n    \"\"\"\n    if not name:\n        raise ValueError(\n            f\"{field_name} is required when backend is EXTERNAL; \"\n            f\"set it to the entry-point name registered under '{group}'.\"\n        )\n    ep = next((e for e in entry_points(group=group) if e.name == name), None)\n    if ep is None:\n        raise ValueError(\n            f\"No entry point registered under '{group}' with name '{name}'. \"\n            f\"Install the package that provides it.\"\n        )\n    return ep.load()(config)\n\n\nasync def create_memory_system(\n    config: MemoryConfig | None = None,\n) -> tuple[MemoryStore, VectorIndex, TextIndex, Embedder, MemoryCache | None]:\n    \"\"\"Create a complete memory system from configuration.\n\n    This factory function creates and initializes all memory system components","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/headroomlabs-ai/headroom/blob/322425c43bffde1ed0b64fecf3cf5951565dd82b/headroom/memory/factory.py#L35-L71","documentation":"When a MemoryConfig sets a backend to the EXTERNAL mode, the factory resolves the implementation via setuptools entry points, which requires a name to look up. `_load_external_backend` raises ValueError when the corresponding *_backend_name field is empty/None, because there is no way to guess which registered plugin to load.","triggerScenarios":"Setting config.store_backend = StoreBackend.EXTERNAL without config.store_backend_name, or config.text_backend = TextBackend.EXTERNAL without config.text_backend_name, then calling create_memory_system(config).","commonSituations":"Copy-pasting a config template that sets the backend enum but not the name; migrating from a built-in backend to a plugin and forgetting the name field; the plugin package defines the entry point under a different name than assumed.","solutions":["Set store_backend_name (or text_backend_name) to the exact entry-point name the plugin package registers, e.g. config.store_backend_name = \"my-org-memory-store\"","Find registered names with `importlib.metadata.entry_points(group='headroom.memory.store')` (the group string shown in the message) and pick one","If you actually wanted a built-in backend, switch store_backend back to SQLITE/enum default instead of EXTERNAL"],"exampleFix":"# before\nconfig = MemoryConfig(store_backend=StoreBackend.EXTERNAL)\nsystem = await create_memory_system(config)  # ValueError\n\n# after\nconfig = MemoryConfig(\n    store_backend=StoreBackend.EXTERNAL,\n    store_backend_name=\"acme-memory-store\",\n)\nsystem = await create_memory_system(config)","handlingStrategy":"validation","validationCode":"if config.store_backend == StoreBackend.EXTERNAL and not config.store_backend_name:\n    raise ValueError(\"store_backend_name required for EXTERNAL store backend\")\n# same check for text_backend/text_backend_name","typeGuard":"def external_backend_ready(cfg: MemoryConfig) -> bool:\n    if cfg.store_backend == StoreBackend.EXTERNAL and not cfg.store_backend_name:\n        return False\n    if cfg.text_backend == TextBackend.EXTERNAL and not cfg.text_backend_name:\n        return False\n    return True","tryCatchPattern":"try:\n    system = await create_memory_system(config)\nexcept ValueError as e:\n    if \"is required when backend is EXTERNAL\" in str(e):\n        # config error: fix and restart, do not retry\n        raise ConfigError(str(e)) from e\n    raise","preventionTips":["Treat EXTERNAL + empty name as a schema error: validate configs at load time","Write a config linter/assert helper covering all EXTERNAL/name pairs","Fail at app startup, not on the first memory call in a request path"],"tags":["memory","configuration","plugins","entry-points"],"backgroundTag":null,"analyzedSha":"322425c43bffde1ed0b64fecf3cf5951565dd82b","analyzedAt":"2026-08-15T01:03:05.481Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}