AWS Builder Center
Introducing strands-code-agent: CodeAct as the Action Paradigm for Strands Agents

Introducing strands-code-agent: CodeAct as the Action Paradigm for Strands Agents

strands-code-agent replaces JSON tool calling with executable Python code as the agent's primary action interface. Instead of serializing intermediate results into context, the agent writes code in a persistent REPL—keeping data in memory, composing multi-step pipelines in a single block, and self-correcting from tracebacks. On the Data Agent Benchmark: +7% accuracy, 78% fewer tokens, 56% faster. Supports multiple interpreter backends including Amazon Bedrock AgentCore and a document knowledge module.

Series: Strands Agents CodeAct (3 articles)

  1. 1
    Introducing strands-code-agent: CodeAct as the Action Paradigm for Strands Agents This article
Senior Applied Scientist @ AWS Prototyping
The dominant pattern for Strands Agents today is tool calling: the LLM outputs structured JSON to invoke a named tool, the framework executes it, and the serialized result is appended to the conversation history. This works well for simple lookups, but for data-intensive tasks—querying databases, transforming DataFrames, composing multi-step analytical pipelines—it introduces significant overhead. Each intermediate result must be serialized into the context window, multi-step transformations require multiple round-trips, and the conversation history bloats rapidly.
This overhead is not just an implementation inconvenience. Wang et al. (ICML 2024) evaluated 17 LLMs across both paradigms and found that executable code actions outperform JSON tool calling by up to 20 percentage points on multi-step tasks—while requiring up to 30% fewer interaction turns. The advantage is structural, not model-specific.
What if the agent could just write code instead?
Today we're introducing strands-code-agent , a library for the  Strands Agents SDK  that replaces tool calling with code generation as the agent's primary action interface. Rather than invoking structured tools, the agent writes Python code in a persistent REPL where domain capabilities are exposed as importable library functions.

The CodeAct Paradigm

CodeAct was introduced by Wang et al. at ICML 2024. In this paradigm, the agent's "actions" are Python code blocks executed in a persistent interpreter. Domain capabilities—database queries, APIs, file operations—are surfaced as regular Python functions the agent can call directly. This means:
  • Data stays in memory. Query results live as native Python objects (DataFrames, lists, dicts) in the REPL state. No serialization into the conversation context. For a real-world illustration of the scale this matters at: Anthropic's own engineering team found that a workflow shuttling a document through tool calls consumed ~150,000 tokens; the equivalent code-execution workflow consumed ~2,000 tokens—a 98.7% reduction (Anthropic Engineering, Nov 2025).
  • Multi-step logic in a single block. One code block can query two databases, join the results, filter, aggregate, and format the answer—work that would require 5+ sequential tool calls in the traditional paradigm.
  • Reduced context pollution. The LLM only sees summaries and final outputs, not thousands of serialized JSON records from intermediate steps. In a tool-calling loop, context grows with every round-trip; with code execution, the intermediate data stays inside the REPL.
  • Self-debugging from tracebacks. When the interpreter returns an error, the Python traceback is the next observation. The LLM reads a structured, grounded explanation of what went wrong and can correct the code in the next turn—with no special error-routing plumbing required. This turns the language runtime into an automatic feedback mechanism.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
┌─────────────────────────────────────────────────────────┐
│ Tool Agent │
│ LLM → tool_call(JSON) → execute → serialize → LLM │
│ LLM → tool_call(JSON) → execute → serialize → LLM │
│ LLM → tool_call(JSON) → execute → serialize → LLM │
│ (each result inflates the conversation context) │
└─────────────────────────────────────────────────────────┘

┌─────────────────────────────────────────────────────────┐
│ Code Agent │
│ LLM → python code block → REPL executes │
│ ↳ data stays in REPL memory as Python objects │
│ ↳ LLM sees only the final printed output │
└─────────────────────────────────────────────────────────┘

Quick Start

1
pip install strands-code-agent
1
2
3
4
from strands_code_agent import CodeAgent

agent = CodeAgent()
response = agent("What is 2 ** 10?")
The agent automatically gets a python_repl tool and solves tasks by writing and executing Python code.

