lib/openagents/continual_learning/evaluator/reference.ex

main at 58e6347eeb72 · 2 KB

defmodule OpenAgents.ContinualLearning.Evaluator.Reference do
  @moduledoc """
  The reference evaluator for the first bounded workflow.

  It grades the surviving checkpoint against the admitted target metric and
  reports its own identity, the falsifier it ran, and one evidence receipt per
  admitted acceptance criterion. The result is deterministic in the checkpoint
  metrics, so an unchanged run grades identically.
  """

  @behaviour OpenAgents.ContinualLearning.Evaluator

  alias OpenAgents.Provenance.Canonical

  @impl true
  def evaluate(%{job: job, checkpoint: checkpoint, policy: policy, corpus: corpus}) do
    metric = Map.get(policy, "target_metric", "score")
    target = Map.get(policy, "target_value", 0.0)
    observed = Map.get(checkpoint.metrics, metric)

    if is_number(observed) do
      passed = observed >= target

      {:ok,
       %{
         verifier: Map.get(policy, "verifier", %{}),
         falsifier:
           "the same checkpoint graded below #{metric} #{target} fails, and a corpus digest " <>
             "that does not match the admitted evaluation inputs fails",
         terminal_result: (passed && :passed) || :failed,
         criteria: criteria(job, policy, corpus, metric, observed),
         metrics: %{
           metric => observed,
           "target_value" => target,
           "corpus_records" => length(corpus)
         },
         usage: %{"input_tokens" => 500, "output_tokens" => 100, "total_tokens" => 600},
         duration_ms: 500
       }}
    else
      {:error, :evaluation_metric_missing}
    end
  end

  defp criteria(job, policy, corpus, metric, observed) do
    corpus_digest = Canonical.digest!(Enum.map(corpus, & &1["artifact_digest"]))

    for criterion <- Map.get(policy, "acceptance_criteria", []) do
      %{
        "criterion" => criterion,
        "receipt" =>
          "continual-learning-evaluation:#{job.id}:#{Canonical.sha256(criterion)}"
          |> String.slice(0, 256),
        "visibility" => "restricted",
        "observed" => %{metric => observed, "corpus_digest" => corpus_digest}
      }
    end
  end
end