Skip to main content

lib/council_ex/auto_council/strategies/llm_build.ex

defmodule CouncilEx.AutoCouncil.Strategies.LLMBuild do
  @moduledoc """
  *Stub.* Synthesizer strategy: an LLM designs a fresh `DynamicCouncil` for
  the prompt — picking members, system prompts, rounds, and a chair — then
  the resolver runs that synthesized council.

  ## Planned interface

      AutoCouncil.new(
        strategy: :llm_build,
        options:  [
          profile:        "openai_balanced",   # mid-tier model recommended
          member_palette: [...],               # optional archetype hints
          max_members:    5,
          allowed_rounds: [:independent_analysis, :peer_critique, :consensus_vote]
        ]
      )

  Returns a `{:built, %DynamicCouncil{}}` decision so callers can tell from
  the meta that the council was synthesized fresh (non-deterministic) rather
  than chosen from a known catalog.

  ## Hard constraints to enforce

    * 2..N members (N from `:max_members`).
    * Rounds restricted to `:allowed_rounds`.
    * `DynamicCouncil.validate/1` must pass before returning.
    * Hard timeout on the planner call to avoid runaway latency.

  ## Open design questions (deferred)

    * Schema enforcement: structured output vs prompt + post-validate.
    * Telemetry on synthesized plans (members chosen, member-prompt length).
    * Whether to store synthesized plans in the registry for reuse (would
      cross from `:built` into `:dynamic` on subsequent hits — opt-in).

  ## Cache notes

  Caching `:built` results is *opt-in only*. Caching turns a non-deterministic
  feature into a deterministic one, which can surprise callers who expected
  fresh plans. If added, key on prompt embedding + catalog version + planner
  model id, and document the change in semantics loudly.

  Calling `resolve/2` today returns `{:error, :not_implemented}`.
  """

  @behaviour CouncilEx.AutoCouncil.Strategy

  @impl true
  def resolve(_prompt, _auto), do: {:error, :not_implemented}
end