Graph
A graph system is a flexible workflow where agents can connect in any direction. Unlike chains (linear) or orchestrators (hub-and-spoke), graphs support branches, merges, cycles, and conditional routing. You define the connections explicitly, and the system follows them dynamically.

When to Use
Graph systems work well for:
- Complex workflows - Tasks with conditional logic and multiple paths
- Iterative refinement - Cycles where agents review and improve each other’s work
- Dynamic routing - When the next step depends on the current output
- LangGraph-style agents - State machines with explicit node transitions
How Marlo Tracks It
Use task.child() for each node execution. Store routing information in task metadata if you need to preserve the explicit graph structure.
Python
with marlo.task(thread_id=thread_id, agent="graph-runner") as graph:
graph.input(initial_state)
current_node = "start"
state = initial_state
while current_node != "end":
# Execute current node as a child task
with graph.child(agent=current_node) as node:
node.input(state)
state = execute_node(current_node, state)
node.output(state)
# Determine next node based on state
current_node = get_next_node(current_node, state)
graph.output(state)TypeScript
const graph = marlo.task(threadId, 'graph-runner').start();
graph.input(initialState);
let currentNode = 'start';
let state = initialState;
while (currentNode !== 'end') {
// Execute current node as a child task
const node = graph.child(currentNode).start();
node.input(state);
state = await executeNode(currentNode, state);
node.output(state);
node.end();
// Determine next node based on state
currentNode = getNextNode(currentNode, state);
}
graph.output(state);
graph.end();What You See in the Dashboard
The dashboard shows all node executions as child tasks, capturing the actual path taken:
📁 Task: "Process request" (graph-runner)
├── 📄 Input: [initial state]
├── 📁 Child Task (classifier)
│ ├── 📄 Input: [state v1]
│ └── 📄 Output: [state v2, route=complex]
├── 📁 Child Task (analyzer)
│ ├── 📄 Input: [state v2]
│ └── 📄 Output: [state v3]
├── 📁 Child Task (reviewer)
│ ├── 📄 Input: [state v3]
│ └── 📄 Output: [state v4, needs_revision=true]
├── 📁 Child Task (analyzer) ← Re-executed due to cycle
│ ├── 📄 Input: [state v4]
│ └── 📄 Output: [state v5]
├── 📁 Child Task (reviewer)
│ ├── 📄 Input: [state v5]
│ └── 📄 Output: [state v6, needs_revision=false]
└── 📄 Output: [final state]Each node agent develops its own learnings based on all its executions across all graph runs.
Example: Review Cycle with LangGraph
from langgraph.graph import StateGraph
# Register node agents
marlo.agent(name="drafter", system_prompt="You write initial drafts.", ...)
marlo.agent(name="reviewer", system_prompt="You review and critique drafts.", ...)
marlo.agent(name="reviser", system_prompt="You improve drafts based on feedback.", ...)
def run_review_cycle(request: str, thread_id: str):
with marlo.task(thread_id=thread_id, agent="review-graph") as graph:
graph.input(request)
# Draft
with graph.child(agent="drafter") as drafter:
drafter.input(request)
draft = create_draft(request)
drafter.output(draft)
# Review loop
approved = False
while not approved:
with graph.child(agent="reviewer") as reviewer:
reviewer.input(draft)
feedback = review_draft(draft)
reviewer.output(feedback)
if feedback["approved"]:
approved = True
else:
with graph.child(agent="reviser") as reviser:
reviser.input({"draft": draft, "feedback": feedback})
draft = revise_draft(draft, feedback)
reviser.output(draft)
graph.output(draft)
return draftRecording Graph Structure
If you need to preserve the explicit graph edges (not just the execution path), include routing metadata:
with graph.child(agent="classifier") as node:
node.input(state)
result, next_node = classify(state)
node.output({
"result": result,
"routing": {
"from": "classifier",
"to": next_node,
"reason": "Classified as complex query"
}
})This metadata appears in the trace and helps you understand routing decisions.
Best Practices
- Limit cycles - Add maximum iteration counts to prevent infinite loops
- Log routing decisions - Record why each transition was chosen
- Unique thread IDs - Each graph execution should have a unique thread ID
- State immutability - Treat state as immutable; create new state objects at each step