Agent Orchestration: Routines and Handoffs
Exploring orchestration patterns from the official OpenAI cookbook: routines as system prompt + tools, handoffs for transferring control between agents, and how this is implemented in Swarm.
Routines and Agents
A routine is a set of steps: a system prompt in natural language plus the tools needed to execute them. LLMs handle conditional branches inside routines robustly without a rigid state machine.
from openai import OpenAI
import json
client = OpenAI()
system_message = (
"You are a customer support agent for ACME Inc."
"Follow the following routine with the user:"
"1. Ask probing questions to understand the user's problem.\n"
"2. Propose a fix (make one up).\n"
"3. ONLY if not satisfied, offer a refund.\n"
"4. If accepted, search for the ID and then execute refund."
)
def look_up_item(search_query):
"""Use to find item ID."""
return "item_132612938"
def execute_refund(item_id, reason="not provided"):
print(f"Refunding item {item_id}, reason: {reason}")
return "success"
Automatic Function Schema Generation
import inspect
def function_to_schema(func) -> dict:
type_map = {str: "string", int: "integer", float: "number",
bool: "boolean", list: "array", dict: "object"}
sig = inspect.signature(func)
parameters = {}
for param in sig.parameters.values():
param_type = type_map.get(param.annotation, "string")
parameters[param.name] = {"type": param_type}
required = [p.name for p in sig.parameters.values()
if p.default is inspect.Parameter.empty]
return {
"type": "function",
"function": {
"name": func.__name__,
"description": (func.__doc__ or "").strip(),
"parameters": {"type": "object", "properties": parameters,
"required": required},
},
}
Executing Tool Calls and Handoffs
tools = [execute_refund, look_up_item]
tool_schemas = [function_to_schema(t) for t in tools]
tools_map = {t.__name__: t for t in tools}
def execute_tool_call(tool_call, tools_map):
name = tool_call.function.name
args = json.loads(tool_call.function.arguments)
return tools_map[name](**args)
When to Use Handoffs
A handoff is a function call that switches the active agent. Each agent gets its own system prompt and tool set. A triaging agent routes, specialized agents execute.
Implement a two-agent system: Triage Agent with a transfer_to_refund tool, Refund Agent with look_up_item and execute_refund tools. Verify that the conversation correctly transfers between agents.
Copy and adapt to your context. Text in angle brackets should be replaced.
tools = [look_up_item, execute_refund]
tool_schemas = [function_to_schema(t) for t in tools]
tools_map = {t.__name__: t for t in tools}
def run_full_turn(system_msg, messages):
while True:
response = client.chat.completions.create(
model="gpt-4o-mini",
messages=[{"role": "system", "content": system_msg}] + messages,
tools=tool_schemas,
)
msg = response.choices[0].message
messages.append(msg)
if not msg.tool_calls:
return msg
for tc in msg.tool_calls:
result = execute_tool_call(tc, tools_map)
messages.append({"role": "tool",
"tool_call_id": tc.id, "content": result})Transferring control to an agent without changing system_message — the agent continues with the old instructions. Not preserving tool_call_id in the response — the API will return a validation error.
function_to_schema via inspect.signature eliminates manual schema writing. OpenAI's Swarm library implements this pattern out of the box with support for context variables.
Systems with multiple specialized roles where routing logic is straightforward. Replacing complex if/else with explicit transitions using an LLM-managed flow.
Single agents with no need for control transfer. Tasks with strict deterministic logic where LLM routing adds unpredictability.