Skip to content

When the Coding Agent Leaves the Codebase: Building a Multi-Runtime AI Agent Infra with Azure KARS

1. The story starts with someone who doesn’t write code



Let me start with a scene I keep running into at community events.


Late last year, after a meetup, I got an email. The sender wasn’t a developer — he was a marketing manager at a manufacturing customer. He wrote: “I installed Claude Code from your tutorial. I wanted it to fix a static web page. Instead I found out it can read the dozen Excel files I drop in, write a script to crunch them, and generate a PowerPoint. My weekly dealer briefing is made by it now.”


Then he asked the most ordinary question in the world: “Can we roll this out to my whole department?”


That question is where this post starts.


1.1 Why the coding agent quietly became the business agent


For the past two years, most of us built agents the other way around: define tool schemas, write function calling, stand up an orchestrator, wire in RAG, tune prompts. Nothing wrong with that path — but it carries a hidden premise: you have to know in advance what the user will do, so you can wrap that capability in a tool.


Coding agents (Claude Code CLI, GitHub Copilot CLI, Codex CLI, and their relatives) came from the opposite direction. Their original design goal was “complete a real software engineering task, on a real filesystem, through a real command line.” To do that, they were forced to acquire four capabilities:


First, the filesystem is a first-class citizen. The agent has a working directory. It can read, write, list, and produce files. To a developer that reads as “editing code.” To a marketing manager it reads as “I drop in a dozen spreadsheets and get a deck back.” The same capability, in a different context, is an entirely different product.


Second, the shell is the universal tool. A traditional agent needs a new tool definition for every new capability. A coding agent has exactly one ultimate tool: run a command. Which means pandas, LibreOffice, curl, ffmpeg, Chromium — anything you can put in a container automatically becomes an agent capability. Extending the toolset shifts from writing code to writing a Dockerfile.


Third, it is natively a long-task machine. Writing code isn’t request/response; it’s plan → execute → read the error → fix → execute again. Move that loop into a business context and it becomes: read the data → notice the columns don’t line up → clean → recompute → chart → write the report. The self-correction loop that chat-shaped agents struggle hardest to implement is the default behaviour of a coding agent.


Fourth, it already has mature extension protocols. MCP connects it to external systems; Skills teach it your domain knowledge and delivery conventions. A single SKILL.md can tell it “this is what our monthly report looks like, these are the definitions, the disclaimer goes on page two” — with no code at all.


So the conclusion is clear: the coding agent runtime wasn’t repurposed into a business agent runtime. It is simply the most general-purpose agent runtime we currently have. Code was just its first landing zone, because code has the cleanest evaluation function (does it run?). But the underlying shape — iteratively producing files, using a pile of tools, inside an isolated environment — describes most knowledge work.


1.2 And then the real problems show up


Back to the email. “Can we roll this out to the whole department?” Between one laptop and one department sits a set of problems that have nothing to do with AI capability:



  • His Claude Code ran on his own Mac, with his own API key, and he’d happily skipped the permission prompts (yes, really).

  • Some colleagues wanted Copilot CLI, because the company has GitHub Enterprise. Others wanted Codex CLI, because they front a self-hosted OpenAI-compatible gateway.

  • Security had exactly one question: “This thing can execute arbitrary commands and reach the network. What of ours can it touch?”


Those three problems have names: runtime heterogeneity, credential governance, and blast radius. And they happen to be precisely what Azure KARS is built to address.


2. Azure KARS: treating an agent as a proper Kubernetes workload


Azure/kars — the Agent Reference Stack for Kubernetes. It’s developed in the open by the Azure Cloud Native team, the same team behind Azure Kubernetes Service and Azure Linux.


The most important sentence goes first: this is not an officially supported Microsoft product. No SLA, no support contract, no product roadmap commitment. It is a reference implementation — something to read, to learn the architecture from, and to grow your own stack on top of.


2.1 The problem, in one line


There’s a line in the KARS README that I think is the thesis of the whole project: giving an AI agent real tools means giving it real credentials and a real network — and in production that is too much blast radius, because a single prompt-injected agent can reach your Azure subscription, your GitHub org, and your customer data.


Its answer is not “restrict what the agent can do.” It is run agents with the same operational discipline as the rest of your services.


2.2 The core design: the trust boundary is the pod, not the cluster


The single most consequential structural decision in KARS: one hardened sandbox per agent, and the agent has no network of its own.


