Disclosure up front: I build
[agentproto], whose spec
registry (the AIPs) is the last third of this piece. The problem in the first
half stands on its own, and the walkthrough uses real, checkable commands.
Corrections welcome — file an issue.
You spent an afternoon writing a good skill. A SKILL.md
, a couple of scripts,
a crisp description so the model knows when to reach for it. Claude Code picks it
up and uses it perfectly.
Now open Codex in the same repo. Nothing. The skill is invisible — wrong folder,
wrong format, wrong dialect. Point a cheap local model at the task and you get
the same blank stare. You wrote the capability once, and exactly one agent can
see it.
The one idea, if you remember nothing else:
Everyone converged on folders of markdown with zero interop. It's Ethereum
before ERC-20 — the primitive is proven, and nothing can hold anyone else's.
Open any serious agent-driven repo in 2026 and you find the same sediment: a
CLAUDE.md
, an AGENTS.md
, maybe a GEMINI.md
saying mostly the same thing; a
.claude/skills/
folder full of SKILL.md
; Codex agents in TOML; a .mcp.json
;
somebody's OPERATOR.md
. Every tool independently discovered the same primitive
— agent behavior configured as files in the repo — and every tool speaks its
own dialect of it.
This is a genuinely good primitive, and the convergence is evidence it's right.
Files are diffable, reviewable, versionable; they ride along in git; agents can
read and write them, which is what lets an agent improve its own tooling.
Anthropic's harness team, describing their long-running app builder (March
2026), put it plainly: "communication was handled via files."
Repository as constitution.Here's why this shape won: each new agent
session arrives like a new contributor with shell access but no knowledge of
your team's norms, so a shortAGENTS.md
acts as the repo's constitution for
agents. The pattern is settled. The interop is what's missing.
The dialects are the whole problem. They don't even agree on names: it's
skill.title
in one flavor, skill.name
in another, three incompatible spins on
AGENTS.md
. A capability you wire into one product's config is invisible to the
other four agents in your fleet — so you re-wire it, by hand, every time you add
a brand.
That manual re-wiring, with your name on it, is the interop tax — and it's the part of "orchestration" nobody sells you a fix for. Before we build one, do the
Where are you?0 duplicated declarations— you're single-agent, not
your problem yet.2–3(aCLAUDE.md
here, anAGENTS.md
there) — you're
paying the tax quietly, once per new brand.4+— youarethe interop
layer, by hand, and it costs an afternoon per new agent.
Watch where the platform layer is heading, because it validates the whole idea.
Anthropic's Managed Agents (April 2026) virtualizes the runtime: the session is
an append-only log, the harness is disposable, the sandbox sits behind one
generic execute
interface. Stable contracts between layers, so each layer swaps
freely.
Design it like an OS.The Managed Agents write-up is explicit about the
principle: to build infrastructure that accommodates "programs as yet unthought
of," you"decouple the brain from the hands via a uniform tool interface"and
put stable interfaces over swappable implementations.
That's exactly the right instinct — and it's hosted, and it's Claude-only. The
same layering, done in the open, is just as valuable one floor below the
runtime: stable contracts for the files. What a tool promises, how a driver
implements it, what a skill declares. Make those contracts public and versioned,
and any host can load any capability — the way any wallet holds any ERC-20.
So let's actually build one and watch it reach three different agents.
The trick is to split what most formats fuse. A capability is two things: a
contract (its name, its inputs and outputs, whether it's allowed to mutate
anything) and an implementation (the code that runs). Fuse them and the
capability is welded to one runtime. Separate them and the contract is the part
that ports.
Here's a docs-search tool as a pure contract — agentproto's defineTool
(the AIP-14 TOOL.md
spec). Note what's not here: no execute body.
// tools/docs-search/TOOL.ts — the contract, and nothing else
import { defineTool } from "@agentproto/tool"
import { z } from "zod"
export const docsSearch = defineTool({
id: "docs.search",
description: "Search the team's internal docs. Returns titles + URLs.",
inputSchema: z.object({ query: z.string(), limit: z.number().default(5) }),
outputSchema: z.object({
hits: z.array(z.object({ title: z.string(), url: z.string() })),
}),
mutates: [], // read-only → no approval gate needed
approval: "auto",
})
The contract carries identity, schemas, and a side-effect profile — mutates
and approval
are how a supervisor later decides whether this call needs a human
(read-only tools sail through; a tool that writes files gets gated). One file,
one declared promise, zero opinion about how it's fulfilled.
This is the piece that makes a capability portable: a promise any runtime can read without running anyone's code.
Now the code. A DRIVER (AIP-30's defineDriver
) bundles one or more
implementations, each bound to a tool contract by implementTool
. The binding is
type-checked against the contract's schemas.
// drivers/docs-meili.ts — one implementation of the contract above
import { defineDriver, implementTool } from "@agentproto/driver"
import { docsSearch } from "../tools/docs-search/TOOL.js"
export default defineDriver({
id: "docs.search.meili",
name: "Docs search via Meilisearch",
kind: "http",
implementations: [
implementTool(docsSearch, async ({ input }) => {
const res = await fetch(`${process.env.MEILI_URL}/indexes/docs/search`, {
method: "POST",
body: JSON.stringify({ q: input.query, limit: input.limit }),
})
const { hits } = await res.json()
return { hits: hits.map((h) => ({ title: h.title, url: h.url })) }
}),
],
network: { egress: ["MEILI_URL"] }, // declared reach, not ambient
})
The codebase makes the analogy for you. The implementTool
doc comment calls it
"equivalent to Solidity's MyToken is IERC20 pattern" — the compiler enforces
docs.search
keeps working.
Why the split earns its keep.A driver declares its ownnetwork.egress
,
its auth, its install steps — so the samedocs.search
contract can have a
local implementation, a hosted one, and a mock for tests, and the resolver
picks one per call. That's "stable interfaces over swappable implementations,"
but foryourtools, inyourrepo, not behind a vendor's API.
Two files. Now the payoff.
Start the daemon in your repo. It reads your tools/
and drivers/
and exposes
them over MCP:
npm i -g @agentproto/cli
agentproto install claude-code # also installs the adapter package
agentproto serve # long-lived daemon, serves the /mcp gateway
Now spawn agents. Each adapter is pointed at the daemon's /mcp
gateway as it
starts, so all of them see the same served tools — including your docs.search
:
agentproto sessions start claude-code --cwd . \
--prompt "Use docs.search to find our retry-policy page, then summarize it."
agentproto sessions start codex --cwd . \
--prompt "Use docs.search to check whether we document the queue timeout."
agentproto sessions start hermes --cwd . \
--prompt "Use docs.search for 'deploy rollback' and list the top 3 hits."
One contract, one driver, three vendors — Claude Code, Codex, and a local model via Hermes all call docs.search with no per-agent wiring. The daemon is
This is the answer to the question the hub piece
left open — when you give one agent a new capability, how many of the others get it? Here the answer is "all of them, for free," and that's the difference between
The same "one place, every agent" trick works for tools you didn't write. MCP
servers are the industry's one real interop win — but each agent brand configures
them separately, so a server wired into Claude Code is still invisible to Codex.
agentproto imports an MCP server once into the daemon's curated set, and then
every session can reach it. The flow is three real tool calls:
mcp_discovered_list → find MCPs already configured in claude/cursor/etc.
mcp_import → snapshot one into the daemon (survives the source
config being deleted later)
mcp_imported_tool_list → search-then-call: list the imported server's tools…
mcp_imported_call → …and invoke one
The import captures a snapshot at import time, so the tool stays usable even if
you later remove the original config. You wire an MCP server up once, in one daemon, and Claude Code, Codex, and your local model all inherit it — instead of
.mcp.json
block into four different agent configs and keepingThat's the whole interop tax, refunded: author-once for tools you write, import-
once for tools you borrow.
Here's the honest edge, because that's what makes the rest trust me. You could do
all of this with a bespoke framework. The bet agentproto makes is not a
framework — it's a registry of numbered specs, modeled deliberately on ERCs, BIPs,
and PEPs. TOOL.md
is AIP-14. DRIVER.md
is AIP-30. Skills, operators,
workflows, policies, sandbox definitions each get a number.
The reason to prefer numbers over a framework is projection. A contract that's a
public spec, not a class in someone's SDK, can be adapted to any host:
toMastraTool(impl)
for a Mastra agent, or
toAiSdkTool(impl)
for the Vercel AI SDK,TOOL.md
(AIP-16 declares the IO as JSON Schema, no compiled module required).
Aligned-with, not forked-from.A Claude Code skill is already most of an
AIP skill; the specs are meant to standardize the format people converged on,
not replace it. And they'reours, plural: Apache-2.0, open to amendment by
issue — the entire point of a numbered public registry is that no single vendor
owns the meaning ofdocs.search
.
The honest caveats, stated plainly: agentproto is 0.5.0-alpha. The daemon, the
MCP surface, the driver doctype, and MCP import are live and hands-on today; the
in-process Mastra / AI-SDK adapters and the wider ~52-spec family are earlier-stage
— the repo publishes a live-vs-roadmap split precisely so you can check. Betting on
a young registry is a real cost. So is re-wiring the same tool by hand forever.
Pick your poison with open eyes.
Once a capability is files with a contract, the loop closes in a way no config
format allows. An agent can write a new TOOL.md
, bind a driver, or scaffold
a skill — and because those are file writes, they're diffs you can gate exactly
like any other code.
This is where this piece hands off to the supervision ladder:
stage the agent-authored tool behind a check and a human ack, and your fleet's
capabilities grow the way your codebase grows — incrementally, reviewed, in git —
instead of the way config sprawl grows. The daemon even exposes the doctype verbs
(create_tool
, create_driver
, list_driver
, self_inspect
) as MCP tools, so
an agent authors its own next capability through the same interface it uses to
call the last one.
That's the actual endgame of files-with-contracts: not tidier config, but agents that safely extend themselves, under the same review discipline as a human PR.
You don't need my daemon to start paying down the tax:
Count your capability re-declarations one more time. Every number above one is a
place a dialect can drift, an agent that's blind to a tool it should have, and an
afternoon of your life spent being human glue. The pattern is settled; the
plumbing is the only thing left to build — and it's a couple of files, not a
platform migration.
We had folders of markdown and called it interoperability. It never was. The fix
isn't another framework that everyone reinvents next quarter — it's a contract
anyone can read and no one owns.
If I've mis-described how your agent stack handles this, or you've solved the
interop tax a cleaner way, tell me where — I'll fix the piece.
Ten pieces, one argument. Start anywhere; each one cross-links the rest.
| Piece | The one idea | |
|---|---|---|
| 1 | ||
Written by the maintainer of agentproto (Apache-2.0, source). Same contract as our /compare page — dated facts, named strengths, corrections by issue. Got something wrong? File an issue.
Building agentproto in the open — follow @theagentproto and @agentik_ai on X.