DECEMBER 9, 2025 · PUBLICATION

Beyond Code Mode: Agentica

Build agents that interact with runtime objects through code.

Agentica is a new open-source agent framework by Symbolica that is built on the premise that code is the most expressive interface through which models can interact with their environment. Cloudflare and Anthropic have explored this for MCP tool calls [1] [2], but code execution unlocks so much more: context management, dynamic orchestration, and memory.

In this article we lay out what we’ve been building at Symbolica for the last few months and how it transforms not only what agents can accomplish, but how developers build with them.

A simple agent built with Agentica achieved state of the art on BrowseComp-Plus; given the same tools (access to a Mixedbread retriever and document access), GPT-5 scored 77.11% with Agentica compared to 73.25% without.
All agents had access to a document get tool and a retriever, where Opus 4.5 used a Qwen3-Embedding-8B retriever, while GPT-5 with and without Agentica used a Mixedbread retriever. GPT 4.1 was used as the LLM judge.All agents had access to a document get tool and a retriever, where Opus 4.5 used a Qwen3-Embedding-8B retriever, while GPT-5 with and without Agentica used a Mixedbread retriever. GPT 4.1 was used as the LLM judge.

Code mode: the status quo

The general thesis of code mode as it has been presented so far is for models to call tools (functions) by writing code in a REPL instead of JSON.

