cd /news/ai-agents/build-a-multi-agent-system-with-open… · home topics ai-agents article
[ARTICLE · art-54585] src=sourcefeed.dev ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Build a Multi-Agent System with OpenAI's Swarm Framework

OpenAI's experimental Swarm framework enables developers to build a multi-agent customer support system that routes requests between specialist agents. The system uses a topic guardrail to filter irrelevant messages before a triage agent hands off billing or technical issues to dedicated agents. Swarm, which is not production-ready, demonstrates agent handoffs through function calls and runs locally with Python 3.10+.

read7 min views1 publishedJul 10, 2026
Build a Multi-Agent System with OpenAI's Swarm Framework
Image: Sourcefeed (auto-discovered)

Route customer support requests between specialist agents using OpenAI's experimental Swarm framework, with a manual topic guardrail in front of it.

Mariana Souza

What you'll build #

A small customer support system: a triage agent reads an incoming message, decides whether it's a billing or technical issue, and hands off to the right specialist agent. A lightweight classifier agent acts as a topic guardrail, blocking anything unrelated to support before the triage agent even sees it. You'll run all of this locally with OpenAI's Swarm framework and confirm the handoff actually happened by checking which agent produced the final reply.

Prerequisites #

  • Python 3.10 or newer (Swarm relies on typing features that don't work on older versions)
  • An OpenAI API key with active billing (platform.openai.com/api-keys)
  • Basic comfort with Python functions and the OpenAI chat completions message format ( {"role": ..., "content": ...}

) pip

,git

, and a terminal

A note on the framework: Swarm is OpenAI's own experimental, educational multi-agent framework (openai/swarm

on GitHub). It's explicitly not meant for production, there's no built-in tracing dashboard, no persistent sessions, no guardrail primitives. It's a thin, readable layer over the standard openai

client that demonstrates one core idea well: an agent can "hand off" a conversation to another agent by returning that agent from a function call. That's exactly what we need here, and it's small enough that you can read the entire source in twenty minutes if you want to know what's happening under the hood.

Important: Swarm is not published on PyPI. There's an unrelated package called swarm

on PyPI (a distributed job queue tool), and pip install swarm

will install that instead and quietly break everything downstream. Install straight from GitHub.

1. Set up the project #

mkdir agent-handoffs && cd agent-handoffs
python3 -m venv .venv
source .venv/bin/activate   # Windows: .venv\Scripts\activate
pip install git+https://github.com/openai/swarm.git

Set your API key as an environment variable rather than hardcoding it:

export OPENAI_API_KEY="sk-..."          # macOS/Linux

Swarm doesn't take the key directly, it builds an openai.OpenAI()

client internally, which reads OPENAI_API_KEY

from the environment automatically.

Create support_system.py

. Everything below goes in that one file, in order.

2. Build the specialist agents #

Start with two agents that each know how to do one job and nothing else. In Swarm, Agent

is a simple object with a name, a model, an instructions string, and a list of callable functions

the model can invoke as tools.

from swarm import Swarm, Agent

client = Swarm()

billing_agent = Agent(
    name="Billing Agent",
    instructions=(
        "You help customers with billing issues: invoices, charges, and "
        "subscription changes. Be concise and specific."
    ),
)

technical_agent = Agent(
    name="Technical Support Agent",
    instructions=(
        "You help customers troubleshoot technical problems with the product. "
        "Ask clarifying questions if you need more detail."
    ),
)

3. Build the topic guardrail #

Swarm has no built-in guardrail concept, so we build one the plain way: a cheap classifier agent that runs before triage and decides whether the message is worth routing at all. It replies with JSON, which we parse ourselves.

import json

topic_guardrail_agent = Agent(
    name="Topic Guardrail",
    model="gpt-4o-mini",
    instructions=(
        "Decide whether the user's message is a customer support question "
        "about billing or technical issues. Jokes, homework, and general "
        "chit-chat are not support related. Reply with ONLY raw JSON, no "
        'markdown fences, in this exact shape: {"is_support_related": true, '
        '"reasoning": "short reason"}'
    ),
)

def check_support_topic(user_message: str) -> bool:
    response = client.run(
        agent=topic_guardrail_agent,
        messages=[{"role": "user", "content": user_message}],
    )
    raw = response.messages[-1]["content"].strip()
    raw = raw.removeprefix("``` json").removeprefix("```").removesuffix("```").strip()
    try:
        parsed = json.loads(raw)
        return bool(parsed.get("is_support_related", False))
    except json.JSONDecodeError:
        return False

