ublo
bogdan's (micro)blog

bogdan

bogdan » A minimum viable platform for enterprise ai agents

07:38 pm on Aug 8, 2026 | read the article | tags: ,

I’ve been thinking lately about what happens when AI agents stop being something that a few people experiment with and become a normal way of doing work inside a company.

The user experience is already surprisingly good. I can ask Claude or ChatGPT to inspect something, write some code, search through documents or interact with other applications. With a little configuration, an agent can use Slack, GitHub, Jira or whatever else I happen to need.

The problems start when I want to treat this as infrastructure.

Imagine I tell an agent:

look at the open production incidents, check whether any recent deployment correlates with them, ask the relevant engineers for context and give me a summary tomorrow morning.

There’s nothing particularly exotic about the task. The agent needs to read a few systems, send a couple of messages and wait.

Still, several questions appear immediately.

Whose permissions does it use? Where are the credentials stored? What happens if one of the Slack messages contains a prompt injection? Does the task stop when I close my laptop? If my GitHub permissions are revoked while the agent is sleeping, can it still act tomorrow? If it decides that restarting a service would help, who makes that decision? And, two weeks later, can somebody reconstruct what actually happened?

These are mostly old infrastructure problems showing up in a new place.

So I tried to design the smallest platform I would be comfortable deploying in a company where agents are expected to become common.

There is a scale assumption hidden in that sentence. If a company has two read-only agents, each talking to one API using tightly scoped credentials, most of what follows is probably unnecessary. The platform starts earning its cost once multiple teams are deploying agents, tasks become long-running, agents can create side effects, integrations are shared, or somebody needs to answer audit and incident-response questions afterwards.

Starting with one assumption

The most useful assumption I found is that the agent harness should not be a security boundary.

By harness I mean the code around the model: Claude Agent SDK, OpenAI Agents SDK, LangGraph, Google ADK or some internal loop that calls a model, gives it tools and keeps going until the task is done.

There will probably be many of them.

More importantly, the harness is processing untrusted information all the time. A GitHub issue can contain instructions. A web page can contain instructions. A PDF can contain instructions. A tool response can contain instructions. The model itself can simply make a mistake.

Trying to make every possible harness perfectly safe doesn’t look like a useful enterprise strategy.

⚠️ I would let the harness decide what it wants to do and move the decision of what it may do somewhere else.

For example, the model can produce:

github.pull.comment(
    repository = "payments",
    pull_request = 842,
    text = "..."
)

The platform decides whether that call actually reaches GitHub.

This distinction simplifies quite a lot of the architecture.

Alice and the agent acting for Alice

The next problem is identity.

Suppose Alice can read a repository, merge pull requests and change repository settings. Giving an assistant Alice’s full GitHub access is technically convenient and a fairly bad default.

The useful permission set is closer to:

So Alice may be able to administer a repository, while a code-review agent acting for Alice can only read code and comment on a particular pull request.

For more sensitive jobs I wouldn’t delegate the capability to a generic assistant at all.

A production deployment agent, for example, can be registered independently, have a dedicated owner, a small set of tools and a separate approval policy. The general assistant can ask that specialized agent to perform a job; it doesn’t acquire the production role itself.

There are already standards that give us most of the primitives needed here. OAuth token exchange, standardized in RFC 8693, can represent a user as the subject and another actor acting on the user’s behalf. Workload identity systems such as SPIFFE solve the related problem of identifying the process making the request.

In this architecture, token exchange is also where I would derive short-lived, audience-restricted authority for the particular run. The harness doesn’t need Alice’s refresh token or another persistent credential. It receives, or ideally merely carries the context for obtaining, authority constrained to something like run-12345, the intended resource and the permissions assigned to that run.

The important part for an MVP isn’t adopting every possible identity mechanism from day one.

It’s preserving three identities separately:

Alice
PR-review-agent
run-12345

Once those three values disappear into a shared service account, most of the interesting governance possibilities disappear with them.

Tools belong behind a gateway

I would apply the same principle to credentials.

An agent shouldn’t receive Alice’s Slack cookie, a GitHub PAT or an OAuth refresh token and then be trusted to behave responsibly with it.

Instead, enterprise tools go behind a gateway.

MCP is convenient here because it gives agents a common way to discover and call tools. MCP is also moving toward more centrally managed enterprise deployments: its Enterprise-Managed Authorization extension allows an organization’s identity provider to participate in how access to MCP servers is provisioned and authorized. I wouldn’t make the architecture depend on every MCP client implementing that model correctly, but the direction is useful.

From the agent’s point of view, things remain simple:

slack.search
slack<.send
github.pull.read
github.pull.comment
jira.issue.read

The gateway performs the less interesting work.

It verifies who is calling, checks the user and agent policy, applies resource constraints, obtains whatever downstream credential is necessary, performs the call and records the result.

