Multi-Agent Systems: Quill Gets a Research Team (smolagents, Module 10)
This is Module 10 of smolagents Mastery, a free 15-module course that takes you from your first code agent to a production-grade one. Start at Module 1 or browse the full syllabus.
The Quill you finished in Module 9 can do everything itself. Hand it a CSV and a question like “compare our Q3 churn to the SaaS industry average,” and it loads the data, writes pandas, draws a chart, and goes to the web — it owns WebSearchTool and VisitWebpageTool and calls them straight from its own code.
That self-sufficiency is exactly the problem. Every page Quill visits lands whole in its memory. The raw text of visit_webpage — 8,000 words of someone’s blog about churn benchmarks — gets stitched into the same context that holds Quill’s pandas code, its plan, and its own reasoning. The doc puts the complaint plainly: “why fill the memory of the code-generating agent with all the content of the webpages visited?” On a real question the context turns into a junk drawer. The model starts forgetting its citation rules and blurs the line between collecting facts and analyzing them.
This module splits the job. Quill stops being a one-person band and becomes a manager that delegates web research to a specialist teammate — and keeps its own head clear.
In this module
You’ll learn
- Decide when a second agent earns its keep — specialization and context isolation versus the real cost of coordination — and when one agent is still the right answer.
- Wire up a multi-agent system the current way in smolagents 1.26.0: a sub-agent gets a
nameand adescription, then rides inmanaged_agents=[...]— not the removedManagedAgent.- Explain how a manager actually calls a sub-agent under the hood — as a Python function for a
CodeAgentmanager, viaexecute_tool_callfor aToolCallingAgent— and why aToolCallingAgentis the right shape for a web worker.- Map Anthropic’s five agent patterns onto smolagents: orchestrator-workers is built in; routing, parallelization, and evaluator-optimizer are patterns you compose.
- Respect the hard constraint: a remote executor plus managed agents raises an exception — isolated multi-agent needs Approach 2 (Module 15).
You’ll build Quill becomes a team: a manager
CodeAgentthat delegates web research to aweb_researchersub-agent, then writes a cited report.Theory areas covered
T10 — Multi-agent systems — 7%·T2 — Anthropic patterns — part of the 10% theory canon(No “exam domain” — this course has no certification.)Prerequisites Module 2 (
CodeAgent, the ReAct loop), Module 3 (Tool,ToolCallingAgent,WebSearchTool/VisitWebpageTool), Module 4 (make_model), Module 5 (sandbox + the remote-executor constraint), Module 6 (memory/steps), Module 7 (planning_interval), Module 8 (QuillReport), Module 9 (web tools already on Quill). AnHF_TOKENconfigured in.env.
Where you are in the course
M1 ✅ M2 ✅ M3 ✅ M4 ✅ M5 ✅ M6 ✅ M7 ✅ M8 ✅ M9 ✅
👉 M10 ⬜ M11 ⬜ M12 ⬜ M13 ⬜ M14 ⬜ M15
Quill so far: one CodeAgent that plans, writes pandas, saves charts, and fetches the web itself. After this module: a manager CodeAgent over a web_researcher sub-agent — the orchestrator-workers pattern. The chain is linear and this module is load-bearing: Module 11 gives Quill an optional vision sub-agent (it reuses the team you build here), and Module 15 runs this whole team inside a sandbox.
Why a second agent? (and when one is still enough)
A second agent is not free. Before you reach for one, you need to know what it buys you and what it costs. There are three real reasons to add an agent:
- Specialization. Each agent gets one focused prompt and one focused toolset. A web worker’s prompt is about searching and reading pages; the analyst’s prompt is about pandas and charts. Neither carries the other’s clutter.
- Parallelism. Independent sub-tasks can run at once. (Mentioned here, not used — we cover it in “The patterns smolagents does NOT give you for free.”)
- Context isolation — the strongest reason. Define it once: context isolation means each agent sees only what it needs. The manager never has to digest the researcher’s raw pages; the researcher never sees the analyst’s pandas. That is the cure for the Module 9 junk drawer — the manager’s memory stays clean because the 8,000-word page lives and dies inside the sub-agent, which hands back only a short summary.
Against those three sits one counterweight: coordination cost. Every delegation is at least one extra LLM call — the sub-agent runs its own ReAct loop, up to its max_steps. That is latency, tokens, and a new failure mode (the sub-agent comes back empty-handed). In my experience a manager-plus-sub-agent run can triple the LLM calls of a single agent. You pay that only when isolation or specialization buys back more than the coordination costs.
This is a move up the agency spectrum (from Module 1). The top rung is the one where one agentic workflow can start another — if llm_trigger(): execute_agent(). You gain capability and you take on more autonomy, more risk, and more cost. Climb deliberately.
⚠️ Common misconception: “More agents = better results.”
No. Each agent you add multiplies tokens, latency, and points of failure. The reason to go multi-agent is not raw power — it is context isolation: a clean manager memory and a specialized role. You adopt it when the coordination cost is lower than the gain from an isolated context. smolagents itself keeps repeating the same discipline — regularize toward not using agentic behavior — and that applies to adding agents too. Default to one agent; promote to a team only when isolation pays.
How smolagents wires a team (the current way)
Here is the single biggest freshness trap in this whole module.
from smolagents import ManagedAgent is dead. ManagedAgent was deprecated in 1.8.0, dropped from the docs in 1.21.0, and is absent from agents.py in 1.26.0. Half the multi-agent tutorials you will find still import it. They will not run. If a snippet wraps your sub-agent in ManagedAgent(agent, name=..., description=...), close the tab.
The current mechanism is simpler. Any agent — a CodeAgent or a ToolCallingAgent — becomes callable by a manager the moment you give it two attributes: a name and a description. The library’s words: these are what make the agent “callable by its manager.” You then pass it to the manager via managed_agents=[...], an argument on MultiStepAgent that both agent types inherit. That is the entire API. Managed agents are not a special class; they are ordinary agents carrying a name and a description.
Quill’s research team is built in quill/team.py. The sub-agent factory is build_web_researcher, and it does exactly the canonical thing — a ToolCallingAgent, two web tools, the canonical name, a focused description, a step budget:
from smolagents import ToolCallingAgent, VisitWebpageTool, WebSearchTool
from .config import make_model
def build_web_researcher(model=None, *, max_steps=10, provide_run_summary=False):
return ToolCallingAgent(
tools=[WebSearchTool(), VisitWebpageTool()], # name="web_search" — exactly ONE search tool
model=model or make_model(role="researcher"), # M4 contract: never HfApiModel
name="web_researcher", # the two attributes that make it callable
description=WEB_RESEARCHER_DESCRIPTION, # by a manager via managed_agents=[...]
max_steps=max_steps, # the per-delegation cost ceiling
provide_run_summary=provide_run_summary,
)
A few details that matter and are easy to get wrong:
- The name is exactly
web_researcher— notweb_agent, notWebAgent. The manager calls it by this string, the tests pin it, and Module 11 extends this same team. Pick a name and freeze it. - The model always comes from
make_model(the Module 4 frozen contract). There is one place to swap the backend;team.pynever builds anInferenceClientModelby hand and never touchesHfApiModel. - The
descriptionis not decoration. For aCodeAgentmanager it becomes the docstring of the delegated function (see below), so it must read like a tool’s docstring — what it does, what to give it, what it returns. Quill’s reads: “Searches the web and visits pages to fetch missing external context… Give it ONE focused question as the task; it returns a short text summary with the source URLs it used. It does NOT see your dataset.” A vague description makes the manager mis-delegate.
How the manager actually calls the sub-agent (the internals)
The mechanism differs by manager type, and this is the part that earns the “200% theory” label.
When the manager is a CodeAgent (Quill’s case), smolagents exposes each managed agent inside the Python sandbox as a callable function. The signature is auto-generated from the system-prompt template (code_agent.yaml in smolagents 1.26.0):
def web_researcher(task: str, additional_args: dict[str, Any]) -> str:
"""<the sub-agent's description>
Args:
task: Long detailed description of the task.
additional_args: Dictionary of extra inputs to pass to the managed agent,
e.g. images, dataframes, or any other contextual data it may need.
"""
So Quill literally writes code to delegate — summary = web_researcher("What is the SaaS industry average churn?") — exactly the way it calls any tool. The run prints Here is the final answer from your managed agent 'web_researcher': .... That additional_args channel is how rich objects (images, DataFrames) flow down to a sub-agent — and it is precisely the hook Module 11 reuses to pass an image to a vision sub-agent.
When the manager is a ToolCallingAgent, the sub-agent is not a Python function — it is dispatched through execute_tool_call(tool_name, arguments) (the same method that runs “a tool or managed agent”), and the manager’s prompt lists the team as - {{ agent.name }}: {{ agent.description }}. Same idea, different surface: a CodeAgent calls the sub-agent in code; a ToolCallingAgent calls it as a tool call.
Why the worker is a ToolCallingAgent and the manager a CodeAgent
This is the Module 3 decision matrix, reused. A CodeAgent is a problem solver: it composes Python, which is ideal for planning and arithmetic — that is the analyst manager, Quill. A ToolCallingAgent is a dispatcher: it emits JSON tool calls validated against a schema, one at a time — a single timeline, which is exactly what stepping through the web looks like (search, read a page, read another, summarize). Web navigation does not need to compose Python; a validated JSON tool call is safer and entirely sufficient. (A ToolCallingAgent can fan out parallel calls via max_tool_threads — a ThreadPoolExecutor under the hood — but we do not use that here: the researcher is a single timeline.)
| Manager (Quill) | Worker (web_researcher) | |
|---|---|---|
| Agent class | CodeAgent | ToolCallingAgent |
| Role | Plan + analyze the dataset | Search the web + summarize |
| Action format | Python code | JSON tool calls (single timeline) |
| Toolset | load_dataset, profile_dataframe, save_chart, + web_researcher | WebSearchTool, VisitWebpageTool (+ auto final_answer) |
| Why this shape | Composes pandas; does arithmetic and planning | No need to write Python; one validated call at a time |
The headline structural change this module makes: the web tools leave the manager’s tools= list and move into the sub-agent. That is the context isolation, made literal — Quill can no longer touch the web directly; it can only delegate.
Orchestrator-workers: the canonical multi-agent demo
Quill’s team is one named pattern, and it helps to place it on the map. In Building Effective Agents (Anthropic, 2024-12-19), Anthropic catalogs five composable patterns for agentic systems. Here is the honest mapping onto smolagents — and which ones are actually library features versus things you build yourself:
| Anthropic pattern | In smolagents |
|---|---|
| Prompt chaining | Deterministic Python — “write all the code yourself” |
| Routing | The if llm_decision(): path_a() else: path_b() line on the agency spectrum — pattern, not API |
| Parallelization (sectioning / voting) | Composed by hand (a loop / several agent.run) — pattern, not API |
| Orchestrator-workers | managed_agents=[...] — built in. The canonical multi-agent demo. |
| Evaluator-optimizer | Composed (two agents, or a judge tool) — pattern, not API |
Only one of the five ships as a built-in: orchestrator-workers. Define it: a central LLM dynamically breaks down a task and delegates the pieces to worker LLMs. That is exactly Quill — the manager CodeAgent decomposes “is our churn high vs the industry?” into “find the industry benchmark” (delegated) and “compute our rate and chart it” (kept). The other four patterns are real and useful, but smolagents does not hand you a class for them; you compose them (next section).
This is not a toy pattern. Hugging Face’s own Open Deep Research — a reproduction built on smolagents — reported 55.15% on the GAIA benchmark, against a prior open-source state of the art of ~46% (Magentic-One), with the code-action format using roughly 30% fewer tokens than JSON tool-calling on the same tasks. Treat those as reported figures from the smolagents launch material (as of smolagents 1.26.0), not guarantees — but they are the existence proof that orchestrator-workers scales to hard, multi-step questions.
Here is Quill’s team after this module:
flowchart TD
U["User question + CSV<br/>(data/customers.csv)"] --> Q
subgraph TEAM["Quill's orchestrator-workers team"]
Q["Quill — manager CodeAgent<br/>plans · writes pandas · charts"]
Q -- "load_dataset · profile_dataframe · save_chart" --> DT["data tools"]
Q -->|"web_researcher(focused question)"| W["web_researcher<br/>ToolCallingAgent · max_steps=10"]
W -- "WebSearchTool · VisitWebpageTool" --> WEB["the web"]
W -- "short summary + source URLs" --> Q
end
Q --> R["cited QuillReport<br/>findings [n] → sources"]
The manager owns the data tools and save_chart; it reaches the web only by delegating to web_researcher, which returns a short summary plus URLs. The run ends in a QuillReport whose [n] markers resolve to its sources list — the same cited contract from Module 8, unchanged.
The patterns smolagents does NOT give you for free
Read the table above carefully, because the trap is real: people go hunting for a Parallelization class or an EvaluatorOptimizer agent. There isn’t one. Routing, parallelization, and evaluator-optimizer are patterns you compose, not library primitives. Knowing that saves you an afternoon looking for an import that does not exist.
Here is how you compose each, one line apiece:
- Routing — an
ifon an LLM decision: ask the model which path to take, then call that function. That is the routing rung of the agency spectrum; you write the branch. - Parallelization (sectioning / voting) — loop and launch several
agent.runcalls (or register severalmanaged_agents), then aggregate the results yourself — take the majority vote, or merge the sections. - Evaluator-optimizer — a second agent (or a judge tool) re-reads the first agent’s output and returns feedback in a loop you write by hand. (That judge is the heart of evaluation in Module 14.)
When you do build a team, smolagents gives you real orchestration knobs:
provide_run_summary(bool) — whenTrue, the manager sees the sub-agent’s full reasoning trace, not just its final answer. Off by default (cleaner manager memory).build_web_researcherexposes it as a keyword; the lab’s “Try it yourself” flips it.- Specialization — separate toolsets and separate memories per agent. That is the whole point of the team.
- Hierarchy depth — a sub-agent can have its own sub-agents, arbitrarily deep. Useful and dangerous; bound it.
planning_intervalon the manager (from Module 7) — periodic planning helps managers in particular, since their job is decomposition.
The constraint nobody warns you about: remote sandbox + multi-agent
This one bites people in production. In smolagents 1.26.0, a remote executor_type plus managed_agents raises an exception. It is not a warning you can ignore — it is a hard refusal at construction time. The guard lives in create_python_executor (agents.py):
def create_python_executor(self) -> PythonExecutor:
if self.executor_type not in {"local", "blaxel", "e2b", "modal", "docker"}:
raise ValueError(f"Unsupported executor type: {self.executor_type}")
if self.executor_type == "local":
return LocalPythonExecutor(...)
else:
if self.managed_agents:
raise Exception("Managed agents are not yet supported with remote code execution.")
...
Why does it refuse? This is Approach 1 (snippet-in-sandbox), from Module 5: only the Python snippets the agent generates go into the container; the model calls and tools stay local, and — crucially — your secrets (the HF_TOKEN) are never shipped into the sandbox. A sub-agent would need its credentials inside the box to authenticate its own LLM calls. It cannot get them. So the combination is impossible, and the library fails loud rather than silently breaking.
The consequence for Quill: this module stays on executor_type="local". Multi-agent works locally. To get isolated multi-agent — the whole agent system running inside a sandbox — you need Approach 2, where you build the sandbox by hand and run the entire team inside it. That is the capstone, Module 15. We will run this team inside a sandbox — Approach 2 — there.
| Approach 1 (snippet-in-sandbox) | Approach 2 (whole agent in sandbox) | |
|---|---|---|
| What runs in the box | Only the generated Python snippets | The entire agent system |
| Setup | One parameter (executor_type="docker") | Manual — you build the sandbox, pass secrets in |
| Multi-agent | No — raises the exception above | Yes |
| Where in the course | Module 5 (Quill stays here in M10) | Module 15 (capstone) |
Note the valid executor_type values as of smolagents 1.26.0: {"local", "e2b", "docker", "modal", "blaxel"}. "wasm" was removed — if a tutorial sets executor_type="wasm", it is stale.
Build it: Quill gets a research team
The goal: turn Quill into a manager CodeAgent over a web_researcher ToolCallingAgent that answers a question needing external context and returns a cited QuillReport. The full, tested code is in smolagents-course-labs/module-10; the offline tests pass with no token and no network.
Step 1 — the sub-agent (quill/team.py)
You saw build_web_researcher above. The constants it pins are worth quoting, because they encode the production reality:
WEB_RESEARCHER_NAME = "web_researcher" # canonical (06 §2) — frozen
WEB_RESEARCHER_MAX_STEPS = 10 # the per-delegation cost ceiling
WEB_RESEARCHER_DESCRIPTION = (
"Searches the web and visits pages to fetch missing external context (industry "
"benchmarks, market averages, public trends, definitions). Give it ONE focused "
"question as the task; it returns a short text summary with the source URLs it used. "
"It does NOT see your dataset — pass any numbers it needs inside the question. Use it "
"only when the answer is not in the local data."
)
max_steps=10 is a budget, not a guess. A sub-agent runs its own ReAct loop up to max_steps, and each step is an LLM call — so this is the ceiling on what one delegation can cost. Ten is enough to search, read a couple of pages, and summarize; lower it and a deep question comes back empty-handed.
Step 2 — make Quill the manager (quill/agent.py)
build_quill is extended by addition — a new keyword-only managed_agents argument, so every call site from Modules 2 through 9 still works. The web tools come out of the manager’s toolbox; the team goes in:
# The manager's toolbox: DATA tools only. The web tools moved to the sub-agent (context isolation).
tools = [load_dataset, profile_dataframe, save_chart()] # NO WebSearchTool / VisitWebpageTool
if managed_agents is _DEFAULT_TEAM:
resolved_managed_agents = [build_web_researcher(model=model)] # Quill's default team
else:
resolved_managed_agents = managed_agents # [] = solo; or your own list
agent = CodeAgent(
tools=tools,
model=model or make_model(role="analyst"),
executor_type=executor_type, # MUST stay "local" with managed_agents (see Step 4)
additional_authorized_imports=authorized_imports,
final_answer_checks=resolved_checks, # still the M8 cited-report contract
managed_agents=resolved_managed_agents, # the team — NEVER ManagedAgent
max_steps=8,
)
A _DEFAULT_TEAM sentinel keeps three intents distinct: omit managed_agents and you get Quill’s default team (just the web_researcher); pass [] for a solo manager (the pre-M10 shape, minus the web tools); pass your own list to customize. When the team is built by default and you injected a model (a fake model in tests), the same model powers the sub-agent — so one fake model drives the whole team offline.
The executor stays local on purpose, with a comment in the code spelling out why (# M15: Approach 2 runs this team inside a sandbox). A QUILL_EXECUTOR=docker plus this default team would raise the documented exception at construction — which is precisely the constraint from the previous section, enforced.
Step 3 — (optional) see the sub-agent’s reasoning
Flip one knob to let the manager audit how the researcher reached its answer:
researcher = build_web_researcher(provide_run_summary=True)
agent = build_quill(managed_agents=[researcher])
Off (the default), the manager gets only the summary string — cleaner memory. On, it sees the full reasoning trace — more context, more tokens. That trade-off is the first “Try it yourself.”
Step 4 — run it on a real question
uv run python -m quill "Is our Q3 churn (in data/customers.csv) high vs the SaaS industry average?" --data data/customers.csv
Expected output (the exact trajectory varies — LLMs are non-deterministic):
[Quill] Backend: hf | Model: Qwen/Qwen2.5-Coder-32B-Instruct
...
─ Executing: summary = web_researcher("What is the SaaS industry average annual churn rate?")
Here is the final answer from your managed agent 'web_researcher':
SaaS median annual churn is ~5% per year [https://...].
... # ← the manager writes pandas on data/customers.csv, draws a chart, save_chart
===== REPORT =====
# Is our Q3 churn high vs the SaaS industry average?
## Findings
- Our churn is above the ~5% SaaS median [1].
## Charts
- `outputs/churn_vs_industry.png`
## Sources
[1] [SaaS Churn Benchmarks](https://...)
[Quill] Run cost — input tokens: ... | output tokens: ... | total: ...
What is guaranteed, regardless of the model’s exact path: the manager delegates to web_researcher, and the run ends in a validated, cited QuillReport. Read the trajectory the supported way — agent.replay() and agent.memory.steps (never the removed agent.logs) — with the step-by-step printer:
uv run python -m quill.agent data/customers.csv "Is our churn high vs the SaaS average?"
Step 5 — the tests
uv run pytest module-10/tests/ # offline: no token, no network, no LLM
QUILL_LIVE_TESTS=1 uv run pytest module-10/tests/ # also the real team run (needs HF_TOKEN)
The offline suite builds both the manager (a fake-model CodeAgent) and the web_researcher (a fake tool-call model that emits a final_answer tool call), so the whole team loop runs deterministically with zero LLM calls. It proves the wiring end-to-end:
def test_no_managed_agent_class_in_smolagents_1_26():
import smolagents
assert not hasattr(smolagents, "ManagedAgent") # the headline freshness ban
def test_build_quill_is_a_manager_codeagent_without_web_tools(fake_model):
agent = build_quill(model=fake_model(["final_answer('ok')"]))
names = set(agent.tools)
assert {"load_dataset", "profile_dataframe", "save_chart"} <= names
assert "web_search" not in names # moved to the sub-agent — context isolation
assert "visit_webpage" not in names
The constraint is tested too — and it is a pure offline test, because the guard fires at construction before any container is built:
def test_remote_executor_plus_managed_agents_raises_the_documented_exception(monkeypatch, fake_model):
monkeypatch.setenv("QUILL_EXECUTOR", "docker")
with pytest.raises(Exception, match="Managed agents are not yet supported"):
build_quill(model=fake_model(["final_answer('ok')"]))
The full offline run is green: 160 passed. Every live-marked test (including the real team run that makes 5–15 LLM calls) is skipped unless QUILL_LIVE_TESTS=1 and HF_TOKEN is set — so the suite stays free and offline by default.
Try it yourself
- See the reasoning. Set
provide_run_summary=Trueon theweb_researcherand re-run. Observe what the manager now sees (the sub-agent’s steps, not just its summary). Decide whether the extra context is worth the memory it costs. - Force the empty-handed failure. Lower
max_stepsto 3 (build_web_researcher(max_steps=3)) and ask a question that needs deeper digging. Watch the researcher run out of steps and how the manager copes with a thin or empty summary.
In production
The token bill is the first thing that changes. One multi-agent run is the manager’s loop plus N sub-agent ReAct loops — easily triple the calls of a single agent. The Hugging Face free tier ($0.10/month, as of smolagents 1.26.0) becomes a real wall fast; that is exactly why the
web_researcheris capped atmax_steps=10. Set the ceiling deliberately, not by default.Debugging a team blind is miserable. A run goes wrong somewhere between the manager and a sub-agent and you have no idea which. Telemetry helps — a trace shows the manager’s span with the sub-agent’s run as a nested span — and that is Module 14.
The production rules I’d apply: cap the team size, bound the hierarchy depth (a sub-agent of a sub-agent of a sub-agent is a debugging nightmare), and cap a worker’s retries at 2 — beyond that you’re burning tokens on a broken loop. And remember the sandbox reality: an isolated multi-agent system needs Approach 2 (Module 15); until then
local“is not a security sandbox” (Module 5). Open Deep Research — built on smolagents — is the proof orchestrator-workers holds up at GAIA scale, so the pattern is sound; the cost discipline is on you.
Concept check
Why this matters. Multi-agent (T10) is where you decide whether a second agent earns its keep, wire one up the current way, and know the one constraint that will otherwise crash your production run. The skills: weigh context isolation against coordination cost; use name + description + managed_agents (not ManagedAgent); pick the right agent class for each role; place a need against Anthropic’s five patterns (T2); and recognize the remote-executor refusal.
Answer all five, then check yourself against the grouped answers below.
1. A teammate’s task is a simple, linear data cleanup: load a CSV, drop nulls, write one summary number. They want to split it into a “loader” sub-agent and a “summarizer” sub-agent “for modularity.” What is the right call?
- A. Split it — more agents always generalize better.
- B. Keep one agent; the split triples LLM calls with no isolation or specialization gain.
- C. Split it, but only if you set
provide_run_summary=True. - D. Split it into three agents so each step is independent.
2. You find a tutorial that starts a sub-agent with from smolagents import ManagedAgent and ManagedAgent(agent, name="x", description="y"). You run it against smolagents 1.26.0. What happens, and what is the fix?
- A. It works fine;
ManagedAgentis the canonical wrapper. - B. It raises an
ImportError; the fix is to give the agent aname+descriptionand pass it viamanaged_agents=[...]. - C. It works but is slower; wrap it in
ToolCallingAgentinstead. - D. It raises a
ValueErrorabout executors; switch toexecutor_type="wasm".
3. Your worker needs to step through the web one page at a time — search, read, read again, summarize — and return a short answer. Which agent class, and why is the manager a different class?
- A. Worker =
CodeAgent(it can write scrapers); manager =ToolCallingAgent. - B. Both should be
CodeAgentfor consistency. - C. Worker =
ToolCallingAgent(a single-timeline dispatcher of validated tool calls); manager =CodeAgent(a problem solver that composes pandas and does arithmetic). - D. Worker =
ToolCallingAgent; manager must also beToolCallingAgentto call it.
4. A stakeholder wants Quill to run a question three times in parallel and keep the consensus answer (a voting setup). You go looking for the smolagents class that does this. What do you find, and what do you do?
- A. The
Parallelizationagent class — instantiate it withn=3. - B. There is no such primitive; parallelization (sectioning/voting) is a pattern you compose — loop several
agent.runcalls and aggregate the results yourself. - C. Set
max_tool_threads=3on the manager and it votes automatically. - D. Use
managed_agents=[a, b, c]and smolagents votes across them.
5. A developer ships Quill with QUILL_EXECUTOR=docker (a remote executor) and the default team (managed_agents=[web_researcher]). The process raises Exception("Managed agents are not yet supported with remote code execution.") at startup. Why, and what are the options?
- A. A Docker bug; retry and it will pass.
- B. Approach 1 ships no secrets into the sandbox, so a sub-agent can’t authenticate its LLM — keep
executor_type="local", or use Approach 2 (whole team in the sandbox) in Module 15. - C. The
web_researchername is wrong; rename it. - D.
managed_agentsmust be a dict, not a list.
Answers
- B. A linear three-step job has no independent sub-tasks and no raw context to isolate, so the only thing a split adds is coordination cost — roughly triple the LLM calls. The reason to go multi-agent is context isolation or specialization; neither applies here. Default to one agent. (A and D worship “more agents = better”; C bolts a knob onto a decision that should be “don’t split.”)
- B.
ManagedAgentis absent fromagents.pyin 1.26.0 (deprecated 1.8.0, dropped from docs 1.21.0), so the import raisesImportError. The current mechanism isname+description+managed_agents=[...]. (A is the stale-tutorial trap; D throws in a bannedexecutor_type="wasm", removed in 1.26.0.) - C. Web navigation is a single timeline of validated tool calls — the dispatcher shape of a
ToolCallingAgent; it does not need to compose Python. The manager plans and does arithmetic on the data, which is the problem-solver shape of aCodeAgent. A manager of either class can call a managed sub-agent — they do not need to match (D is wrong). - B. Only orchestrator-workers is built into smolagents. Parallelization, routing, and evaluator-optimizer are patterns you compose. You loop
agent.run(or fan out) and aggregate the votes yourself. (max_tool_threadsparallelizes oneToolCallingAgent’s tool calls — not whole runs, and it does not vote.) - B. A remote executor +
managed_agentsis refused increate_python_executor: Approach 1 keeps secrets out of the sandbox, so the sub-agent cannot authenticate its own model from inside. Staylocal, or use Approach 2 (the whole team inside the sandbox) in Module 15. The error fires at construction, before any container starts.
Common pitfalls
- Reaching for
ManagedAgent. It is gone in 1.26.0. A sub-agent is just an ordinary agent with anameand adescription, registered viamanaged_agents=[...]. This is the single most common stale-tutorial error in multi-agent smolagents. - Believing “more agents = better results.” Each agent multiplies tokens, latency, and failure modes. The real reason is context isolation; the coordination cost is real. Justify the team before you build it.
- Putting a remote
executor_typewithmanaged_agentsand expecting it to work. It raises immediately. Isolated multi-agent needs Approach 2 (Module 15). And don’t confuse “the sub-agent is callable like a tool” with “the sub-agent is aManagedAgent” — it is just a normal agent with a name and a description.
Key takeaways
- The real reason to go multi-agent is context isolation — a clean manager memory and a specialized role — not raw power. Each agent you add costs tokens, latency, and failure modes.
- The current mechanism is
name+description+managed_agents=[...].ManagedAgentis removed in smolagents 1.26.0; any tutorial importing it is dead. - A
CodeAgentmanager calls a sub-agent as an auto-generated Python functiondef {name}(task, additional_args); aToolCallingAgentmanager dispatches it viaexecute_tool_call. - The worker is a
ToolCallingAgent(single-timeline dispatcher) and the manager aCodeAgent(problem solver) — the Module 3 decision matrix, applied per role. - Orchestrator-workers is the only one of Anthropic’s five patterns that smolagents gives you built in; routing, parallelization, and evaluator-optimizer are patterns you compose by hand.
- A remote
executor_type+managed_agentsraisesException("Managed agents are not yet supported with remote code execution.")— isolated multi-agent needs Approach 2 (Module 15). Quill stayslocal. - Multi-agent has a real cost. Cap team size, bound hierarchy depth, cap worker retries — and justify the team before you adopt it.
What’s next
Your team can read the web — but can it see? Module 11 gives Quill eyes: it re-reads its own charts with a vision-language model and gets an optional vision-browser sub-agent that reuses the exact team you just built (the additional_args channel is how the image flows down).
Want the full smolagents concept-check bank and the next module in your inbox? Subscribe here — it’s free, like everything in this series.
Links: Module 10 lab code · previous: Module 9 — Tool Interop · next: Module 11 — Vision and Multimodal · course index
References
- smolagents — Orchestrate a multi-agent system: https://huggingface.co/docs/smolagents/examples/multiagents
- smolagents — Guided tour (the
managed_agentsexample): https://huggingface.co/docs/smolagents/guided_tour - smolagents — Secure code execution (Approach 1 vs 2; the remote + multi-agent constraint): https://huggingface.co/docs/smolagents/tutorials/secure_code_execution
- smolagents — Agents API reference (
managed_agents,execute_tool_call,provide_run_summary): https://huggingface.co/docs/smolagents/reference/agents - Anthropic — Building Effective Agents (the five patterns): https://www.anthropic.com/engineering/building-effective-agents
- Hugging Face blog — Open Deep Research / beating GAIA with a smolagents multi-agent system: https://huggingface.co/blog/open-deep-research