Set per-flow config values
Create flow config profiles in the console — secret overrides — pick one in the Run dialog or via config_id, and see how resolved values reach nodes.
View as MarkdownA flow config is a named set of runtime parameters for a flow — today, secret overrides. Configs are not part of the flow itself: they are resolved at invocation time and injected into the run, so changing one affects the next run without editing or re-saving the flow. A config scoped to one flow is called a profile; the tenant default applies to every flow you own.
Prerequisites: you are logged in to the Axiom editor and have saved at least one flow (Push and run your first flow).
Create a per-flow profile
On the Flow Configs page (/console/flow-configs):
- Pick your flow in the Flow picker. With no profiles yet, the page shows "No profiles for this flow yet."
- Click + New profile.
- Enter a Profile name (left blank, it becomes
default— a flow'sdefaultprofile is applied automatically; see "How config values are resolved" for the exact layering and its one exception). - Optionally add Secret overrides: click + Add override, pick a Secret name from your existing secrets (or type a name when you have none yet), enter a Value, and click Save override.
- Click Create.
Each profile row lists its name and the names of its secret overrides, with Edit and Delete buttons. Override values are write-only: the page (and the API behind it) returns override names, never values.
Two more ways to reach the same controls:
- From the editor: with nothing selected on the canvas, the inspector shows a Run configuration card containing the same profile list and create form for the open flow, plus a link to the tenant default.
- Deep link:
/console/flow-configs?graph=<flow id>preselects that flow in the picker.
To manage the secrets themselves (the tenant-wide values that overrides replace), see Manage secrets in a flow.
Pick a profile when you run
In the editor's Run dialog, a Config profile selector appears once any config applies to the flow — a per-flow profile or the tenant default. It defaults to Tenant default; pick a profile to run with its values instead. The selection applies to that run only.
When invoking over HTTP, set config_id in the request body. It accepts
either a profile name (for the flow being invoked) or a profile ID; when
omitted, the default hierarchy applies (tenant default, then the flow's
default profile). See Invoke a flow via the API
for the endpoint, authentication, and graph_id.
# Run the flow with the "load-test" profile (assumes AXIOM_API_KEY is exported)
curl -X POST "https://api.axiomide.com/invocations/v1/flows/invoke" \
-H "Authorization: Bearer $AXIOM_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"graph_id": "01JX3F8Q4ZJ4M9W4Y0B8T2K7RD",
"input": {"name": "Ada"},
"wait": true,
"config_id": "load-test"
}'How config values are resolved
At invocation time the platform merges up to three layers, later layers overriding earlier ones parameter by parameter:
- Tenant default — your config named
defaultthat is not scoped to any flow (the one the Flow Configs page edits under "Tenant default"). - Flow default — the invoked flow's profile named
default, when one exists. - Selected profile — the profile named in the Run dialog or in
config_id, when it isn'tdefault.
Secret overrides merge across the same layers, with the later layer winning
for a given secret name. One exception: selecting a profile by its ID (which
is what the Run dialog sends) applies that profile directly on top of the
tenant default — the flow's default profile is not layered in between.
How nodes receive config values
Resolution happens before the flow starts: the merged parameters and secret values are injected into the run's variables, which travel with the execution to every node.
- Secret overrides are invisible to node code. A node reads secrets
through
AxiomContextexactly as described in Manage secrets in a flow; when the run's profile overrides a secret, the same read returns the override value instead of the tenant-wide one. No code change is needed to support per-profile values:
# nodes/summarize.py — gets the tenant-wide OPENAI_KEY, or the selected
# profile's override of it, through the same call
from gen.messages_pb2 import SummarizeRequest, SummarizeReply
from gen.axiom_context import AxiomContext
def summarize(ax: AxiomContext, input: SummarizeRequest) -> SummarizeReply:
api_key, found = ax.secrets.get("OPENAI_KEY")
if not found:
ax.log.error("OPENAI_KEY is not set for this tenant")
return SummarizeReply()
ax.log.info("key resolved for this run") # never log the value itself
return SummarizeReply()This snippet assumes a Python package with SummarizeRequest and
SummarizeReply messages and a Summarize node — see
Create a node in Python for the scaffold.
Configure per-node retry and backoff
Retry and backoff work differently from everything else on this page: they
aren't a config profile resolved at invocation time — they're a per-node
policy compiled into the flow itself, authored in the node's retry:
block in flow.yaml (or the canvas Reliability tab), the same way you
author edges and node placement. A node with no retry policy keeps today's
behavior: any failure ends the execution (subject to
failure edges
and compensation).
Author a policy
nodes:
- id: call_provider
node: acme/http-connector.CallProvider
retry:
max_attempts: 5
initial_interval_ms: 1000
backoff_coefficient: 2.0
max_interval_ms: 60000
overall_timeout_ms: 300000
non_retryable_codes: [USER]The same fields live on the node's Reliability tab in the canvas, with a live preview of the resulting attempt schedule.
How backoff is computed
max_attemptscaps the total number of attempts, including the first.0(or omitting the field) means "use the platform default" — 3 attempts — and any value is clamped to a hard platform ceiling of 10, to bound retry storms.- Between attempts, the wait grows exponentially:
initial_interval_ms × backoff_coefficient ^ (attempt - 1), capped atmax_interval_msso a fast-growing backoff never waits longer than the ceiling. - Retries never hold a worker slot. On a retryable failure the worker durably records the attempt and releases the slot immediately; a background sweeper re-dispatches the node when its backoff elapses. This is why a connector can wait minutes for a rate limit to clear without starving other work on the same worker.
Per-attempt and overall timeouts
per_attempt_timeout_msbounds a single attempt — it can only narrow, never widen, the node's existing timeout.overall_timeout_msbounds the total time spent across every attempt. Once it's spent, retries stop even ifmax_attemptshasn't been reached — a node exhausted this way falls through toFailed(or a failure edge) exactly like exhausting attempts.
Retryable vs. non-retryable failures
Every failure is classified into a typed error code before the policy is
consulted: TRANSPORT and TIMEOUT are retryable by default (a dropped
connection or a slow upstream is worth retrying); USER (a deterministic
business failure — retrying it just fails the same way again), PANIC, and
CANCELLED are not. non_retryable_codes can only remove a code from
retrying — it can never grant retryability to USER, PANIC, or
CANCELLED.
Scope: unary nodes only
Retry policy applies to unary nodes only. Pipeline nodes (streaming,
type: pipeline in axiom.yaml) and heartbeat-based liveness are not
retried in this release — a pipeline node's retry: block, if set, is
ignored.
Side-effecting nodes must be idempotent
A retried node runs again from the top — the platform has no way to know whether the previous attempt's side effect (a charge, a write, a provisioned resource) actually landed before it failed. Give any node that calls an external system, especially marketplace connectors, an idempotency key or an equivalent check-before-write guard.
Marketplace connectors are a particular case worth calling out explicitly:
a connector is a generic engine (HTTP, database, queue) re-typed per use — a
kind: generic Generic node typed once, server-side, by an Instance
(see the axiom.yaml reference),
not a hand-authored node with its own idempotency logic baked in. The retry
policy has no visibility into what the underlying call actually does, so it cannot
guarantee exactly-once delivery to the external system on your behalf — the
idempotency key or check-before-write guard above is still yours to provide
at the connector's configured operation.