Code-writing agents — the pattern smolagents is built around — have the model generate and execute code to accomplish a task, rather than selecting from a fixed menu of predefined tool functions. That design choice is a real efficiency win for multi-step tasks, and it trades a small, enumerable action surface (a list of tools you defined) for a large, effectively unbounded one (anything the execution environment permits). Governing this pattern means securing the execution environment itself, not trying to review what the agent might write.

What the code-agent pattern provides

Most tool-calling agent frameworks work by giving the model a fixed set of function definitions — name, description, parameter schema — and letting it choose which to call and with what arguments on each step. For a multi-step task, this typically means one model round-trip per tool call: call a tool, get the result back, decide the next step, call another tool.

The code-agent pattern, as implemented in smolagents and similar designs, instead has the model write a snippet of code — commonly Python — that can call several available functions, use control flow like loops and conditionals, and process intermediate results, all within a single generated block that then executes in one pass. Instead of "call tool A, wait, call tool B, wait, call tool C," the model can write "call A, loop over the result, and call B for each item that matches a condition" as one piece of code.

This is a genuine efficiency and expressiveness gain. Tasks that would take many round-trips of tool selection under the fixed-menu pattern can be expressed as a single piece of composed logic, and the model can use ordinary programming constructs — variables, conditionals, loops — instead of forcing every intermediate decision through another model call.

Where the risk profile changes

1. The action surface is no longer enumerable

With a fixed tool-calling agent, you can list every action the agent is capable of taking: it is exactly the set of tools you defined, each with a specific, reviewed implementation. With a code-writing agent, the "actions" are whatever the generated code can express within the execution environment — which, for a general-purpose code interpreter, is close to whatever that language and its available libraries can do. You cannot produce a complete list of what a code-writing agent might do the way you can for a tool-calling one; you can only describe what its execution environment allows.

This is not a minor implementation detail — it changes what "reviewing the agent's capabilities" even means. For a tool-calling agent, least privilege means restricting the tool set. For a code-writing agent, it means restricting what the code interpreter itself can reach: which modules are importable, what file system paths are visible, what network access exists, what process-level permissions the interpreter runs with.

2. A successful prompt injection has a shorter path to code execution

In a tool-calling agent, an attacker who successfully injects instructions into content the agent processes is still constrained to getting the agent to call one of its predefined tools with attacker-influenced arguments — a real risk, covered in indirect prompt injection threat modeling, but bounded by what those specific tools can do. In a code-writing agent, the equivalent attack can potentially get the agent to generate and execute arbitrary code, which is a categorically larger outcome if the execution environment is not tightly sandboxed. The difference between "the injected instruction got a tool called with bad arguments" and "the injected instruction got arbitrary code executed" is the difference this design pattern introduces, and it is the reason sandboxing is not optional for this pattern the way it might be treated as a nice-to-have elsewhere.

3. Sandboxing is the control that actually matters here

Because the action surface cannot be enumerated in advance, the only reliable way to bound what a code-writing agent can do is to bound what its execution environment permits, independent of what code it happens to generate on any given run. This means running generated code in an isolated environment — with no ambient file system access beyond an explicit working directory, no network access unless a specific outbound call is deliberately allowed, no credentials available beyond what that specific task needs, and resource limits on CPU, memory, and execution time to prevent runaway loops. A code-writing agent running with the same file system and network access as the host process it executes in has effectively no security boundary at all — the sandbox is not a hardening layer on top of the design; it is the design's load-bearing control.

4. Review shifts from "what tools exist" to "what the sandbox allows"

For a tool-calling agent, a security review can reasonably ask "what does each of these ten tools do, and is each one appropriately scoped." For a code-writing agent, that question does not apply the same way — there may be no fixed list to review. The review question becomes "what can code running in this sandbox reach," which is a question about infrastructure configuration (network egress rules, mounted file system paths, available credentials, process permissions) rather than about a specific agent's tool definitions. This is a genuinely different kind of review, and a team that only knows how to audit tool lists will miss it.

What good looks like

Concern Tool-calling agent Code-writing agent
Action surface Enumerable — the defined tool set Not enumerable — bounded only by the execution environment
Primary control Scope and authorize each tool Sandbox and restrict the execution environment
Injection blast radius Bounded by what the called tool can do Bounded only by sandbox configuration
Security review focus Tool definitions and their scopes Sandbox network, file system, and credential configuration

Concrete checklist for operating a code-writing agent safely:

  1. Run generated code in an isolated sandbox with no ambient credentials beyond what the current task explicitly needs.
  2. Deny network egress by default; allow specific outbound destinations only where the task requires them.
  3. Restrict file system access to an explicit, scoped working directory — never the host's broader file system.
  4. Set hard resource and time limits on execution to contain runaway or looping generated code.
  5. Log the generated code and its execution result for every run, since there is no fixed tool-call log to fall back on for audit — the same durability requirement covered in audit trails that hold up.
  6. Treat the sandbox boundary, not code review of any individual run, as the actual security control — you cannot review your way to safety against code you have not seen yet.

Code-writing agents are a legitimate and increasingly common design choice for multi-step tasks that benefit from composed logic. The trade is explicit: a larger, non-enumerable action surface in exchange for fewer round-trips and more expressive task completion. Governing that trade means investing in the execution environment's isolation, not in trying to anticipate every piece of code the agent might generate. For the broader coding-agent context this pattern shows up in, see securing AI coding agents and OWASP's agentic AI risk categories.

Common questions

Can static analysis of the generated code substitute for a sandbox?

Static analysis of generated code before execution can catch some obviously dangerous patterns — an import of a networking module where none should be needed, a call to a file-deletion function — and is worth adding as a cheap early filter. It is not a substitute for sandboxing, because static analysis can be evaded by code that only becomes dangerous at runtime through dynamic behavior, string-built calls, or logic that depends on data not available at analysis time. Treat it as a secondary check layered on top of a sandbox, not a replacement for one.

Does restricting the set of importable libraries solve the problem?

Restricting importable libraries reduces the tool surface available to generated code, which is a genuinely useful control, but it does not replace sandboxing the execution environment itself. Code limited to a small set of libraries can still read or write any file the process has access to, make network calls if any networking capability remains available, or exhaust resources through pure computation. Library restriction narrows what the code can easily do; it does not bound what the environment allows the code to reach.

Is the code-agent pattern inappropriate for any task involving sensitive data or systems?

Not inherently — the pattern is a design choice about how the agent expresses its actions, not a statement about what data it should be trusted with. What changes is the sandboxing bar: a code-writing agent operating near sensitive data or systems needs a more tightly restricted execution environment — narrower file system access, tighter network egress rules, shorter-lived and more narrowly scoped credentials — than one operating in a low-stakes context. The pattern is usable at any sensitivity level provided the sandbox is scaled to match.