The agent container runs as UID 1000 with no egress of its own; it can only reach the inference router over localhost inside the same pod. Every byte that leaves the pod leaves through that Rust router. NetworkPolicy and the egress-guard iptables init container are safety nets that contain the blast radius if the router is ever bypassed. The result: compromising the agent does not compromise the cloud account, the model, the audit log, or the peer mesh.


That router is the load-bearing wall of the security model. It runs as a separate container under a different UID (1001), holds the credentials the agent never sees, and is the single enforcement point for:


Capability What it does
Identity & token brokering Exchanges the per-sandbox Entra Agent ID (or cluster Workload Identity) for backend tokens via federated OIDC / IMDS, refreshing automatically. The agent process holds no long-lived key.
Inline content safety Reads Foundry’s prompt_filter_results on every completion (jailbreak / indirect attack / hate / violence / self-harm / sexual) and enforces a configurable severity floor.
Token budgets & rate limits Per-tenant token ceilings and request rate limits, enforced before the call leaves the pod.
L7 egress allowlist + blocklist Every outbound CONNECT is checked against the per-sandbox allowlist and the daily-refreshed OISD + URLhaus blocklist; EgressApproval CRDs add time-boxed exceptions.
MCP gateway Brokers calls to external MCP servers with OAuth and per-tool allowlists.
Tamper-evident audit Every decision is written to an append-only, SHA-256 hash-chained audit log (each entry’s hash covers the prior entry’s) in a stable JSONL format.


One common objection deserves a direct answer: “We already have an API gateway — isn’t this redundant?” No. A north-south gateway governs traffic at the cluster boundary; the KARS router is an in-pod policy enforcement point sitting on localhost between the agent and everything else, so the agent has no network path that bypasses it. They operate at different layers and are complementary, not interchangeable — a cluster-edge gateway can front KARS, and the per-pod router still does the per-sandbox identity, content-safety, budget, and audit enforcement that a shared edge cannot. That’s the structural core of the zero-trust model: the trust boundary is the pod, not the cluster perimeter.


2.3 Multi-runtime: one YAML, eight agent frameworks


This is the part most directly relevant to our theme. You pick the runtime via KarsSandbox.spec.runtime.kind, and the router, governance, isolation, and audit chain are identical across all of them.


The built-in runtimes: OpenClaw (default, TypeScript/Node), Hermes (Nous Research, Python), OpenAI Agents SDK, Microsoft Agent Framework (Python; .NET deferred), LangGraph, LangGraph.js, Anthropic Claude Agent SDK, Pydantic-AI — plus BYO: your image, our contract.


As an advocate, I think “identical router, governance, isolation and audit chain” is the most under-appreciated sentence in the repo. It means the security review happens once. Your security team doesn’t review LangGraph, then review the Claude Agent SDK, then review your CLI wrapper. They review the pod shape, the CRDs, and the audit format. In the project’s own framing: security teams review YAML, not Python. Approval gates, rate limits, tool allowlists, content-safety floors, token budgets, and trust topology are declarative Kubernetes resources — commit them to a repo, reconcile with Argo/Flux, audit with git log.


2.4 One mental model, three ways to run it


All three modes share the same KarsSandbox YAML. The difference is where it runs and what isolates it.



  • Local kind (recommended) — a multi-container pod: agent + router + init egress-guard. The real production shape, with the same NetworkPolicy and egress-guard as AKS. This is the dev loop to use, because what you test locally is what ships.

  • Local Docker — a single container with agent and router co-located. Fastest prompt/tool inner loop, but not the production shape.

  • AKS (production) — multi-container pod: agent (UID 1000) + router (UID 1001) + init egress-guard, with optional Kata + AMD SEV-SNP confidential containers (requires a Kata node pool).


Same CRDs. Same router code path. Same audit format. Same governance profiles. Graduating from local to AKS is a one-line CLI change, not a port to a new system.


And the on-ramp is unusually gentle for a Kubernetes project: npm i -g @kars-runtime/cli, then kars dev –release –target local-k8s. On first run you pick a provider, and kars brings up the controller, the encrypted mesh, and a sandboxed agent on a local kind cluster. The images are multi-arch (amd64 + arm64, native on Apple Silicon) and cosign-signed; –release pulls them, so there’s no Rust, no clone, no build.


3. BYO: what to watch for when you bring a coding-agent CLI into the contract



Now back to what the email actually asked for. My marketing friend doesn’t want OpenClaw, and he doesn’t want LangGraph. He wants the CLI he already knows how to use. That’s the BYO (Bring Your Own runtime) scenario.


