Here's a question that keeps architects up at night: what happens when your AI agent writes Python code, executes it, and that code ends up running next to your production database credentials?

If you're building agent systems — the kind where an LLM generates and runs code dynamically — you've faced this exact problem. The agent needs to execute code to do its job. But that code is untrusted. It might be buggy, manipulated by a prompt injection, or just plain weird. Run it inside your application process and you've essentially handed a language model the keys to your infrastructure.

In Azure, the capability built for exactly this scenario is called dynamic sessions in Azure Container Apps. It gives you per-execution, Hyper-V-isolated environments with prewarmed pools that allocate sessions in milliseconds. I've been using this pattern with enterprise clients across Malaysia and Southeast Asia, and in this article I'll walk through how it actually works, how to set it up, and the pitfalls I keep seeing in the field.

A quick naming note. In June 2026, Microsoft announced a separate public-preview resource type literally called Azure Container Apps Sandboxes (Microsoft.App/SandboxGroups), positioned as the next evolution of dynamic sessions — adding snapshot-based suspend/resume, lifecycle policies, and per-sandbox egress rules. Microsoft has said dynamic sessions will continue to be supported, but new investment is going into Sandboxes. This article focuses on dynamic sessions: they are broadly available across Azure regions today, and the isolation model, security rules, and operational patterns here transfer directly to the newer Sandboxes resource.

The Problem: Why the Obvious Approaches Fall Short

Before the solution, let's be honest about why the alternatives don't cut it for untrusted code execution.

  • Shared-process execution. Running agent-generated code in your application's own process (or a subprocess on the same host). Fast, simple, and catastrophic from a security standpoint: a prompt-injected script can read environment variables, grab connection strings, and pivot to internal services.
  • VM-per-execution. Bulletproof isolation, but cold starts of 30–90 seconds. Users will not wait that long for an agent to "think," and you'll pay for the whole VM while it idles.
  • Kubernetes pods. Better cold start than VMs, but now you're operating a cluster, node pools, and a scheduling layer just to sandbox snippets of Python. Overkill for most teams.
  • Shared serverless runtimes. Functions platforms give you resource limits, but they were not designed as hard isolation boundaries for hostile code. You want a dedicated, disposable boundary per execution — not a multi-tenant worker.

What you actually need is the combination of four properties: strong isolation, millisecond allocation, disposable state, and no infrastructure to babysit. That's precisely the gap dynamic sessions were built to fill.

What Dynamic Sessions Actually Are

Azure Container Apps dynamic sessions provide secure, sandboxed execution environments that are ideal for running code requiring strong isolation from other workloads. The official documentation lists "safely execute AI-generated code" as the headline scenario.

The key properties:

  1. Hyper-V isolation. Each session is isolated by a Hyper-V boundary — hardware-level separation, not just Linux namespaces. Sessions are isolated from each other and from the host.
  2. Prewarmed pools, subsecond allocation. A session pool keeps ready-but-unallocated sessions warm. When a request arrives, the pool allocates an existing session instead of creating one from scratch — allocation in milliseconds, not seconds.
  3. Ephemeral by design. Sessions are short-lived. After a configurable cooldown with no activity, the session is destroyed and resources are cleaned up automatically.
  4. Network egress disabled by default. Sessions cannot reach the internet or your internal services unless you explicitly enable egress on the pool.
  5. Managed lifecycle. No container orchestration, no node pools, no scaling rules to tune for the sandbox itself.

There are two pool types:

Code interpreter poolCustom container pool
ImageNone — platform-built runtimes (PythonLTS, NodeLTS, Shell)Your own container image
Best forLLM-generated Python/JS/shell execution, fastest setupCustom runtimes, libraries, binaries, any TCP protocol
EnvironmentRequires nothing extraRequires a workload-profiles Container Apps environment
BillingPer allocated session duration, in 1-hour incrementsDedicated plan, based on E16 instances backing the pool

For most AI agent code-execution scenarios, the PythonLTS code interpreter pool is where you start. It comes preloaded with the packages agents typically reach for — NumPy, pandas, scikit-learn — so generated data-analysis code just runs.

Hands-On: Building the Sandbox

Step 1 — Create a code interpreter session pool

Update the CLI and Container Apps extension first (sessions support lives in the preview extension track):

az upgrade
az extension add --name containerapp --upgrade --allow-preview true -y

Then create the pool. Note the network setting — leave egress disabled for untrusted workloads:

az containerapp sessionpool create \
  --name agent-sandbox-pool \
  --resource-group rg-ai-agents \
  --location southeastasia \
  --container-type PythonLTS \
  --max-sessions 100 \
  --cooldown-period 300 \
  --network-status EgressDisabled

The knobs that matter:

  • --max-sessions: maximum concurrent allocated sessions (up to 600).
  • --cooldown-period: idle seconds before a session is terminated, 300–3600. Every request resets the timer.
  • --network-status: EgressDisabled (default) or EgressEnabled.

For teams serving Malaysia, southeastasia (Singapore) is a supported region and the closest one — dynamic sessions are available across a long region list including East Asia, Japan, Korea, Central India, and Australia.

