langchain-ai/deepagents · error · ExtensionError

{source.path} does not define a callable 'extension' factory

Error message

{source.path} does not define a callable 'extension' factory

What it means

Raised by `_import_factory` in the extension loader when a dynamically imported extension module does not expose a module-level `extension` callable. The loader expects every extension source file to define an `extension` async factory that the host calls to obtain the extension API. If the attribute is absent or not callable, the module is evicted from `sys.modules` and an `ExtensionError` is raised pointing at the offending file path.

Source

Thrown at libs/code/deepagents_code/extensions/loader.py:59

    module = importlib.util.module_from_spec(spec)
    sys.modules[name] = module
    try:
        spec.loader.exec_module(module)
    except (KeyboardInterrupt, SystemExit, Exception) as exc:
        sys.modules.pop(name, None)
        if isinstance(exc, KeyboardInterrupt):
            raise
        msg = (
            f"Extension import in {source.path} attempted to exit: {exc}"
            if isinstance(exc, SystemExit)
            else f"Failed to import {source.path}: {exc}"
        )
        raise ExtensionError(msg) from exc
    factory = getattr(module, "extension", None)
    if not callable(factory):
        sys.modules.pop(name, None)
        msg = f"{source.path} does not define a callable 'extension' factory"
        raise ExtensionError(msg)
    if not inspect.iscoroutinefunction(factory):
        sys.modules.pop(name, None)
        msg = f"Extension factory in {source.path} must be declared with 'async def'"
        raise ExtensionError(msg)
    return name, factory


async def load_extension(
    source: SourceInfo,
    registry: ExtensionRegistry,
    *,
    cwd: Path,
    mode: ExtensionMode,
) -> ExtensionAPI:
    """Load one extension transactionally.

    Args:
        source: Extension entry file and import shape.

View on GitHub (pinned to a1af029e6e)

Solutions

  1. Define a module-level async factory named exactly `extension` in the extension file
  2. If the factory exists, check for a typo or shadowing of the name `extension` in the module
  3. Verify the file is an actual dcode extension and not a generic Python script placed in the extensions directory

Example fix

// before
# my_ext.py
def setup(api):
    ...

// after
# my_ext.py
import inspect

async def extension(api):
    ...
Defensive patterns

Strategy: validation

Validate before calling

import inspect, importlib.util
spec = importlib.util.spec_from_file_location("my_ext", path)
mod = importlib.util.module_from_spec(spec); spec.loader.exec_module(mod)
if not callable(getattr(mod, "extension", None)):
    raise SystemExit(f"{path}: no callable 'extension' factory")

Type guard

def has_extension_factory(mod) -> bool:
    return callable(getattr(mod, "extension", None))

Try / catch

try:
    name, factory = _import_factory(source)
except ExtensionError as exc:
    logger.error("bad extension %s: %s", source.path, exc)

Prevention

When it happens

Trigger: Importing an extension file (via `load_extension`/`_import_factory`) whose module defines no `extension` attribute, or defines `extension` as a non-callable value (e.g. a class instance, string, or constant).

Common situations: Hand-written extension files copied from outdated templates that named the factory differently (e.g. `main`, `setup`, `create_extension`); typos like `extention`; extensions written for another plugin system with a different entry-point convention.

Related errors


AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29). Data as JSON: /api/errors/5f2444fd50277c2a. Report an issue: GitHub.