For the MVP, I would treat authorization policy like any other versioned infrastructure configuration. Something like OPA/Rego, Cedar or an equivalent policy engine can express the rules; the exact choice matters less than making the policy reviewable, testable and attaching its version to every consequential decision.

A rule might express:

agent = pr-review-agent
user = Alice
repository = payments
action = github.pull.comment
expires < 17:00

The credential never needs to enter the model context.

It doesn’t necessarily need to enter the harness process either.

This is useful even if prompt injection becomes dramatically easier to detect.

Security scanners and model guardrails can reduce the number of bad requests. Authorization still has to decide whether a request is legal.

If the incident-analysis agent suddenly requests:

github.repository.delete(...)

the answer should come from policy rather than from our confidence in the prompt.

There is an obvious cost to this design: every tool call now crosses another network boundary and may involve policy evaluation or credential brokerage. A chatty agent making dozens of calls will notice that latency. Some decisions and short-lived credentials can be cached safely, but the gateway is still deliberately on the critical path.

It also becomes critical infrastructure. For write operations, I would rather fail closed during a policy-engine or credential-broker outage than let the harness bypass the gateway. That availability trade-off is part of the price of making the gateway the authorization boundary.

Moving the task away from the laptop

So far this still works with an agent running on somebody’s MacBook. That stops being convenient as soon as tasks take a long time.

The incident example might send a Slack message and then wait six hours for somebody to answer. A procurement agent may wait two days for an approval. Another task may need to run every Monday morning. At this point I don’t think the employee’s computer should have anything to do with the lifetime of the task.

The useful invariant is:

worker process lifetime != workflow run lifetime

A worker may exist for a few minutes while the run may exist for two days.

For an MVP I would use Kubernetes for compute and something like Temporal for the logical execution. They solve different problems: Kubernetes gives me workers, scheduling, resource limits, network policies and workload identity, while Temporal gives me a workflow that can sleep, survive a worker crash, wait for an external signal and continue later from durable state.

So the incident investigator can query GitHub, send three Slack messages and reach:

waiting for replies

The worker that performed those operations can disappear completely. Six hours later, a Slack event wakes the workflow, Kubernetes gives the platform another worker, and the task continues.

A laptop can therefore initiate:

run incident-investigator

receive:

run-12345 started

and disappear.

This also gives the company a very simple API to expose to clients:

start run
get run
signal run
cancel run
read run events

Claude, ChatGPT, an IDE plugin or another agent can all sit in front of the same API.

I wouldn’t make MCP the internal runtime protocol. I would probably expose a boring REST or gRPC API and put an MCP adapter in front of it for clients that speak MCP. That gives us the option to replace protocols on either side without rewriting the execution system.

The agent registry can initially be boring too

A company running enough agents eventually needs to know which ones exist. For the first version, I don’t think this requires a complicated product.

A Git repository containing reviewed agent definitions would probably be enough. Something like:

id: incident-investigator
owner: sre-platform

implementation:
  image: registry/incident-agent@sha256:...

tools:
  - datadog.query
  - kubernetes.read
  - pagerduty.read

max_runtime: 4h
max_llm_budget: 25

risk_class: medium

The important properties are ownership, immutable versions and declared authority.

The runtime shouldn’t execute incident-investigator:latest. It should know which image digest, policy version and skill versions belong to the run.

Limits such as max_llm_budget also have to be enforced rather than documented. The runtime and LLM gateway should account for spend per run and, eventually, across concurrent runs belonging to the same agent, team or cost center.

The same idea applies to skills. Skills are becoming a convenient way to package instructions, reference material and scripts for agents. They can initially live in an ordinary reviewed repository and later be distributed as signed immutable artifacts using the same OCI and Sigstore infrastructure already used for software artifacts.

I don’t think a sophisticated skill marketplace belongs in the MVP.

One invariant does:

⚠️ Loading a skill cannot give an agent more permission than the run already has.

A Kubernetes troubleshooting skill may declare that it works better with kubernetes.pod.delete.

If the agent only has kubernetes.pod.read, the skill doesn’t get to fix that inconvenience by changing the security policy.

The slightly annoying distributed systems part

There is another problem that is easy to miss while drawing boxes.

Suppose the agent sends a Slack message. Slack receives it successfully. Immediately afterwards the worker crashes, before recording that the operation succeeded. The runtime wakes up on another machine and sees an unfinished step.

Should it send the message again?

This isn’t really an AI problem. It’s the usual distributed-systems problem of performing external side effects when failures are ambiguous.

Every tool invocation that can produce a side effect should therefore receive a persistent identifier:

tool_call_id = 7df1...

That identifier belongs to the logical operation, not to the worker or retry attempt. If the request times out and another worker retries it, the same tool_call_id is reused.

