boxkite
← All posts
Multi-tenancy

Multi-Tenant Code Execution for Your AI Coding Agent Startup

If you're building a vertical AI coding agent, the product only works if your agent can run real shell commands, real build steps, and real test suites against a customer's actual private repository. That means every one of your customers is, functionally, asking you to run arbitrary code on their behalf — and you're doing it for many customers, at once, on shared infrastructure you own. At two or three engineers with no dedicated security hire yet, that's the scariest sentence in your architecture doc.

The shape of the problem

Strip away the product framing and the infrastructure problem underneath a vertical coding agent is always the same shape: many end-customers, each one letting your agent execute bash, run a build, run a test suite, or edit files against their ownprivate repository. None of those customers can see each other's code. None of them should be able to affect each other's session, even by accident. And your agent has to be able to do genuinely powerful things inside that session — clone a repo, install nothing extra because the tools it needs are already there, kick off a long-running build, stream its output back — without any of that power leaking sideways into someone else's tenant.

That's a multi-tenant code execution problem, not a coding-agent problem. The LLM part — prompting, tool-calling, context management — is the part you actually want to spend your limited engineering time differentiating on. The sandbox underneath it is infrastructure you'd rather not have to invent.

Why a shared container pool is a liability, not a shortcut

The obvious first instinct at a small team is a pool of shared containers or a VM per customer that you provision once and reuse — it's the fastest thing to stand up, and it works fine in a demo. The trouble is what it takes to keep it safe once real customers are running arbitrary agent-generated commands inside it: a permissions layer you have to design (who can see what filesystem, what network egress is allowed, what happens when two sessions land on the same host), an audit trail you have to build to prove tenant A never touched tenant B's files, and a growing list of edge cases you have to keep discovering and patching — usually the hard way, after something goes wrong.

That's a full-time job for a security engineer. A two-to-three person founding team building a vertical agent product doesn't have that person yet, and shouldn't have to hire one before it can ship a second customer. Every hour spent hardening a shared container pool is an hour not spent on the part of the product that's actually differentiated.

One Kubernetes pod per session: isolation as a structural property

boxkite's isolation model sidesteps the permissions-layer problem by not making it a permissions problem at all: every session gets its own Kubernetes pod, non-root, every Linux capability dropped, a read-only root filesystem where applicable, and network access denied by default. There is no shared container for a bug in your authorization logic to leak across — tenant separation is enforced by the Kubernetes scheduler and the kernel, the same primitives that already isolate any two unrelated pods on a cluster, not by application code your own team wrote and has to keep auditing.

One pod per session — many tenants, one product surface

Tenant Aprivate repoTenant Bprivate repoTenant Cprivate repoYour agent productone session percustomer requestPod — Tenant A sessionnon-root · capabilities droppednetwork egress denied by defaultbash_tool · python_interpreter · git toolsPod — Tenant B sessionnon-root · capabilities droppednetwork egress denied by defaultbash_tool · python_interpreter · git toolsPod — Tenant C sessionnon-root · capabilities droppednetwork egress denied by defaultbash_tool · python_interpreter · git tools
Each tenant's session gets its own pod: its own filesystem, its own dropped-capability non-root process, its own network posture. Nothing about that isolation depends on application code your team wrote — it's the same pod boundary Kubernetes already enforces between any two unrelated workloads.

Nobody on your team wrote the code that keeps tenant A's pod from touching tenant B's — the pod boundary is what does it. That's the difference between isolation as a policy you have to get right every time and isolation as a structural property of how sessions are provisioned in the first place.

Wiring it into the product

In practice this is a small amount of integration code, because the tool surface your agent needs already exists as framework-agnostic ToolSpecs: bash_tool for arbitrary shell/build/test commands, python_interpreter for a persistent, kept-alive interpreter, the file tools (file_create, view, str_replace, ls, glob, grep), and the process tools (start_process, get_process_output, send_process_input, stop_process, list_processes, watch_directory) for the long-running build or test job your agent kicks off and then checks in on. If the workflow needs to actually touch the customer's repo, an opt-in git tool set (enable_git_tools=True) adds git_clone, git_status, git_add, git_commit, git_push, git_pull, git_branch, and git_checkout.

