Microsoft Agent FrameworkAI Agents.NETArchitecture

CodeAct in the Microsoft Agent Framework: What .NET Developers Need to Know

CodeAct collapses the tool-calling loop into one generated program and reports large latency and token savings — but it is Python-only today. What it is, why it works, and what a .NET team can use right now.

CodeAct in the Microsoft Agent Framework: What .NET Developers Need to Know

CodeAct was one of the more interesting things Microsoft announced for the Agent Framework at Build 2026, and it is also the one most likely to waste a .NET team’s afternoon — because the write-ups rarely lead with the fact that you cannot use it from C# yet.

So let us get that out of the way, then talk about why the idea matters anyway, and what you can take from it today.

The status, plainly

CodeAct ships in the agent-framework-hyperlight package. It is alpha. The model writes Python, which executes in a Hyperlight micro-VM sandbox, and every published example is Python.

There is no C# equivalent at the time of writing. If you are building on the .NET side of the Agent Framework, CodeAct is something to understand and watch, not something to schedule.

That is worth stating clearly because the framework is genuinely dual-language across most of its surface, and it is reasonable to assume any given feature landed in both. This one did not.

The problem it attacks: round trips, not reasoning

Think about what a five-step task costs in a normal tool-calling loop.

The model reads the conversation and asks for tool one. Your host executes it, appends the result, and sends the whole conversation back. The model reads it again and asks for tool two. Repeat.

That is five model turns for one user request. Each turn re-reads everything before it, so token consumption grows quadratically with the number of steps, and each turn adds a full network round trip plus queueing to the wall clock. The reasoning was never the bottleneck — the loop was.

Put arithmetic on it, because the shape matters more than any particular benchmark. Say your system prompt plus tool schemas come to 2,000 tokens and each tool result adds 300. Turn one bills 2,000 input tokens. Turn two bills 2,300. Turn five bills 3,200. That five-step task has cost roughly 13,000 input tokens to accomplish work a single program describes in a couple of hundred. Nothing in that calculation depends on the vendor, the model, or how well you wrote your prompts — it is simply what re-sending the transcript costs, and it gets worse as tool outputs get larger, not better.

The latency side is simpler and frequently the one that hurts. Each turn is a full inference pass over the whole transcript, and inference time scales with input length, so the fifth turn is slower than the first before you have added a single millisecond of network. If your p95 turn is two seconds, five sequential turns is a ten-second floor that no amount of tool optimisation touches. Check that against your latency budget before deciding whether any of this is your problem.

CodeAct’s answer: stop asking the model to make one decision at a time. Ask it to write the whole procedure at once.

# Roughly what the model emits — one program, executed once in a sandbox
orders = call_tool("get_orders", customer_id="C-8841", since="2026-07-01")
late   = [o for o in orders if o["shipped_days"] > 5]
for o in late:
    call_tool("issue_credit", order_id=o["id"], amount=o["total"] * 0.1)
return {"credited": len(late), "total": sum(o["total"] * 0.1 for o in late)}

One turn. The filtering, the loop and the arithmetic happen in the sandbox rather than as three more conversations with a language model. Microsoft’s published comparison on a representative multi-step workload: 27.81s and 6,890 tokens for the traditional loop versus 13.23s and 2,489 tokens for CodeAct.

The important insight is that a language model is a poor choice for control flow. Filtering a list and summing a column are things a computer does perfectly and a model does expensively and sometimes wrongly. CodeAct moves that work to where it belongs.

What the loop gives you that a program does not

The traditional loop is inefficient, but it is also the reason a lot of agents degrade gracefully, and the benchmark numbers do not capture that. When a tool throws, the error text goes back into the conversation, the model reads it, and it tries something else — a different argument, a different tool, or an honest “I could not find a customer with that id”. You get that behaviour for free, which is precisely why the loop feels sturdier in production than its cost profile suggests.

A generated program gets none of it by default. If call_tool("get_orders", ...) throws on the first line, the program dies, and whether the agent recovers depends entirely on what your host does next. The usual answer is to feed the traceback back to the model and let it write a corrected program — which restores the resilience and also restores a round trip. So the honest accounting is that CodeAct is fastest on the happy path and converges towards the loop’s cost as the failure rate climbs. If your tools are flaky, or your data is messy enough that the model regularly guesses a wrong field name, the gap narrows considerably.

The nastier failure is partial completion. In the credit example above, if the third issue_credit fails, the first two already happened. There is no transaction around them and the retry does not know that: re-running the program issues those two credits a second time. Any tool that a generated program may call in a loop needs to be idempotent on a caller-supplied key, or you need an explicit compensating action. This is a decision to make before adopting the pattern, not after finance asks why a customer received the same goodwill credit three times. It is the ordinary distributed-systems problem; having a model write the orchestration does not exempt you from it.

There is an observability cost too, and it only shows up when you are debugging something real. In the tool-calling loop, every decision is a message in the transcript, so a trace tells you exactly what the model chose and in what order. In CodeAct the reasoning collapses into a single artefact — the program — and the intermediate values live inside a sandbox that exits and takes them with it. Unless call_tool emits a span per invocation and you retain the generated source next to the trace, a wrong answer leaves you with a result and no route back to how it was produced. Log the program. It is the same instinct as logging the generated SQL, for the same reason.

Why the sandbox is not optional

Generated code that calls your tools is, definitionally, arbitrary code execution driven by untrusted input. The Hyperlight micro-VM is not incidental — it is the entire reason this is publishable rather than reckless.

