← All Writing

Running Agents Is a Platform Problem

Microsoft's Agent Harness reached GA the same week InfoQ published Deutsche Telekom's LMOS talk, and the two argue the same point from opposite ends of the market: the hard part of running agents is not the agent. In ShipMore, the product I sell on an agent-first story, the entire agent-facing surface is 703 lines against 22,769 lines of service layer behind it.

Dark navy diagram of an agent pipeline: five rounded blocks outlined in thin glowing blue sit in one horizontal row, linked by a straight blue line that runs off both edges of the frame, so the chain continues beyond what is shown. Four of the blocks are wide and completely empty. The second block from the left is much smaller, barely more than a square, and it is the only one holding anything: a single warm amber circle with a soft glow around it. The agent surface is that one small lit stage; everything to the left and right of it is plain infrastructure.

Two items landed on the same day and make the same argument from opposite ends of the market.

Microsoft moved the Agent Framework’s Agent Harness and Foundry Hosted Agents to general availability. InfoQ published Arun Joseph’s talk on LMOS, the agentic platform he built as head of AI engineering at Deutsche Telekom before leaving to found Masaic. One is a vendor selling a runtime. The other is an operator describing what he had to build after the SDKs ran out.

They converge on a claim I can check against a product I ship: the hard part of running agents is not the agent.

Both ends of the market arrive at an operating system#

Read what Microsoft says the harness ships with, enabled by default: “function invocation, per-call history persistence, context compaction, a todo list with plan and execute modes, file memory, skills, web search, tool approval, and built-in OpenTelemetry”. Persistence, scheduling, memory, capability approval, telemetry. Strip the vocabulary and that is a process supervisor’s feature list. The framing in the announcement is explicit about which questions it answers: “where agents execute, what they are allowed to touch, and how their behavior surfaces in existing observability and policy systems”. Principal engineer Wes Steyn’s line for why any of it exists is that “a model on its own can only generate text”.

Joseph arrives from the other side. His account of enterprise agent work is that every vendor turns up holding an SDK, one for evals, one for telemetry, one for memory, and each one costs a license and a container, until “one line of code is now five containers”. LMOS went the other way and pulled session handling, context, telemetry fan-out, model switching and remote tool execution down into platform primitives, so the agent itself stays small. He compresses it into two rules: build agents with existing teams and stacks, and platformize the hard parts and get out of the way.

Neither of them is describing a model problem. Both are describing what has to exist around the model before anyone will let it write to something that matters.

The agent surface is the small half#

ShipMore is a multi-tenant CMS I build and sell, and agents are a first-class user class in it rather than a bolt-on. A Claude Code session, or any MCP client, can create pages, add blocks and drive a data import. The agent story is a large part of why anyone buys it, so if the SDK were the hard part, it would show up in the code.

The entire agent-facing surface is four files.

src/domains/cms/mcp/page-tools.ts 447
src/domains/cms/mcp/tenant-tools.ts 137
src/domains/cms/mcp/composer-tools.ts 91
src/domains/cms/payload/plugins/plugin-mcp.ts 28

703 lines. The domain and service layer those tools call into is 22,769. Roughly thirty-two lines of platform for every line of agent interface, in the product whose agent access is the selling point.

The handlers themselves are deliberately dull. A tool translates its arguments, calls a service, and wraps whatever comes back:

src/domains/cms/mcp/page-tools.ts
handler: async (args: Record<string, unknown>, req: PayloadRequest) => {
try {
const result = await PageService.createPage(req.payload, args as PageService.CreatePageInput)
return ok(result)

That is a rule in the architecture doc, not a habit that happened to hold:

docs/agent-patterns.md
MCP tool handlers, REST handlers, CLI scripts, and webhook handlers are **adapters** — they translate inputs, call a service method, and return the result. No business logic lives in them.

Every capability an agent has is a thin door onto something a human interface can reach the same way. The MCP server is not where the work is.

What the platform owns that no prompt can#

Three things from that codebase, because the general version of this argument is boring and the specifics are not.

The gate. An agent does not publish. It proposes, and the write lands as a draft in a review queue that a human clears.

docs/agent-patterns.md
Agents propose changes as **drafts** by default. Humans review and approve before
anything goes live. Every agent action is auditable and reversible via Payload's
version history.

The default is enforced at the tool boundary, so an agent has to opt out of it explicitly rather than remember to opt in: Pages are created as drafts by default. Returns the created page document.

Ordering and concurrency. Data import stages through DuckDB, and two operators running an import against the same tenant at once would overwrite each other’s staging table. The fix is a lock, and the reasoning behind it is the kind of thing no prompt is in a position to decide:

src/domains/import/staging-lock.ts
/**
* Per-tenant session lock for the DuckDB staging table.
*
* V1 constraint: only one `schema infer` → `import` flow at a time per tenant.
* Two concurrent CLI processes targeting the same tenant would otherwise clobber
* each other's staging table (`CREATE OR REPLACE TABLE shipmore_staging_<id>`),
* so the second operator could silently read the first operator's data.
*/

That is a property of how processes interleave, decided before any model is invoked and enforced whether the caller is an agent, a CLI or a webhook.

Reach. The tenant an agent may touch is an explicit argument, and the service refuses to infer it:

docs/agent-patterns.md
The service layer takes `tenantId` as an explicit argument. It **does not resolve**
"which tenants can this user access?" — that belongs to the Payload RBAC layer.

Bounding reach at the service boundary means a confused agent gets an authorization error rather than another tenant’s data. None of these three are model work. None of them ship in an SDK.

Where this loses#

The 32:1 ratio flatters the argument and I should say so. Most of those 22,769 lines are a CMS that would exist if no agent ever touched it: Stripe billing, auth, SEO, media. The number shows a product that grew an agent door, not the marginal cost of supporting agents. The honest figure is smaller, and I cannot cleanly separate the lines that exist because agents write to this system from the ones that would be there anyway.

The thin adapter is also somewhat self-fulfilling. I wrote the rule that says handlers hold no logic, so of course the handlers are small. Someone who puts orchestration in their tool layer gets a different ratio and might reasonably say the platform work simply moved.

And there is a real counterargument, published the same week from a third direction. LangChain took managed deepagents to public beta bundling evals, agent and user memory, tool OAuth and sandboxing, and described that layer as boring and undifferentiated. If it is boring, buying it is a legitimate answer, and the honest position is that I have not tested it against what I built.

The takeaway#

An SDK gets a model to call your functions. That was the hard part for about a year, and it is not the hard part now.

What is hard is everything with a clock, a tenant or a filesystem attached: what runs first, what the caller is allowed to reach, who decides the write is good enough to go live, and where the durable state sits when the model is wrong. Microsoft is now selling that layer, Deutsche Telekom built it on the JVM with the teams it already had and handed it to the Eclipse Foundation, and in my own product it outweighs the agent interface by more than thirty to one. Three very different budgets, same shape.

If you are picking an agent framework and have not yet decided who owns ordering, approval and state, you are picking the small half.

I’m a platform/SRE engineer writing about making agentic AI reliable in production. If you are standing up a runtime for agents rather than another SDK integration, get in touch.