Grab the pool management endpoint — this is what your agent calls:

az containerapp sessionpool show \
  --name agent-sandbox-pool \
  --resource-group rg-ai-agents \
  --query "properties.poolManagementEndpoint" \
  --output tsv

Step 2 — Grant your agent's identity access

Authentication uses Microsoft Entra tokens and a purpose-built RBAC role:

az role assignment create \
  --role "Azure ContainerApps Session Executor" \
  --assignee <AGENT_APP_PRINCIPAL_ID> \
  --scope <SESSION_POOL_RESOURCE_ID>

For direct REST calls, the token must carry an audience (aud) claim of https://dynamicsessions.io:

from azure.identity import DefaultAzureCredential

credential = DefaultAzureCredential()
token = credential.get_token("https://dynamicsessions.io/.default")

If you use an LLM framework integration (LangChain, LlamaIndex, Semantic Kernel, AutoGen all have official tutorials), the framework handles token generation for you — you just give the app a managed identity with the role assignment above.

Step 3 — Execute code over the REST API

Each request carries an identifier query parameter — a session ID you define (4–128 characters). Same identifier means the same session is reused; a new identifier allocates a fresh one.

import requests

POOL_ENDPOINT = "https://southeastasia.dynamicsessions.io/subscriptions/<SUB_ID>/resourceGroups/rg-ai-agents/sessionPools/agent-sandbox-pool"

def execute_code(code: str, session_id: str) -> dict:
    resp = requests.post(
        f"{POOL_ENDPOINT}/executions",
        params={"api-version": "2025-10-02-preview", "identifier": session_id},
        headers={"Authorization": f"Bearer {token.token}"},
        json={
            "properties": {
                "codeInputType": "inline",
                "executionType": "synchronous",
                "code": code,
            }
        },
        timeout=240,
    )
    resp.raise_for_status()
    return resp.json()

result = execute_code(
    "import pandas as pd\nprint(pd.DataFrame({'x': [1, 2, 3]}).describe())",
    session_id="conversation-4f8a2c",
)

Each individual execution is capped at 220 seconds — dynamic sessions are built for short-lived, interactive code, not hour-long batch jobs. Files you upload land in /mnt/data inside the session, and you can upload, list, and download them through the files endpoints (128 MB upload limit).

For a LangChain agent, the entire integration is this small:

from langchain_azure_dynamic_sessions.tools.sessions import SessionsPythonREPLTool

repl = SessionsPythonREPLTool(
    pool_management_endpoint=POOL_ENDPOINT,
    session_id="conversation-4f8a2c",
)
tools = [repl]

The agent now has a code interpreter tool; every snippet it writes executes inside an isolated session instead of your server.

Step 4 — When you need more than Python: custom container pools

If your agents need specific libraries, binaries, or a non-Python runtime, bring your own container:

az containerapp sessionpool create \
  --name custom-sandbox-pool \
  --resource-group rg-ai-agents \
  --environment aca-env-workloadprofiles \
  --container-type CustomContainer \
  --image myregistry.azurecr.io/agent-sandbox:1.0 \
  --registry-server myregistry.azurecr.io \
  --cpu 0.5 --memory 1Gi \
  --target-port 8080 \
  --max-sessions 10 \
  --ready-sessions 5 \
  --cooldown-period 300 \
  --network-status EgressDisabled \
  --location southeastasia

--ready-sessions keeps a warm pool so allocation stays instant even under burst. Custom container pools also support an OnContainerExit lifecycle (the session lives until the container exits or a max alive period) and liveness/startup probes so the pool can replace unhealthy sessions.

If you prefer infrastructure-as-code, session pools are ARM resources (Microsoft.App/sessionPools). A trimmed Bicep example for a code interpreter pool:

resource sessionPool 'Microsoft.App/sessionPools@2024-08-02-preview' = {
  name: 'agent-sandbox-pool'
  location: 'southeastasia'
  properties: {
    poolManagementType: 'Dynamic'
    containerType: 'PythonLTS'
    scaleConfiguration: {
      maxConcurrentSessions: 100
    }
    dynamicPoolConfiguration: {
      executionType: 'Timed'
      cooldownPeriodInSeconds: 300
    }
    sessionNetworkConfiguration: {
      status: 'EgressDisabled'
    }
  }
}

The schema is still on preview API versions and evolves — always check the SessionPools REST API reference before pinning this in production. In Terraform, the same body maps directly onto an azapi_resource block.

Hardening: The Security Model in Practice

Dynamic sessions are explicitly designed to run untrusted code, and the defaults are sane. But a few rules make or break the design:

1. Keep egress disabled unless you have a concrete reason. The documentation is blunt about this: if you enable egress, code in the session can reach the internet, which a hostile script can use for exfiltration or denial-of-service. For pure code-interpretation agents, EgressDisabled is the right answer.

2. Never put secrets in the session. Microsoft's own guidance is to assume the code is malicious and has full access to the container — including environment variables, files, and anything you upload. Don't inject connection strings into a custom container sandbox "just for this one call." If the agent needs data, fetch it in your trusted orchestration layer and pass only the necessary values into the session.

