Public beta — not for production use. Data may be wiped at any time. Questions? Contact us.
Documentation menu

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 Markdown

A 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):

  1. Pick your flow in the Flow picker. With no profiles yet, the page shows "No profiles for this flow yet."
  2. Click + New profile.
  3. Enter a Profile name (left blank, it becomes default — a flow's default profile is applied automatically; see "How config values are resolved" for the exact layering and its one exception).
  4. 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.
  5. 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:

  1. Tenant default — your config named default that is not scoped to any flow (the one the Flow Configs page edits under "Tenant default").
  2. Flow default — the invoked flow's profile named default, when one exists.
  3. Selected profile — the profile named in the Run dialog or in config_id, when it isn't default.

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 AxiomContext exactly 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_attempts caps 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 at max_interval_ms so 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_ms bounds a single attempt — it can only narrow, never widen, the node's existing timeout.
  • overall_timeout_ms bounds the total time spent across every attempt. Once it's spent, retries stop even if max_attempts hasn't been reached — a node exhausted this way falls through to Failed (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.