What Are Code Agents? From LLM Calls to smolagents (smolagents, Module 1)

Module 1 of 15 12 min read Lab code ↗

This is Module 1 of smolagents Mastery, a free 15-module course that takes you from your first code agent to a production-grade one. Browse the full syllabus.

The word “agent” has stopped meaning anything

Three scripts walk into a standup. One chains a couple of prompts and stuffs the second model’s output into the first. One wraps a single tool call in a for loop. One actually decides, on its own, what to do next and keeps going until it’s done. All three get called “an agent.” None of the engineers in the room agree on which is which.

That confusion is expensive when you’re the one who has to build the thing. You open a tutorial and it imports HfApiModel. You open another and it wraps sub-agents in a ManagedAgent. A third pins its sandbox to executor_type="wasm". None of those exist anymore — they were renamed or deleted — but the blog posts never got the memo, and now you’re debugging a stack trace that has nothing to do with your problem. You have six frameworks to pick from, two opposing philosophies (your agent emits JSON, or your agent emits code), and a real risk of writing code that’s stale in three months.

This course fixes both problems at once. It’s the only free, code-first, end-to-end course on smolagents, built around one real project — Quill, a code-first data analyst — and every API in it is verified against the smolagents source at version 1.26.0. By the end of this module you’ll be able to place any system on the spectrum of agency, you’ll understand why a smolagents agent writes Python instead of JSON, and you’ll have run your first CodeAgent.

In this module

You’ll learn how to:

  1. Place any system on smolagents’ six-level spectrum of agency — from a simple LLM call to a full code agent — and tell a workflow from an agent.
  2. Explain the code-as-action paradigm: why a CodeAgent writes Python actions instead of JSON, and the four advantages this buys.
  3. Trace the theory canon behind smolagents: the CodeAct paper, ReAct, and Anthropic’s “Building Effective Agents” workflow-vs-agent distinction.
  4. Position smolagents among LangGraph, CrewAI, PydanticAI, and the OpenAI Agents SDK — and decide when its minimalist, Hub-native, code-first design is the right tool.

You’ll build: your dev environment plus your very first CodeAgent — an empty-toolset agent that writes and runs Python to solve a calculation. No Quill yet: that starts in Module 2, on purpose.

Theory areas covered:

  • T1 — ReAct loop & agent classes — 13% (foundations only; the loop itself is built in Module 2).
  • T2 — Code-as-action & the theory canon — 10%.

T1 is intentionally partial here. This module sets up the agency spectrum and the agent classes; the mechanics of the ReAct loop — what a step is, how agent.run() drives it, how final_answer ends it — are the whole subject of Module 2. I’ll flag those boundaries as we go.

Prerequisites: Python 3.11 or 3.12, comfort on the command line and with an LLM API. No agent experience required. You’ll create a Hugging Face account and an HF_TOKEN during the lab.

The series, and the project you’ll build

The promise of this course is simple: build a real code-first agent, module by module — and learn every moving part of smolagents, from the ReAct loop to secure sandboxing, multi-agent teams, and production telemetry.

The thing you build is Quill:

Quill is a code-first data analyst: hand it a dataset and a question, and it writes and runs Python in a sandbox to clean, analyze and visualize the data, browses the web for missing context, cross-checks its findings, and returns a cited report with charts.

That’s the finished system. Here’s its target architecture — annotated with the module where each piece arrives. You build this over 15 modules. Don’t worry about understanding every box yet; this is the map, not the territory.

