Workflows
Steps the agent walks through, and intents that interrupt them from anywhere in the call.
A workflow is a graph the agent walks while it talks: named steps, each with a line to say and the branches leading out of it. It replaces the flat system prompt as the source of truth for what happens next.
The difference shows up under pressure. A flat prompt asks a model to remember its agenda while improvising. A workflow makes the agenda structural: the same call runs the same way twice, and every branch is inspectable before you dial.
Steps
Each step carries a say line delivered verbatim and an instruction for what to do with the answer. Most also declare slots — small typed values the model extracts from the caller's reply.
{
"id": "confirm_time",
"kind": "message",
"say": "Does Thursday at 2pm still work for you?",
"slots": [
{ "key": "keeps_slot", "type": "enum", "enum": ["yes", "no", "question"] }
]
}Slots are what the step routes on. An edge is a condition plus a destination:
{ "id": "e_ask", "from": "confirm_time", "to": "answer_question",
"condition": { "type": "expression", "expr": "keeps_slot == 'question'" } }Each step also has one unconditional edge — where the call goes when nothing more specific matches.
Globals: intents from any step
A normal edge belongs to one step. A global edge has no from, so it is evaluated from every step in the workflow. Declare an intent once and it is live for the whole call.
{
"globalEdges": [
{ "id": "g_cancel", "to": "cancel_booking",
"condition": { "type": "llm", "when": "asks to cancel the appointment" } },
{ "id": "g_human", "to": "transfer",
"condition": { "type": "llm", "when": "asks to speak to a person" } }
]
}Without one, a caller can only cancel at the step that happens to ask about it:
agent Please arrive ten minutes early and bring your insurance card…
caller Actually, I need to cancel it.
agent Parking is behind the building, and the entrance is on Oak Street… ← kept reading its script
caller I need to cancel the appointment.
agent Does Thursday at 2pm still work for you? ← finally the step that asksWith g_cancel declared, the same sentence lands wherever it is said:
agent Please arrive ten minutes early and bring your insurance card…
caller Actually, I need to cancel it.
agent No problem — Thursday at 2pm is cancelled. You'll get a text confirming it.A workflow may declare up to 8 global edges.
Which wins, the step or the global
The step's own edges are resolved first, but only on a turn that actually filled one of its slots. If the caller answered the step's question, the step keeps the turn. If they said something else, a matching global takes it.
That ordering matters in both directions. Globals evaluated last are unreachable mid-script. Globals evaluated first steal turns the step understood perfectly well — a caller answering "no" to do you have a referral? is not cancelling the appointment.
An expression edge pointing at the same step your unconditional edge already points at changes no routing, but it does claim the turn — which silently suppresses every global from that step. Delete it; the default already goes there.
Excluding a step
Where a global would misread a step's own answers, name that step in exceptNodeIds and the global stops firing there.
{ "id": "g_cancel", "to": "cancel_booking",
"condition": { "type": "llm", "when": "asks to cancel the appointment" },
"exceptNodeIds": ["answer_question", "cancel_booking"] }Reading and writing a workflow
GET /v1/agents/{id}/graph returns the live workflow, its version, and a revision you send back on write. PUT publishes a new one:
curl -X PUT https://api.speko.ai/v1/agents/$AGENT_ID/graph \
-H "Authorization: Bearer $SPEKO_API_KEY" \
-H 'Content-Type: application/json' \
-d '{ "version": 12, "ifRevision": 3, "graph": { "entryNodeId": "intro", "nodes": [], "edges": [] } }'version is the version you edited from. Publishing cuts a new agent version, so a workflow change moves the same number a config change does — which is how rollback can reach a previous workflow. A stale ifRevision answers 412 rather than overwriting whoever published in between.