How we avoid vendor lock-in in AI agents: an employent stack no provider owns

AI-generated image for Orbitant's blog about avoiding vendor lock-in in AI agents, featuring a central glass block connected to three separate glass blocks by cyan light lines against a dark background, symbolizing interoperability.

Picking a framework for your agents, a model provider, or a deployment host looks like just another technical decision — until that provider changes the rules and your whole architecture turns out to depend on them not doing it. When we built Mercuria, our first employent, the AI agent with an employee’s role, we decided that none of those three things would hold that kind of power over our architecture.

This article is about how to avoid vendor lock-in in AI agents without giving up the speed you get from leaning on third-party tools. Depending on somebody somewhere in the stack is unavoidable, but what decides whether that dependency ends up being expensive is which layer you accept it in and which one you don’t. The platform repository is still private: what we’re defending here, layer by layer, is which provider controls each one — not whether the code is open.

The brain is the asset; the runtime is disposable

The AI agent space changes direction every few months, so betting your entire architecture on this season’s harness still being the right call a year from now is the fastest way to end up trapped. That’s why we drew the portability boundary around the role’s knowledge, not around the runtime that executes it — a separation we call brain and harness, following the discipline of harness engineering: the brain is the role’s knowledge in markdown, versioned and published outside the code; the harness is everything else, disposable by design.

Mercuria’s brain lives in its own repository and ships as a public npm package (@orbitant/brain-marketing), pinned to a specific version with its lockfile. It gets updated through a reviewed pull request, which doesn’t get merged if the evals fail. The harness, for its part, hides every external integration (the model itself, Slack, Zoom, Notion…) behind a thin interface, so that swapping providers shouldn’t touch the knowledge the agent applies.

This thesis has already survived two real tests:

  • We reverted our first runtime choice the same day we proposed it. That change only required updating an Architecture Decision Record (ADR — the document we use to record every architecture decision): the delivery schedule didn’t slip at all.
  • Moving from a fixed default model to trying European open models through an OpenAI-compatible endpoint had zero knock-on effects anywhere else in the architecture: all we changed was the provider configuration.
 "platform": {   "models": {
-    "orchestrator": "anthropic:claude-sonnet-5",
+    "orchestrator": "openai-compatible:gemma4",
     "content": "anthropic:claude-sonnet-5",
     "image": "gemini-3.1-flash-image"
   },
   "timezone": "Europe/Madrid",
   "webhooks": { "port": 3000 }
 },

The proof that portability sits where it should is that reverting a runtime decision costs one document and zero replanning. If that boundary had been in the runtime, any change of framework would have meant rewriting the role’s entire knowledge base — which is exactly the cost we set out to avoid.

How to avoid vendor lock-in in AI agents: four layers, four decisions

Saying “no provider owns our stack” might sound like a slogan, but the moment there’s a way to verify it, it stops being a marketing promise. You have to break it down into the four layers where it actually gets decided, along with what each one proves:

LayerHow we avoid lock-inWhat it proves
ModelThe provider sits behind the AI SDK; we use open models for most of the work and reserve a frontier model only where we’ve measured that we need oneWe switched from a fixed default model to European open models through an OpenAI-compatible endpoint without touching anything else in the system
FrameworkMastra is sealed behind our own boundary (core/agent); no Mastra type escapes itIf we had to replace Mastra tomorrow, the impact would stay confined to the integration layer: we’d adapt the imports and a few wiring points, with no need to rethink the rest of the platform
KnowledgeThe role’s knowledge lives in portable markdown, versioned and published independently of the codeAnyone can install the exact version we use with a plain npm install, just like any other public dependency
InfrastructureThe path we’ve chosen is our own cluster, not a serverless hosting providerWe corrected an ADR in writing that had named one specific provider: the real requirement is “any host that runs Node persistently”, not a brand

The number that carries the most weight of the four is the framework one: two imports in a 176-line file is our entire exposure to Mastra. It answers two questions at once — why we didn’t pick a different alternative, and whether Mastra genuinely buys us anything — and it turns an argument about taste into a number anyone can check with a grep.