flowchart TB
    user([user question + dataset CSV]) --> ui["GradioUI / Space<br/>(M13)"]

    subgraph sandbox["SANDBOX — Docker / E2B (M5, M15)"]
        quill["Quill = manager CodeAgent (M2 → M10)<br/>planning_interval (M7)<br/>QuillReport via final_answer (M8)"]
        quill --> datatools["data tools (M3)<br/>load_dataset · profile_dataframe · save_chart"]
        quill --> retr["RetrieverTool (M12)"]
        quill --> mcp["MCP tools (M9)"]
        quill --> team["managed_agents (M10)<br/>web_researcher · vision_browser (M11)"]
    end

    ui --> quill
    quill --> report(["QuillReport (M8)<br/>findings · charts · cited sources · caveats"])

    model["make_model() (M4)"] -.-> quill
    mem["memory + step_callbacks (M6)"] -.-> quill
    vis["vision: re-reads its charts (M11)"] -.-> quill
    tel["telemetry: OpenTelemetry (M14)"] -.-> quill

Each module ships one increment of Quill plus the theory behind it, and each one shows the theory areas it covers up top. Here’s the full road map. From now on, every module marks where you are with (done), 👉 (you’re here), and (ahead):

  • 👉 M1 — What Are Code Agents? Setup + your first CodeAgent that computes.
  • M2 — Your First CodeAgent: The ReAct Loop, Step by Step. Quill v0 answers a question about a CSV.
  • M3 — Tools: Giving Quill New Powers. Custom data tools plus web search.
  • M4 — Models and Providers. make_model() — one place to swap the model.
  • M5 — Running Untrusted Code Safely: Sandboxing. Quill runs in Docker/E2B.
  • M6 — Memory, State, and Inspecting Runs. Multi-turn Quill, callbacks, replay.
  • M7 — Planning and Building Good Agents. planning_interval and sharp prompts.
  • M8 — Reliable Agents: Structured Output, Validation, Errors. A validated QuillReport.
  • M9 — Tool Interop: MCP, the Hub, and Other Ecosystems. Quill talks to an MCP server.
  • M10 — Multi-Agent Systems: Quill Gets a Research Team. A managed web researcher.
  • M11 — Vision and Multimodal. Quill re-reads its own charts.
  • M12 — Agentic RAG: Grounding Quill in a Knowledge Base. A RetrieverTool over docs.
  • M13 — Deploying Quill: Gradio UI, the Hub, and the CLI. A real UI and a Space.
  • M14 — Observability and Evaluation. Telemetry plus an eval harness.
  • M15 — Capstone: Ship Quill, a Production-Grade Code Agent. Assemble everything.

One honest note before you spend a token. You’ll start for free with a Hugging Face token, but as of smolagents 1.26.0 the free HF Inference credit allowance is tiny and subject to change. A multi-step CodeAgent burns through it faster than you’d think. Module 4 documents how to swap to a free provider key (think Groq or Cerebras) or a local model. For Module 1 — one short calculation — the free tier is plenty.

The spectrum of agency: from an LLM call to a code agent

Start with the definition smolagents itself uses, because it cuts through the standup argument cleanly:

An agent is a program where LLM outputs control the workflow.

Read that again, because the whole module hangs on it. The question is never “is there an LLM in here?” It’s “who owns the control flow — your code, or the model?” If your code decides what happens next and the model just fills in a blank, that’s a workflow. If the model’s output decides what happens next, you’ve handed it the steering wheel, and that’s an agent.

And crucially, agency is not binary. It’s not a 1 or a 0; it’s a dial. smolagents lays this out as a spectrum with six levels, ordered by how much of the control flow the LLM’s output owns. This is the table to internalize:

LevelNameWhat the LLM output controlsCode shape
☆☆☆Simple processorThe LLM output has no effect on program flowprocess_llm_output(llm_response)
★☆☆RouterThe output decides an if/else switchif llm_decision(): path_a() else: path_b()
★★☆Tool callThe output picks which function to run, with which argsrun_function(llm_chosen_tool, llm_chosen_args)
★★☆Multi-step AgentThe output controls iteration and continuationwhile llm_should_continue(): execute_next_step()
★★★Multi-AgentOne agentic workflow starts anotherif llm_trigger(): execute_agent()
★★★Code AgentsThe LLM acts in code, can define its own tools, can launch other agentsdef custom_tool(args): ...

