For the last few years, "an agent uses a tool" meant one specific mechanism: the model emits a structured blob — usually JSON — naming a function and its arguments, a runtime parses it, calls the function, and feeds the result back into the model's context. This tool-calling pattern powered the first generation of agent frameworks and it works. But it has a quiet inefficiency that becomes obvious once you look for it: every action is a separate round-trip through the model. Want to call three tools and combine their results? That is at least three model calls, each re-reading the accumulated context, because the model can only emit one JSON tool call at a time and must wait to see each result before deciding the next. A growing school of thought in 2026 argues there is a better way, and it is hiding in plain sight: let the agent write code.
This is the code-first agent paradigm, and it is reshaping how the more efficient agent frameworks work. Instead of emitting JSON, the agent writes a snippet of Python — which can call multiple tools, loop, use variables, and combine results — and the runtime executes it. This guide explains why that shift matters, how it changes agent performance and cost, and how the leading frameworks implement it, through smolagents (the code-first approach in its purest form) and Pydantic AI (type-safe agents from the Pydantic team). It also covers the essential flip side: running model-generated code safely.
The problem with JSON tool calls
To see why code-first matters, look closely at what JSON tool calling costs. Consider a task that requires fetching data from an API, filtering it, and computing a summary. With tool calls, the model emits a call to the fetch tool, waits for the result, emits a call to a filter tool, waits, then emits a call to a compute tool, waits, and finally composes an answer. That is four model invocations, and each one re-processes the entire growing context — the original task, plus every intermediate result accumulated so far. The token cost and latency scale with the number of steps, and complex tasks have many steps.
There is a subtler problem too: JSON tool calling constrains how the agent can compose actions. Each step is a single function call with literal arguments. The model cannot easily express "call this tool for each item in that list," or "if the result is empty, try a different query," or "combine these two results with a calculation," without breaking each of those into separate round-trips and reasoning about intermediate state across model calls. The expressiveness of ordinary programming — loops, conditionals, variables, composition — is exactly what is missing, and those are the constructs that make multi-step logic natural. Tool calling asks the model to be a program counter emitting one instruction at a time, when what the task needs is a program.
The code-first insight
The code-first approach resolves both problems at once. Instead of emitting a single JSON tool call, the agent writes a block of Python code that can call several tools, use control flow, hold intermediate values in variables, and return a result. The runtime executes that code, and the tools the agent can call are simply Python functions available in its execution environment. The API fetch, the filter, and the compute from the earlier example become one code block with three function calls and a bit of logic between them — executed in a single step, from a single model invocation.
The efficiency gain is real and measurable. Because one code block can accomplish what previously took several tool-call round-trips, code-first agents make meaningfully fewer LLM calls — reported reductions around 30% — which cuts both cost and latency. And because the agent expresses actions in a real programming language, it handles compositional tasks (iterating over results, branching on conditions, combining computations) far more naturally than a sequence of isolated JSON calls could. There is also a training-data argument: models have seen enormous amounts of code and are genuinely good at writing it, arguably better than they are at producing perfectly-formatted tool-call JSON for novel schemas. Asking a model to express its intent as code plays to a strength.
smolagents: the paradigm in its purest form
smolagents, from Hugging Face, is the clearest expression of the code-first idea. It is a deliberately minimal library built around the CodeAgent: an agent whose actions are Python code. Give it a set of tools (ordinary Python functions) and a model, hand it a task, and it works by writing and executing code that calls those tools, loops, and computes until it has an answer. The library stays small and hackable on purpose — its philosophy is that the agent loop should be simple enough to read and modify, with the intelligence coming from the model and the code-execution approach rather than from a heavy framework.
What makes smolagents compelling beyond the efficiency is how naturally it handles real tasks. Because the agent writes code, a research task can search, parse results, filter them, and compute over them in one coherent step; a data task can load, transform, and analyze without a JSON round-trip per operation. It is model-agnostic — working with Hugging Face models, local models, or any provider through LiteLLM — so you are not locked to one backend, and it supports the sandboxed execution that code-first fundamentally requires (more on that below). For teams that want the code-first efficiency in a small, understandable package, smolagents is the reference implementation, and the smolagents cheatsheet covers its agents, tools, and sandboxing.
The tradeoff of smolagents' minimalism is that it provides less scaffolding than a heavyweight framework — fewer built-in abstractions for complex multi-agent orchestration, durable state, or elaborate control flow. For single-agent code loops that is a feature, not a limitation; for sprawling stateful systems you might reach for something heavier. But as the purest, most efficient expression of code-first agents, it is hard to beat.
Pydantic AI: type safety meets agents
Pydantic AI, from the team behind Pydantic (the validation library that underpins FastAPI and much of the Python AI ecosystem), approaches agents from a different angle: type safety and developer experience. Its bet is that the same rigor Pydantic brought to data validation — declaring the shape of your data with Python type hints and getting automatic validation — should apply to agents. Every agent input, output, and tool call is typed, the framework validates that model outputs match the expected structure, and it can automatically ask the model to correct itself when the output does not conform. The result is a FastAPI-style developer experience for building agents: familiar, type-checked, and production-oriented.
Type safety matters for agents more than it might seem. A recurring pain of LLM applications is that model output is unstructured text, and turning it reliably into the structured data your program needs — the right fields, the right types, valid values — is fiddly and error-prone. Pydantic AI makes structured output a first-class guarantee: you declare the output type, and the framework enforces it, retrying with the model if necessary. That turns "parse the model's text and hope it is valid" into "receive a validated, typed object," which is exactly what production code wants. Its V2 redesign in 2026 pushed further with a harness-first model that bundles tools, instructions, and settings into composable capabilities. The Pydantic AI cheatsheet covers its typed agents and tools.
Pydantic AI and smolagents are not really competitors so much as expressions of different priorities. smolagents optimizes for the efficiency and expressiveness of code-as-action; Pydantic AI optimizes for type safety, validated structured output, and production ergonomics. A team that values fewer LLM calls and natural multi-step logic leans smolagents; a team that values type-checked, validated, FastAPI-style agent code leans Pydantic AI. Both represent the 2026 move away from the first-generation, JSON-only frameworks toward something more programmer-native.
The safety problem: running model-generated code
Code-first agents have an obvious and serious catch: they execute code written by a language model, and model-generated code is untrusted by definition. An agent that can write and run arbitrary Python can, if compromised by a prompt injection or simply by being wrong, delete files, exfiltrate data, make network calls, or consume resources without bound. This is not a hypothetical concern — it is the central operational risk of the entire paradigm, and taking it seriously is non-negotiable for any production deployment.
The answer is sandboxing: run the generated code in an isolated environment with restricted capabilities. The frameworks support this directly — smolagents can execute code in remote sandboxes like E2B, or in Docker or Modal containers, rather than in the host process, and you can restrict which modules the generated code is even allowed to import. The principle is least privilege: the sandbox should have only the access the task genuinely requires — no filesystem access it does not need, no network egress beyond what is necessary, no ability to affect the host. Running agent-generated code directly in your application process, with your application's permissions, is the mistake that turns a useful agent into a remote code execution vulnerability wearing a friendly interface.
The practical guidance is straightforward but must be followed. In development, local execution is convenient and acceptable for trusted, non-sensitive tasks. In anything approaching production, or any task touching untrusted input or sensitive systems, run the code in a sandbox with least-privilege access, restrict imports, and treat the agent's code output the way you would treat any untrusted input — because that is what it is. The efficiency and power of code-first agents are real, but they come with a responsibility that JSON tool calling (which is more constrained by design) did not impose as sharply. Accept that responsibility and sandbox properly, and the paradigm is both powerful and safe.
When JSON tool calls are still the right choice
Code-first agents are more efficient and expressive, but it would be a mistake to conclude that JSON tool calling is obsolete. There are real situations where the constrained, one-call-at-a-time model is the better fit, and understanding them keeps the choice honest. The clearest is when actions must be tightly controlled and audited. A JSON tool call is a discrete, inspectable, easily-validated event: the agent wants to call this function with these arguments, and you can approve, log, or reject it before anything runs. That auditability is valuable in high-stakes domains — financial actions, infrastructure changes, anything with irreversible side effects — where you want a human or a policy check between the agent's intent and its execution. Code that runs as a block is harder to gate at that granularity.
The second case is simplicity. Many agents genuinely only need to call one tool at a time in a straightforward loop — answer a question by calling a search tool, look something up, perform one action. For those, the expressiveness of code-as-action is unused capability, and the extra machinery of a code sandbox is overhead without payoff. A simple tool-calling agent is easier to reason about and deploy when the task does not need multi-step composition. The third case is safety posture: because JSON tool calling only lets the agent invoke predefined functions with structured arguments, it is inherently more constrained than executing arbitrary code, and for some threat models that smaller blast radius is worth the efficiency cost even when a task could benefit from code.
The mature view is that these are two tools for different jobs, not a winner and a loser. Reach for code-first when tasks involve genuine multi-step composition, iteration, or computation over intermediate results, and you can sandbox properly. Reach for JSON tool calling when actions need fine-grained control and auditing, when the task is simple enough that code-as-action is overkill, or when the tighter constraint of predefined-functions-only suits your safety requirements. Several frameworks, including smolagents, support both a CodeAgent and a tool-calling agent for exactly this reason — the paradigm is a choice you make per use case, not a doctrine you adopt wholesale.
The bottom line
The move from JSON tool calls to code-first agents is one of the meaningful shifts in how agents are built in 2026, and it is driven by a simple observation: letting an agent write code, rather than emit one tool call at a time, is more efficient and more expressive. Fewer LLM calls, natural multi-step logic, and models playing to their code-writing strength are the payoff, visible in the roughly 30% call reductions code-first frameworks report. smolagents expresses the paradigm in its purest, most minimal form; Pydantic AI brings type safety and validated structured output to agent building. Both point away from the first generation of JSON-only frameworks toward something more programmer-native. The one rule you cannot skip: model-generated code is untrusted, so sandbox it with least privilege. Do that, and code-first agents are the faster, more capable way to build.
References and Resources
Frameworks
Background and analysis
- The best open source frameworks for building AI agents in 2026 — Firecrawl
- Python AI Agent Library Comparison 2026 — Pydantic AI vs Instructor vs Smolagents
- Best Python AI Agent Frameworks in 2026 — Uvik
Related 1337skills cheatsheets