CouncilEx.Registry (CouncilEx v0.1.0)

Copy Markdown View Source

Lookup table for the building blocks dynamic councils reference by string name: profiles, tools, output schemas, routers, custom rounds.

Two storage tiers:

  • Config (boot-time) — set in config :council_ex, :registry, profiles: %{...}. Read on every lookup; no copy. Safe across releases.

  • Runtime overrides (in-memory)register_*/2 writes to a shared ETS table. Useful for hot-loaded modules that should not be persisted to config (e.g., experiment branches, plugin code). Survives until the BEAM dies. Wins over config on lookup conflict.

Example

# config/config.exs
config :council_ex, :registry,
  profiles: %{
    "openai_mini" => CouncilEx.Profiles.OpenAIMini,
    "anthropic_balanced" => CouncilEx.Profiles.AnthropicBalanced
  },
  tools: %{
    "calculator" => MyApp.Tools.Calculator
  }

# at runtime
:ok = CouncilEx.Registry.register_tool("scratchpad", MyApp.Tools.Scratchpad)
MyApp.Tools.Scratchpad = CouncilEx.Registry.lookup!(:tool, "scratchpad")

Summary

Functions

Return the merged map (config + runtime) for a kind.

List the known registry kinds.

Sorted list of registered names for a kind.

Look up a value by kind + name. Returns nil if not registered. Runtime overrides win over config.

Look up a value by kind + name. Raises if not registered.

Generic register — kind must be one of [:profile, :tool, :schema, :router, :round, :sub_council, :input_mapper, :council].

Register a routable council under name. Used by CouncilEx.AutoCouncil to discover councils a strategy can pick from.

Register an input mapper under name. The value must be a 1-arity function (typically a remote capture like &MyApp.Mappers.to_seo_input/1) used by dynamic sub-council members to project the parent input.

Register a profile module (or struct) at runtime under name.

Register a round module at runtime under name.

Register a router module at runtime under name.

Register an output schema (Ecto module or JSON Schema map) under name.

Register a sub-council target at runtime under name. Accepts either a static council module or a %CouncilEx.DynamicCouncil{} struct. Used by dynamic councils that reference sub-councils by string name.

Register a tool module at runtime under name.

Wipe ALL runtime registrations. Config entries unaffected.

Remove a runtime registration. Has no effect on config-defined entries.

Functions

all(kind)

@spec all(atom()) :: %{required(String.t()) => term()}

Return the merged map (config + runtime) for a kind.

kinds()

@spec kinds() :: [atom()]

List the known registry kinds.

list(kind)

@spec list(atom()) :: [String.t()]

Sorted list of registered names for a kind.

lookup(kind, name)

@spec lookup(atom(), String.t()) :: term() | nil

Look up a value by kind + name. Returns nil if not registered. Runtime overrides win over config.

lookup!(kind, name)

@spec lookup!(atom(), String.t()) :: term()

Look up a value by kind + name. Raises if not registered.

register(kind, name, value)

@spec register(atom(), String.t(), term()) :: :ok | {:error, term()}

Generic register — kind must be one of [:profile, :tool, :schema, :router, :round, :sub_council, :input_mapper, :council].

register_council(name, entry)

@spec register_council(String.t(), map()) :: :ok

Register a routable council under name. Used by CouncilEx.AutoCouncil to discover councils a strategy can pick from.

The value should be a map with at least:

%{
  council:    module() | CouncilEx.DynamicCouncil.t(),
  desc:       String.t(),       # human-readable description (used by LLM strategies)
  match:      Regex.t() | (binary() -> boolean()) | nil,   # optional, used by Rules
  vector:     [float()] | nil,  # optional, used by Embedding
  tags:       [atom()] | nil,   # optional, free-form
  providers:  MapSet.t(atom()) | nil   # cached at registration; see below
}

Strategies may ignore fields they don't need. Only :council is mandatory.

At registration time the registry computes the council's provider set (via CouncilEx.AutoCouncil.Providers.used/1) and stashes it under :providers. This cache backs provider_check: true so the resolver doesn't re-walk the council on every route.

Cache invalidation

Re-register to invalidate. The cache only goes stale if the underlying council's member providers change and the entry isn't re-registered. Static councils carry their own __providers__/0, so the cache is effectively free; dynamic councils get a single to_spec/1 walk per registration.

Pass providers: nil (or any explicit MapSet) to override the auto-computed value.

register_input_mapper(name, fun)

@spec register_input_mapper(String.t(), (term() -> term())) :: :ok

Register an input mapper under name. The value must be a 1-arity function (typically a remote capture like &MyApp.Mappers.to_seo_input/1) used by dynamic sub-council members to project the parent input.

register_profile(name, value)

@spec register_profile(String.t(), term()) :: :ok

Register a profile module (or struct) at runtime under name.

register_round(name, module)

@spec register_round(String.t(), module()) :: :ok

Register a round module at runtime under name.

register_router(name, module)

@spec register_router(String.t(), module()) :: :ok

Register a router module at runtime under name.

register_schema(name, value)

@spec register_schema(String.t(), module() | map()) :: :ok

Register an output schema (Ecto module or JSON Schema map) under name.

register_sub_council(name, target)

@spec register_sub_council(String.t(), module() | CouncilEx.DynamicCouncil.t()) :: :ok

Register a sub-council target at runtime under name. Accepts either a static council module or a %CouncilEx.DynamicCouncil{} struct. Used by dynamic councils that reference sub-councils by string name.

register_tool(name, module)

@spec register_tool(String.t(), module()) :: :ok

Register a tool module at runtime under name.

reset_runtime()

@spec reset_runtime() :: :ok | {:error, term()}

Wipe ALL runtime registrations. Config entries unaffected.

unregister(kind, name)

@spec unregister(atom(), String.t()) :: :ok | {:error, term()}

Remove a runtime registration. Has no effect on config-defined entries.