Walk down it once. A simple processor runs the model and then does the same thing regardless of what came back — there’s no agency at all. A router lets the output flip a single branch. A tool call lets the output select a function and its arguments, but your code still decides when that happens. A multi-step agent is the first real jump: the model’s output decides whether to keep going, so the model owns the loop. Multi-agent lets one such loop kick off another. And at the top, code agents let the model express its action as Python code — which means a single action can call functions, loop, branch, and even spin up another agent.

The multi-step level is where most of the action lives, and smolagents writes the canonical loop like this. This is the heart that the framework implements:

memory = [user_defined_task]
while llm_should_continue(memory):       # the multi-step part
    action = llm_get_next_action(memory) # the tool-calling part
    observations = execute_action(action)
    memory += [action, observations]

Two comments do a lot of work there. while llm_should_continue(memory) is the multi-step part — the model decides whether the loop runs again. action = llm_get_next_action(memory) is the tool-calling part — within each turn the model chooses what to do. Put differently, a multi-step agent is a tool-call wrapped in a model-controlled loop. (How smolagents actually runs this loop — what a step is, how the model is prompted, how the loop terminates — is Module 2. Here it’s enough to see the shape.)

The deeper question: why is “Code Agents” at the top of the spectrum? It looks like just another row, but it’s the argument that justifies the entire framework. Notice that code-as-action subsumes the levels beneath it. A tool call is “run this function with these args” — but writing a line of Python that calls a function is a tool call, and you can write several of them, and nest them, and feed one’s result into the next. Launching another agent (the multi-agent level) is, in a code agent, just calling a function that happens to be another agent. Looping and branching (the multi-step and router levels) are while and if — native code constructs. So a code agent isn’t a seventh option bolted on; it’s a strictly more expressive action space that contains the others. That’s why smolagents builds on it by default. Everything below the top row is something a code agent can already do in a single action.

Workflow vs agent — and when you don’t need an agent

The spectrum has a fault line running through it, and Anthropic drew it precisely in their widely-cited piece “Building Effective Agents.” Their two definitions are worth memorizing verbatim:

  • Workflows are “systems where LLMs and tools are orchestrated through predefined code paths.”
  • Agents are “systems where LLMs dynamically direct their own processes and tool usage.”

The umbrella term for both is agentic systems. And the basic building block underneath all of it is the augmented LLM — “an LLM enhanced with augmentations such as retrieval, tools, and memory.” A bare model that can also search, call functions, and remember is the atom; workflows and agents are two different molecules you build out of it.

Map that back to the spectrum and “who owns the control flow” is exactly the dividing line. Predefined code paths (rows up to and including “tool call,” when your code decides when the tool fires) are workflows. The model dynamically directing its own process (the multi-step level and above) is an agent. Same building blocks, opposite owner of the loop.

⚠️ Common misconception: “If it calls a tool, it’s an agent.”

No. A single tool call inside a hard-coded flow is still a workflow — it sits at the ★★☆ “tool call” level precisely when your code decides to make the call. The model picked the arguments; it did not decide to keep going. An agent only emerges when the model’s output controls the iteration toward a goal — when it owns the loop. Tool calling is necessary for most agents, but it is nowhere near sufficient. Always come back to: who owns the control flow?

Here’s the part most agent tutorials skip, and it’s the most senior-engineer thing in this module: a lot of the time you should not use an agent at all. If a deterministic workflow can cover every case your task throws at it, just code everything. You’ll get a 100% reliable system that you can test, debug, and reason about. An agent trades that reliability away for flexibility, and you only want that trade when a predetermined workflow fails too often to be worth maintaining. smolagents says this almost word for word — it tells you to “regularize towards not using any agentic behaviour” and reach for an agent only when the rigid version keeps breaking. That’s not anti-hype for its own sake; it’s the cheapest bug you’ll never have to fix.

