Claude Code Is Not a Model
Claude Code Is Not a Model
I run different models through Claude Code with a local gateway. At first, it feels like a clean substitution: change the route, keep the terminal, the repository, the tools, and the workflow.
Then an agent fails in a strange way.
It can reason about code. It can write a shell command. It may even see the same tools as Claude. Yet it does not start a local coordination protocol, does not load the relevant capability, or treats a repository rule as optional background reading.
The tempting conclusion is that the replacement model is worse.
That is sometimes true. It is also incomplete.
The more useful explanation is that Claude Code is an agent runtime, not a model selector. Replacing the model means replacing one part of a system that also contains tool definitions, permissions, instructions, MCP servers, skills, local state, and conventions learned from repeated use.
A gateway can preserve the request and response path. It cannot automatically give every model the same working knowledge of the runtime around it.
The failure was not about writing code
One of my projects uses an agent-bus for coordination across sessions. The expected bootstrap sequence is deliberately boring:
- register the current agent identity
- read messages received while the process was away
- arm a persistent listener
- when the listener reports a new event, poll and act on it
In command form, the workflow looks like this:
register(team/agent)poll()monitor("agent-bus doorbell --team <team> --as <agent>")
on BUS: poll() act on new messagesThe key detail is not the command syntax. It is the order.
A model that has not learned or been told this contract might see an MCP server and still conclude that no coordination mechanism exists. Or it might use the bus only after being explicitly asked to send a message. It has access to a capability without understanding the operational rule that makes the capability useful.
That distinction matters:
"I can call this tool"is not the same as:
"I know when to call it, in what order, and what state it creates."Calling a tool is syntax. Operating an agent is protocol.
The hidden contract of a coding agent
A normal chat API has a relatively small contract: messages go in, text comes out.
A coding agent has a larger one. It needs to know which actions exist, what they mean, when they are allowed, and how the environment expects them to be combined.
A useful mental model is:
agent behaviour = model ability + tool schemas + system instructions + project instructions + runtime state + provider and gateway behaviourEvery part can change the result.
For example, Claude Code may expose a dedicated tool for reading a file, another for changing it, another for asking a human a blocking question, and another for loading further tools only when needed. A model does not merely need the names. It needs the practical rules:
- use a dedicated edit tool rather than trying to rewrite a file blindly
- do not answer a user decision with an invented default when the decision changes scope
- load a deferred tool before attempting to invoke it
- read local instructions before treating a repository as a generic codebase
- distinguish safe inspection from a destructive or external action
- leave a persistent listener running when a protocol depends on future events
None of that is ordinary programming knowledge. It is knowledge of the harness.
Why Claude can appear to work better inside Claude Code
The native experience has an advantage that is easy to miss: the model, the tool surface, and the surrounding product conventions were designed together.
That does not mean a non-Claude model cannot use Claude Code. Many can do useful work through a compatible gateway. It means they may arrive with different priors about the runtime.
They may be less likely to infer that:
- a
CLAUDE.mdfile is an operational contract rather than optional prose - a deferred tool should be discovered before use
- a tool result is part of a multi-step protocol, not merely an answer
- a local MCP server has a bootstrap sequence
- a persistent monitor is required after initialization
- the host expects a particular format for tool calls, tool results, or state transitions
This is not a binary property. It is a gradient.
One model may be excellent at planning but reluctant to use tools. Another may call tools eagerly but lose the thread after several results. A third may follow a detailed local protocol once it sees it, but never discover that the protocol exists.
The model is still important. But comparing models without comparing their fit to the harness produces misleading conclusions.
A gateway transports requests. It does not transfer product knowledge
A local gateway such as CLIProxyAPI solves an important problem: it can let a client designed around one API route requests to another provider or model.
That is transport compatibility. It is not behavioural compatibility.
A route can be technically successful while the agent is operationally weak:
Request reaches another model yesTool calls parse correctly yesTool results return to the client yesModel follows the local workflow maybeModel discovers the right capability maybeModel keeps the protocol across turns maybeThere are several places where a model swap can change behaviour even when no request fails.
Tool representation
The model must receive the actual tool definition, including the tool name, arguments, descriptions, and any rule for deferred loading. An instruction that says “use the bus” cannot help if the bus tool was never made available in context.
Tool calling behaviour
Models vary in their willingness to call a tool, their ability to produce valid arguments, and their ability to recover from an error result. A compatible JSON shape is only the first requirement.
System and project instructions
The same repository instructions can be treated as a hard operating policy by one model and as optional context by another. The remedy is not to repeat every rule in capital letters. It is to make the contract clear, short, specific, and testable.
Runtime features
Skills, deferred tools, MCP servers, persistent monitors, and subagents all depend on the agent deciding that a capability applies. If that decision is weak, exposing more tools can make the agent worse rather than better.
The answer is a harness compatibility layer
The practical fix is not a giant prompt claiming that every model is Claude.
It is a small, explicit compatibility layer that describes the parts of the runtime the project actually relies on. In a Claude Code repository, that layer usually belongs in CLAUDE.md, CLAUDE.local.md, a skill, or a tool description.
Here is the difference between vague documentation and an operational contract.
Too vague:
## Agent bus
Use agent-bus to coordinate with other agents.Useful:
## Agent bus
At session start, register the declared identity, then poll the inbox.Arm a persistent doorbell listener after polling.
When a `BUS:` event arrives, poll again and act on the new messages.Do not infer an identity from the repository name. If no identity is declared,ask for one instead.The second version gives the model information it cannot safely infer:
- the lifecycle order
- what starts the process
- what event changes the state
- what not to guess
It is not more forceful. It is more complete.
Write protocols, not wishes
Custom instructions often fail because they describe an aspiration rather than a procedure.
This instruction is weak:
Always use the right tools and follow project conventions.It leaves the agent to decide what “right” means.
This one is much stronger:
## Before changing files
1. Read the project instructions that apply to the target directory.2. Check whether an existing script or command already performs the task.3. Ask before an irreversible or outward-facing action.4. Run the relevant validation after a change.5. Report failures with their output. Do not claim a check passed when it was skipped.It tells the model what the repository considers a valid execution path.
The same principle applies to tools. A tool description should answer more than “what does this function do?” It should say when the tool applies, what it returns, and what the caller must do next.
{ "name": "load_project_skill", "description": "Load the full instructions for a named project skill. Call this before performing work covered by that skill. The response contains operating constraints and commands; follow them for the remainder of the task.", "input_schema": { "type": "object", "properties": { "skill": { "type": "string", "description": "The skill name shown in the available-skills list." } }, "required": ["skill"] }}The description is not marketing copy. It is part of the agent’s control plane.
Instructions cannot create a missing capability
There is an important limit here.
A custom instruction can explain a protocol. It cannot make a missing tool appear. It cannot repair a gateway that corrupts tool calls. It cannot make a model reliably execute a long sequence that exceeds its practical planning or context limits.
This is why the compatibility layer has multiple levels:
| Layer | Question it answers |
|---|---|
| Gateway | Does the request, streaming response, and tool-call format survive the route? |
| Tool surface | Does the model receive the tools, schemas, and discovery mechanism it needs? |
| Runtime contract | Does it know when and how to use each capability? |
| Project policy | Does it know the local security, Git, deployment, and coordination rules? |
| Evaluation | Can we prove the expected behaviour still works after changing models? |
If the first two layers are broken, no amount of prose fixes the system.
If the first two layers work but the model skips a required protocol, explicit instructions and better tool descriptions can make a large difference.
Treat portability as an engineering problem
The useful question is not:
Can this model run inside Claude Code?
Almost any model can produce text inside a terminal interface.
The useful question is:
Can this model satisfy the operating contract of this Claude Code environment?
That question suggests a small test suite.
## Harness acceptance checks
- Starts the declared coordination protocol without a reminder- Discovers a deferred capability before attempting to use it- Reads repository instructions before editing- Requests approval for an external or irreversible action- Returns a useful response after a tool failure- Does not invent missing tools, identities, or permissions- Preserves the required state across several tool callsRun those checks when changing the model, gateway version, system prompt, tool definitions, or project bootstrap instructions.
This is less glamorous than model benchmarks. It is more predictive of whether the agent will help on a real repository.
A benchmark can tell you whether a model writes a correct function. It will not tell you whether the model starts a listener, discovers a skill, avoids guessing an identity, or respects the project’s release boundary.
The agent interface is the contract
There is a broader lesson here.
For people, an application interface is buttons, menus, permissions, and documentation. For an agent, the interface is mostly text and schemas:
- tool descriptions
- system instructions
- repository rules
- skills
- MCP capability definitions
- event and state conventions
That makes instruction files closer to an API contract than a README.
A vague instruction creates undefined behaviour. A duplicated instruction can drift. A missing lifecycle rule forces the model to guess. An undocumented tool can be as useless as a missing tool.
There is no need to turn every repository into a massive prompt. Most agent instructions should stay out of the way.
But anything the agent cannot infer safely should be stated once, near the capability it governs, in a form that can be checked.
The model is only one dependency
Using alternative models through Claude Code is worth doing. It can provide different cost, latency, reasoning, or coding trade-offs without throwing away a terminal workflow that already works.
The mistake is to think a gateway performs a complete substitution.
It swaps a model endpoint. It does not automatically swap in:
- familiarity with Claude Code conventions
- reliable tool selection
- knowledge of your MCP lifecycle
- your project’s security boundaries
- local workflows accumulated in
CLAUDE.md
Those have to be made explicit.
Once they are explicit, the result is better for every model, including Claude. The repository becomes easier to restart, easier to share, easier to test, and less dependent on undocumented habits.
That is the real payoff of model portability: not pretending every model behaves the same, but giving each model a clear contract for operating safely in the environment you built.