Cookbook
Storylines · for AI agents

Parallel steps — do several things, then continue

An AND-join: several sub-flows run in any order, each with its own checks; continue only when all are done.

MCP tools:create_storylinevalidate_storyline

Some steps aren't a line — they're "do all of these, in any order, each with its own follow-ups, and continue only when every one is done." A visa application is the classic case: passport, bank statement and invitation letter each need collecting and a different review, but you can't submit until all three pass. That's a parallel node (a fork / AND-join), a first-class node type — you don't hand-wire completion flags.

How it works

A parallel node declares several required branches, each pointing to the entry of its own sub-flow. The engine presents the unfinished branches as choices, tracks completion natively (entering a branch records it; returning to the parallel node marks it done), and takes the node's join exit — the first exit with a to_node_keyautomatically, once every branch is complete. You don't evaluate that condition yourself; the label is just for humans. Each branch can be a full multi-node sub-flow with its own checks and loops; its last step simply takes a normal exit back to the parallel node.

create_storyline(
  agent_name="Visa", key="visa-docs", name="Visa documents",
  graph={"nodes": [
    {"node_key": "collect", "title": "Collect all documents", "type": "parallel",
     "flags": {"is_entry": True},
     "parallel": {"branches": [
       {"key": "passport", "label": "Passport",         "to_node_key": "p1"},
       {"key": "bank",     "label": "Bank statement",   "to_node_key": "b1"},
       {"key": "invite",   "label": "Invitation letter","to_node_key": "i1"}
     ]},
     "exits": [{"kind": "ai", "label": "all documents cleared", "to_node_key": "submit",
                "ai_criteria": "all required documents have been checked"}]},
 
    {"node_key": "p1", "title": "Check passport", "task": "Verify validity ≥ 6 months; if not, tell the user what to renew.",
     "exits": [{"kind": "ai", "label": "passport ok", "to_node_key": "collect", "ai_criteria": "passport is valid and legible"}]},
    {"node_key": "b1", "title": "Check bank statement", "task": "Confirm 3 months of history and a sufficient balance.",
     "exits": [{"kind": "ai", "label": "bank ok", "to_node_key": "collect", "ai_criteria": "statement covers 3 months and meets the threshold"}]},
    {"node_key": "i1", "title": "Check invitation", "task": "Confirm host details and dates match the trip.",
     "exits": [{"kind": "ai", "label": "invite ok", "to_node_key": "collect", "ai_criteria": "invitation is consistent with the trip"}]},
 
    {"node_key": "submit", "title": "Submit application", "task": "Package the cleared documents and submit.",
     "flags": {"is_terminal": True}, "exits": []}
  ]}
)

The applicant can supply the three documents in any order; each returns to collect when it clears. submit is unreachable until all three branches are done — the engine only takes the join exit then.

Give each branch as much of its own flow as it needs — a passport that's expiring can loop through a "renew and re-upload" node before it returns; the bank branch can ask for a top-up statement. Keep the branch keys unique. Then validate_storyline before publishing — it flags a parallel node with no branches, a branch pointing at a missing node, or no join exit.