When you do build agentic systems, Anthropic catalogs five composition patterns — prompt chaining, routing, parallelization, orchestrator-workers, and evaluator-optimizer. Of those, only orchestrator-workers is a first-class primitive in smolagents (via managed_agents, which you’ll build in Module 10); the rest you compose by hand in code. We’ll leave it there — patterns are Module 10’s subject.

Code as action: why smolagents writes Python, not JSON

Most agent frameworks make the model emit a tool call as JSON: a name and an arguments object that your runtime parses and dispatches. smolagents makes the default agent — the CodeAgent — emit a snippet of Python instead, which the framework then executes. This is the code-as-action paradigm, and it isn’t a stylistic preference. It rests on a 2024 research result.

The CodeAct paper

The founding paper is “Executable Code Actions Elicit Better LLM Agents” (Xingyao Wang et al., arXiv 2402.01030, ICML 2024). Its thesis: instead of constraining an agent’s actions to JSON or free text, use executable Python code as a single, unified action space. The agent — CodeAct — generates a code action, the system runs it, and the agent reads the result and revises its next action across multiple turns. Code gives you loops, conditionals, and function composition for free, and — this is the quiet superpower — LLMs are pretrained on enormous amounts of it.

The reported headline number: across 17 benchmarked LLMs (on API-Bank plus a benchmark the authors built), code actions delivered up to a 20% higher success rate than JSON or text actions. Treat that as reported, not as a guarantee you’ll see on your task — it’s a result from their setup, not a promise about yours. The paper also released CodeActInstruct (about 7k multi-turn interactions) and CodeActAgent (a Llama2/Mistral fine-tune) to show the approach is trainable, not just promptable.

The four advantages of code over JSON

Why does code beat JSON? smolagents distills it to four concrete advantages. This is the table to keep:

AdvantageWhy it mattersThe JSON limitation
ComposabilityNest actions, define reusable functions, feed one result into the nextYou can’t nest JSON actions inside each other
Object managementHold and reuse runtime objects — e.g. the output of a generate_image callHow do you even store an image object in a JSON field?
GeneralityCode can express anything a computer can doJSON expresses a fixed, pre-declared call shape
Representation in the LLM training corpusHigh-quality code is already all over the training dataTool-call JSON schemas are comparatively rare in pretraining

The object-management row is the one that bites in practice. Say a tool returns an image, or a fitted model, or a DataFrame. In a JSON world the model has to describe what it wants done to that object in the next turn, by reference, through text. In a code world it just keeps the object in a variable and uses it: img = generate_image(...) then caption = describe(img). Quill will lean on exactly this — df = load_dataset(path) and then a dozen lines of pandas against df — which is why a data analyst is such a natural fit for code-as-action.

CodeAgent vs ToolCallingAgent — a first look

smolagents ships two agent classes, one for each philosophy. You’ll meet them properly in Modules 2 and 3; for now, the lay of the land:

CodeAgent (default)ToolCallingAgent
Action formatA Python snippet the agent writes and runsA JSON tool call (name + arguments)
When to useOpen-ended problem solving; the agent is a programmerDispatching to a fixed set of atomic actions; the agent is a controller
StrengthComposability, object reuse, fewer stepsPlays to provider-native tool-calling; tighter, more constrained outputs
LimitationExecutes generated code — security is now your problemCan’t nest or compose actions in a single turn

CodeAgent is the default in smolagents and the focus of this course. How each class actually parses, executes, and loops is Modules 2 and 3 — we’re staying at the “what and why” level here.

ReAct, in one line

One more name from the canon. ReAct (“Synergizing Reasoning and Acting in Language Models,” arXiv 2210.03629, ICLR 2023) is the pattern that interleaves reasoning (a Thought) with acting (a tool use), turn after turn, so the model can plan, act, observe, and re-plan. smolagents states plainly that “the ReAct framework is currently the main approach to building agents,” and — this is the structural fact to file away — all of its agent classes derive from a single ReAct base, MultiStepAgent. CodeAgent and ToolCallingAgent are both ReAct agents; they differ only in how the action turn is expressed (code vs JSON). The step-by-step walk through that loop — Thought, code, observation, repeat — is Module 2.