And BYO is exactly where people get hurt — because the essence of BYO is: the platform gives you the skeleton for isolation and governance, but “how the agent actually runs” is back on you.


3.1 Trap #1: mistaking “it starts” for “it’s governed”


This is the misjudgement I see most. The documentation is unusually candid about it: the CLIs still use their native protocols to reach configured model services, and a BYO runtime integration does not imply that every optional KARS capability is enabled — Token Budget, Content Safety, complete /agt/evaluate evaluation for every native tool call, or cross-agent AgentMesh orchestration. Passing strict image-label or CR admission alone does not implement the entire runtime plugin contract.


In plain terms: a pod that comes up is not a pod that is governed. Your container can perfectly satisfy the upstream BYO quickstart image and HTTP adapter conventions — org.kars.runtime.contract=v1, UID 1000, writable /sandbox and /tmp, port 8080, and validation of SANDBOX_NAME and KARS_RUNTIME_CONTRACT_VERSION — while still shipping model calls straight out to the public internet.


So a real BYO adoption works through this list, in order:



  1. Deploy upstream KARS, enable controller.byoStrict=true, and push the runtime images to your own registry.

  2. Follow the matching CRD and controller implementation when mounting agent configuration and writable workspaces.

  3. Route Claude and Codex inference through the router’s /anthropic and /v1 endpoints when adopting zero-credential routing.

  4. Integrate /agt/evaluate for each CLI tool execution and route MCP through /mcp before claiming complete tool governance.

  5. Validate controlled egress and proxy compatibility separately for Copilot CLI’s native token authentication and network traffic.


That last one deserves emphasis. Copilot CLI authenticates differently from Claude and Codex — it is not a matter of pointing a base URL somewhere else. Any CLI that carries its own auth chain needs its governed egress validated on its own. Never by analogy.


3.2 Trap #2: the tools toggle is an authorization, not an approval


This is the most honest paragraph in the BYO Agent Studio project: agents use restricted tool settings by default, and enabling tools authorizes the CLI to run commands, modify its workspace, and invoke configured MCP servers inside its own runtime. This is a broad capability grant, not per-command approval. Disabling tools also disables configured MCP servers, but native restriction behaviour differs between CLIs, and it is not an additional operating-system isolation boundary.


Plenty of teams develop a dangerous illusion here: that the CLI’s own permission prompt is the security boundary. It isn’t. The real boundary is pod isolation and the router’s egress policy. The CLI-level switches are convenience configuration.


A related detail: the system writes the configured name into SKILL.md frontmatter so all three CLIs recognize the Skill consistently — but a Skill provides instructions; it does not grant tool permissions. If a Skill has to read files or execute a workflow, you must enable tools as well.


3.3 Trap #3: treating dev-mode credential storage as a secret vault


Credentials are used only by local configuration and the active runtime; they don’t enter the image build context or the process command line. But the docs say it outright: this is not a secret vault. The local user, Docker administrators, and tool-enabled agents can all access runtime credentials. Treat MCP environment variables and headers as secrets too.


And one boundary worth tattooing somewhere: do not expose the local control plane directly to the public internet. It is a single-user development workspace — no multi-user login, no application-level RBAC, no remote Docker TLS, no production-grade secret management.


3.4 Trap #4: only trusted MCP, and stop fetching tools with ad hoc npx


Only connect trusted external MCP servers, because they have their own permissions and side effects. On the implementation side: stdio MCP processes run inside the agent runtime, not on the host; when an additional executable is required, extend containers/Dockerfile — downloading tools with ad hoc npx calls is discouraged under a read-only root filesystem.


Behind that rule is a bigger principle: in a BYO world, capability should be declared by the image, not improvised at runtime. An image is auditable, signable, and rollback-able. Something npx pulled down mid-conversation is none of those things.


3.5 Trap #5: version drift


Runtime images pin CLI versions — for example Claude Code 2.1.263, GitHub Copilot CLI 1.0.83, Codex CLI 0.152.0. Copilot’s restricted mode uses a non-empty tool allowlist validated for that version; Codex uses native feature settings and bundled model metadata. Revalidate the adapters whenever a CLI version changes.


Coding-agent CLIs iterate weekly. In a BYO architecture, the CLI version is an infrastructure dependency, not a client you casually upgrade. Put it under change management, or one morning your tool allowlist will silently stop matching reality.


3.6 One more note, about documentation itself


