CouncilEx.Round behaviour (CouncilEx v0.1.0)

Copy Markdown View Source

Behaviour for round implementations.

A round describes how members are invoked at a given stage. It produces a per-member input shape, parses each member's raw response, and may optionally aggregate the per-member outputs into a single round-level result (used by ranking/voting rounds).

Summary

Callbacks

Aggregate per-member outputs for the round (e.g., Borda count).

Optional convergence check used by Iterate when no explicit :until opt is provided. Returns true to halt iteration after the current round.

Optional metadata describing this round's data-flow contract, used by CouncilEx.Diagram.sequence/2 to render the sequence diagram.

Whether the round runs members in parallel. Most rounds: yes. Synthesis: no.

Parse a member's raw provider response into a structured form for the round.

Build the per-member input for this round.

Functions

Default parallel?/0 is true. Override in sequential rounds.

Callbacks

aggregate(member_outputs, opts)

(optional)
@callback aggregate(member_outputs :: %{required(atom()) => map()}, opts :: keyword()) ::
  {:ok, map() | nil} | {:error, term()}

Aggregate per-member outputs for the round (e.g., Borda count).

Default is nil — no aggregation. Implementations that aggregate should return {:ok, %{...}}.

converged?(prev, curr, opts)

(optional)
@callback converged?(
  prev :: CouncilEx.RoundResult.t() | nil,
  curr :: CouncilEx.RoundResult.t(),
  opts :: keyword()
) :: boolean()

Optional convergence check used by Iterate when no explicit :until opt is provided. Returns true to halt iteration after the current round.

Receives the previous iteration's %RoundResult{} (or nil on the first iteration), the current iteration's %RoundResult{}, and the round's opts.

diagram_meta()

(optional)
@callback diagram_meta() :: %{
  input_scope: :original | :previous_round | :all_prior,
  sharing: :self_only | :all | :peers,
  anonymized?: boolean(),
  sequential?: boolean(),
  summary: String.t()
}

Optional metadata describing this round's data-flow contract, used by CouncilEx.Diagram.sequence/2 to render the sequence diagram.

Returns a map with keys :input_scope (`:original:previous_round
:all_prior),:sharing(:self_only:all:peers),:anonymized?`,

:sequential?, and :summary (a short newline-free note). Rounds that don't implement this get a generic fallback in the diagram renderer.

parallel?()

(optional)
@callback parallel?() :: boolean()

Whether the round runs members in parallel. Most rounds: yes. Synthesis: no.

parse_output(member_id, response, context)

@callback parse_output(
  member_id :: atom(),
  response :: CouncilEx.Response.t(),
  context :: CouncilEx.Context.t()
) :: {:ok, map()} | {:error, term()}

Parse a member's raw provider response into a structured form for the round.

For most rounds this is a passthrough. Ranking/Voting rounds use this to extract structured ranking data.

prepare_input(member_id, input, context)

@callback prepare_input(
  member_id :: atom(),
  input :: map(),
  context :: CouncilEx.Context.t()
) :: map()

Build the per-member input for this round.

Receives the member id, the council's original input, and the run context (which carries prior round results). Returns the map that will be passed to the member's transform_input/2 and rendered into the LLM request.

Functions

__using__(opts)

(macro)

Default parallel?/0 is true. Override in sequential rounds.