Where smolagents fits: a minimalist, Hub-native, code-first framework

smolagents is a deliberately small library. Its core is on the order of ~1,000 lines of code — thin abstractions sitting directly on top of raw Python rather than a heavy framework. It’s code-first (the CodeAgent is the headliner), Hub-native (you can load and share tools and agents through the Hugging Face Hub, the same way you’d share a model or a Space), model-agnostic (any backend behind a common Model interface), and modality-agnostic (text, vision, audio). It was released in early 2025 as the successor to the older transformers.agents, and the current version — the one this whole course is verified against — is v1.26.0 (latest at build time).

On traction: it sits near the top of the lightweight-agent-framework pack, with around 28k GitHub stars as of mid-2026 — read that as an order of magnitude, not a frozen number. The launch blog also reported that code agents take roughly 30% fewer steps than JSON-based agents on its benchmarks. Same caveat as the CodeAct figure: that’s a reported claim from a launch-time graph, not a guarantee for your workload.

Here’s how it stacks up against the frameworks you’re most likely to be choosing between:

FrameworkAngle in one lineWhen to consider it
smolagentsMinimalist, code-first (CodeAgent), Hub-native; ~1,000-LOC coreYou want the lightest, most Pythonic path to an autonomous, code-writing agent
LangGraphExplicit graph of nodes and edges with first-class state and checkpointingYou need fine-grained control of a complex, stateful, branching workflow
CrewAIRole-based “crews” of agents with high-level orchestrationYou’re modeling a team of specialized agents around roles and tasks
PydanticAIType-safe agents built around Pydantic models and validated outputsStructured, validated I/O and strong typing are your top priority
OpenAI Agents SDKLightweight agent loop tuned to the OpenAI ecosystemYou’re committed to OpenAI models and want their native agent primitives

Why is smolagents the right code-first entry point? Three reasons stacked: its minimalism means there’s almost no framework between you and the Python; code-as-action is the most expressive action space (it subsumes the others, as we saw); and Hub-native sharing makes tools and agents reusable artifacts, not local scripts. If you’ve taken the catalog’s LangGraph course (course 1) or the Strands/Bedrock course (course 2), smolagents is their natural lightweight neighbor — and a great on-ramp to both.

⚠️ Common misconception: “minimalist means toy, not production-ready.”

Backwards. The core stays around 1,000 lines because the hard parts — sandboxing, multi-agent orchestration, telemetry, structured output — are composed explicitly on top rather than hidden inside the framework. That composition is exactly what this course builds: by Module 15 you have a production-grade agent, and you understand every layer because you added each one yourself.

Build it: your first CodeAgent

Goal: get a working dev environment and run a bare CodeAgent that writes and runs Python to compute an answer.

What you’ll see: the agent prints its Thought, the Python code it writes, the observation from running that code, and the final answer 5050.

The full, tested code lives in smolagents-course-labs/module-01. Its tests pass offline — no token, no network — so you can verify the harness before you spend a single inference credit.

Step 1 — Get a Hugging Face token. Create a free account, then a fine-grained token at https://huggingface.co/settings/tokens with the “Make calls to Inference Providers” scope. Copy .env.example to module-01/.env and paste it in. Never commit .env — the repo’s .gitignore already excludes it.

# module-01/.env  (copied from .env.example — never committed)
HF_TOKEN=hf_xxx

Step 2 — Install with uv (Python 3.11 or 3.12). The lab pins smolagents[toolkit]==1.26.0 (the [toolkit] extra pulls in the default-tool dependencies — ddgs and markdownify — which later modules use) and huggingface_hub>=1.0,<2. The uv.lock is committed for reproducibility.

