The host-side contract for a node's guards (see Baton.Flow.NodeSpec).
After Baton.Flow.Workers.LLM has decoded a reply and run the node's
handle_response, it hands the outcome — together with the dumped node,
whose "guards" list carries the specs — to the runner configured under
config :baton, flow_runtime: [guard_runner: MyApp.GuardRunner]. The runner
returns a possibly transformed step outcome: pass the result through, rewrite
it (degrade a field, mark it partial), or reject the sample with {:error, reason} so the step retries and the model is asked again.
Baton knows nothing about guard kinds. It validates the specs' shape at
definition load, refuses to run a guarded node when no runner is configured
(a declared check silently skipped is worse than a loud stop), and stays out
of the way otherwise. Budget policy — how many fresh samples a rejection is
worth — belongs to the runner; Baton.Backoff.deflate/1 gives it the
genuine-failure count to build on.
The runner is called on every outcome the response pipeline produces,
including {:error, _} — so it can observe rejected samples if it wants to —
but a runner that only inspects {:ok, result} and passes everything else
through untouched is the expected shape.
Summary
Callbacks
Apply the node's guard specs to a step outcome.
Callbacks
@callback run(outcome :: term(), node :: map(), job :: Oban.Job.t()) :: Baton.Worker.step_result()
Apply the node's guard specs to a step outcome.
outcome is whatever handle_response returned (before usage attachment);
node is the dumped flow node (node["guards"] holds the specs);
job is the executing Oban job, for budget and provenance.