Build a branching automation¶
You can have Hadron sort incoming reports for you: a model reads each one, and if it is confident the report is urgent, a task is created on the spot; if it is not sure, the report is filed for a person to review.
This is a headless flow of three nodes: one classifies, then the flow branches on how confident the classification was. High confidence routes to an act node that creates a task; low confidence falls through to a defer node that files it for human review.
flow:classify ──next.1.high (confidence > 0.8)──▶ flow:act (creates a task node)
│
└────────next.2.low (no condition)────────▶ flow:defer (files for review)
This mirrors the flow in hadron-server's appRunFlow.test.ts, the
faithful source for every step. For the mental model behind graphs,
routing edges, and the envelope, read
How multi-node automations work;
for the field-level rules, Multi-node run flows.
Three ways to build it¶
The steps below use the CLI, because every value is visible in one place. The same flow can be built without a terminal:
- In the portal. Open a node and go to its Flow tab: Extracted
fields (with Add field, a Type and a Description) sets up the
classify step. Open flow canvas draws the nodes and edges; selecting an
edge opens Edit edge, with Relationship name, Priority and a
visual Condition builder. A node's tool list has no form yet — enter
data.toolsas JSON on its Data tab. Run it from the node's ⋯ menu (Convert to task, then Run). - Through an agent. An agent connected to Hadron can build and start the
flow:
hadron_create_taskfor nodes that carrydata,hadron_create_edgethenhadron_update_edgeto add a condition (hadron_create_edgetakes none), andhadron_run_taskto start the run.hadron_run_taskneeds the entry node's fullurnand anappRef— withoutappRefit only hands the task back for the agent to carry out itself, and no run starts. An agent cannot yet read the run's result (hadron-server#1561); check it on the run's page in the portal, or with the GraphQL query in Step 5.
Either way, routing conditions are JSONLogic — the same rules the portal's condition builder writes. See Edge conditions.
Prerequisites¶
- For the steps below: the
hadronCLI installed and authenticated. (The portal and agent paths above don't need it.) - An App in your org to run under, and a memory the App can read
and write — this guide uses App
acme.com:opsand memoryacme.com:ops. See Building an agent for the App/memory setup. - An LLM provider configured for the App (or pass
--ai-config <name>at trigger time).
Step 1: Create the classify node¶
The entry node runs first. It carries an extraction spec that pulls two
fields out of the model's response — an intent string and a numeric
confidence — into the run envelope. Its prompt renders {{topic}} from
the trigger payload.
hadron node create -m acme.com:ops \
--loc flow:classify \
--name "Classify report" \
--content 'Classify the report about {{topic}}. Score your confidence from 0 to 1.' \
--data '{"extractionSpec":[
{"field":"intent","description":"what the report is about"},
{"field":"confidence","shape":"number"}
]}'
shape: "number" makes confidence a number the condition can compare;
without it, extracted values default to strings. Fields are best-effort —
design the branch to tolerate a missing one.
Step 2: Create the act and defer nodes¶
The act node handles the high-confidence branch. It declares a
tool so it can create a task node rather than just emit text (a node
that declares tools cannot also carry an extractionSpec):
hadron node create -m acme.com:ops \
--loc flow:act \
--name "Act on report" \
--content 'The {{intent}} report about {{topic}} is urgent. Create a task node at urn hrn:node:acme.com:ops:tasks:investigate-{{topic}} titled "Investigate {{topic}}" describing the first thing to check.' \
--data '{"tools":["hadron_create_node"]}'
The defer node handles the fall-through branch — plain text, no tools:
hadron node create -m acme.com:ops \
--loc flow:defer \
--name "Defer report" \
--content 'Save the {{topic}} report for human review. Summarize why it was low-confidence.'
Step 3: Wire the routing edges¶
Two routing edges leave flow:classify. Edge names decide that they
route (next*); among edges of equal priority, the numeric index
orders them — next.1 before next.2. These edges use the default
priority, so the index governs here; if you set explicit priority values,
those sort first (see
routing-edge selection).
The high-confidence edge carries a JSONLogic condition reading the
extracted score from message.data:
hadron edge create -m acme.com:ops \
--from flow:classify --to flow:act \
--name next.1.high \
--condition '{">":[{"var":"message.data.confidence"},0.8]}'
The low-confidence edge has no condition, so it always fires — the
fall-through. Because it's next.2, it's only reached after the next.1
condition is evaluated and found false:
That's the whole branch: the walker sorts the two edges (next.1 before
next.2), takes next.1.high when confidence > 0.8, and otherwise falls
through to next.2.low.
Attach a playbook without routing into it
To give the act node reference material, add a non-routing edge —
e.g. --name documents — from flow:act to a playbook node. Its
content is inlined into the act node's prompt, but the walker never
treats it as a step. Only next* edges route.
Step 4: Trigger the run¶
Trigger the flow at its entry node, passing topic as the trigger
payload. --wait blocks until the run reaches a terminal status and exits
non-zero if it didn't complete:
hadron run trigger --app acme.com:ops \
--entry hrn:node:acme.com:ops:flow:classify \
--arg topic=outage \
--wait --json
The command prints the run, including its id — a 32-character hex string. A schedule or webhook pointed at the same entry node runs the identical walk — see Headless runs.
Step 5: Read the result¶
Inspect the run (in the portal, the run's page shows the same fields):
Look for:
data— the envelope, e.g.{ "intent": "alert", "confidence": 0.92 }. These are the extracted fields the branch read.curNodeUrn— the checkpoint. At completion it references the result record.
The path it walked — which edge each node took — is the run's hops
trail. Neither hadron run get nor the portal shows it; ask GraphQL:
The first element is flow:classify with edgeOut: "next.1.high" (the
edge it took); the second is flow:act with edgeOut: null (no edge
fired — the flow ended). A tool hop also carries toolCalls (e.g.
{ "name": "hadron_create_node", "ok": true }) and any logs.
Then confirm the two writes the flow made:
# The task node the act node created:
hadron node get hrn:node:acme.com:ops:tasks:investigate-outage
# The run's default result record (written by the last node):
hadron node ls -m acme.com:ops --prefix runs:
Trigger it again with a topic your model scores below 0.8 (or lower the
threshold to test) and the run walks flow:classify → next.2.low →
flow:defer instead — no task created, the report filed for review.
Common issues¶
| Symptom | What to check |
|---|---|
Creating the edge fails INVALID_EDGE_CONDITION, or the CLI rejects the JSON. |
The condition is malformed or names a scope that doesn't exist. Both are caught when you save the edge, not at run time. |
Run fails EDGE_CONDITION_ERROR. |
A saved condition could not be evaluated during the run. The walker never guesses a branch. (Encrypted memories fail earlier: an encrypted entry memory is rejected at launch with ENCRYPTED_MEMORY_UNSUPPORTED, and a downstream node in an encrypted memory fails with NODE_LOAD_FAILED. Headless runs can't use encrypted memories.) |
| High branch never fires even on urgent input. | confidence came back as text the comparison can't read as a number ("high" rather than 0.92), so > 0.8 is silently false. Add shape: "number" to the extraction field and re-run. |
Run fails NODE_LOAD_FAILED on the act node. |
A node can't declare both tools and extractionSpec. Split classification and action into separate nodes (as here). |
Run fails ACTIVATION_DENIED at the act node. |
The App's policy chain doesn't grant tool.hadron_create_node, or the action budget is exhausted. Grant the action / raise the budget. |
| Task node created in the wrong memory, or denied. | Run tools are same-org only and can't reach encrypted or (without --as-self) personal memories. Target a memory the run can write. |
| Both branches skipped; run completes with no action. | Neither routing edge fired — the next.1 condition was false and there's no fall-through. Add a condition-less next.2 edge as the default. |
What you can build from here¶
The classify-then-branch shape generalizes:
- Multi-way triage — several
next.<n>.<label>edges with mutually exclusive conditions and a final condition-less fall-through. - Consult while acting — give an action node
hadron_read_nodeso it can read other nodes during its own turn, shaping the text it writes or the node it creates. (Reads don't populate the envelope — only anextractionSpecnode does that — so branch on extracted fields, and use reads for in-turn context.) - Mid-flow outputs — give an interior node a
data.outputspec to write a record partway through, not just at the end.
Each stays in the same model: prompt nodes, next* routing edges with
JSONLogic conditions, an accumulating envelope. See
Multi-node run flows for every field.
Related¶
- How multi-node automations work — the mental model this how-to assumes.
- Multi-node run flows — the field-level
reference for
extractionSpec,tools,output, and the run record. - Edge conditions — the JSONLogic conditions the routing edges use.
- Headless runs (CLI) — scheduling or webhook-triggering the same flow.