uv venv --python 3.11
uv pip install "smolagents[toolkit]==1.26.0" "huggingface_hub>=1.0,<2"

Step 3 — Write the agent. This is the whole thing — the exact minimal example, with one deliberate choice. We pass an explicit model_id instead of relying on InferenceClientModel’s default, because that default is documented as “subject to change” (as of smolagents 1.26.0), and a code-writing agent wants a coder/instruct model. This is quill_intro/first_agent.py:

import os
from smolagents import CodeAgent, InferenceClientModel, Model

DEFAULT_MODEL_ID = "Qwen/Qwen2.5-Coder-32B-Instruct"  # explicit: the default is "subject to change"


def make_intro_model() -> Model:
    """Build the hosted model. Reads HF_TOKEN from the environment (never hard-coded)."""
    return InferenceClientModel(model_id=DEFAULT_MODEL_ID, token=os.environ.get("HF_TOKEN"))


def build_first_agent(model: Model | None = None) -> CodeAgent:
    """A CodeAgent with an empty toolset — `tools=[]` is valid and deliberate."""
    return CodeAgent(tools=[], model=model or make_intro_model())

Two things worth pausing on. tools=[] is valid and intentional — a CodeAgent can compute plenty with an empty toolset, because it has all of Python. And build_first_agent accepts an optional model, which is what lets the tests run offline with a fake model; in normal use it builds the hosted Hugging Face model for you.

Step 4 — Wire up the CLI. The package’s __main__.py takes a task on the command line, runs the agent, and prints the result:

import sys
from .first_agent import build_first_agent

DEFAULT_TASK = "Calculate the sum of all integers from 1 to 100"


def main() -> None:
    task = sys.argv[1] if len(sys.argv) > 1 else DEFAULT_TASK
    agent = build_first_agent()
    result = agent.run(task)
    print(result)

Step 5 — Run it. Point it at a task and watch the trajectory:

uv run python -m quill_intro "Calculate the sum of all integers from 1 to 100"

You’ll see something like this (trimmed). Notice what the agent did: it wrote Python — it didn’t reason its way to 5050 in prose, it generated a one-liner and ran it. That’s your first contact with code-as-action.

─ Step 1 ────────────────────────────────────────────────
Thought: I'll compute the sum of integers from 1 to 100 in Python.
 ─ Executing code: ──────────────────────────────────────
  total = sum(range(1, 101))
  final_answer(total)
 ─ Execution logs: ──────────────────────────────────────
Out: 5050
─ Final answer: 5050 ────────────────────────────────────
5050

(That trajectory — the Step boxes, the Thought, the final_answer call, the observation — is the ReAct loop in action. How it works is Module 2; for now, just feel it run.)

Step 6 — The smoke test. The lab ships a smoke test that verifies the agent harness without a token or network, by feeding the loop a deterministic FakeModel (defined in the repo-root conftest.py). The fake model returns scripted code, so the whole loop — parse, execute, final_answer — runs offline. There’s exactly one live test that hits a real LLM, and it’s skipped unless you opt in:

def test_first_agent_runs_offline(fake_model):
    """The bare CodeAgent writes & runs Python and returns the answer — no token needed."""
    agent = build_first_agent(model=fake_model(["result = sum(range(1, 101))\nfinal_answer(result)"]))
    out = agent.run("Calculate the sum of all integers from 1 to 100")
    assert out == 5050


@pytest.mark.live
def test_first_agent_live():
    assert os.environ.get("HF_TOKEN"), "live test needs HF_TOKEN"
    agent = build_first_agent()
    out = agent.run("Calculate the sum of all integers from 1 to 100")
    assert "5050" in str(out)

Run the offline suite (no token needed); the live test stays skipped:

uv run pytest module-01/tests/
# 3 passed, 1 skipped

To also run the single real LLM call (needs HF_TOKEN):

QUILL_LIVE_TESTS=1 uv run pytest module-01/tests/