The upstream quickstart README refers to k8s/karssandbox.yaml, while the example file on the current main branch may still be named k8s/clawsandbox.yaml — use the actual repository contents rather than relying on a potentially stale path. A small detail, but it makes a larger point: BYO means you are working against a contract that is actively evolving.


4. What it looks like in practice: KARS BYO Agent Studio


Enough principles. Here’s an example that lands all of them — KARS BYO Agent Studio.


It’s an agent workspace for three runtimes: Claude Code CLI, GitHub Copilot CLI, and Codex CLI. You create agents, configure MCP servers and Skills, and run streaming conversations from one UI. The project supports both local Docker development and an Azure deployment backed by AKS/KARS.


Which is exactly what my marketing friend needed: he never has to learn Kubernetes, and his agent still runs in a governed sandbox.


4.1 Creating an agent: four steps



  1. Select a runtime and enter a name, model, endpoint, and credential.

  2. Add agent instructions and optionally enable tools, MCP servers, and Skills.

  3. Save the agent, build and start its runtime, then create a Session in the shared chat.

  4. Start a Session with @AgentName to select an agent. Later messages keep that agent until another explicit @AgentName mention switches the executor.


The three runtimes differ sharply in what they need — which is the concrete form of “validate BYO per runtime”:


Runtime Credential Default model Endpoint
Claude Code CLI API key for an Anthropic-compatible service claude-sonnet-4-6 https://api.anthropic.com
GitHub Copilot CLI GitHub token accepted by the CLI with Copilot access gpt-6-astra Managed by Copilot CLI
Codex CLI API key for an OpenAI-compatible service gpt-5.4 https://api.openai.com/v1


The details are where deployments live or die. For Claude, enter the service root; the CLI calls the Messages API. For Codex, enter a base URL containing /v1, and the provider must implement the Responses API, not only /chat/completions. The Copilot credential must be a GitHub OAuth token supported by the CLI, or a fine-grained PAT whose account has Copilot Requests access — a classic PAT is not supported, and an arbitrary OpenAI key is not a Copilot credential. And the single most common local-debug trap: from a local runtime container, reach a model service on the host with http://host.docker.internal:<port>, not localhost.


Also worth knowing: an agent draft may be saved without credentials, but chat stays unavailable until valid credentials are configured — and runtime type is immutable after creation; to switch runtimes you create a new agent.


4.2 Long-running tasks and deliverables: where business work really differs


This section shows better than any other how different non-coding work is from coding work. Generating a complete deck can run for ten-plus minutes — far beyond an ordinary conversational turn.


The Website and KARS runtime send heartbeats every 15 seconds to keep long file-generation streams alive. The Website does not impose an early deadline on an active task. The runtime allows an agent to run for 60 minutes by default, and CHAT_TIMEOUT_MS can configure a limit between one minute and 24 hours.


More importantly, disconnect resilience: if the browser-to-Website stream is interrupted, the agent continues in the background. The UI polls the current Session and restores the final response and deliverables when the task finishes. Only an explicit Stop generating action cancels the runtime task. Close the laptop, ride through a tunnel — the work isn’t wasted.


And a beautifully business-shaped engineering detail: suppressed tool events have a 64 MiB default budget, so long-running PPT jobs aren’t killed when intermediate tool output crosses 4 MiB. User-visible assistant text remains capped at 4 MiB, and the structured output budget is configured with CLI_STRUCTURED_OUTPUT_LIMIT_BYTES. Anyone who has generated Office documents programmatically will smile at that — the intermediate output is far bigger than the final answer.


Generated files are surfaced below the assistant response, with type-aware previews: Word, Excel, and PowerPoint are converted to PDF with LibreOffice for preview while the download stays the original file; PDFs use a native embedded viewer; HTML renders in a sandboxed iframe without script permission; SVG/PNG/GIF/JPG/WebP get native image preview; Markdown is safely rendered as GFM.


The security boundary runs all the way through to deliverables: tool results are excluded from browser output and Session persistence, and deliverables are read only from the matching agent workspace after passing path-traversal, symlink, extension, file-count, and size checks.


4.3 Skills: putting your business knowledge into the agent


This is the decisive piece for non-coding business agents. Skill names use lowercase letters, numbers, and hyphens. Each Skill is uploaded as a ZIP of at most 5 MiB, with SKILL.md either at the ZIP root or inside its only top-level directory, and the archive may include scripts, templates, and other resources.


