Actions & workflow control
Actions are the steps an interface runs when called. Each action does one thing — fetch data or transform it — and actions can be ordered, retried, and run conditionally on each other's results.
An action's data comes from an input: selector (request data or a previous
action's output), or from a fetch: http,
database, command,
email, or a lookup
to fan out over an array. Then validate with assert, reshape
with post_transforms, and order with run_when_succeeded
/ run_when_failed / depends_on.
actions:
- name: FetchUser
http:
url: https://api.example.com/users/a|params::id|
- name: Notify
run_when_succeeded: [FetchUser]
http:
url: https://hooks.example.com/notify
body: { email: a|FetchUser::email| }
Reference a previous action's output with a|ActionName::field| — see
Interpolation. All action fields follow.
What retry repeats
retry only repeats failures that could plausibly succeed on another attempt. By default
that is 408, 429, 500, 502, 503, 504 and transport failures (connection refused,
DNS, timeout). A 400 or 401 is attempted once: repeating it cannot change the answer,
and at scale it turns a partner's bad day into a retry storm.
- name: Push
retry:
attempts: 3
delay: 100
exponential_backoff: true # 100ms, 200ms, 400ms...
max_delay: 30000 # ...capped
http: {url: https://api.example.com/x, method: POST}
The default set is the intersection of what Polly, the AWS and Azure SDKs and urllib3 all
treat as transient. It deliberately excludes 501 and 505, which are permanent — proxies
blanket all of 5xx because they cannot know better, but on: [5xx] is available if you
want that.
| Field | Effect |
|---|---|
on | replace the default set — codes (429), classes (5xx), timeout, connect, any |
except | subtract from whatever on resolves to, including the default |
respect_retry_after | honour a Retry-After response header over the computed backoff (default true, still bounded by max_delay) |
jitter | full (default) or none |
max_delay | upper bound on a single wait, in milliseconds |
Jitter is on by default because without it every client that failed together retries together, and a recovering upstream is hit by the same synchronised wave it just fell over to.
Two narrowings worth knowing:
retry.on: [connect]retries only failures that provably never reached the server — the strictest safe setting for a non-idempotent endpoint whose provider has no idempotency-key support. A timeout after the request was sent leaves the outcome unknown.- Classification applies only when an unexpected status is why the action failed. An action
that returned a fine status and failed its
assert— polling until a record appears — is retried as it always was.
Retrying anything that is not an HTTP call
A database, command, email or state action has no status code to classify, so every
failure of one is retryable — a query error, a failed assert, a non-zero expect_exit.
That is unchanged, and it is what makes the common shapes work:
# retry until the row shows up
- name: WaitForRow
database: main
query: SELECT id FROM orders WHERE external_id = $1;
params: [a|body::externalId|]
retry: {attempts: 5, delay: 200, exponential_backoff: true}
assert:
tests:
- value: count()
is_greater_than: 0
Two things follow from that:
on/exceptmatch statuses and transport failures, so on a non-http action they match nothing and the filter is inert. The config-load lint warns if you set one there. To narrow what counts as a failure on these actions, useassert(orexpect_exitfor a command) — that is the knob with meaning here.delayis an upper bound, not a fixed wait, because jitter applies to every action type:delay: 500sleeps somewhere in[0, 500). Setjitter: noneif you need the exact curve (a test asserting on elapsed time, say).
Action
| Field | Type | Description |
|---|---|---|
replayable | boolean (nullable) | Whether a captured run containing this action may be replayed. Replay re-runs an action for real, so an action with a side effect the world can see — charging a card, sending… Example ↓ |
timeout | any | Maximum execution time for this action in milliseconds. Example ↓ |
input | string (nullable) | Where this action's data comes from. Common sources: - a|body| - HTTP request body - a|params| - URL parameters - a|action_name::field| - output from an earlier action Example ↓ |
input_fallbacks | Array<string> (nullable) | Alternative input sources to try if the primary input is not available. Example ↓ |
conditional_input | ConditionalInput (nullable) | Define conditional input based on runtime conditions. Uses Simple (list of input strings) or Complex (with tests). Example ↓ |
pre_log | Array<LogMessage> (nullable) | Log messages before action execution. Example ↓ |
post_log | Array<LogMessage> (nullable) | Log messages after successful action completion. Example ↓ |
error_log | Array<LogMessage> (nullable) | Log messages when action fails. Example ↓ |
success_log | Array<LogMessage> (nullable) | Log messages when action succeeds. Example ↓ |
retry_log | Array<LogMessage> (nullable) | Log messages when action retry is attempted. Example ↓ |
retry | Retry (nullable) | Configure automatic retry behavior. Example ↓ |
lookup | string (nullable) | Name of a lookup configuration to use for pre-fetching data. Example ↓ |
item_timeout | any | Per-item execution timeout for lookup iterations. Controls how long each individual item in the lookup array is allowed to run. Use timeout to cap the total loop duration. Example ↓ |
lookup_concurrency | number (nullable) | Maximum number of lookup items to execute concurrently. Defaults to 10. Capped at 50 in managed run modes. Example ↓ |
lookup_partition | boolean (nullable) | Separate per-item successes from failures in the lookup output. When true, the action returns `{ "succeeded": [...item data...], "failed": [{ item, http_code?, error?, data? }]… Example ↓ |
lookup_inherit_jq | Map<string, string> (nullable) | JQ transformations to apply to lookup results. Example ↓ |
lookup_inherit | Map<string, string> (nullable) | Fields to inherit from lookup results. Example ↓ |
actions | Array<Action> (nullable) | Nested actions for lookup. Example ↓ |
json_output | any | Capture specific JSON values from output. Example ↓ |
command | CommandRun (nullable) | Execute a system command. Example ↓ |
output_stdout | boolean (nullable) | Include command stdout in output. Example ↓ |
http | HttpRequest (nullable) | HTTP request configuration. Example ↓ |
database | string (nullable) | Database name for SQL operations. Example ↓ |
google | Google (nullable) | Google API integration. Example ↓ |
aws | Aws (nullable) | AWS service integration. Example ↓ |
state | StateAction (nullable) | Persistent state operation (durable key/value): polling cursors, dedupe sets, idempotency keys, counters. Backed by a pluggable backend (in-memory for local single-node,… Example ↓ |
sftp | Sftp (nullable) | Move files over SFTP: list a directory, fetch a file, write one, move or delete it. Example ↓ |
only_new | OnlyNew (nullable) | Keep only the records this config has not already handled. Polling is the shape behind most "trigger" integrations — fetch a page on a schedule, act on what is new, remember… Example ↓ |
discover | Discover (nullable) | Discover the live members of a service — container endpoints, pods, replicas — as an array, so a later action can fan out over them with lookup: instead of a hard-coded… Example ↓ |
delay | any | Pause before continuing. Accepts a duration string ("2s", "500ms", "1m", "1d") or a number of milliseconds. Short and long delays behave differently, and the difference… Example ↓ |
wait_for_callback | WaitForCallback (nullable) | Suspend the run until something outside calls back — an approval, a signature, a third-party webhook saying the job is done. Unlike delay, which waits for a time you chose,… Example ↓ |
email | Email (nullable) | Email sending action. Example ↓ |
emit_metric | EmitMetric (nullable) | Emit a Prometheus metric as part of this action step. Requires expose_metrics: true on the parent config. Example ↓ |
ws_publish | WsPublish (nullable) | Publish a payload to realtime WebSocket channels (server push / fan-out). |
mqtt_publish | MqttPublish (nullable) | Publish a payload to MQTT topics (pipeline → topic, cross-node fan-out). |
agent | AgentAction (nullable) | Run a tool-calling AI agent loop: ask a model, run whatever tools it asks for, feed the results back, repeat until it answers or the iteration budget runs out. The tools are… Example ↓ |
action | string (nullable) | Action identifier for referencing outputs. Example ↓ |
depends_on | RunCondition (nullable) | Run this action when specified actions complete. Can be a list of action names or a RunCondition config. Example ↓ |
run_when_succeeded | RunCondition (nullable) | Run this action when specified actions succeed. Example ↓ |
run_when_failed | RunCondition (nullable) | Run this action when specified actions fail. Example ↓ |
run_on_assertion | Assert (nullable) | Run this action based on assertion results. Example ↓ |
params | Array<any> (nullable) | Parameters for SQL queries. Example ↓ |
post_transforms | Array<Transform> (nullable) | Transformations to apply to action output. Example ↓ |
name | string (nullable) | Action identifier/name. Example ↓ |
description | string (nullable) | Description of the action. Example ↓ |
query | string (nullable) | SQL query for database actions. Example ↓ |
multi | boolean (nullable) | Run this action's query as a multi-statement batch (Postgres only). The native driver prepares every query, and a prepared statement can hold only one command — so a query… Example ↓ |
url | string (nullable) | URL for HTTP actions. Example ↓ |
conn_string | string (nullable) | Database connection string override. Example ↓ |
output | string (nullable) | Output destination handler. Example ↓ |
assert | Assert (nullable) | Validation and response configuration. Example ↓ |
document_operation | DocumentOperation (nullable) | Document database operations. Example ↓ |
hide_action | boolean (nullable) | Hide entire action data. Example ↓ |
hide_data_on_success | boolean (nullable) | Hide action data on success. Example ↓ |
hide_data_on_error | boolean (nullable) | Hide action data on error. Example ↓ |
hide_data_on_empty | boolean (nullable) | Hide action data when empty. Example ↓ |
hide_errors | boolean (nullable) | Hide error details. Example ↓ |
hide_metrics | boolean (nullable) | Disable metrics collection. Example ↓ |
response_on_success | ActionResponse (nullable) | Custom response on success. Example ↓ |
response_on_error | ActionResponse (nullable) | Custom response on error. Example ↓ |
Field examples
replayable
Whether a captured run containing this action may be replayed.
Replay re-runs an action for real, so an action with a side effect the world can see —
charging a card, sending an email, publishing to a topic — must say so. Any action
marked replayable: false makes the whole run inspect-only, and the run records
which action did it so the answer is "ChargeCard cannot be replayed" rather than a
disabled button with no explanation.
Declared rather than inferred, in the same spirit as expect_status: guessing which
actions are safe to repeat is exactly the judgement that should not be automatic. n8n
simply re-runs, and it bites people.
Example
- name: ChargeCard
replayable: false
timeout
Maximum execution time for this action in milliseconds.
Example
timeout: 5000 # 5 seconds
input
Where this action's data comes from. Common sources:
a|body|- HTTP request bodya|params|- URL parametersa|action_name::field|- output from an earlier action
Example
input: a|body|
Example
input: a|LoginBody::email|
Closing pipe
input accepts the bare form too — a|body|, with no trailing pipe — because
this field names a source directly rather than interpolating one into a larger
string. It is the only field where that is true.
Everywhere else the closing pipe is required and its absence is silent.
url: https://x/a|body| does not resolve to anything; it sends the literal text
a|body|, because the scanner that finds references only matches a closed
marker. Nothing warns, and the request goes out wrong.
Write a|body| everywhere, including here. The bare form is kept for configs
that already use it and is not offered in new examples.
input_fallbacks
Alternative input sources to try if the primary input is not available.
Example
input_fallbacks:
- a|header::Authorization|
- a|params::token|
conditional_input
Define conditional input based on runtime conditions. Uses Simple (list of input strings) or Complex (with tests).
Example - Simple
conditional_input:
- a|body::premium| # Try this first
- a|body::standard| # Fallback
Example - Complex
conditional_input:
- input: a|body::type|
tests:
- is_equal_to: "premium"
pre_log
Log messages before action execution.
Example
pre_log:
- msg: "Starting validation"
level: info
post_log
Log messages after successful action completion.
Example
post_log:
- msg: "Validation succeeded"
level: info
error_log
Log messages when action fails.
Example
error_log:
- msg: "Validation failed"
level: error
success_log
Log messages when action succeeds.
Example
success_log:
- msg: "Email sent"
level: info
retry_log
Log messages when action retry is attempted.
Example
retry_log:
- msg: "Retrying..."
level: warn
retry
Configure automatic retry behavior.
Example
retry:
attempts: 3 # Number of retry attempts
delay: 1000 # Delay between retries (ms)
exponential_backoff: true
lookup
Name of a lookup configuration to use for pre-fetching data.
Example
lookup: user_lookup
item_timeout
Per-item execution timeout for lookup iterations.
Controls how long each individual item in the lookup array is allowed to run.
Use timeout to cap the total loop duration.
Example
item_timeout: 30s
lookup_concurrency
Maximum number of lookup items to execute concurrently. Defaults to 10. Capped at 50 in managed run modes.
Example
lookup_concurrency: 20
lookup_partition
Separate per-item successes from failures in the lookup output. When true, the
action returns { "succeeded": [...item data...], "failed": [{ item, http_code?, error?, data? }] }
instead of a flat array, so a poller can advance its cursor / mark items seen
only for successes and let failed items be retried on the next run. Failed items
are those whose iteration returned an HTTP code >= 400 or timed out.
Example
lookup_partition: true
lookup_inherit_jq
JQ transformations to apply to lookup results.
Example
lookup_inherit_jq:
token: .result.token
lookup_inherit
Fields to inherit from lookup results.
Example
lookup_inherit:
api_key: lookup.api_key
actions
Nested actions for lookup.
Example
actions:
- name: GetToken
http:
url: https://auth.example.com
json_output
Capture specific JSON values from output.
Example
json_output:
user_id: .data.id
command
Execute a system command.
Example
command:
run: "echo hello"
shell: bash
output_stdout
Include command stdout in output.
Example
output_stdout: true
http
HTTP request configuration.
Example
http:
url: https://api.example.com
method: POST
headers:
Content-Type: application/json
body:
key: value
database
Database name for SQL operations.
Example
database: main
query: SELECT * FROM users
google
Google API integration.
Example
google:
credential: my_google_cred
get_signed_upload_url:
bucket: my-bucket
key: file.txt
aws
AWS service integration.
Example
aws:
credential: my_aws_cred
get_signed_upload_url:
bucket: my-bucket
key: file.txt
state
Persistent state operation (durable key/value): polling cursors, dedupe sets,
idempotency keys, counters. Backed by a pluggable backend (in-memory for local
single-node, Postgres for durable/shared, the AirPipe backend in managed mode).
Read state inline with a|state::KEY|.
Example
state:
advance:
key: last_seen
value: a|Fetch::max_updated_at|
sftp
Move files over SFTP: list a directory, fetch a file, write one, move or delete it.
Example
sftp:
host: files.example.com
user: airpipe
private_key: a|secret::SFTP_KEY|
host_key: "SHA256:kW8xDm2YuGv9NsLzalFWnMJ3j28XTnQJzw7/5GkE9+w"
operation: list
path: /incoming
only_new
Keep only the records this config has not already handled.
Polling is the shape behind most "trigger" integrations — fetch a page on a
schedule, act on what is new, remember what you acted on. Every piece was already
here (schedule, http, state.seen), but assembling them meant iterating the
page with lookup, asking about each record separately and gating on the answer:
correct, verbose, and one state round trip per record.
Example
- name: NewOrders
input: a|Fetch->orders|
only_new:
id: .id
key: orders
ttl: 30d
discover
Discover the live members of a service — container endpoints, pods,
replicas — as an array, so a later action can fan out over them with
lookup: instead of a hard-coded address list.
Example
- name: Members
discover:
kubernetes:
label_selector: app=api
port_name: http
- name: PollAll
lookup: a|Members|
actions:
- name: Health
http:
url: a|body::url|/healthz
delay
Pause before continuing. Accepts a duration string ("2s", "500ms", "1m", "1d")
or a number of milliseconds.
Short and long delays behave differently, and the difference matters. Up to
AIRPIPE__MAX_DELAY_SECS (default 300s) the run simply sleeps — pacing between steps.
Past that a sleep would be the wrong instrument, so the run is suspended instead:
what has completed is written to a durable store, the caller is answered 202 with a
run id, and the scheduler resumes the rest when it comes due. Nothing is held open and
a restart does not lose the run.
Suspending needs somewhere to write. Managed has one; self-hosted needs
AIRPIPE__DATABASE_URL. Without it a long delay is refused rather than quietly
shortened into a sleep a restart would drop.
A delay wakes once, at the time you asked for. If you are waiting on work that might
not be finished by then, poll for it instead — an assert that fails until the job
reports done, plus a retry, which suspends between attempts in the same way.
Example
# sleeps in process
delay: "2s"
# suspends the run and resumes it tomorrow
delay: "1d"
wait_for_callback
Suspend the run until something outside calls back — an approval, a signature, a third-party webhook saying the job is done.
Unlike delay, which waits for a time you chose, this waits for an event you cannot
predict. The run is parked in the durable store and the caller is answered 202; when
POST /_ap/resume/<token> arrives the run continues from the next action, and whatever
was posted becomes this action's output.
The token is yours to choose, and must be unguessable — anyone holding it can resume
the run. Mint one with a|uuid| in an earlier action and build the link from it:
Example
- name: Ticket
json_output: '{"token": "a|uuid|"}'
- name: AskApprover
email:
to: approvals@example.com
subject: Approve this refund
body: 'Approve: https://api.example.com/_ap/resume/a|Ticket::token|'
- name: Approval
wait_for_callback:
token: a|Ticket::token|
timeout: "7d"
- name: Refund
run_when_succeeded: [Approval]
http:
url: https://api.example.com/refunds
body: '{"approved_by": "a|Approval::approver|"}'
email
Email sending action.
Example
email:
from: noreply@example.com
to: user@example.com
subject: Hello
text: Body text
emit_metric
Emit a Prometheus metric as part of this action step.
Requires expose_metrics: true on the parent config.
Example
- name: TrackRevenue
emit_metric:
name: revenue_total
type: gauge
value: a|OrderAction::amount|
labels:
tier: a|OrderAction::tier|
agent
Run a tool-calling AI agent loop: ask a model, run whatever tools it asks for, feed the results back, repeat until it answers or the iteration budget runs out.
The tools are this config's own interfaces. Their JSON Schemas come from the asserts those interfaces already declare, so a tool is documented, validated and callable over HTTP and MCP by the same definition — there is no second place to describe it, and nothing to keep in step.
Example
- name: Assistant
agent:
url: https://api.openai.com/v1/chat/completions
model: gpt-4o-mini
headers:
authorization: "Bearer a|ap_var::OPENAI_API_KEY|"
system: "You help staff answer order questions."
input: a|body::messages|
tools: [orders/search, orders/refund]
max_iterations: 6
action
Action identifier for referencing outputs.
Example
action: ValidateUser
# Reference later: a|ValidateUser::result|
depends_on
Run this action when specified actions complete. Can be a list of action names or a RunCondition config.
Example - Simple list
depends_on:
- LoginBody
- InputValidation
Example - Config with at_least
depends_on:
at_least: 1
actions:
- OptionalStep1
- OptionalStep2
run_when_succeeded
Run this action when specified actions succeed.
Example
run_when_succeeded:
- PreviousAction
run_when_failed
Run this action when specified actions fail.
Example
run_when_failed:
- MainAction
run_on_assertion
Run this action based on assertion results.
Example
run_on_assertion:
tests:
- jq: .valid
is_equal_to: true
params
Parameters for SQL queries.
Example
params:
- a|body::user_id|
- "pending"
post_transforms
Transformations to apply to action output.
Example
post_transforms:
- extract_with_jq: ".[0]"
name
Action identifier/name.
Example
name: GetUserDetails
description
Description of the action.
Example
description: "Fetches user details"
query
SQL query for database actions.
Example
query: SELECT * FROM users WHERE id = $1
multi
Run this action's query as a multi-statement batch (Postgres only).
The native driver prepares every query, and a prepared statement can hold
only one command — so a query with several ;-separated statements
(e.g. a schema/seed block) normally fails with SQLSTATE 42601. Set
multi: true to run it via the simple protocol instead, which executes
all statements. Only valid for queries with no params (a batch
cannot be parameterized); it returns an empty result set.
Example
multi: true
query: |
CREATE TABLE IF NOT EXISTS a (id int);
CREATE TABLE IF NOT EXISTS b (id int);
url
URL for HTTP actions.
Example
url: https://api.example.com/users
conn_string
Database connection string override.
Example
conn_string: postgresql://user:pass@host:5432/db
output
Output destination handler.
Example
output: http
assert
Validation and response configuration.
Example
assert:
tests:
- jq: .status
is_equal_to: "success"
success_message: "OK"
error_message: "Failed"
document_operation
Document database operations.
Example
document_operation:
database: users_db
collection: profiles
operation: insertOne
insert:
name: test
hide_action
Hide entire action data.
Example
hide_action: true
hide_data_on_success
Hide action data on success.
Example
hide_data_on_success: true
hide_data_on_error
Hide action data on error.
Example
hide_data_on_error: true
hide_data_on_empty
Hide action data when empty.
Example
hide_data_on_empty: true
hide_errors
Hide error details.
Example
hide_errors: true
hide_metrics
Disable metrics collection.
Example
hide_metrics: true
response_on_success
Custom response on success.
Example
response_on_success:
http_code: 200
body:
status: success
response_on_error
Custom response on error.
Example
response_on_error:
http_code: 500
body:
status: error
ActionResponse
| Field | Type | Description |
|---|---|---|
http_code | number (nullable) | |
headers | object (nullable) | |
body | string (nullable) |
Retry
Try an action again when it fails.
Retry is also how you poll. An upstream job that is still running answers 200 with a
status that is not "done" — a success as far as HTTP is concerned — so pair retry with an
assert that fails until the job reports finished, and the action repeats until the answer
changes rather than only after an error.
A poll is a series of short waits whose total is long, so once they add up past
AIRPIPE__MAX_DELAY_SECS the run is suspended between attempts instead of sleeping
through them: parked in the durable store, the request closed, resumed by the scheduler for
the next attempt. A job that finishes quickly never touches the store; one that does not is
polled for as long as attempts × delay allows without holding anything open. The budget
is spent across resumes, not restarted by them, so a job that never finishes still stops.
Suspending needs a durable store — managed has one, self-hosted needs
AIRPIPE__DATABASE_URL. Without one the waits simply happen in process as before.
Example
# poll a render every minute for up to 40 minutes, suspending between attempts
assert:
tests:
- value: body.status
is_equal_to: completed
retry:
attempts: 40
delay: 60000
| Field | Type | Description |
|---|---|---|
attempts | number | Required. How many times to attempt the action in total, including the first try. 1 (the default) means no retry. |
delay | number (nullable) | Milliseconds to wait between attempts. With exponential_backoff this is the base the delay grows from rather than a fixed pause. |
exponential_backoff | boolean (nullable) | Double the wait after each failed attempt instead of using delay unchanged. Worth setting against an upstream that is rate limiting or overloaded, where retrying at a fixed… |
on | Array<StatusMatcher> (nullable) | Which failures are worth repeating. Accepts codes (429), classes (5xx), any, and the transport classes timeout / connect. Unset uses the default set — `408, 429, 500,… |
except | Array<StatusMatcher> (nullable) | Subtracted from whatever on resolves to, including the default set. Use it to carve an exception out without restating the whole list. |
respect_retry_after | boolean (nullable) | Honour a Retry-After response header when the upstream sends one (RFC 9110 §10.2.3), in preference to the computed backoff. Defaults to true: a server that tells you when to… |
jitter | Jitter (nullable) | Randomisation applied to the computed backoff. Defaults to full, per AWS's "Exponential Backoff and Jitter": without it, every client that failed together retries together,… |
max_delay | number (nullable) | Upper bound on a single wait, in milliseconds. Without it, exponential backoff on a late attempt can schedule a wait longer than any caller is willing to hold for. |
LogMessage
| Field | Type | Description |
|---|---|---|
msg | string | Required. |
level | string | Required. |
json | boolean (nullable) |
RunCondition
One of:
- Array<string>
- RunOnConfig
RunOnConfig
| Field | Type | Description |
|---|---|---|
at_least | number (nullable) | |
actions | Array<string> | Required. |
http_code_on_error | number (nullable) |
ConditionalInput
One of:
- Array<string>
- Array<ConditionalInputConfig>
ConditionalInputConfig
| Field | Type | Description |
|---|---|---|
input | string | Required. |
tests | Array<Test> | Required. |
AgentAction
| Field | Type | Description |
|---|---|---|
url | string | Required. An OpenAI-compatible chat-completions endpoint. Anything speaking that shape works: OpenAI, Azure OpenAI, Groq, Together, vLLM, Ollama, or a gateway of your own. |
model | string | Required. Model id, passed through verbatim. |
headers | Map<string, string> (nullable) | Headers for the model call — where the API key goes. |
system | string (nullable) | System prompt, prepended as the first message. |
input | any | The conversation. Either a plain string (becomes one user message) or an array of {role, content} objects — the shape a chat UI already holds. |
tools | Array<string> (nullable) | Interfaces in THIS config to offer as tools, by name. |
mcp | Array<AgentMcpServer> (nullable) | Remote MCP servers to draw tools from, in addition to tools. Their tools are discovered at run time via tools/list, so the model is offered whatever the server currently… |
max_iterations | number (nullable) | How many model round trips the loop may take. Default 4, hard ceiling 12. This is the bound that makes an agent safe to expose: each iteration is a model call plus every tool… |
memory | AgentMemory (nullable) | Remember this conversation across requests. Without it an agent starts every request with an empty history: it cannot answer "and what about tomorrow?" because it never saw… Example ↓ |
temperature | number (nullable) | Sampling temperature, passed through when set. |
timeout | number (nullable) | Seconds to allow each model call. Default 60. |
Field examples
memory
Remember this conversation across requests.
Without it an agent starts every request with an empty history: it cannot answer "and what about tomorrow?" because it never saw yesterday's question. With it, the last exchanges for a session are replayed into the next call.
Example
memory:
key: a|body::session_id| # what makes one conversation distinct from another
window: 10 # how many past messages to replay
ttl: 24h # how long a silent conversation survives
AgentMcpServer
| Field | Type | Description |
|---|---|---|
url | string | Required. The server's JSON-RPC endpoint. |
headers | Map<string, string> (nullable) | Headers for every call to it — where its credential goes. |
only | Array<string> (nullable) | Offer only these of the server's tools. Absent means all of them. Worth setting deliberately: a remote server decides what it publishes, so without a list the model's abilities… |
AgentMemory
Air Pipe interface definition. An interface represents an endpoint that can be invoked (via HTTP, schedule, etc.) and executes a chain of actions.
Example
myApiEndpoint: # Interface name (can be used as route path)
summary: Get users # Short description for documentation
description: | # Long description (optional)
Retrieves a list of all
users in the organization.
method: POST # HTTP method (for HTTP interfaces)
route: /users # URL path (optional, defaults to interface name)
tags: # OpenAPI tags for grouping
- Users
- API
output: http # Output type: http
templates: # Interface-specific response templates
success_template: |
{ "status": "ok" }
actions: # Ordered list of actions to execute
- name: ValidateJwt # Action name (used for references)
input: a|headers| # Input source (a|headers|, a|body|, etc.)
- name: FetchData
database: main # Database connection to use, assuming global is defined
query: SELECT * FROM users WHERE org_uuid = $1
params:
- a|CheckBody::organization_uuid|
assert: # Final assertion against action results
tests:
- value: count()
is_equal_to: 1
response: # Response configuration
http_code_on_error: 400 # HTTP status code on error
http_code_inherit_error: [FetchData] # Inherit error code from action
Field defaults an interface hands to every action it contains.
The outcome of an action is DECLARED (expect_status / expect_exit), never inferred from
an assert. Declaring it per action is right for a pipeline where each call has its own
contract, and tedious for an interface where they share one — a test runner asserting on the
status of thirty routes under test, a health-check fan-out that records whatever it gets. So
the interface can say it once and each action may still override it.
Resolved at config load (see IntegrationConfig::compile_expressions), so the interpreter
and the compiled fast path both see a plain action-level value and the request path pays
nothing for it.
Example
interfaces:
tests/all:
defaults:
expect_status: any # the statuses ARE what this interface tests
actions: [...]
Conversation memory for an agent: action.
| Field | Type | Description |
|---|---|---|
key | string | Required. What makes one conversation distinct from another — a session id, a chat id, a user id. Usually a marker: a|body::session_id|. There is no default on purpose. A shared… |
window | number (nullable) | How many stored messages to replay. Defaults to 10, capped at 50. Every replayed message is sent to the model on every call, so a larger window means a larger prompt. Whether… |
ttl | string (nullable) | How long a conversation survives without activity, e.g. 24h, 7d. Defaults to 24h. |
namespace | string (nullable) | State namespace to store under. Defaults to the config name. |