3. Treat session identifiers as credentials. A valid Entra token can create and access any session in the pool; the identifier is what separates one user's session from another's. Generate identifiers cryptographically (never sequential IDs), never expose them in URLs or logs, and ensure each user or conversation can only ever reach its own identifier. If you build one session per conversation, make sure the end user cannot modify the identifier value.

4. Leave managed identity access off. Custom container pools can expose the pool's managed identity inside the session, but this is disabled by default for good reason — any code in the session could then mint Entra tokens as your identity. Only enable it with eyes open.

5. Remember isolation is per-session, not per-request. Anything inside a single session — files, variables, state — is visible to every subsequent request to that same session. One session per end user or per conversation, not one shared session for everyone.

What This Costs

Two billing models, depending on pool type:

  • Code interpreter sessions are billed by allocated session duration, in increments of one hour, from allocation to deallocation. This is the number that surprises people: a session that runs for four minutes and then cools down still bills for one hour. The lever is your cooldown period — keep it as short as your interaction pattern allows (minimum 300 seconds), and reuse sessions aggressively per conversation so you're not allocating a fresh session per message.
  • Custom container sessions run on dedicated E16 compute instances under the Dedicated plan; the pool's nodeCount grows with active and ready sessions. This is a heavier cost model — right for specialized runtimes, overkill for basic Python execution.

Check the Azure Container Apps pricing page for current rates in your currency; for a typical interactive agent workload (code interpreter pool, modest concurrency), the sandbox itself is usually a small line item next to the LLM inference costs.

Observability: Know What Your Agents Ran

One asymmetry to be aware of: code interpreter sessions don't emit session logs to Log Analytics. Execution outputs (stdout and stderr) are returned in the API response, and usage metrics come back as HTTP response headers — so your orchestration layer must capture them at the app boundary. Log the request, the code submitted (sanitized), and the result in your own application telemetry.

Custom container sessions do emit AppEnvSession Log Analytics tables when the container writes to stdout/stderr, plus platform logs for pool lifecycle events. Either way, wire alerting on execution error rates and unusual patterns — a sudden spike in failed executions or network-related errors is often the first sign someone is probing your sandbox.

Pitfalls I Keep Seeing in the Field

  1. Treating it like a shared scratch space. Teams reuse one session identifier across all users to "save sessions," then discover that files and state leak between tenants. One session per user or conversation, always.
  2. Enabling egress for convenience. "The agent needs to pip-install things" is the usual justification. It doesn't — the built-in Python image is preloaded with the common data packages; pre-bake anything else into a custom container instead of opening egress.
  3. Ignoring the 1-hour billing increment. Short-lived, throwaway sessions per request multiply your bill. Batch executions into the same session and tune cooldown.
  4. Expecting long-running jobs. The 220-second per-execution cap means dynamic sessions are the wrong tool for training runs or long ETL. Use them for the interactive, LLM-in-the-loop execution loop.
  5. Skipping adversarial testing. Have someone craft prompt-injection payloads that steer the agent into generating malicious code, then verify the sandbox contains the blast radius. If it escapes, you want to find that in UAT, not production.
  6. Forgetting the orchestration layer is your first line of defense. The sandbox is the last line. Input validation, permission scoping on your agent's tools, and review gates for high-risk operations all sit in front of it. Don't skip them just because the sandbox exists.

Getting Started

If you're building agents that execute code in production, here's the path I recommend:

  1. Create a PythonLTS code interpreter pool with EgressDisabled in your nearest supported region.
  2. Wire it into your agent with the official framework integration for your stack (LangChain, Semantic Kernel, LlamaIndex, AutoGen) or the REST API directly.
  3. Use one session identifier per user/conversation, generated cryptographically.
  4. Capture stdout/stderr and execution metrics in your own telemetry.
  5. Load-test with adversarial prompts before going live.
  6. Move to a custom container pool only when the built-in runtimes genuinely can't cover your dependencies.

Dynamic sessions give you the pragmatic middle ground between "run everything in one process" and "a VM per execution": hardware-level isolation, millisecond allocation, and nothing to operate. For any team putting code-execution tools in front of an LLM — which, in 2026, is most agent teams — it should be your default answer on Azure.


Key Takeaways

  1. Azure Container Apps dynamic sessions are the purpose-built sandbox for untrusted AI-agent code — Hyper-V isolated, prewarmed for millisecond allocation, ephemeral by design.
  2. The defaults are secure: egress is disabled out of the box. Only enable it with a concrete, reviewed justification — it's the single biggest risk lever.
  3. Never place secrets inside a session and treat session identifiers as credentials — one cryptographically generated identifier per user or conversation, never exposed in URLs or logs.
  4. Understand the billing model before you scale: code interpreter sessions bill in 1-hour increments per allocated session, so reuse sessions and keep cooldown tight.
  5. The sandbox is your last line of defense, not your only one. Validation, tool permissioning, and adversarial testing in your orchestration layer are where most attacks get stopped first.