The cross-runtime consistency is the design highlight here. Uploaded Skills are safely extracted into persistent agent storage and synchronized to /sandbox/agents/<agent-id>/skills/<skill-name>/ before each KARS conversation, which avoids losing Skills when a KARS pod restarts. From there the runtime maps them to each CLI’s native location:



  • Claude Code: ~/.claude/skills/

  • GitHub Copilot CLI: <workspace>/.github/skills/

  • Codex CLI: ~/.agents/skills/


One ZIP, three runtimes. Which means “how our company writes a monthly report” — your actual institutional asset — is not bound to any single model vendor.


4.4 Architecture: control plane and execution plane, cleanly split



The project supports Azure-managed and local Docker modes. In Azure, the Website/control plane runs in Azure Container Apps and agents execute in a KARS Sandbox on AKS.


The control plane owns agent/provider/Skill configuration, Session and message persistence, the browser stream plus background recovery, artifact metadata/preview/download proxying, and Managed Identity authentication to AKS. Persistent state — configurations, credentials, ZIP Skills, Sessions and history — lives on Azure Files.


The execution plane is the pod described earlier: the BYO agent container at UID 1000 with a read-only root filesystem, next to the KARS governance/runtime — inference router, egress guard/proxy, network and tool policy. What’s installed inside is emphatically business-shaped: the three CLIs, Chromium and curl, LibreOffice, and MCP clients. And the browser gets no exemption — the chrome, chromium, google-chrome, and google-chrome-stable commands all use the KARS egress proxy and do not bypass sandbox network governance.


The agent’s working area splits three ways under /sandbox/agents/<agent-id>/: workspace (generated deliverables), home (native CLI configuration), and skills (synchronized ZIP Skills).


One last trade-off I genuinely admire: Sessions are portable transcripts, not converted native session IDs shared among the three CLIs — and oversized conversation context fails explicitly rather than being silently truncated. Silent truncation is the most insidious bug in agent products; the user never learns why the agent suddenly “forgot.” Failing loudly is the right call.


5. What we are actually building


Back to the email. Here’s how I’d answer it today:


Yes, you can roll it out to the department. But what you’re building isn’t “a bigger Claude Code” — it’s a layer of agent infrastructure.


That layer has four levels you have to think through:


Level 1 — Runtime: embrace heterogeneity, don’t place a bet. Coding-agent CLIs became the best general-purpose business agent runtime because they bundle the filesystem, the shell, the iteration loop, and the extension protocols into one thing. But this space changes monthly. Your architecture should let Claude Code, Copilot CLI, and Codex CLI coexist, with users choosing per task — rather than the company betting on one. Note that runtime type is immutable after an agent is created: that constraint is itself a reminder that runtime choice is a one-shot decision, which is exactly why it shouldn’t be made once for the entire company.


Level 2 — Governance: put the boundary where the agent can’t reach it. KARS’s answer is that the trust boundary is the pod, not the cluster perimeter. The agent has no network of its own, credentials live in a container under a different UID, every egress is checked at L7, and every decision lands in a hash-chained audit log. Anything you restrict inside the agent container is convenience. Only what you restrict outside it is security.


Level 3 — Knowledge: this is your moat. Models change. CLIs upgrade. But “how we write a quarterly review, what the dealer-briefing definitions are” is your asset. Write it as a Skill, feed one ZIP to all three runtimes — that is the only way a business agent becomes genuinely portable.


Level 4 — Experience: long tasks and deliverables are the watershed. Fifteen-second heartbeats, a 60-minute default, background continuation across disconnects, Office-to-PDF previews, a 64 MiB intermediate-output budget — not one of these is an “AI capability,” and without any one of them my friend’s deck never gets generated. Most of the success or failure of a non-coding business agent lives in engineering details like these.


One closing note for fellow advocates. KARS is not an officially supported product; it’s in active development, CRDs are at v1alpha1 and that surface may change between minor releases, while the data path, security model, and audit chain are stable. Its README carries something I find genuinely admirable: a Known limitations list, introduced with the line “We would rather you find these in this list than in production.”


That candour is the real value of a reference implementation. You aren’t buying a promise — you’re reading an engineering answer sheet on how to run agents safely. And for those of us who do advocacy, the message worth carrying isn’t “look how powerful this is.” It’s this: we finally know where to draw the lines.

Please check this repo –
https://github.com/kinfey/kars-byo-demo 


Further reading


Microsoft Tech Community originally posted this article on 22 September 2026 at 8:00 AM.

Leave a Reply