That live marker convention — skipped by default, enabled with QUILL_LIVE_TESTS=1, with a documented live-call budget — is how every module in this course keeps its tests runnable for free. The live-call budget for Module 1 is documented at the top of smoke_test.py: exactly one real call.

What this lab does NOT do (yet), on purpose. No tools, no CSV, no pandas (Quill v0 is Module 2). No make_model() or provider swap (Module 4). No sandbox, no executor_type, no additional_authorized_imports (Module 5). No multi-turn memory, no replay() (Module 6). No QuillReport (Module 8). No multi-agents (Module 10). The package is even named quill_intro, not quill — Module 1 freezes nothing. No tools, no CSV, no sandbox yet — the point of Module 1 is to feel a bare CodeAgent run before we give it powers.

Try it yourself:

  1. Change the task to “Find the 10th Fibonacci number and tell me if it’s prime” and read the code the agent writes. Does it write a loop, or recursion?
  2. Pass verbosity_level=2 to the CodeAgent to see more of the trajectory. (We won’t explain the levels here — that’s Module 6.)

In production

A one-line agent.run() hides three things that matter the moment this is real.

The cost of a CodeAgent is real. Every step is one LLM call, and a multi-step agent chains several per run. The free HF Inference allowance (tiny and subject to change, as of smolagents 1.26.0) empties fast under that load. In production you cap the loop with max_steps, track cost per run (smolagents gives you Monitor and TokenUsage for that — Module 4), and choose your provider deliberately rather than coasting on the default.

The model moves under your feet. InferenceClientModel() with no model_id uses a default that changes between versions — naming it here as permanent would be a lie. The opinionated move, and the one this lab makes, is to pin an explicit model_id from day one. That’s exactly what make_model() formalizes in Module 4: one place, one pinned model, no surprises on upgrade.

A CodeAgent executes code the LLM wrote. In local dev, for trusted code, that’s acceptable. In production it’s the structural risk of code-as-action — the model can write any Python, including the kind you don’t want running on your box, especially once it’s reading untrusted web content or tool output. This is why sandboxing is a front-and-center theme of this course (it starts in Module 5), not an appendix. Saying that plainly is the difference between a course built around code agents and a tutorial that hands you a loaded gun.

Concept check

Why this matters. This module anchors three skills you’ll use in every module after it: classifying any system on the spectrum of agency, distinguishing a workflow from an agent, and knowing why code beats JSON as an action space. It does not teach the mechanics of the ReAct loop or the agent classes — what a step is, how agent.run() drives the loop, how final_answer ends it. That’s Module 2. The questions below stay inside what this module actually taught.

  1. A reporting pipeline runs the same three steps on every request: call an LLM to summarize a ticket, call an LLM to classify its priority, then call a Jira API to file it — always in that order, hard-coded. Is this a workflow or an agent, and why?

    • A. An agent, because it calls an LLM twice.
    • B. An agent, because it calls an external tool (Jira).
    • C. A workflow, because the control flow is fixed in code; the model fills in blanks but never decides what happens next.
    • D. A multi-agent system, because there are multiple LLM calls.
  2. An agent calls an image-generation tool, then needs to pass that exact image into a captioning tool on its next action. Which advantage of code-as-action makes this natural, and why does JSON struggle?

    • A. Generality — code can express anything.
    • B. Object management — the agent holds the image in a variable and reuses it; JSON has no clean way to store a runtime object.
    • C. Representation in the training corpus — models have seen lots of image code.
    • D. Composability — you can nest the two calls.
  3. A task’s steps are fully enumerable in advance and identical on every request, with no branching. What should you build?

    • A. A CodeAgent, because code agents are the most powerful option.
    • B. A multi-agent system, for reliability.
    • C. A deterministic workflow — just code everything; no agent needed.
    • D. A ToolCallingAgent, to constrain the outputs.
  4. You have two jobs. Job A: open-ended data analysis where the steps depend on what the data turns out to look like, and you want the model to compose its own logic. Job B: a single atomic “look up an order status” action where you want maximally constrained, predictable output. Which class fits each?

    • A. CodeAgent for both.
    • B. ToolCallingAgent for A, CodeAgent for B.
    • C. CodeAgent for A, ToolCallingAgent for B.
    • D. ToolCallingAgent for both.
  5. A team wants the lightest, most Pythonic, code-first, Hub-native framework to prototype an autonomous agent that writes its own logic. Which fits best, per the positioning table?

    • A. LangGraph, for its explicit state graph.
    • B. CrewAI, for role-based crews.
    • C. smolagents, for its minimalist, code-first, Hub-native design.
    • D. PydanticAI, for type-safe outputs.

