Build a Multi-Agent AI System
GreenDesk's real customer onboarding involves genuinely distinct, real tasks — provisioning an account, generating personalized welcome content, scheduling a kickoff call. This part builds a real, complete multi-agent system, rather than one, overloaded single agent trying to do everything.
Why one, real, single agent genuinely struggles here
A single agent with 12+ tools spanning account provisioning,
content generation, and calendar scheduling: the model has to
correctly choose among a genuinely large, varied toolkit at every
real step — directly the tool-count problem part 5 flaggedSplitting into real, specialized sub-agents — each with a genuinely narrow, focused toolkit — is the concrete, practical resolution to that exact concern, applied at system scale rather than within a single agent.
The real, complete architecture
Orchestrator Agent
├── Account Setup Agent (provisions the real GreenDesk workspace)
├── Welcome Content Agent (generates real, personalized onboarding
│ content — directly reusing the AI Projects series' own
│ content generator)
└── Scheduling Agent (books a real kickoff call)The real orchestrator: deciding which sub-agent handles what
def run_orchestrator(new_customer: dict) -> str:
request = f"Onboard this new real GreenDesk customer: {new_customer}"
messages = [{"role": "user", "content": request}]
orchestrator_tools = [
{"name": "account_setup_agent", "description": "Handles real GreenDesk account provisioning", "input_schema": {...}},
{"name": "welcome_content_agent", "description": "Generates real, personalized welcome content", "input_schema": {...}},
{"name": "scheduling_agent", "description": "Books a real kickoff call", "input_schema": {...}},
]
for _ in range(6):
response = client.messages.create(
model="claude-sonnet-5", max_tokens=2048,
system="Coordinate GreenDesk's onboarding by delegating to the correct real sub-agent for each part of the task.",
tools=orchestrator_tools, messages=messages,
)
messages.append({"role": "assistant", "content": response.content})
if response.stop_reason != "tool_use":
return response.content[0].text
tool_call = next(b for b in response.content if b.type == "tool_use")
# each "tool" here actually RUNS an entire, real sub-agent
sub_agent_result = sub_agents[tool_call.name](**tool_call.input)
messages.append({
"role": "user",
"content": [{"type": "tool_result", "tool_use_id": tool_call.id, "content": str(sub_agent_result)}],
})
return "Onboarding needs human review — exceeded expected coordination steps."Notice the real, structural pattern: each of the orchestrator's "tools" isn't a simple function like part 6's check_inventory — it's an entire, real, independent agent loop (part 1's full mechanism), called AS a tool. This is genuinely the core architectural idea behind multi-agent systems: an agent can itself be wrapped and used as a tool by a higher-level, coordinating agent.
The real, narrowly-scoped Welcome Content Sub-Agent
def welcome_content_agent(customer_name: str, company: str, plan: str) -> str:
# directly reuses the AI Projects series' own on-brand generation
return generate_on_brand_content(
topic=f"Welcome email for {customer_name} at {company}, {plan} plan",
funnel_stage="onboarding",
brand="greendesk",
)This sub-agent is genuinely narrow — it has exactly one real job, directly reusing the AI Projects series' own content generator rather than reimplementing content generation from scratch. Real multi-agent systems benefit enormously from reusing existing, well-tested single-purpose tools this way.
The real Account Setup Sub-Agent, with its own scoped tools
account_setup_tools = [
{"name": "create_workspace", "description": "Create a real GreenDesk workspace", "input_schema": {...}},
{"name": "assign_default_permissions", "description": "Set real, default role permissions", "input_schema": {...}},
]
def account_setup_agent(customer_id: str, team_size: int) -> str:
# a real, complete, independent agent loop — genuinely its own
# part 1 loop, scoped to ONLY account-provisioning toolsEach real sub-agent has its OWN, narrow toolkit — the Account Setup Agent structurally cannot call scheduling tools, and vice versa, directly extending part 10's principle that safety and correct scope belong in code structure, not just prompt instructions.
Why this real architecture is genuinely more reliable than one large agent
Real, narrow scope per sub-agent: dramatically reduces the real
tool-selection confusion part 5 flagged for oversized toolkits
Real, independent testability: each sub-agent (per part 19 of the
LLM & Advanced AI series' own evaluation techniques) can be tested
and evaluated in genuine isolation
Real, clear failure attribution: if onboarding fails, the orchestrator's
real trace shows exactly WHICH sub-agent's step failedFAQ
Does calling a sub-agent as a "tool" cost more than a single agent would? Yes, meaningfully — each sub-agent invocation is its own full agent loop, potentially several model calls deep, not one call. The orchestrator pattern trades that added cost for the reliability and scoping benefits described above; it's the right trade for a task complex enough to need it, and the wrong one for a task a single agent handles fine.
What happens if one sub-agent fails partway through onboarding? This implementation's orchestrator sees the failure as a tool result like any other and reasons about what to do next — it doesn't automatically roll back a sub-agent that already partially succeeded (a workspace already created, say). A production version needs an explicit compensation or rollback strategy per sub-agent, which this part doesn't build.
Could the sub-agents run in parallel instead of one at a time? For genuinely independent steps (welcome content generation doesn't depend on account setup finishing first), yes — this implementation's orchestrator loop calls one tool per iteration sequentially for clarity, but a production version could dispatch independent sub-agent calls concurrently and wait for all of them before proceeding.
Next: multi-agent communication patterns — the real, different ways agents can be architected to work together beyond this part's orchestrator model.