gpt-4o-mini

is a reasonable default here: it's cheap and fast, and a yes/no classification doesn't need a frontier model. The .removeprefix()

/.removesuffix()

cleanup exists because small models occasionally wrap JSON in a markdown code fence even when told not to, don't skip it, it's the most common way this guardrail silently breaks.

4. Build the triage agent with handoffs #

In Swarm, a handoff is nothing special, it's just a Python function that returns an Agent

instance instead of a string. When the model calls that function, Swarm swaps the active agent and keeps running the conversation against it. Define the specialists first (already done above), then the transfer functions, then the triage agent that uses them.

def transfer_to_billing():
    """Call this when the customer's issue is about billing, invoices, or charges."""
    return billing_agent

def transfer_to_technical():
    """Call this when the customer has a technical problem with the product."""
    return technical_agent

triage_agent = Agent(
    name="Triage Agent",
    instructions=(
        "Determine whether the user needs billing help or technical support, "
        "then call the matching transfer function. Don't try to solve the "
        "problem yourself."
    ),
    functions=[transfer_to_billing, transfer_to_technical],
)

The docstring on each transfer function isn't decoration, Swarm sends it to the model as the function's description, same as any other tool. Vague docstrings are the number one reason triage picks the wrong specialist or doesn't hand off at all.

5. Run it #

def handle_message(user_message: str):
    if not check_support_topic(user_message):
        print("Blocked: this doesn't look like a support question.")
        return

    response = client.run(
        agent=triage_agent,
        messages=[{"role": "user", "content": user_message}],
    )
    print(f"Handled by: {response.agent.name}")
    print(response.messages[-1]["content"])

if __name__ == "__main__":
    handle_message("My last invoice charged me twice, can you fix it?")

Run it:

python support_system.py

Verify it works #

You should see something like:

Handled by: Billing Agent
I'm sorry about the duplicate charge. I can start a refund for the extra charge...

response.agent.name

confirms the handoff actually happened, triage routed to billing, not itself. Swap the call at the bottom to handle_message("my app crashes when I upload a file")

and confirm it routes to the Technical Support Agent instead. Then try handle_message("write me a poem about the ocean")

, it should print the guardrail block message and never touch the triage agent at all.

If you want to see it handle a few cases in one run, loop over a list of test messages instead of calling handle_message

once, it's cheap since the guardrail model is small.

Troubleshooting #

or import errors that don't match anything in this tutorial: you likely ranModuleNotFoundError: No module named 'swarm'

pip install swarm

, which grabs an unrelated PyPI package. Uninstall it (pip uninstall swarm

) and install from GitHub as shown above.:AuthenticationError: Incorrect API key provided

OPENAI_API_KEY

isn't set in the shell that's running the script. Confirm withecho $OPENAI_API_KEY

(macOS/Linux) before running, env vars set in one terminal tab don't carry to another.: the guardrail model wrapped its answer in a code fence or added a stray sentence before the JSON. Tighten the instructions further ("reply with nothing but the JSON object") or, if it keeps happening, printjson.JSONDecodeError

fromcheck_support_topic

raw

before parsing so you can see exactly what came back.Triage agent answers the question directly instead of transferring: this usually means the transfer function docstrings are too vague or the triage instructions don't clearly forbid answering directly. Make both more explicit, and double checkfunctions=[...]

on the triageAgent

actually lists both transfer functions.

Next steps #

Swarm is deliberately minimal, there's no persistent session across separate script runs, no built-in tracing, no output guardrails. If you outgrow it, the next stop is OpenAI's Assistants API (via the standard openai

package), which adds persistent threads, built-in file retrieval, and hosted tool execution at the cost of more moving parts to configure. For now, try adding a third specialist agent (say, a "refunds" agent) and a third transfer function, or add a second guardrail that checks the specialist's reply before it's printed, useful if you're worried about a specialist agent giving out information it shouldn't. The openai/swarm

repo's examples/

directory has a few more handoff patterns worth reading, including one with shared context_variables

passed between agents.

Mariana Souza· Senior Editor

Mariana covers the fast-moving world of machine learning and generative AI, with a particular focus on how these technologies are reshaping development workflows. When she isn't stress-testing the latest foundation models, she's usually at a local hackathon.

Discussion 0 #

No comments yet

Be the first to weigh in.

── more in #ai-agents 4 stories · sorted by recency
oncue.thelettertwo.com · · #ai-agents
OnCue
── more on @openai 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/build-a-multi-agent-…] indexed:0 read:7min 2026-07-10 ·