agent/tools.py
from uuid import uuid4
from boxkite import SandboxManager
from boxkite.tools import create_sandbox_tool_specs
from boxkite.tools.adapters import to_langchain_tools, to_openai_agents_tools

manager = SandboxManager()
session_id = str(uuid4())
await manager.create_session(organization_id=customer.org_id, session_id=session_id)

specs = create_sandbox_tool_specs(
    sandbox_manager=manager,
    session_id=session_id,
    enable_git_tools=True,  # git_clone, git_status, git_add, git_commit, git_push, git_pull, ...
)

# Wire the same specs into whichever agent framework this customer's
# workflow runs on -- no per-framework reimplementation of the tool surface.
langchain_tools = to_langchain_tools(specs)
openai_agents_tools = to_openai_agents_tools(specs)

One SandboxManager.create_session call per customer session, one create_sandbox_tool_specs call to get the tool surface scoped to that session, and one adapter call to hand it to whatever your agent loop already runs on — to_langchain_tools() and to_openai_agents_tools() here, or to_llamaindex_tools(), plain ToolSpec.handler() calls for CrewAI/AutoGen/a hand-rolled loop, to_openai_functions(), the same shape for Anthropic tool use, or JS's createSandboxTools()if your product's agent loop runs on the Vercel AI SDK instead. The tenant isolation underneath doesn't change depending on which of those you pick — it's a property of the session, not the framework.

Network-dark by default: the answer to "what if a session gets prompt-injected"

Every founding engineer building on top of an LLM agent eventually asks the same question out loud: what happens if a customer's repo — or a file the agent reads, or a dependency it installs — contains something that convinces the agent to run a command it shouldn't? A vertical coding agent is exposed to this constantly, because the whole point of the product is letting the agent read and act on content it didn't write.

boxkite's answer isn't a smarter prompt or a content filter layered on top of the agent's reasoning — it's that the session it's running in has nowhere to exfiltrate to in the first place. Network access is denied by default at the pod level, so even a fully successful prompt injection that gets the agent to run an arbitrary shell command still can't phone home, hit an attacker-controlled endpoint, or push data anywhere outside the pod, unless an operator has deliberately widened that specific session's network policy (the same exception the git tools above rely on). Command output the sandbox returns to your agent is also regex-scrubbed for accidentally-echoed, labeled secrets — API keys, cloud credentials, tokens — before it reaches your agent loop, which catches the common case of a secret innocently showing up in a file the agent reads. Worth being precise about what that is: it's a cosmetic backstop, not a security boundary, and it won't catch a deliberately evasive agent working around it (unlabeled values, encoding, a secret split across calls). The real control for a credentialed operation is keeping the credential out of agent-visible execution entirely and brokering it server-side instead — the network-dark pod boundary above is what's actually load-bearing against exfiltration.

The enterprise-readiness story this buys you early

A customer big enough to ask your two-to-three person startup a real security-review question — "how do you isolate my code from your other customers'?" — usually asks it before you've had the headcount to build a good answer from scratch. "A separate Kubernetes pod per session, non-root, every capability dropped, read-only root filesystem where applicable, no network access unless we explicitly turn it on for a specific case" is a structural answer you can give on day one, backed by the same primitives Kubernetes already uses to isolate unrelated workloads from each other — not a bespoke permissions system you built under deadline pressure and are still finding edge cases in.

That answer doesn't remove the need to eventually hire a security engineer — it buys you the runway to hire one on your own timeline instead of your first enterprise prospect's. It's also worth being as plain with yourself about this as boxkite is in its own README: the isolation approach here is a proven pattern, but boxkite itself is early-stage (v0.1.1) and self-hostable under the MIT license — the production track record is something you build over time, not something a pod boundary hands you on its own. Session start itself is not free, either: the benchmarked figure is roughly 750-950ms median to a usable sandbox, and that's reported as a real measurement against one hosted deployment, not a lab-condition promise you should assume holds on your own infrastructure without checking.

For a team that size, that trade is usually the right one: spend the engineering hours you have on the agent behavior that makes your product worth paying for, and let the pod boundary carry the isolation guarantee your customers actually need before you can staff a security team to build it yourself.