Each of the four decisions comes with a concrete cost we accepted: fewer things automated by default, more code of our own, and the odd future migration if the requirement changes. In exchange, no provider can unilaterally force us to rewrite the entire system.

Explanatory diagram for Orbitant's blog showing a 4-layer AI agent architecture (Model, Framework, Knowledge, Infrastructure) mapping internal code boundaries against replaceable vendors to prevent lock-in.
Explanatory diagram for Orbitant’s blog showing a 4-layer AI agent architecture (Model, Framework, Knowledge, Infrastructure) mapping internal code boundaries against replaceable vendors to prevent lock-in.

The graveyard of frameworks we never got to use

Ruling out an option you’ve never tried is cheap and unconvincing, so our search criteria didn’t stop at comparing feature tables: we wanted something high-level, with memory and automatic evals already solved, that wouldn’t marry us to one specific model provider and that fit our TypeScript stack. That filter got us to a short list of serious candidates. Most of them we didn’t rule out by reading the docs: we took them to real code before making the decision. These are the three blocks that kept us on the fence the longest, and why none of them ended up part of Mercuria’s harness.

Eve (Vercel): the one we liked most on paper

It was the option we liked most on paper: an agent as a folder with skills, channels, calendars and approvals, with durable execution and human review out of the box — almost our own vision, written by another company. We didn’t rule it out by reading their docs: we built a real pilot of the marketing employent on top of it and took it to production. What broke was the Slack Connect integration, with three separate incidents across message forwarding, token rotation and file permissions, plus a production layer still in beta and coupled to Vercel’s platform.

Hermes (Nous Research): same thesis, different problem

Hermes shares our underlying thesis — portable knowledge in markdown — but it solves a different problem: it rewrites its own skills with nobody reviewing them first, which breaks precisely what we consider the asset, brand voice. On that one specific point we needed the opposite: a source of truth reviewed by humans before it gets published.

Ruled out for being too low-level or the wrong stack: LangGraph, Agno, Letta and CrewAI

None of these four made it to the pilot stage: the comparison with Eve and Hermes was already enough to rule them out, each for a specific reason.

  • LangGraph: graph-based orchestration, ruled out for the same reason as the Claude Agent SDK — a level of abstraction too low for what we needed to solve around memory.
  • Agno, Letta and CrewAI: Python frameworks in a stack that is TypeScript, and on top of that, to varying degrees, too specialised for the problem we had in front of us.

A “we liked it” during the pilot validates the product, the voice and the content — not the harness holding it up. Separating those two conclusions is what saved us from marrying the wrong tool for the right reasons.

Why we reverted the Claude Agent SDK the same day we proposed it

Of the whole rejected list, the one that carries the most weight in this thesis isn’t Eve or Hermes: it’s Anthropic’s Claude Agent SDK. It was our first harness proposal, and we reverted it the same day the first review comment came in.

The reason wasn’t that it’s a bad SDK. It was two very specific things — the same two that explain the rest of this thesis:

  1. It’s low-level: we’d have had to build almost everything Mastra already ships with on top of it — memory, workflows that pause and resume, MCP and evals.
  2. It contradicts the principle everything else rests on: if the rule is model behind an interface with no provider coupling, then adopting the harness built by the model provider itself breaks that rule on the very first architecture decision, before you’ve even written a line of product.

Reverting it cost a single ADR and zero replanning of the work already done, which confirms the thesis all over again: nothing depended on that choice yet, because the role’s knowledge lived outside it from the start.

One clarification is worth putting in writing, because it’s easy to misread: the platform repository is still private. What anyone can check are two specific pieces we do publish: the knowledge package on the public npm registry, and the architecture decisions documented layer by layer, like the ones we’ve walked through here. The claim we’re defending is about which provider controls each layer of the stack, not about whether the code is open.

This is the first chapter in a series about how we’re building Mercuria out in the open. The next one covers what happened when a post’s draft stopped living in a Slack thread and became a row in Notion that a person reviews before anything gets published — the guardrail that makes it possible to show all of this without fear: nothing we build goes out the door without someone approving it first.

Follow our journey building AI agents from the inside