This is also the honest reason a C# version is harder than it looks. Python has a mature story for running restricted code in a hostile-input setting. Doing the same for .NET means either a separate scripting surface or a genuine isolation boundary around generated assemblies, and neither is a small piece of work. If you are wondering why .NET support is not simply “next sprint”, that is why.

Whatever eventually ships, apply the same rule from our prompt injection guide: the generated program runs with the caller’s authority, and call_tool enforces permissions on every call. A sandbox that contains the code but hands it ambient credentials has solved the less important half of the problem.

What a .NET team can do today

Most of CodeAct’s win comes from removing sequential round trips, and two-thirds of that is available now.

Give the model batch tools. A tool that processes a collection removes an entire class of loops from the conversation. This single change is the highest-value thing on this list.

// Instead of one tool the model must call in a loop...
[Description("Issue a credit for a single order.")]
public Task<string> IssueCreditAsync(string orderId, decimal amount);

// ...give it one that takes the whole set. Five turns collapse into one.
[Description("Issue credits for several orders at once.")]
public Task<CreditResult> IssueCreditsAsync(IReadOnlyList<CreditRequest> credits);

Design that batch tool for partial failure from day one. A batch that throws on the first bad item is worse than the loop it replaced, because now the model has to work out which of the ten went through. Return a per-item result — id, status, and a message on the failures — so the model can retry only what needs retrying and a human can reconcile the rest. The result type matters more than the parameter type here, and it is the part people get wrong.

Let independent calls run in parallel. When the model requests several tools in one turn and they do not depend on each other, run them concurrently rather than in sequence. Check your framework version’s behaviour here — it is not always the default.

Parallelism brings its own sharp edges, and they are all resource-shaped: connection pool exhaustion when eight tools each want a database connection, a third-party rate limit that was perfectly comfortable at one call per second, and ordering assumptions that were implicit while everything ran in sequence. Bound the concurrency rather than firing the whole set at once, and be deliberate that a tool with side effects is not automatically safe to run alongside another one.

Push filtering into the tool, not the transcript. If a tool can return the twelve rows that matter instead of four hundred for the model to sift, you save the tokens and the reasoning step. This is the same instinct behind curating a query surface in text-to-SQL.

Do the arithmetic in C#. Any total, percentage or date difference computed by a model is slower, costlier and less reliable than the two lines of C# that compute it exactly. Return the computed value in the tool result.

Do those four and you will recover a large share of the benchmark gap without a sandbox, without alpha packages, and without Python in your stack.

When this is not the right trade

The saving is proportional to the number of sequential tool calls, which makes the first question an empirical one: how many does your workload actually make? Instrument it before planning anything. Agents that everyone describes as multi-step routinely turn out to average one or two tool calls per request, and a one-turn task gains nothing from CodeAct except a code-generation step to pay for and a sandbox to operate.

It is also a poor fit anywhere a human approves individual actions. The entire premise is that the model settles the control flow up front and the program then runs unattended; if every write needs a confirmation, you are back to a round trip per action and you have bought a sandbox for nothing. The same conclusion applies to workloads where the model genuinely needs to see an intermediate result before deciding — an ambiguous customer match, a document that turns out to be the wrong revision, a search that returns nothing useful. Those are reasoning branches, not control flow, and a program forced to guess at them will guess confidently.

Then there is the ordinary organisational objection, which is not small. This puts a Python runtime and a micro-VM into the operational surface of a .NET service. Somebody patches it, somebody monitors it, somebody explains it to a security reviewer who has never heard of Hyperlight. For a team already running Python that is marginal. For a pure .NET shop it is a genuinely new thing to own, and it should be weighed against how much sequential tool calling you do rather than against how interesting the idea is.

Note: CodeAct is alpha and the Agent Framework moves quickly — package names and status change between releases. Verify against the Microsoft Agent Framework documentation and the Build 2026 announcement before planning around any of it. This article describes the position as of August 2026.

Takeaway

CodeAct is a good idea with a clear result behind it, and the underlying lesson does not depend on the feature at all: stop paying a language model to do control flow.

You do not need a sandbox to act on that. You need tools shaped like the work — batched, filtered, and doing their own arithmetic. Reshape your tools now, and if a .NET CodeAct arrives you will be in a better position to use it, because the interfaces will already be the right size.


Have a correction or a topic you want covered? Email mani.bc72@gmail.com.

Frequently asked questions

Is CodeAct available in C# or .NET?

Not at the time of writing. CodeAct ships in the agent-framework-hyperlight package, it is alpha, and the model writes Python that runs in a Hyperlight micro-VM. The published examples are Python-only. Anyone telling you to add a CodeAct NuGet package today is guessing.

What problem does CodeAct actually solve?

Round trips. A five-tool task normally costs five model turns, each re-reading the whole conversation. CodeAct asks the model to write one short program that calls all five, runs it once, and returns a single consolidated result.

How much faster is it?

Microsoft published a representative multi-step benchmark at 27.81s and 6,890 tokens for the traditional loop versus 13.23s and 2,489 tokens for CodeAct — roughly 52% faster and 64% fewer tokens. Treat that as a shape, not a promise; your saving depends entirely on how many sequential tool calls your workload makes.

Can I get the same benefit in .NET without CodeAct?

Partly. Most of the win comes from removing sequential round trips, and you can do that today with batch tools and parallel tool invocation. You do not get the model-authored control flow, which is the part that needs the sandbox.