Saltar al contenido principal

ADR 019 — Unified component-registration metaclass toolkit

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-style class/module_path/kwargs factory, 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.

  1. 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.
  2. SchemaBoundMeta(RegisteredComponentMeta) — additionally stamps cls.__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.
  3. CapabilityMeta(SchemaBoundMeta) — carries the CapabilityManifest (ADR 020) onto agent tools / adapters / MCP servers.
  4. Re-base, one at a time. Each existing metaclass becomes a thin subclass; the @register decorator 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_models gains metaclass guarantees.
  • SchemaBoundMeta makes 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 @register factory surface (it is load-bearing for Hard Rule 8 and the LLM research loop) — wrap it, don't remove it.

Rollout order​

  1. Land alphaswarm_core/registration.py + the duplicate-key CI test.
  2. Re-base RLComponentMeta first (it has the dedup gap), then the alphaswarm_core *Meta family, then convert alphaswarm_models' decorator.
  3. Layer SchemaBoundMeta onto DTOs, then CapabilityMeta (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.py ships RegisteredComponentMeta (id-unique (kind, id) registry, abstract-skip), CapabilityManifest + CapabilityMeta (ADR 020), and SchemaBoundModel.
  • ✅ Duplicate detection — provided by the unified registry's id-uniqueness (DuplicateComponentError); each re-based metaclass surfaces a genuine collision as a loud WARNING instead of the previous silent last-writer-wins shadow. alphaswarm_rl additionally exposes rl_registry_duplicates() as a queryable audit; a static audit of the RL tree is currently collision-free.
  • ✅ Re-bases landed — InfrastructureProviderMeta and IntegrationMeta (alphaswarm_core); RLComponentMeta (alphaswarm_rl) — the metaclass this ADR flags as actively buggy — now audits + mirrors into the unified registry.
  • ⏳ Remaining — the monolith *Meta family (SecretStoreMeta, IdentityProviderMeta, OrchestrationAdapterMeta, KBAdapterMeta, CanonicalSchemaMeta), converting the alphaswarm_models decorator registry, then layering SchemaBoundModel onto DTOs and CapabilityMeta onto 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.