Skip to content

How to use re-usable workflows

Move a repeated sequence into its own DOT file and import it. The imported file is a complete workflow with its own version, entry and terminals, so you can validate and run it on its own before any parent depends on it.

This one implements and reviews a single piece of work. Its two terminals, delivered and rejected, become the exits the parent binds.

subgraphs/ticket.dot
digraph ticket {
graph [version="1", entry="implement", description="Implement and locally review one sub ticket"]
implement [prompt="Implement the current work described below. Satisfy only its requirements. {{ current_work }}", outputs="implementation_summary:text"]
review [prompt="Review the implementation against the current work described below. Choose pass when it satisfies the work, or reject when it does not. {{ current_work }}", outputs="verdict:choice"]
delivered [shape="Msquare", outcome="success"]
rejected [shape="Msquare", outcome="failed", label="Five reviews rejected the same sub ticket"]
implement -> review
review -> delivered [condition="verdict == 'pass'", weight="3", repair_budget="sub_ticket", repair_round="reset"]
review -> implement [condition="verdict == 'reject'", weight="2", repair_budget="sub_ticket", repair_round="retry"]
review -> rejected [condition="verdict == 'reject'", weight="1", repair_budget="sub_ticket", repair_round="exhausted"]
}

Validate it alone:

Terminal window
orbital validate .orbital/subgraphs/ticket.dot

It reads {{ current_work }} rather than a declared input, so the parent decides what one piece of work is. That keeps the sub graph usable from any parent that sets current work.

workflow.dot
digraph epic {
graph [version="1", entry="select", inputs="work", description="Work through an epic one sub ticket at a time", max_visits="200"]
select [prompt="Read the epic at {{ inputs.work }} and every sub ticket under it. Choose the next unfinished sub ticket and report it as the current work. Choose ticket while one remains, or none once every sub ticket is finished.", outputs="current_work:work,remaining:choice"]
ticket [import="subgraphs/ticket.dot"]
finished [shape="Msquare", outcome="success"]
stalled [shape="Msquare", outcome="failed", label="A sub ticket could not be delivered"]
select -> ticket [condition="remaining == 'ticket'", weight="2"]
select -> finished [condition="remaining == 'none'", weight="1"]
ticket -> select [exit="delivered", loop_restart="true"]
ticket -> stalled [exit="rejected"]
}

ticket [import="subgraphs/ticket.dot"] is the placeholder. Orbital replaces it with the imported graph before validation, prefixing every imported node ID, so the flattened graph contains ticket.implement and ticket.review. Each edge leaving the placeholder names the exit it binds, and every exit must be bound exactly once: delivered returns to the selector, rejected ends the run. A bare edge is enough only when the sub graph has a single exit.

Relative imports and prompt files resolve from the file that names them, so the parent’s subgraphs/ticket.dot is relative to the parent. Import cycles are errors.

Include every imported file and sidecar with relative paths intact. Download the outer loop archive to see the layout, or the ticket workflow for a parent with two sub graphs and twelve prompt files.

Graph discovery searches custom graphs first, then the primary project folder and remaining folders, the operator library, and shipped graphs. A name collision selects the first match, so pass an explicit path to orbital validate when diagnosing one.

Imported graph settings do not become parent defaults. Put shared turn configuration in the parent’s profiles or stylesheet. See sub graphs for the exit seam rules.