DEV Community

Cover image for Your Claude Skill Is Invisible to Codex. Here's How to Fix It.
Agentik
Agentik

Posted on

Your Claude Skill Is Invisible to Codex. Here's How to Fix It.

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.

Everyone reads files. Nobody agrees on the field names.

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 short AGENTS.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
self-diagnosis: count how many separate places you re-declare the same
capability — the docs-search glue, the deploy runbook, the linter rule — once
per agent that needs it.

Where are you? 0 duplicated declarations — you're single-agent, not
your problem yet. 2–3 (a CLAUDE.md here, an AGENTS.md there) — you're
paying the tax quietly, once per new brand. 4+ — you are the interop
layer, by hand, and it costs an afternoon per new agent.

The vendors already proved the fix — then locked it in their house

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.

Step 1 — author the contract (what the tool promises)

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",
})
Enter fullscreen mode Exit fullscreen mode

The contract carries identity, schemas, and a side-effect profilemutates
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.

Step 2 — bind an implementation (a DRIVER)

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
})
Enter fullscreen mode Exit fullscreen mode

The codebase makes the analogy for you. The implementTool doc comment calls it
"equivalent to Solidity's MyToken is IERC20 pattern" — the compiler enforces
that the body's inputs and outputs match the contract's generics, no string-keyed
guessing. The contract is the interface; the driver is the class that
implements it.
Swap Meilisearch for Postgres FTS later, keep the contract, and
every agent that used docs.search keeps working.

Why the split earns its keep. A driver declares its own network.egress,
its auth, its install steps — so the same docs.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 for your tools, in your repo, not behind a vendor's API.

Two files. Now the payoff.

Step 3 — serve it to every agent, from one place

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
Enter fullscreen mode Exit fullscreen mode

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:

# a frontier agent…
agentproto sessions start claude-code --cwd . \
  --prompt "Use docs.search to find our retry-policy page, then summarize it."

# …the exact same tool, a different vendor…
agentproto sessions start codex --cwd . \
  --prompt "Use docs.search to check whether we document the queue timeout."

# …and a cheap local model, no MCP config of its own
agentproto sessions start hermes --cwd . \
  --prompt "Use docs.search for 'deploy rollback' and list the top 3 hits."
Enter fullscreen mode Exit fullscreen mode

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
the single place capabilities live; agents mount it, they don't each re-declare
it. (Hermes ships with zero MCP by default, so the daemon mounts the gateway for
it — the cheap open model gets the same toolbox as the frontier one.)

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
managing agents and building with them.

Import someone else's MCP once, hand it to all of them

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
Enter fullscreen mode Exit fullscreen mode

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
copy-pasting a .mcp.json block into four different agent configs and keeping
them in sync by hand.

That's the whole interop tax, refunded: author-once for tools you write, import-
once for tools you borrow.

Numbered specs, not yet another framework

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:

  • served over MCP by the daemon (what you just saw — this is live),
  • projected in-process with toMastraTool(impl) for a Mastra agent, or toAiSdkTool(impl) for the Vercel AI SDK,
  • authored by a human in TypeScript, or by an agent as a plain YAML 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're ours, 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 of docs.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.

The endgame: agents that write their own tools

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.

What to do Monday, whatever you run

You don't need my daemon to start paying down the tax:

  • Stop duplicating dialects. Pick one canonical instructions file; make the others thin pointers so they can't drift.
  • Separate contract from implementation in your internal tools, even informally. The day you add a second agent brand, the contract is the only part that ports — so it's worth naming today.
  • Treat agent-authored config as code: PR review, tests where you can, no direct writes to main. Files-as-behavior means files-as-attack-surface, and routing cheap execution through a judge only pays off if the capabilities those cheap agents wield are gated too.
  • If the interop problem is biting you, the AIPs are Apache-2.0 and open to amendment. The point of numbered public specs is that fixing them is your right, not a feature request in someone's queue.

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.


The series — Orchestration, Honestly

Ten pieces, one argument. Start anywhere; each one cross-links the rest.

Piece The one idea
1 You can't parallelize the trust Amdahl's Law: why your fifth agent slows you down
2 Harness engineering you rent the model; the harness is the part you own
3 The supervision ladder five rungs of trusting an agent you don't watch
4 The approval plane auto-approve reads, gate writes — wire the line between
5 Kill the loop why "keep going until done" compounds a wrong turn
6 Route by cost plan expensive, execute cheap, verify independently
7 Files with contracts (you're here) the interop layer every agent system reinvents
8 Knowledge is power give your agent your knowledge, not the internet's average
9 Paseo, hands-on a full real-session review of the daemon
10 9 orchestrators, compared the tool-by-tool teardown + a decision table

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.

Top comments (0)