Skip to content

Add the shared backend registry (item 5, step 1 of 3) - #162

Merged
isayev merged 1 commit into
mainfrom
refactor/engine-registry
Aug 13, 2026
Merged

Add the shared backend registry (item 5, step 1 of 3)#162
isayev merged 1 commit into
mainfrom
refactor/engine-registry

Conversation

@isayev

@isayev isayev commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

This lands a module nothing calls yet. ModelFactory and
IsomerEngineFactory move onto it in steps 2 and 3; until then main carries
~130 lines with no consumers. Merged deliberately rather than by oversight, so
the next reader doesn't have to wonder.

Non-breaking. Suite: 1756 passed, 1 skipped, 70 deselected.

Why a registry

Auto3D has two families of swappable backend, and each carries its own bespoke
lookup — a dict plus an if-chain for models, a tuple plus an if/elif ladder for
isomer engines. Adding a model backend means editing five places; an isomer
backend, six. One of the five is the presentation layer.

Scope, narrowed from the plan on purpose

The plan's item 5 reads "one registry + entry-point discovery, promote
ModelAdapter to public and version it". Entry points and the public interface
are out of scope, because they solve a problem this package doesn't have:
Auto3D already accepts a third-party model as a file path
(--engine /path/to/my_nnp.pt), checked against the CustomNNP contract at
load. Entry points would add only named, installable backends — no
demonstrated demand — and would mean freezing ModelAdapter while it is still
gaining members (analytic_hessian in 3.0.0, native_dtype still wanted for
D4). A registry is what an entry-point loader would populate, so this doesn't
foreclose that.

Three things it deliberately does not do

It does not own construction. A model adapter is built with
(device, compile_model); an isomer engine takes eight-odd keyword arguments. A
signature both satisfy is a keyword bag that hides what each backend needs and
stops the type checker helping — the same defect the plan records for
threshold meaning three different quantities.

It is not a plugin system. See scope above.

It does not decide case-folding. Model names resolve case-insensitively;
isomer engine types are exact lowercase. Both are current, tested behavior, so
it is a constructor flag rather than a policy this module picks.

Registering a name twice raises rather than overwriting — the failure a plain
dict gives you, where a duplicate makes behavior depend on import order.

Found while starting step 2, and worth recording here

There are three parallel lists of engine names, not the two I had measured:

list contents
ModelFactory._adapters 2 — ANI2X, ANI2XT only
ModelFactory.available_models() 6 — a hand-written literal, derived from nothing
ENGINE_INFO 6 — AIMNET, three aimnet variants, two ANI

They hold different sets. _adapters holds only engines built from a local
adapter class; the four aimnet entries are names resolved through the aimnet
registry by resolve_engine_name. Nothing connects the three — they agree by
hand.

So the win is larger than estimated (three lists collapse to one) and step 2 is
broader than "swap the dict for a Registry": the registry must be keyed by
user-facing engine name, with each entry saying how the engine is built and
carrying its display metadata, and available_models() derived from it.

This is recorded in the PR body because the design spec lives under
docs/superpowers/, which is gitignored — so the spec's own copy of this
correction is not durable.

Layering

Auto3D.registry imports only Auto3D.exceptions, so it is a genuine
foundation leaf, and it is mapped into L0. The layer map's totality check failed
the moment the file appeared, which is what it is for.

The mechanism only. No caller uses it yet -- `ModelFactory` and
`IsomerEngineFactory` move onto it in the next two commits, and this should not
merge on its own, because a registry nobody registers with is dead code.

Auto3D has two families of swappable backend and until now each carried its own
bespoke lookup: a dict plus an if-chain for models, a tuple plus an if/elif
ladder for isomer engines. Adding a model backend means editing five places and
an isomer backend six. One of those five is the *presentation* layer, where
`cli/commands/models.py`'s `ENGINE_INFO` keeps a parallel table of display
metadata keyed by engine name that nothing checks against the set of real
backends -- so a backend registered without an entry there silently stops
appearing in `auto3d models info`. `Entry.info` is what removes that table.

Three things it deliberately does not do:

  - **It does not own construction.** A model adapter is built with
    `(device, compile_model)`; an isomer engine takes eight-odd keyword
    arguments. A signature both satisfy is a bag of keywords that hides what
    each backend needs and stops the type checker helping -- the same defect the
    plan records for `threshold` meaning three different quantities. Each
    factory resolves a name and then calls its own constructor.
  - **It is not a plugin system.** Auto3D already accepts a third-party model as
    a file path, checked against the `CustomNNP` contract at load, so
    entry-point discovery would add only *named, installable* backends, and
    would mean freezing `ModelAdapter` while it is still gaining members
    (`analytic_hessian` in 3.0.0, `native_dtype` still wanted for D4). A
    registry is what such a loader would populate, so this does not foreclose
    it.
  - **It does not decide case-folding.** Model names resolve case-insensitively
    and isomer engine types are exact lowercase; both are current, tested
    behavior, so it is a constructor flag.

Registering a name twice raises rather than overwriting -- the failure a plain
dict gives you, where a duplicate registration makes behavior depend on import
order.

`Auto3D.registry` imports only `Auto3D.exceptions`, so it is a genuine
foundation leaf, and it is mapped into L0. The layer map's totality check failed
the moment the file appeared, which is what it is for.

Suite: 1756 passed, 1 skipped, 70 deselected.
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.

1 participant