The Toolkit Abstraction

The library introduces a Toolkit class that bundles everything the REPL needs for a specific domain:
1
2
3
4
5
6
7
8
9
10
11
12
from strands_code_agent import CodeAgent, Toolkit

def calculate_roi(investment: float, returns: float) -> float:
"""Calculate return on investment as a percentage."""
return (returns - investment) / investment * 100

agent = CodeAgent(
system_prompt="You are a finance assistant.",
toolkits=[Toolkit(domain_specific_code=[calculate_roi])],
)

response = agent("What is the ROI if I invest 1000 and get back 1250?")
When you pass functions via domain_specific_code, the CodeAgent automatically:
  1. Makes them importable in the REPL
  2. Documents their signatures and docstrings in the system prompt so the LLM knows how to call them
The library ships with built-in toolkits for common workloads:
1
2
3
4
5
6
7
from strands_code_agent import CodeAgent
from strands_code_agent.toolkits import DATA_ANALYSIS_TOOLKIT, VISUALIZATION_TOOLKIT

agent = CodeAgent(
system_prompt="You are a data analyst.",
toolkits=[DATA_ANALYSIS_TOOLKIT, VISUALIZATION_TOOLKIT],
)

Combining Toolkits and Tools

toolkits and tools serve different purposes and can be used together in the same agent.
  • toolkits configure the Python REPL environment. They pre-load libraries, inject initialization code, and document callable functions so the agent can call them from inside code blocks.
  • tools are standard Strands tools registered alongside the REPL. The agent invokes them through the normal JSON tool-calling mechanism—one call per turn, result serialized into context.
This means you can give a CodeAgent both: let it write code for data-heavy, compositional work while still having access to structured tools for actions that benefit from the JSON calling interface—external APIs, sending notifications, writing to external systems.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
from strands import tool
from strands_code_agent import CodeAgent, Toolkit
from strands_code_agent.toolkits import DATA_ANALYSIS_TOOLKIT

@tool
def send_report(recipient: str, subject: str, body: str) -> str:
"""Send a report by email."""
# ... email logic ...
return f"Sent to {recipient}"

def load_sales_data(region: str):
"""Load sales DataFrame for the given region from the data warehouse."""
# ... data loading logic ...

agent = CodeAgent(
system_prompt="You are a sales analyst.",
# toolkits configure what's available inside the Python REPL
toolkits=[
DATA_ANALYSIS_TOOLKIT,
Toolkit(domain_specific_code=[load_sales_data]),
],
# tools are available as regular JSON tool calls alongside the REPL
tools=[send_report],
)
In this example the agent can write a multi-step analysis pipeline (load_sales_data → pandas → summary) entirely in code, keeping all intermediate DataFrames in REPL memory, and then call send_report as a discrete tool call when it's ready to deliver the result.

Empirical Results: Data Agent Benchmark

To validate this approach, we ran a controlled comparison on the Data Agent Benchmark (DAB) —a challenging benchmark of 54 queries across 12 datasets, 9 domains, and 4 database management systems (PostgreSQL, MongoDB, SQLite, DuckDB). Both agents use the same backbone LLM (Claude Opus 4.6 via Amazon Bedrock), the same Strands Agents SDK, and the same database access layer. They differ only in their action paradigm.
The evaluation uses the more realistic no-hints setting (hints are rarely available in production deployments).
Our results align with — and in some dimensions exceed — the published CodeAct findings: Wang et al. (ICML 2024) measured up to +20pp success rate and ~30% fewer turns on multi-step tool tasks across 17 LLMs. The token efficiency gap we observe (−78%) is larger than typical multi-step benchmarks because the DAB specifically involves large intermediate data (database query results, multi-table joins), which is exactly the regime where CodeAct's "keep data in the REPL" property matters most.

Accuracy: +7% Relative Improvement

Overall accuracy comparison between Code Agent and Tool Agent on DAB
Overall accuracy comparison between Code Agent and Tool Agent on DAB
The Code Agent achieves 55.6% Pass@1 accuracy (30/54 queries) versus 51.9% (28/54) for the Tool Agent—a 7% relative improvement.

Token Efficiency: 78% Fewer Input Tokens

