Skip to main content

lib/agent_sea/crew/delegation.ex

defmodule AgentSea.Crew.Delegation do
  @moduledoc """
  Strategy behaviour for assigning a task to an agent. Each strategy is a module
  implementing `c:delegate/3`; the coordinator selects one by config.
  """

  defmodule Result do
    @moduledoc "The outcome of a delegation decision."

    @enforce_keys [:selected_agent]
    defstruct [:selected_agent, :reason, :confidence, alternatives: [], decision_time_ms: 0]

    @type t :: %__MODULE__{
            selected_agent: term(),
            reason: String.t() | nil,
            confidence: float() | nil,
            alternatives: [term()],
            decision_time_ms: non_neg_integer()
          }
  end

  @typedoc "A candidate agent: its name, pid, and (optional) role."
  @type agent_ref :: %{
          required(:name) => term(),
          required(:pid) => pid() | nil,
          optional(:role) => AgentSea.Role.t() | nil
        }

  @typedoc "Strategy context, e.g. `:counter`, `:bidding_time_ms`, `:minimum_bid`, `:selection_criteria`."
  @type ctx :: map()

  @callback delegate(task :: term(), [agent_ref()], ctx()) ::
              {:ok, Result.t()} | {:error, term()}

  @doc "Run a delegation strategy, timing the decision."
  @spec select(module(), term(), [agent_ref()], ctx()) ::
          {:ok, Result.t()} | {:error, term()}
  def select(strategy, task, agents, ctx \\ %{}) do
    started = System.monotonic_time(:millisecond)

    case strategy.delegate(task, agents, ctx) do
      {:ok, %Result{} = result} ->
        {:ok, %{result | decision_time_ms: System.monotonic_time(:millisecond) - started}}

      other ->
        other
    end
  end
end