Tutorials
Each tutorial adds one new node, building toward a full pipeline. Each tutorial is a complete, runnable workflow (the files live in the source examples directory and are compile-tested). Read them in order - each introduces one new idea.
Assumed setup: an $AXL_HOST and a $TOKEN, and agent keys/tools that exist in your deployment
(swap the example keys like greeter, web_fetch for yours). Submit any of these with:
curl -sX POST "$AXL_HOST/api/v1/workflow/flow/source?name=NAME" \
-H "Authorization: Bearer $TOKEN" --data-binary @FILE.axgHello, workflow (hello.axg)
The smallest useful workflow: take an input, call an agent, return its answer.
axg 1
workflow hello {
input name
greet = agent greeter:
"Write a one-line friendly greeting for {{ input.name }}."
greet -> return {
greeting: greet.response
}
}New ideas: input, an agent node, {{ … }} templating, node.response, return.
Try: add a second input and reference it in the prompt.
Two steps in sequence (a chain)
Feed one agent's output into the next with an edge.
axg 1
workflow writeup {
input topic
research = agent researcher:
"Research {{ input.topic }} and list findings."
draft = agent writer:
"Write a short article from these findings: {{ research.response }}"
research -> draft
draft -> return {
article: draft.response
}
}New ideas: an edge (research -> draft) creates a dependency; draft waits for research and
reads its output. Chain as long as you like: a -> b -> c -> ….
Try: add context handoff to draft so it continues research's conversation instead of
starting fresh (see ).
Branching (triage_branch.axg)
Route to different handling based on a condition.
axg 1
schema Triage """
{ "type": "object", "properties": { "category": { "type": "string" } }, "required": ["category"] }
"""
workflow triage_branch {
input message
classify = agent triager:
"Classify this support message: {{ input.message }}"
schema Triage
classify -> billing when classify.structured.category == "billing"
classify -> general else
billing = agent billing_agent:
"Resolve the billing issue: {{ classify.response }}"
context handoff
general = agent general_agent:
"Answer this general question: {{ classify.response }}"
context handoff
billing -> return {
handled: "billing",
reply: billing.response
}
general -> return {
handled: "general",
reply: general.response
}
}New ideas: schema Triage asks the agent for structured output, read as
classify.structured.category. Triage is the file-level schema Triage """…""" definition above
the workflow (in the same .axg file), referenced by name - inline schema """{…}""" on the node
works too for one-offs. The when … / else edges route to exactly one arm. Each arm must reach its
own return - the engine rejects a branch where one path can't complete.
Try: add a third category by chaining another when/else edge off the general arm.
Draft-and-refine with a loop (refine_loop.axg)
Iterate a step until it's good enough (or hit a cap).
axg 1
workflow refine_loop {
input topic
seed = agent writer:
"Write a first draft about {{ input.topic }}."
draft = loop max 5 until prev.score >= 8 from seed {
agent critic:
"Improve the draft and rate it 0-10 (return {text, score}): {{ prev.text }}"
}
seed -> draft
draft -> return {
final: draft.text
}
}New ideas: a loop runs its body repeatedly. from seed seeds the first iteration's prev from
the seed node; each iteration sees the previous one as prev. It stops at max 5 iterations or
when until prev.score >= 8 holds. Put retry/timeout on the body, not the loop.
Try: drop until so it always runs the full max iterations.
Fan out over a collection (map_node.axg)
Run the same step once per item, concurrently, and collect the results.
axg 1
workflow map_node {
input docs
summaries = map over input.docs {
agent summarizer:
"Summarize: {{ $.item }}"
}
summaries -> done
done = return {
all: "{{ $.nodes.summaries.output }}"
}
}New ideas: map over <collection> fans the body out - one run per element, with {{ $.item }}
(or item) as the current element. The node's output is the ordered array of per-item results.
Bounded to 256 items.
Try: pass input.docs as ["a", "b", "c"] and inspect the aggregated output.
Run things in parallel and merge (parallel_join.axg)
Run two independent agents at once, then combine the results.
axg 1
workflow parallel_join {
parallel max 2
input topic
a = agent researcher:
"Find pros of {{ input.topic }}."
b = agent researcher:
"Find cons of {{ input.topic }}."
merge = join all [a, b]
merge -> done
done = return {
pros: a.response,
cons: b.response
}
}New ideas: parallel max 2 lets two durable nodes run at once. a and b have no edge between
them, so they run concurrently; join all [a, b] waits for both before merge. Use join any […]
to take the first to finish, or join quorum 2 […] for "at least 2 of N."
Try: change to join any [a, b] and notice the other branch is cancelled once one completes.
Call a tool (tool_call.axg)
Invoke a registered tool with rendered arguments.
axg 1
workflow tool_call {
input url
fetch = tool web_fetch {
url: input.url,
depth: "basic"
}
fetch -> return {
page: fetch.result
}
}New ideas: a tool node calls a registered tool; the object is its arguments, and the tool's
result is the node's output (fetch.result). Read-only tools run inline; write tools run durably and
are guarded against double-execution on retry.
Try: chain fetch -> summarize where summarize is an agent reading {{ fetch.result }}.
Pause for human approval (human_approval.axg)
Park the run until a person resumes it.
axg 1
workflow human_approval {
input request
draft = agent drafter:
"Draft a response to: {{ input.request }}"
gate = suspend: "Approve this draft before it is sent?"
draft -> gate -> done
done = return {
approved: gate.ok,
draft: draft.response
}
}New ideas: suspend parks the run indefinitely. A human (or another system) resumes it with
POST /api/v1/workflow/flow/{run_id}/resume and a JSON payload; that payload becomes the node's
output (gate.ok here). The run survives restarts while parked.
Try: resume with {"ok": true} and watch the run complete.
Compose with a sub-workflow (sub_workflow.axg)
Call another registered workflow as a step.
axg 1
workflow parent_flow {
input doc
prep = tool clean {
text: input.doc
}
call = workflow summarize_doc {
doc: input.doc
}
prep -> call -> done
done = return {
result: "{{ $.nodes.call.output }}"
}
}New ideas: a workflow node runs another registered workflow (summarize_doc) as a child
run; the object is the child's input. The child must be registered before you submit the parent - it
is pinned by content hash, so a child re-registered mid-run can't silently swap under you.
Try: register a tiny summarize_doc definition first, then run this parent.
Coordinate with events (event_driven.axg)
Emit a signal, or wait for one, across runs.
axg 1
workflow event_driven {
input order_id
notify = event emit "order.placed" {
id: input.order_id
}
gate = event wait "payment.confirmed"
notify -> gate -> done
done = return {
paid: "{{ $.nodes.gate.output }}"
}
}New ideas: event emit "<name>" { payload } publishes an owner-scoped event; event wait "<name>" parks until that event fires (its payload becomes the node's output). Events let separate
runs coordinate - one run's emit can wake another run's wait.
Try: run a second workflow that event emit "payment.confirmed" and watch this one unblock.
Where to go next
- How-To Recipes - task-oriented answers (structured output, retries, error handling, parallelism, human-in-the-loop, durable timers).
- - every node's full field set.
- Operations Guide - submitting, polling, streaming, config, and failure modes.
Next
- How-to recipes - patterns for specific tasks.
- Workflow operations - submitting, streaming, and operating runs.
- - every node kind and field.