Token usage comparison showing dramatic reduction for Code Agent
Token usage comparison showing dramatic reduction for Code Agent
The Code Agent consumes 78% fewer input tokens (7.9M vs. 35.8M) and 67% fewer output tokens (222K vs. 675K). The reduction comes from keeping data in the REPL state rather than serializing it into the conversation context. This translates directly into lower inference costs—roughly a 4.5× cost reduction.

Speed and Cycles: 56% Faster

Execution efficiency showing fewer cycles and faster completion for Code Agent
Execution efficiency showing fewer cycles and faster completion for Code Agent
The Code Agent averages 15.0 cycles per query vs. 23.0 (35% fewer) and completes in 90 seconds vs. 206 seconds per query (56% faster). The ability to compose entire analytical pipelines in a single code block eliminates round-trips.

Efficiency Frontier

Scatter plot showing Code Agent's position on the accuracy-efficiency frontier
Scatter plot showing Code Agent's position on the accuracy-efficiency frontier
The Code Agent achieves a Pareto improvement: it is simultaneously more accurate, more token-efficient, and faster than the Tool Agent. This is not a tradeoff—it's a strict domination across all metrics.

Summary

MetricTool AgentCode AgentΔ
Pass@1 Accuracy51.9% (28/54)55.6% (30/54)+7% rel.
Total Input Tokens35.8M7.9M−78%
Total Output Tokens675K222K−67%
Avg. Cycles / Query23.015.0−35%
Avg. Duration / Query206s90s−56%

Why Code Generation Wins for Data Tasks

Four mechanisms explain the improvement:
  1. Native data manipulation. The Code Agent operates on pandas DataFrames directly—df.merge(), df.groupby(), vectorized operations. The Tool Agent must serialize results to JSON, pass them through the context, then run separate Python to process them.
  2. Reduced context pollution. The Tool Agent's history grows with every tool result (potentially thousands of JSON records per call). On a task with N tool calls that each return large results, total input tokens scale roughly as O(N²)—later turns re-read all prior results. The Code Agent keeps data in REPL memory and only surfaces summaries to the LLM, making context growth O(N) in the number of turns regardless of result size.
  3. Compositional actions. A single code block can express a complete pipeline: query → join → filter → aggregate → format. The Tool Agent decomposes this into 5+ sequential calls, each requiring a full reasoning cycle and introducing opportunities for errors. The published benchmark (Wang et al., ICML 2024) confirms this structurally: code actions required up to 30% fewer turns on multi-tool tasks across 17 different LLMs.
  4. Grounded self-correction. SQL errors, pandas exceptions, and import failures produce Python tracebacks—factual, grounded observations that the LLM can read and act on. The Tool Agent has no equivalent built-in: error feedback requires custom routing logic. This self-debug loop is a key reason code agents complete more tasks (Wang et al. selected training trajectories specifically where the model hit errors and recovered).
  5. Natural alignment with data work. SQL queries, pandas operations, and statistical computations are inherently programmatic. Code generation matches the task's natural representation; JSON tool calling adds a translation layer on top of code that will ultimately run anyway.

Python Interpreter Backends

By default, CodeAgent runs code in a local sandboxed interpreter that restricts imports to an explicit allowlist. Two additional backends are available for different deployment needs.
ExecPythonInterpreter — local execution via exec() with no import restrictions. Lightweight and fast for trusted environments:
1
2
3
4
from strands_code_agent import CodeAgent
from strands_code_agent.python_environments.local_exec import ExecPythonInterpreter

agent = CodeAgent(python_interpreter_class=ExecPythonInterpreter)
AgentCorePythonInterpreter — remote execution via Amazon Bedrock AgentCore Code Interpreter . Code runs in a managed cloud sandbox with 200+ pre-installed libraries (pandas, numpy, scikit-learn, torch, boto3, and more). No local package management, no dependency conflicts, and the sandbox is isolated from the host machine:
1
pip install strands-code-agent[agentcore]
1
2
3
4
5
6
from strands_code_agent import CodeAgent, AgentCorePythonInterpreter