Answers.

  1. C. The pipeline’s order is hard-coded; the model never decides what happens next, so your code owns the control flow. Two LLM calls and a tool call don’t make an agent — this sits at the “tool call”/predefined-path level of the spectrum, i.e. a workflow.
  2. B. Object management. In code the agent keeps the image in a variable and feeds it to the next call; JSON actions can’t cleanly hold or pass a runtime object like an image. (Composability is real here too, but the specific problem of holding and reusing the object is object management.)
  3. C. If everything is enumerable and unchanging, a deterministic workflow gives you a 100% reliable system you can test and debug. smolagents itself says to regularize toward not using agentic behavior unless a rigid workflow fails too often. Reaching for an agent here only adds cost and nondeterminism.
  4. C. Job A is open-ended composition — the CodeAgent sweet spot. Job B is a single atomic action where you want constrained, predictable output — the ToolCallingAgent framing. (The deeper mechanics of each class are Modules 2 and 3.)
  5. C. smolagents is the minimalist, code-first, Hub-native option in the table. LangGraph is for explicit stateful graphs, CrewAI for role-based crews, PydanticAI for type-safe I/O — all heavier or differently focused than “lightest path to a code-writing agent.”

Common pitfalls.

  • Treating tool calling as the bar for “agent.” A tool call inside a hard-coded flow is a workflow. The agent only appears when the model’s output drives the loop. Always ask: who owns the control flow?
  • Reading “minimalist” as “not production-ready.” The ~1,000-LOC core is a design choice; production robustness — sandboxing, telemetry, validation — is composed explicitly on top. That’s the course.
  • Copying dead APIs from old tutorials. In smolagents 1.26.0, HfApiModel is gone (it’s InferenceClientModel now, and the old name raises an ImportError), ManagedAgent was deleted (sub-agents use managed_agents=[...]), and executor_type="wasm" was removed entirely. These three are all over the internet and all wrong. Verifying against the source — which this course does — is the whole point.

Key takeaways

  • The one question that classifies any system: who owns the control flow — your code (workflow) or the model (agent)?
  • Agency is a continuum, not a switch. smolagents’ six-level spectrum runs processor → router → tool-call → multi-step → multi-agent → code agent, ordered by how much of the flow the model owns.
  • Code-as-action beats JSON for four reasons: composability, object management, generality, and fit with the LLM training corpus. The CodeAct paper reports up to a 20% higher success rate.
  • Build a workflow first; reach for an agent only when a deterministic version fails too often. An agent trades reliability for flexibility — make that trade on purpose.
  • smolagents is minimalist, Hub-native, and code-first, with a CodeAgent as the default; all its agent classes derive from one ReAct base, MultiStepAgent.
  • A CodeAgent executes code the model wrote — which is exactly why security is a front-and-center theme of this course, starting in Module 5.

What’s next

In Module 2 you’ll watch a CodeAgent actually think — Thought, code, observation, repeat — as it writes pandas to answer a question about a CSV, and corrects its own mistakes along the way. That’s where Quill v0 is born.

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: Course index · Module 2: Your First CodeAgent → · Lab code for this module

References