Tool-calling agents usually emit a sequence of JSON objects: select a tool, provide arguments, wait for a result, and ask the model what to do next.

smolagents' CodeAgent changes the action representation. The model emits Python in which tools are callable functions. Python is not merely an output artifact; it is the intermediate representation of the agent's policy for that step.

Why an action language helps

Consider a task that searches five locations, filters the results, computes a statistic, and fetches details only for outliers. A JSON tool-calling agent may require several model round trips. A code action can express the loop, condition, variables, and multiple tool invocations in one generated program.

That gives the model several forms of composition:

  • control flow without another inference call;
  • local variables and reusable intermediate values;
  • direct manipulation of structured objects;
  • multiple tool calls under one explicit algorithm;
  • persistent state across action steps.

The gain is not that Python is shorter than JSON in every case. The gain is that a familiar language already defines sequencing and data flow, so the framework does not need to invent a large workflow schema.

The loop remains ReAct

CodeAgent inherits the lifecycle from MultiStepAgent. A run creates a task memory step and then repeats action steps until a final answer or max_steps:

  1. serialize system, task, planning, action, observation, and error steps into model messages;
  2. generate a response containing a code block;
  3. parse and normalize the code, including final_answer handling;
  4. record a synthetic python_interpreter tool call;
  5. execute the code through a PythonExecutor;
  6. append logs and the last value as the observation;
  7. continue unless the executor reports a final answer.

Failures are model-visible observations. An unauthorized import, parse error, timeout, or tool exception becomes the next step's evidence. The model can repair the program using the same action language.

Planning is optional and interval-driven rather than a second agent architecture. Managed agents are exposed as callable tools. The core abstraction remains a sequence of typed memory steps plus callbacks and monitoring.

The executor is the real trust boundary

LocalPythonExecutor implements a restricted evaluator. It controls authorized imports, blocks dangerous modules and functions, limits loops and operations, caps print output, restricts dunder access, and enforces a timeout.

The source is explicit that this is not a security sandbox. An in-process evaluator is a useful guard against accidental behavior, but it is not the correct boundary for untrusted generated code.

For isolation, CodeAgent can select E2B, Modal, Blaxel, or Docker executors. All implement the same small contract:

  • receive tools;
  • receive variables;
  • execute a code action;
  • return output, logs, and a final-answer flag.

Remote executors patch final-answer behavior into a transportable termination signal, then deserialize the value on the client side. The agent loop does not need to understand whether Python ran in-process, in a notebook kernel, or in a remote sandbox.

This is the cleanest architectural boundary in the project: code is the policy representation; the executor owns execution semantics and isolation.

Expressiveness has a cost

JSON schemas enumerate a tool's argument surface. Generated code can call a tool repeatedly, construct arguments dynamically, and consume unbounded resources unless the executor intervenes. A code agent therefore needs stronger controls around CPU, memory, network, filesystem access, and audit.

The README still describes the agent logic as roughly 1,000 lines, but the current agents.py file is larger. The useful minimalism is not a line-count stunt. It is the small set of stable concepts: model, memory step, tool, executor, and loop.

Personal take

smolagents will resonate with analysts, researchers, and developers whose tasks already resemble notebook programs. Their work naturally contains loops, transformations, and structured intermediate objects. Code is a better action medium than a procession of isolated buttons.

The market consequence is that automation and software generation converge. Instead of writing a durable program first, an agent synthesizes a temporary program inside the task. Value shifts from individual tools toward hardened executors, observability, and reusable runtime state.

The design is also refreshingly honest: greater expressiveness and greater risk arrive together. A mature code-agent platform cannot pretend the prompt is the security boundary. The executor deserves the same architectural attention as the model.

Sources