ADR 019 — Unified component-registration metaclass toolkit
- Status: Accepted (2026-06-19) — partially implemented (toolkit shipped + first re-bases landed; see Implementation status). Roadmap: Architecture Enhancement Guide (enhancement E6, Wave 2)
- Authors: Platform team
- Related: Enhancement Guide §6/E6, ADR 004, ADR 020; Hard Rules 19/25/45/58 (component registration)
Context
Definition-time auto-registration is a backbone idiom of the platform, but it is implemented ~10 times with diverging quality:
- Metaclasses:
RLComponentMeta,InfrastructureProviderMeta,IntegrationMeta,SecretStoreMeta,KBAdapterMeta,IdentityProviderMeta,OrchestrationAdapterMeta,CanonicalSchemaMeta, … - A decorator registry:
alphaswarm/core/registry.py@register(the Qlib-styleclass/module_path/kwargsfactory, Hard Rule 8). - A tuple-keyed registry: the Predictor Hub (
alphaswarm_models/predictors/hub.py).
The divergence has teeth. RLComponentMeta (alphaswarm_rl/core/base.py)
performs last-writer-wins registration with no dedup-by-id and does not
validate the abstract-method contract; alphaswarm_models registers models via a
decorator, not a metaclass, so it has neither the metaclass guarantees nor
alias-dedup. A duplicate component id silently shadows an earlier one — a latent
correctness bug that surfaces as "the wrong strategy/model ran."
Decision
Add alphaswarm_core/registration.py, a single metaclass toolkit that the
existing metaclasses subclass — preserving their behavior while fixing the gaps
uniformly.
RegisteredComponentMeta(ABCMeta)— definition-time registry keyed on(component_kind, component_id), with: abstract-skip (never register abstract bases or__abstract__markers), id-uniqueness (raise on a genuine duplicate instead of silently overwriting), and tag/source/category metadata carried forward from@register.SchemaBoundMeta(RegisteredComponentMeta)— additionally stampscls.__json_schema__ = cls.model_json_schema()(JSON Schema 2020-12 / OpenAPI 3.1, free from Pydantic v2) and records(schema_id, schema_version)on command / event / DTO / tool-IO classes.CapabilityMeta(SchemaBoundMeta)— carries theCapabilityManifest(ADR 020) onto agent tools / adapters / MCP servers.- Re-base, one at a time. Each existing metaclass becomes a thin subclass;
the
@registerdecorator and Predictor Hub keep their public surface but route through the shared base. A CI test asserts the global registry has no duplicate(kind, id)keys — which immediately flushes out today's silent collisions.
Consequences
Positive
- One correct registration mechanism; the silent last-writer-wins class of bug is
eliminated platform-wide;
alphaswarm_modelsgains metaclass guarantees. SchemaBoundMetamakes every command/event/tool-IO type self-describing, feeding the WS-schema and MCP-tool-IO work (E10) and the capability gate (E7).
Negative / risks
- Touching registration is high-blast-radius; migrate one metaclass per PR with the duplicate-key test as the guardrail, and keep the public decorator/alias API byte-compatible.
- A genuine duplicate id that was previously tolerated will now raise — that is the point, but it may surface latent config that needs cleanup first.
Explicitly rejected
- A runtime service registry (registration is a definition-time concern; keep it in the type system).
- Replacing the Qlib-style
@registerfactory surface (it is load-bearing for Hard Rule 8 and the LLM research loop) — wrap it, don't remove it.
Rollout order
- Land
alphaswarm_core/registration.py+ the duplicate-key CI test. - Re-base
RLComponentMetafirst (it has the dedup gap), then thealphaswarm_core*Metafamily, then convertalphaswarm_models' decorator. - Layer
SchemaBoundMetaonto DTOs, thenCapabilityMeta(ADR 020) onto tools.
Implementation status
Accepted and partially implemented (2026-06-19). Landed additively — one metaclass per PR, each re-base keeping the existing dedicated registry byte-for-byte unchanged:
- ✅ Toolkit —
alphaswarm_core/registration.pyshipsRegisteredComponentMeta(id-unique(kind, id)registry, abstract-skip),CapabilityManifest+CapabilityMeta(ADR 020), andSchemaBoundModel. - ✅ Duplicate detection — provided by the unified registry's id-uniqueness
(
DuplicateComponentError); each re-based metaclass surfaces a genuine collision as a loudWARNINGinstead of the previous silent last-writer-wins shadow.alphaswarm_rladditionally exposesrl_registry_duplicates()as a queryable audit; a static audit of the RL tree is currently collision-free. - ✅ Re-bases landed —
InfrastructureProviderMetaandIntegrationMeta(alphaswarm_core);RLComponentMeta(alphaswarm_rl) — the metaclass this ADR flags as actively buggy — now audits + mirrors into the unified registry. - ⏳ Remaining — the monolith
*Metafamily (SecretStoreMeta,IdentityProviderMeta,OrchestrationAdapterMeta,KBAdapterMeta,CanonicalSchemaMeta), converting thealphaswarm_modelsdecorator registry, then layeringSchemaBoundModelonto DTOs andCapabilityMetaonto tools.
As-built deviation from the Decision sketch. To compose cleanly with
Pydantic v2's own metaclass, schema identity ships as SchemaBoundModel — a
Pydantic base that stamps __json_schema__ and registers via
__pydantic_init_subclass__ — rather than a SchemaBoundMeta metaclass; and
CapabilityMeta extends RegisteredComponentMeta directly. The id-unique,
abstract-skip semantics are unchanged. The adopted re-base is detect-and-warn
(best-effort, never raises on a collision) ahead of any future
detect-and-fail flip, which stays gated on a clean platform-wide duplicate
audit.