Choosing a Pattern
And gates in plain code.
Now you've got five workflow patterns, and an agent loop. So how do you choose? Here's a simple way to decide, and a technique that makes every one of them safer.

- Five patterns and a loop
- How do you choose?
- One technique for all of them
Which pattern fits?
Fixed steps, use chaining. Different kinds of input, use routing. Independent checks, use parallelism. Subtasks that you can't predict, use orchestrator workers. Clear quality rules, use evaluator optimizer. And if you can't list the steps at all, use an agent. They also combine. Juno routes first, and then some routes run chains, and others run an agent.
| Your situation | Pattern |
|---|---|
| Fixed steps | Chaining |
| Different kinds of input | Routing |
| Independent checks | Parallelism |
| Unknown subtasks | Orchestrator-workers |
| Clear quality rules | Evaluator-optimizer |
| Unknown steps | Agent |
Gates in plain code
Whichever pattern you use, put hard rules in code. Here's a refund gate. Is the amount more than the order? Is the order too old? These aren't for the model to decide. Run it. A valid refund passes. Too much money gets refused. An old order gets refused. Whatever the model says, or however it's been tricked, this function won't change its mind.
def can_refund(order, amount):
if amount > order["price"]:
return False, "More than the order total"
if order["days_ago"] > 30:
return False, "Outside the thirty-day window"
return True, "OK"
from shop import get_order
print(can_refund(get_order("A17"), 40))
print(can_refund(get_order("A17"), 900))
print(can_refund(get_order("C09"), 40))(True, 'OK') (False, 'More than the order total') (False, 'Outside the thirty-day window')
Also uses shop.py
ORDERS = {
"A17": {"item": "Carbon wheelset", "price": 640, "status": "delivered", "days_ago": 12},
"B42": {"item": "Chain", "price": 28, "status": "shipped", "days_ago": 2},
"C09": {"item": "Helmet", "price": 95, "status": "delivered", "days_ago": 45},
}
STOCK = {"Carbon wheelset": 3, "Chain": 14, "Helmet": 0}
REFUNDS = []
def get_order(order_id: str) -> dict:
"""Look up one order by its ID, such as A17."""
return ORDERS.get(order_id, {"error": f"No order {order_id}"})
def check_stock(item: str) -> dict:
"""Return how many of an item are in stock."""
return {"item": item, "in_stock": STOCK.get(item, 0)}
def refund(order_id: str, amount: float) -> dict:
"""Refund money to the customer for an order."""
REFUNDS.append((order_id, amount))
return {"refunded": amount, "order_id": order_id}Also uses scripted.py
class Scripted:
"""A stand-in model that replays a fixed list of replies."""
def __init__(self, replies):
self.replies = list(replies)
self.calls = 0
def ask(self, messages):
self.calls += 1
return self.replies.pop(0)The golden rule
Here's the golden rule of agent engineering. Use the least autonomy that solves the problem. Each step up in freedom costs you predictability, speed, and money. A good system is mostly plain code, with a little bit of model judgment at exactly the right places.
Summary
Use the least autonomy that works, and keep hard rules in code. Next, a big step. Our agent has been forgetting everything. Let's give it a memory.
Key takeaways- Pick by the shape of the task
- Patterns combine
- Least autonomy that works