Hugging Face's entry, and a genuinely different paradigm from the other three. Instead of the model requesting a JSON tool call one at a time, smolagents' default CodeAgent has the model write actual Python code that runs in a constrained interpreter.
Core concepts
CodeAgentvsToolCallingAgent:CodeAgentwrites and executes real Python per turn;ToolCallingAgentis the drop-in JSON-tool-calling alternative if you want parity with other frameworks. smolagents Docs- Why code execution is faster for multi-step tasks: A model can loop, branch, and combine several tool calls in one generated code block instead of one round trip per tool call.
- The
@tooldecorator andArgs:docstrings: smolagents parses theArgs:section of a docstring directly to build the tool schema — a malformed block breaks tool discovery, not just the docs. - Security is not automatic: The default local executor is explicitly documented as not a sandbox — escaping it is expected behavior. Use
executor_type="e2b"or"docker"for anything beyond local experimentation. additional_authorized_imports: Any import a tool needs beyond the safe allowlist must be explicitly declared when constructing the agent.
Resources
YouTube learning
Core concepts from video walkthroughs:
The 60-second story
Two ways to send an intern to the stock room. One way: they write you a slip, “please fetch SKU 14,” you fetch it, you hand it back, they write another slip. That is JSON tool calling. It is clear and slow when the job is “fetch, filter, add, repeat.”
The other way: they write a short Python script, run it in a pen, and come back with the answer. That is CodeAgent in smolagents. One turn can loop and branch. ToolCallingAgent is still there if you want the slip style so you match every other framework in the building.
The @tool decorator publishes a function into that pen. The Args: block in the docstring is not decoration. The library reads it to build the schema. A sloppy block does not just look bad. The tool does not show up. That is a memorable failure, like a vending machine that ignores a button because the label was crooked.
Here is the part people skip until the headline. The default local executor is not a locked room. The docs say escaping it is expected. Your laptop is not a sandbox because a README used the word “agent.” For anything past a toy, set the executor to E2B or Docker. additional_authorized_imports is the visitor list. If the script needs datetime or your helper package and it is not on the list, it does not get in. That list is a safety control, not paperwork.
Map the intern.
- A JSON tool call is one slip per trip.
CodeAgentis a short script in a pen.ToolCallingAgentis the slip style, still available.- The
Args:docstring is the label the machine actually reads. - The default executor is an unlocked room.
- E2B or Docker is a real door.
- Authorized imports are the visitor list.
from smolagents import tool
@tool
def add(a: int, b: int) -> int:
"""Add two numbers.
Args:
a: left
b: right
"""
return a + bThe docstring is part of the mechanism. Treat it like code.
Code agents exist because a dozen round trips for a simple loop burn time and tokens. The risk exists because running model-written Python is running code. Those two sentences are the whole product decision. Speed in the pen, walls around the pen.
Mental model: a script in a pen, not a slip for every step. If the pen has no walls, you do not have an agent platform. You have a model with a shell.
You can explain the page if you say why code can beat tool-calling on multi-step jobs, and why “local” does not mean “safe.”

