Baton.Flow.NodeSpec (Baton v0.27.4)

Copy Markdown View Source

A portable node in a serialized Baton.Flow.Definition.

Unlike Baton.Flow.Step, a node spec contains only JSON-compatible values: string identifiers, dependency identifiers, configuration, and optional editor position. Runtime modules are resolved later by a host registry.

Tolerating dead dependencies

ignore_discarded and ignore_cancelled are the portable spelling of the options Baton.add/4 already accepts, and they behave identically: they describe what this node tolerates in its dependencies, so the node that survives a dead dep is the one that sets the flag — not the dep itself.

They matter most for a node that depends on a fan-out. A fan-out over N items is N independent jobs, and by default one of them exhausting its retries cancels every downstream node, discarding the work of the other N-1 along with every unrelated branch of the graph. When the reader can produce a useful result from a partial collection, ignore_discarded: true is what lets it.

The flags are node-wide, not per-dependency: a node that tolerates a discarded fan-out branch also tolerates a discarded required dep. That is usually safe, because a reader whose required input is missing fails on binding resolution instead, but it is a real trade and worth stating.

Retry budget and backoff

max_attempts is the portable spelling of the Oban option of the same name: when set, the compiler builds this node's job(s) with that max_attempts instead of the worker default, and a fan-out stamps it onto every expansion. It budgets genuine attempts — snoozes inflate the job's counters symmetrically and Baton.Backoff.deflate/1 rebases them away — so a node whose host guards resample aggressively (cheap calls, strict output checks) can buy more draws without touching the worker. nil (the default) leaves the worker's own setting in force.

retry_backoff_seconds is the delay (plus a few seconds of jitter) between those attempts. Unlike max_attempts, this is not stamped onto the Oban job at compile time — a flow worker's backoff/1 (Baton.Backoff.node_backoff/1) reads it straight from the node config carried in job.args on each attempt, so it needs no compiler support. Left nil, the worker falls back to its own default curve — Oban's own exponential formula for Baton.Flow.Workers.Action and any code-defined non-LLM worker, Baton.LLMWorker's jittered failures^3 + 15 for Baton.Flow.Workers.LLM and every Baton.LLMStep — both deflated first so snoozes don't count against them. Either is the right curve for a failure that might mean something is actually wrong. A guard resample is a different kind of retry: "draw again", not "something is wrong", so a node whose guards resample often is a good candidate for a short, roughly flat delay instead of a climbing one.

Guards

guards declares the result checks the host applies to this node's output — degeneration screens, length floors, and whatever else the host's guard runner implements. Baton validates only the shape (a list of maps, each with a non-empty string "kind") and threads the list to the configured Baton.Flow.GuardRunner after handle_response; the kinds and their semantics are entirely the host's business, exactly like the rest of config. A top-level field rather than a config key so the shape is enforced at load time and the list is visible to definition tooling without knowing any host conventions — the same treatment fan_out gets.

Summary

Functions

Dump a node to a JSON-compatible, string-keyed map.

Load a node from a string- or atom-keyed map.

Types

t()

@type t() :: %Baton.Flow.NodeSpec{
  config: map(),
  deps: [String.t()],
  fan_out: Baton.Flow.FanOutSpec.t() | nil,
  guards: [map()],
  id: String.t(),
  ignore_cancelled: boolean(),
  ignore_discarded: boolean(),
  max_attempts: pos_integer() | nil,
  metadata: map(),
  position: %{optional(String.t()) => number()} | nil,
  retry_backoff_seconds: pos_integer() | nil,
  type: String.t()
}

Functions

dump(node)

@spec dump(t()) :: map()

Dump a node to a JSON-compatible, string-keyed map.

load(attrs)

@spec load(map()) :: {:ok, t()} | {:error, term()}

Load a node from a string- or atom-keyed map.