We Built an AI Agent Platform on .NET. Then Microsoft Shipped Agent Framework 1.0.
Six architecture mistakes from building an internal agent platform on Semantic Kernel — and the handful of decisions that meant the ground…

We Built an AI Agent Platform on .NET. Then Microsoft Shipped Agent Framework 1.0.
Six architecture mistakes from building an internal agent platform on Semantic Kernel — and the handful of decisions that meant the ground moving under us barely hurt.
On April 3, 2026, Microsoft shipped Agent Framework 1.0.
I read the announcement on my phone and recognized our codebase in it. Not metaphorically. The provider-agnostic chat client, the graph-based workflow engine, the handoff orchestration — we had built worse versions of all three over the previous several months for an internal agent platform. Microsoft had just shipped the good versions, stamped them 1.0, and committed to long-term support.
Semantic Kernel — which we’d built the whole platform on — went into maintenance mode the same day. Security patches, no new features. The framework under our platform was now a legacy framework.
This isn’t a complaint about Microsoft. Converging Semantic Kernel and AutoGen into one framework is the right call, and the API they shipped is better than what we had. This is a post about what we got wrong: the decisions that made the ground moving under us hurt more than it should have, and the few decisions that meant it barely hurt at all.
If you’re building agents on .NET right now, some of this will save you a quarter.
What we built
The short version: an internal agent platform for our engineering org. Not customer-facing. The agents did unglamorous things — answered questions against our internal docs and runbooks, triaged incoming support tickets to the right team, and gave first-pass feedback on pull requests. A few specialized agents, a shared orchestration layer, Azure OpenAI behind all of it, a vector store for retrieval.
We chose Semantic Kernel because it was the most mature .NET-native option and we didn’t want to run a separate Python service just for the AI layer. That decision was correct. Most of what came after it is where we went wrong.
Mistake 1: We hand-rolled the orchestration
When the ticket-triage agent needed to hand off to a billing-knowledge agent, that handoff was our code. A switch statement, a queue, state threaded through by hand. Sequential chains, fan-out to parallel agents, the handoff pattern — all bespoke.
It worked. It was also around four hundred lines of orchestration logic that existed only because the framework didn’t offer it yet. Agent Framework ships exactly these patterns — sequential, concurrent, handoff, group chat — as a graph-based workflow engine with streaming built in:
// What took us ~400 lines is now this
Workflow workflow = AgentWorkflowBuilder.BuildSequential(triageAgent, billingAgent);
Orchestration was always going to commoditize. Sequential and handoff and fan-out are the obvious shapes; every framework converges on them. Building them yourself before the ecosystem settles means you own hundreds of lines that a 1.0 release makes redundant.
The lesson isn’t “never build orchestration.” It’s: if you’re early, keep that layer as thin as you can stand, and put it behind your own interface so replacing it later is a contained change instead of a rewrite.
Mistake 2: We hard-coupled to one model provider
Azure OpenAI was wired through everything. Not behind an interface — the concrete AzureOpenAI types showed up in our agent classes, our prompt construction, our token counting. We told ourselves we'd abstract it later if we ever needed a second provider.
Then we needed a second provider. A cheaper model for the high-volume triage agent. A local model via Ollama for a data-sensitive workflow that couldn’t leave our network. Each of those was a change in roughly fifteen files.
Agent Framework programs against IChatClient — the evolution of SK's IChatCompletionService — and the design is that every provider's NuGet package ships its own implementation. Anthropic's SDK, AWS Bedrock, ONNX Runtime GenAI, Ollama: each exposes an IChatClient. Swapping providers becomes a one-line DI change:
// Pick the provider once, at composition root
IChatClient chatClient = useLocalModel
? new OllamaChatClient("http://localhost:11434", "llama3.3")
: azureChatClient;
ChatClientAgent triageAgent = new(chatClient, instructions, name: "triage");
This one wasn’t the framework’s fault. The abstraction was available the whole time and we chose not to use it. “We’ll abstract it later” is the most expensive sentence in this entire post. Program to IChatClient from the first commit.
Mistake 3: We treated tool-calling as a private concern
Our agents called tools — query the docs index, look up a ticket, fetch a PR diff. We hand-wrote the function schemas, registered them with the kernel, and maintained the JSON contracts by hand. Every tool was bespoke glue specific to our platform.
The industry standardized this while we were heads-down. Model Context Protocol is now how agents discover and call tools, and Agent Framework treats MCP as first-class alongside agent-to-agent (A2A) communication. A tool exposed over MCP is reusable across agents, across frameworks, across languages. Our hand-wired tools are reusable across exactly nothing.
When a protocol emerges for something you’re currently doing ad hoc, the protocol wins. Not because it’s technically superior on day one, but because reusability compounds and bespoke glue doesn’t. We should have wrapped our tools as MCP servers the moment MCP had real traction. Today it isn’t optional — it’s the default, and building tools any other way is building a migration for yourself.
Mistake 4: We shipped without evals
This is the one I’d undo first.
We tested agents the way you test a demo. Run it, read the output, looks good, ship. No eval suite. No golden set of inputs paired with expected behavior. No regression detection. And we changed prompts constantly — so every change was a blind change.
We found out in production. A prompt tweak that improved triage accuracy on the cases we eyeballed quietly degraded it on a category we didn’t look at. We noticed three weeks later, from a complaint, not a dashboard.
Agent Framework’s observability story is better than what we had — but no framework saves you here. Evals are your code and your responsibility. The framework cannot know what “correct” means for your triage agent. Only you can encode that.
An agent without an eval suite isn’t a product. It’s a demo that happens to be running in production. Build the eval harness before the second prompt change, not after the first incident.
Mistake 5: We had no cost ceiling
Token spend was invisible. We knew the monthly Azure bill; we did not know which agent, which workflow, or which user was responsible for it. A retrieval agent that over-fetched context — stuffing far more into the prompt than the model needed — was quietly the most expensive thing we ran, and we didn’t find that out for two billing cycles.
There was also no ceiling. No per-run token budget, no daily cap. An agent in a bad state could spend real money fast, and nothing would stop it.
Instrument cost per agent run from day one: tokens in, tokens out, model, workflow, tagged by agent. Treat it like any other production metric, on a dashboard, with alerts. And put a hard ceiling somewhere — per run, per day, per agent. The framework gives you the hooks. You have to decide the numbers, and you have to decide them before the bill does.
Mistake 6: We let the agent loop run unbounded
An agent that can call tools can loop. Call a tool, read the result, decide to call another, repeat. Usually it terminates in two or three steps. Occasionally it doesn’t — a tool returns something ambiguous, the model decides to try again, and now you have an agent burning tokens in a circle.
We had no max-iteration guard for the first few months. We added one after a code-review agent spent a genuinely embarrassing amount of money re-analyzing the same diff, because a tool kept returning an error it didn’t know how to interpret and the agent kept “trying again.”
Every agent loop needs a hard iteration cap and a circuit breaker, the same way every retry policy needs a maximum attempt count. This is not an edge case you might hit. It’s an edge case you will hit. Bound the loop before you ship it.
What actually survived
Not everything we built was wrong — and the difference between what survived the migration and what didn’t is the real lesson here.
What didn’t survive: the orchestration layer, the provider coupling, the hand-wired tools. Everything that was a bet on the framework’s shape.
What survived almost untouched: our domain logic — the rules for which team owns which ticket category, the structure of a useful code-review comment, the retrieval logic tuned to our specific doc corpus. The eval harness, once we finally built it. The cost guardrails. Everything that was about our problem rather than about the framework.
That’s the line. The parts of an agent platform that are genuinely yours — domain modeling, evaluation, guardrails, business logic — are stable, and worth investing in early. The parts that are plumbing — orchestration, provider abstraction, tool transport — are commoditizing fast, and anything you over-build there is a short-term loan against a framework that will eventually ship the real version.
We migrated to Agent Framework over a few weeks. The migration was annoying in exact proportion to how much plumbing we’d hand-rolled. The domain logic moved across almost as-is.
If you’re starting today
Building on a pre-1.0 ecosystem means accepting that the ground will move. That’s not a reason to wait — waiting has its own cost, and the teams that started early learned things the teams that waited still haven’t. It’s a reason to build so that being wrong stays cheap.
We weren’t wrong to build an agent platform on .NET. We were wrong about where to spend the effort. We poured months into plumbing and bolted on the evals and cost controls as afterthoughts, when the correct split was the exact opposite.
So: start on Agent Framework 1.0. Program to IChatClient. Expose your tools over MCP. Write your first eval before your second prompt, and put a token ceiling and an iteration cap in before you ship. The plumbing is a solved problem now. Spend your months on the part that's actually yours.
메타데이터
- post_id
- b3a778dd96d6
- slug
- we-built-an-ai-agent-platform-on-net-then-microsoft-shipped-agent-framework-1-0-b3a778dd96d6
- url
- https://medium.com/@krativarshney7/we-built-an-ai-agent-platform-on-net-then-microsoft-shipped-agent-framework-1-0-b3a778dd96d6
- canonical_url
- https://medium.com/@krativarshney7/we-built-an-ai-agent-platform-on-net-then-microsoft-shipped-agent-framework-1-0-b3a778dd96d6
- author_url
- https://medium.com/@krativarshney7
- status
- ok
- fetched_at
- 2026-06-09 14:34:10