For APIs that support idempotency keys, the gateway passes that identifier downstream. A service that has already processed 7df1... can return the original result instead of repeating the action.

For APIs that don’t support idempotency, the connector needs another strategy: query whether the operation happened, deduplicate at the gateway, compensate afterwards or explicitly accept that the operation can happen more than once.

I would make these semantics part of the tool definition. An agent calling a tool should be able to know whether retrying it is harmless.

It’s boring infrastructure, yet it also becomes quite important once the software making the decision can autonomously generate hundreds of side effects.

Approvals should approve an action, not an agent

Let’s return to the incident from the beginning.

The investigator has read the monitoring data, correlated the incident with a deployment and waited for the engineers to respond. Eventually it concludes that restarting payments/prod/api is the most reasonable next action.

The investigator itself only has read permissions, but it can nevertheless request:

kubernetes.deployment.restart(
    cluster = "production",
    namespace = "payments",
    deployment = "api"
)

Policy classifies the request as requiring human approval.

The naive implementation is to pause the agent, ask Alice to approve, obtain a fresh Alice token and resume the agent using Alice’s authority.

That gives the agent more than Alice intended to approve.

⚠️ Approvals should approve an action, not an agent.

I would create something closer to:

run:       run-12345
action:    kubernetes.deployment.restart
resource:  production/payments/api
arguments: sha256(...)
expires:   15 minutes

Alice approves that object.

Before execution, the gateway verifies that Alice is allowed to approve production restarts, that the original run is still authorized, and that the agent is still attempting exactly the operation Alice saw. The approval is tied to the hash of the arguments.

If the agent changes the target from:

production/payments/api

to:

production/billing/api

the approval is useless. It needs another one.

Temporal happens to make waiting for this kind of signal convenient, but the security property doesn’t depend on Temporal. The approval is narrow and disposable.

There is a UX problem hiding here too. If an agent asks for approval twenty times a day, people will eventually approve requests without reading them. High-risk approvals therefore need to be relatively rare and present enough context to make the decision understandable; routine operations should be covered by explicit policy rather than repeated confirmation dialogs.

Reconstructing what happened

If the platform is going to mediate these actions, it should leave enough information behind to investigate them.

I don’t think this means recording every internal model thought. It means preserving a useful decision trace:

run started by Alice
agent version 14
policy version 31

monitoring query → result
GitHub query → deployment 8f72...
Slack messages → responses

agent requested restart production/payments/api

policy → human approval required
Alice approved tool_call_id 7df1...

restart executed → success

For consequential operations, I would want to correlate the model and tool interaction that preceded the request with the authorization decision and the eventual side effect.

That gives an operator a reasonable answer to:

⚠️ why did this run restart that deployment?

without pretending that storing private chain-of-thought is either necessary or desirable.

What I would actually build first

After removing everything that can reasonably wait, the first version becomes fairly small.

  • I need the company’s existing identity provider.
  • I need a registry containing approved agents and their capability ceilings.
  • I need an authenticated run API.
  • I need Kubernetes and a durable workflow engine so the run survives the process executing it.
  • I need one gateway for enterprise tools, with credential brokerage, versioned authorization policy and structured audit events.
  • I need the existing company LLM gateway, assuming one already exists, for model routing, budgets, data policy and basic guardrails.
  • I need enough structured run history to correlate tool observations, authorization decisions and actual side effects.

That’s enough to run a useful class of agents.

I would leave long-term agent memory, universal multi-agent protocols, a microVM requirement for every workload, a sophisticated internal skill marketplace and any attempt to standardize how agents plan or reason outside the first version. Agent-behavior evals belong in deployment CI as well—testing whether an agent behaves sensibly within its allowed envelope—but I would keep that separate from the runtime authorization mechanism itself.

The architecture I’d start with is roughly this:

Identity and policy follow the run through the whole system. The harness remains replaceable.

That last part is probably the reason I like this design.

A year from now the agent loop we consider state of the art will almost certainly look different. Models will get better, frameworks will disappear, new ones will become fashionable and some of the things we currently implement in harnesses will move into models themselves.

I don’t see a similar reason for GitHub credentials to move into prompts, for a six-hour task to depend on a laptop staying open, or for an agent framework to decide whether an employee is allowed to restart a production service.

So for a first enterprise agent platform I wouldn’t try to build the perfect agent runtime. I’d build a small execution environment around the parts that are unlikely to change: identity, delegated authority, durable execution, controlled side effects and enough evidence to understand what happened afterwards.

The agents can change on top of it.

aceast sait folosește cookie-uri pentru a îmbunătăți experiența ta, ca vizitator. în același scop, acest sait utilizează modulul Facebook pentru integrarea cu rețeaua lor socială. poți accesa aici politica mea de confidențialitate.