An example of an agent's output with access to traditional JSON tool-calling.
[User] How has the weather in SF been this month? [Agent] Let me check! [ {"tool": "get_weather", "args": {"location": "San Francisco", "date": "2025-11-01"}}, {"tool": "get_weather", "args": {"location": "San Francisco", "date": "2025-11-02"}}, {"tool": "get_weather", "args": {"location": "San Francisco", "date": "2025-11-03"}}, {"tool": "get_weather", "args": {"location": "San Francisco", "date": "2025-11-04"}},
An example of an agent’s output with code mode.
[User] How has the weather in SF been this month? [Agent] Let me check! ```python [ get_weather( "San Francisco",

From the perspective of the agent there are two key differences:

  1. The language itself provides helpful primitives (iteration in this example) with which the model is already intimately familiar.
  2. Intermediate results flow from one tool to another by reference instead of by value (this would require the model having to repeat values).

Typical JSON tools can be adapted to achieve a similar effect without code mode (files are often used as a form of cache for subsequent reference), but why adapt when modern programming languages offer a solution? The very composability and modularity that make functions usable to developers - key reasons modern programming languages were invented - are lost when serialized to JSON. But this isn’t only the case for functions and MCP tools.

Code mode: from first principles

The status quo of agentic tooling, even with code mode, requires that tools:

  1. Are declared upfront.
  2. Consist of a flat set of functions.

Code mode's success, whether it meant to or not, ended up exposing a harder truth; modern agentic tooling is unnatural, and unlike anything a developer would build for themselves.

Structure

Developers define tools through interfaces and object hierarchies, a far cry from the ones we currently define for agents. Functions and methods return much more than data, they return objects with rich structure and live methods.

class User: def update(self) -> None: ... def delete(self) -> None: ... def get_posts(self) -> list[Post]: ... class Database: def get_user(self, user_id: str) -> User: ... def create_user(self, name: str, posts: list[Post]) -> User: ...

Consider the example above. Although short, it conveys something powerful: the structure of the code organically encodes capability. Methods return objects, which in turn have their own methods, and so it goes on. For instance, calling database.get_user(id) returns a User , and from there the available operations update(), delete() and get_posts() are obvious.

Scope

The previous example suggests a different model: rather than a static toolbox, agents have a scope, a dynamic collection of objects. As the agent interacts with the objects in its scope, the scope itself evolves naturally. It's not a radical idea but simply how object-oriented programming already works.

This is a core feature of agents in Agentica. When agents are created, an initial set of objects are placed in its scope. From there, the agent discovers capabilities through the methods those objects expose, and the scope grows as new objects are returned without requiring explicit registration.

# Initialise a database connection database = Database(...) # Make a new agent with the database in scope agent = await spawn("You are a helpful assistant.", database)

The structure of any neatly organized codebase can be used as context. This includes structures that are awkward to express in natural language or JSON schemas; methods are often required to be called in a particular order, an example of this being methods that return objects with more methods. When agents are writing code, they don’t have to be begged “DO NOT CALL <X> UNTIL YOU HAVE CALLED <Y>”.

Agents that can engineer their own context

Instead of forcing everything through the context window, agents can engineer their context by extracting what matters and discarding what doesn't.

An example of the output of an agent (built with Agentica) from the BrowseComp-Plus benchmark.
[Agent] I'll open the 2014 article and scan the 2015 post for the specific statements to firmly establish the individual and context before searching for the father's full name. ```python import re # Fetch the 2014 article doc_id_2014 = '5024' text_2014 = get_doc_by_id(doc_id_2014)

Once data is in scope, agents can manipulate it using methods that they already know. This could take the form of an agent regex-ing through a large user prompt, or dispatching sub agents to analyze smaller subsections of some tabular data. Prompts don’t need to be passive instructions, they can live in scope, be analyzed and actively reshaped.

Agents that can go-to-definition

Exploring a codebase in an IDE isn't done by reading it in one fell swoop; it's done by inspecting objects, reading method signatures, and jumping to definitions. Programming languages are designed for this kind of incremental discovery, abstractions that link related functionality together.

These same mechanisms can be used by agents; if code already organizes tools into discoverable structures, all that remains is to provide a way for agents to discover them. Agentica does this via the show_definition function, printing definitions into context for any object that it chooses. This is for agents what “go-to-definition”/CMD + Click is for humans.

An example of the output of an agent (built with Agentica) with a Twelve Data client in its scope.
[Agent] ```python price_builder = market_data.price(symbol="XAU/USD") show_definition(price_builder) ``` [Execution] ```

Consider the example above. Rather than the developer or framework choosing to load the definition of PriceEndpoint into the context window, the agent chose to retrieve its definition on demand as it deemed necessary. Context stays lean all while access stays complete.

Stop wrapping, start importing

Libraries can often be used by agents as is; if they are sufficiently well-documented with doc-strings and type annotations, developers don’t need to go through the hassle of wrapping their objects into an explicit set of tools and corresponding descriptions.

from twelvedata import TDClient # Twelve Data Python SDK market_data = TDClient() finance_agent = await spawn( "You are a financial analysis assistant.", { 'market_data': market_data } # Make a new agent with the relevant scope )

Importing a third party library, instantiating a client with a well-scoped API key and passing it to an agent can be done in Agentica without the need for additional prompting. Doc-strings and type annotations are shown to the agent for every object in scope. This is precisely how the multi-agent demo on the Agentica landing page is connected to live market data.

Types in, types out

When an agent is called or invoked with Agentica, the user specifies the type of the object it must return. This isn't just validation; it's what makes agents composable. An agent's well-typed output can be passed directly into another agent's scope or used in further code.

agent = await spawn( "You are a helpful assistant.", { "database": database } ) new_user = await agent.call(User, "Add a new random user to the database") # Call the agent specifying the return type await new_user.onboard() # The variable new_user can be relied on to be of the right type

In the example above, the agent is told to return an instance of User, and it’s execution environment won’t let it terminate until either it successfully does so or raises an exception. This means that any subsequent code can rely on having a User.

Giving Agentica to agents

We can place parts of the Agentica SDK itself in an agent’s scope. This gives agents the ability to spawn other agents in a truly autonomous manner, passing on whatever they choose from their scope to another agent. Multi-agent orchestration shouldn’t be only the delegation of tasks, it should include the delegation of capabilities.

agent = await spawn( "You are an orchestrator.", { "spawn_agent": spawn, "database": database } # Passing spawn into the agents scope ) await agent.call( dict[UserId, str], "For each user, summarise their spending habits." )
An example of the output of an orchestrator agent (built with Agentica) with a database object and Agentica.spawn in its scope.
[Agent] I will spawn a sub agent for each user, and analyse them all in parallel! ```python users = await database.get_users() async def summarise_user(user: User) -> str: agent = await spawn() return await agent.call(
An example of the output of a subagent with a user object in its scope that was spawned by an orchestrator agent (built with Agentica).
[Agent] Let's review the user's spending habits. ```python orders = await user.all_orders() # Calculate total spend total_spend = sum(o.amount for o in orders) # Group by category

In the example above, Agentica’s spawn function is placed in the agent’s scope. The orchestrator is able to parallelize the instantiation and invocation of N sub agents. Agentica is designed to be understood and used by agents themselves, thereby empowering developers to build well-typed, dynamic, multi-agent systems.

Agentica in your architecture

Agents that have the ability to execute arbitrary code can cause headaches when it comes to deploying them in production. From accessing private methods to exposing environment variables, whether the result of malicious prompt injection or mistakes, mitigating these risks requires careful consideration and design. Agents should only be able use the objects they’ve been given, shouldn’t be able to read and leak objects in their scope to others, nor damage or interfere with the computer or web server that they’re running on.

A diagram depicting the remote object proxying protocol that Agentica uses. The database instance `db` exists local to the user and is virtualized in the agent's execution environment. The method `db.get_user()` called by the agent triggers a remote procedure call.A diagram depicting the remote object proxying protocol that Agentica uses. The database instance db exists local to the user and is virtualized in the agent's execution environment. The method db.get_user() called by the agent triggers a remote procedure call.

This can be achieved by having the agent’s code execute in a remote sandbox, all while virtualizing objects local to the client using remote object proxying. This allows agents to interact with objects across the network or process boundaries in a well-typed manner as though they were local. Consider a database object that is sufficiently well-scoped to allow agents to perform arbitrary operations on it (for example, a Supabase client). The agent can interact with that object over the network while the object remains local to the user at all times.

Where performance and security really matter, the Symbolica platform secures agents using two layers of sandboxing: WASM sandboxes are nested within microVMs for per-agent isolation by default. This protects users from other tenants by giving agents a full microVM to operate on, the gold standard for arbitrary code execution (trusted by Vercel for their build system [3], AWS for their Fargate and Lambda services [4], and Fly.io for their hosting platform [5]). Head to Agentica to get started with the Symbolica platform.

For more information on how we proxy and execute objects with a language-agnostic object model (we support Python and TypeScript) all with runtime type checking, stay tuned for a subsequent blog post!

Happy programming!


References

[1] Cloudflare. Code Mode. Cloudflare Blog.

[2] Anthropic. Code Execution with MCP. Anthropic Engineering Blog.

[3] Vercel. A Deep Dive into Hive: Vercel's Builds Infrastructure. Vercel Blog.

[4] Amazon Web Services. Firecracker – Lightweight Virtualization for Serverless Computing. AWS Blog.

[5] Fly.io. Architecture Reference. Fly.io Documentation.


Beyond Code Mode: Agentica | Symbolica Blog