🧱 Treat the node as a unit of work
A node can be an AI agent, a model call, a piece of regular code, or even a human decision in the middle of the workflow. The question you need to ask before drawing any node is simple: does this actually need AI? The answer isn’t always yes.
A deterministic in the — one if, a query to a
database, a script that only formats data—it’s cheaper, faster, and more reliable than throwing everything at the model.
The rule the industry has converged on: code handles predictable routing, while the model handles what requires
interpretation or judgment.
🆕 New here? Two words before you continue
- Deterministic node: a step that always produces the same result for the same input—with no AI model in the middle. A
if valor > 100is deterministic. - Orchestrator: the node (or agent) that decides where to send the next task — the graph’s “manager,” not the one that performs the task itself.
What to look at: the five cyan boxes inside the node. Each one can be different in each node of your graph — which is why they stay within in the box, not scattered throughout the system. The two outgoing arrows already show what topic 2 will unpack: the condition lives there, on the edge.
✓ Good candidate for a non-deterministic approach
- ✓Validate email format — a regex is enough; no judgment needed
- ✓Checking a balance through an API — direct call, exact response
- ✓Route by a category already marked in the database — a
switch
✗ Do you really need a model
- ✗Judge whether a text is offensive — requires interpreting context
- ✗Summarize a long document in natural language
- ✗Decide which of the three ambiguous responses the client meant
Key concepts
Agent, code, human, or model
Cheaper and more reliable
Model judges
Industry rule of thumb
🔀 Put the decision on the edge
A conditional edge — the one that carries an "if" — and where routing lives: "if approved, continue; if rejected, go back." That’s exactly what separates a graph from a queue. If all your edges are unconditional (they always go to the same next place), you haven’t written a graph — you’ve written a pipeline disguised.
💡 Quick test: is your workflow a graph or a pipeline?
Look at each edge in your diagram and ask: "does this line exist only because a specific condition was met, or will it always be followed?" If the answer is always "it always follows," you have a sequence — useful, but not really a graph.
- •Pipeline: step 1 → step 2 → step 3, always in that order, without exception
- •Graph: step 1 → (if X) step 2, (if Y) step 3, (if neither X nor Y) go back to step 1
What to look at: the two arrows leaving the validator go to different places, depending on a condition. This branching — not the number of nodes — is what makes this diagram a graph. If only the green arrow existed, it would just be a three-step pipeline.
Key concepts
Loads an “if”
Lives on the edge, not the node
Sequence without a condition
"Does this edge always follow?"
📦 Define the state that gets passed along
Exactly what passes from one node to the next? This question seems small, but it defines half the success of your graph. The state schema — the agreed format for what travels along the edges—it's a contract, and contracts are written, not improvised.
Passing the entire conversation from one node to another seems safe, but cancels out the benefit of clean context: the next node sees again how the work was done, not just what was produced — and this fills its context with noise. Passing too little makes the next node work blind, without the data it needed.
Pass too much: the entire conversation
Full history of messages, intermediate reasoning, and previous attempts.
The next node spends tokens reading the “how” when it only needed the “what.” Cluttered context, higher cost.
Pass too little: just a loose text
A "result" field with no status, no source, and no confidence metadata.
The next node doesn’t know whether it was validated, who generated it, or whether it can be trusted. It works blind.
The middle ground: explicit state schema
Named fields, with a type and an owner: resultado, status, origem.
Each node reads only the fields it needs and writes the fields it’s responsible for filling in. That’s a contract.
💡 Practical tip
Write the state schema before before writing the first node. If you can’t list the fields that will travel, you don’t understand the flow well enough to code it yet — and you’ll end up dumping the entire conversation because it’s easier than thinking.
Key concepts
Agreed-upon state format
If it writes, it isn't improvised
Cancels clean context
That works blind
⚙️ Configure Each Node Separately
Model, tools, permissions, instructions, and cost cap (the maximum limit that node can spend before it’s interrupted) can differ for each node. A strong model for the hard step and a cheap one for the easy step; a writing tool only on the node that writes; internet access only for whoever researches.
This per-node configuration is what gives the graph real economic value—and where the cost truly drops. A system that uses the same expensive model at every step just because “it’s simpler to configure once” is burning money on steps that a cheaper model could handle just as well.
📊 What to configure, node by node
- Model — the stronger (more expensive) one only where the decision is truly difficult
- Tools — writing, code execution, internet access: enabled only for those who need it
- Permissions — who can change sensitive data, who can only read it
- Cost cap — a token/dollar limit per node, so no step can exceed the budget on its own
Key concepts
Maximum per node
Not one global config
And where the cost drops
Only those who need it receive it
🚫 Avoid the degenerate graph
A single-node graph with all the tools and all the instructions, running until it runs out: that’s a reinvented loop, and the graph structure isn’t doing anything for you. You paid the complexity cost of "thinking in graphs" and didn’t reap any of the benefits — per-node configuration, differentiated costs, conditional routing.
Practical test: if you can’t give two nodes in your diagram different configurations, you probably didn’t need two nodes. Merge them. A degenerate graph like this isn’t “wrong”—it’s just not a graph; it’s a loop with a pretty drawing on top.
What to look at: the equation doesn’t have "fewer loops" anywhere—it has loop more loop more a new ingredient: the explicit state that travels between them (what you saw in topic 3). A graph with just one node, without this composition, is only a loop pretending to be a graph.
⚠️ Warning: complexity without benefit
If you designed five nodes but they all use the same model, the same tools, and no edge has a condition, you gained nothing from the design—you just paid the cost of maintaining five configuration files instead of one. Simplify back to one loop, or add the differentiation that was missing.
Key concepts
A node, everything allowed
Different configs or combine
Graph = loops + state
Graph doesn’t kill the loop
📏 Choose the right granularity (and write your grafo.json)
Too large and the graph doesn't help at all—you’re back to the black box of a orchestrator doing everything alone. When it’s too small, you pay coordination overhead on top of trivial work — more edges, more state to pass around, more chances for errors, with no benefit.
Practical rule: one node per change in context, tool, or responsibility. If two steps use the same context and the same tools, they're probably just one node. If a step changes from "writing" to "validating," that's a change in responsibility—two nodes.
✓ Split into two nodes when
- ✓One step writes, the other validates — different responsibilities
- ✓One uses an expensive model, the other only needs a script
- ✓One needs internet access, the other can’t have it
✗ Merge into a single node when
- ✗Both steps use exactly the same model and tools
- ✗You only separated them to "keep things organized," with no real difference
- ✗The edge between them never had any condition
🧪 Practical example: grafo.json with per-node configuration
Objective: evolve the graph skeleton introduced in module 1.3, now with config per node (model, tools, cost cap) and conditional edges quando. Copy and paste into a file grafo.json, and read it with any script.
{
"nos": {
"escritor": {
"modelo": "<modelo-medio>",
"ferramentas": ["escrever_arquivo"],
"teto_custo_usd": 0.05
},
"validador": {
"modelo": "<modelo-forte>",
"ferramentas": [],
"teto_custo_usd": 0.02
},
"formatador": {
"modelo": null,
"ferramentas": ["script_deterministico"],
"teto_custo_usd": 0.00
}
},
"arestas": [
{ "de": "escritor", "para": "validador", "quando": "sempre" },
{ "de": "validador", "para": "formatador", "quando": "resultado_valido == true" },
{ "de": "validador", "para": "escritor", "quando": "resultado_valido == false" }
]
}
How to verify: check that
escritor e validador there are models
different ({'<'}medium-strength model{'>'} vs. {'<'}strong model{'>'}) and that formatador
doesn't even use a model—it's a pure deterministic node. If two of your nodes had the same config, the test from topic 5
applies: combine them; you didn't need two.
Now swap it for yours: replace
<modelo-medio> e <modelo-forte>
using the actual names of your models, and the conditions in quando according to the
business rules of your workflow. If a node is left with no exit condition that differs from the others, review the granularity.
Quick check (nothing is blocked): You have two nodes that use the same model, the same tools, and the same unconditional outgoing edge. What should you do?
Key concepts
Neither large nor microscopic
A node per context change
Explicit condition on the edge
Model, tools, ceiling
📌 Module Summary
Next Module:
4.2 — Org graph vs. work graph: how the agent graph reflects (or doesn’t reflect) your company’s people graph.