agent = CodeAgent(
python_interpreter_class=AgentCorePythonInterpreter,
python_interpreter_kwargs={"region": "us-east-1"},
)
The AgentCore backend uses active consumption-based pricing — you only pay for actual CPU and memory consumed during computation, not for idle time while the LLM is thinking. An agent running 3 code executions per request (2 minutes each, 2 vCPU, 4 GB memory) costs roughly $0.0036 per request. See AgentCore pricing  for details.
domain_specific_code functions are automatically serialized and sent to the remote session, so the same Toolkit abstraction works across all three backends without changes.

Document Processing: Giving Agents Access to Large Documents

A new strands_code_agent.knowledge module lets you give a CodeAgent structured access to large documents — PDFs, specifications, manuals — without stuffing them into the context window.
The module uses the Open Knowledge Format (OKF)  to represent documents as a hierarchy of searchable concepts. A PDF's table-of-contents bookmarks become the structure:
1
2
3
from strands_code_agent.knowledge import pdf_to_okf_bundle

pdf_to_okf_bundle("technical_spec.pdf", "spec_bundle/")
The agent then navigates the bundle through a minimal 4-method API (find, read, children, toc) exposed via a Toolkit:
1
2
3
4
5
6
7
8
9
10
11
12
from strands_code_agent import CodeAgent, Toolkit
from strands_code_agent.knowledge import OKFBundle

agent = CodeAgent(
system_prompt="You have access to `docs`, an OKFBundle. Use docs.find(query) then docs.read(id).",
toolkits=[
Toolkit(
initialization_code='from strands_code_agent.knowledge import OKFBundle\ndocs = OKFBundle("spec_bundle/")',
domain_specific_code=[OKFBundle],
)
],
)
The agent writes code to search (docs.find("latency requirements")), drill into subsections (docs.children(concept_id)), and read content (docs.read(concept_id)) — all within the REPL. The document content stays out of the context window until explicitly retrieved. The default keyword search can be replaced with a custom semantic/embedding backend via a SearchIndex subclass.

When to Use Each Paradigm

The choice is not one-size-fits-all. Three factors dominate:
  • Payload size. If tool results are small (IDs, single numbers, short strings), context inflation is negligible and tool calling is fine. If results are large (documents, query results, DataFrames), CodeAct's "keep it in the REPL" property becomes the difference between a 2K-token run and a 150K-token one.
  • Sandbox availability. CodeAct requires somewhere safe to run model-written code. Without a sandbox (container, WASM runtime, AgentCore), the correct answer is the tool-calling loop.
  • Model capability. The CodeAct advantage scales with model capability. For frontier models (Claude Opus, GPT-4 class), code generation is reliable. For smaller or cheaper models, JSON schema constraints are more forgiving—bad JSON is caught by the schema; bad Python fails hard.
Use CaseRecommended Paradigm
Multi-step data analysis, ETL, analyticsCode Agent
Database querying and transformationCode Agent
Statistical computation, ML pipelinesCode Agent
Document Q&A over large PDFs or specsCode Agent + Knowledge Module
Isolated or untrusted code executionCode Agent + AgentCore backend
Large intermediate payloads between toolsCode Agent (keep data in REPL)
Simple API lookups with strict validationTool Agent
High-stakes actions requiring human approvalTool Agent (JSON is auditable before execution)
Smaller/budget models, regulated no-sandbox envTool Agent
Reliable parsing + code expressiveness neededStructured Code Agent (JSON wrapper around code)
The recommended pattern is a single CodeAgent configured with both: toolkits for the data-heavy, compositional work (kept in the REPL), and tools for discrete, consequential actions (send this email, write this record) where the JSON calling interface and per-action auditability matter.

Get Started

1
pip install strands-code-agent

The benchmark methodology and full results are described in "Code Agents vs. Tool Agents: An Empirical Study on the Data Agent Benchmark" (under review). The CodeAct paradigm was introduced in "Executable Code Actions Elicit Better LLM Agents" (Wang et al., ICML 2024, arXiv 2402.01030).

Series: Strands Agents CodeAct (3 articles)

  1. 1
    Introducing strands-code-agent: CodeAct as the Action Paradigm for Strands Agents This article
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article