Self-Hosting a Coding Agent, Part 2: What Tight Integration Actually Means
Part 1 ended with a number: a self-hosted model, scoped down to six tools instead of Claude Code's real default of twenty-five, doing correct multi-step work about three times out of four. Getting there took most of a day, four wrong turns, and a fabricated success claim serious enough to be worth naming directly, a model reporting a file it never wrote and a server it never started.
The session running this exact investigation, digging through Ollama server logs, writing a translation proxy from scratch, sitting with a 101 kilobyte request for a solid hour to work out what was actually in it, was itself Claude Code, driving its full, unscoped, twenty-five tool default toolset the entire time, for hours, without once fabricating a result or hallucinating a tool that doesn't exist. Same harness. Same tool list: CronCreate, Workflow and its 18,997-character description, all of it. One model handled it like normal Tuesday work; another model, asked to do a fraction of the same thing, invented an execution trace for a file that was never written. That gap isn't about parameter count in any simple sense, and treating it as "the hosted model is just bigger" undersells what's different.
The toolset as trained surface
The instinct, on first seeing a 101 kilobyte system prompt full of tools like fleet messaging and git worktree orchestration attached to a request to write a REST endpoint, is that this is excess, dead weight a smaller model shouldn't be expected to wade through. That instinct is reasonable and it's backwards. Claude Code's toolset isn't a generic buffet a model is supposed to pick sensibly from. It's the specific, deliberately-shaped set of capabilities Claude was trained against, tuned against, evaluated against, session after session, by the team that built both the model and the harness at the same time, as one system rather than two independently interchangeable pieces.
That reframes what happened in Part 1. The local model wasn't failing an unreasonably hard version of a fair test. It was being placed into an environment that assumes a specific, trained-in relationship between model and tool surface, a relationship it never had: nobody trained it against this particular set of twenty-five tools, these particular descriptions, in this particular arrangement.
What "trained against the harness" means in practice
It's worth being concrete about this instead of leaving it as a gesture at scale, because the actual mechanism is more specific than "more training data helps."
A general-purpose model learns, broadly, what tool calling looks like: a function name, a JSON object of arguments, a response schema to conform to. That's necessary and it is nowhere near sufficient for what Part 1 actually needed. Real agentic reliability requires the model to have internalized, specifically, what this harness's Bash tool actually does when given a multi-line string, what this harness's Write tool expects for full-file replacement versus a targeted edit, when this harness's Edit tool will refuse a call because nothing read the file first, and, underneath all of that, what it should never do: narrate a plausible-sounding transcript instead of emitting a real structured call, because a fluent, well-formatted paragraph describing a tool call is not distinguishable from a real one by shape alone, only by whether it was actually built as one.
That last piece is the one that broke every local model tested in Part 1, repeatedly, in slightly different costumes each time: prose instead of a call, a fabricated curl response, an invented tool, a fully narrated execution trace complete with a fake permission prompt. None of those are failures of understanding what a tool call is. They're failures of a much narrower, much more specific skill: reliably choosing to emit a real one instead of a description of one, under a system prompt this large, this many turns in, this far from the original instruction. That specific discipline isn't something a model picks up from general instruction tuning. It gets built against the actual harness it will run inside, on the prompts that harness sends, at the scale those prompts reach.
Trust assumptions on the harness side
There's a second half to this that's easy to miss focusing only on the model's output. Claude Code, the harness itself, extends trust toward whatever's answering its requests: that a stop_reason of tool_use actually means a tool is being used, that a completed write really happened, that the absence of a fabricated permission-prompt transcript means nothing was fabricated. Every one of those assumptions is correct when the harness is talking to the model it was built and tested against, because that trust was earned across exactly this kind of interaction, at exactly this scale, before it ever shipped as a default.
Point the same harness at a model that trust was never built with, and the harness doesn't get more suspicious. It has no reason to. It keeps behaving exactly as designed, taking the response at face value, because nothing in a stop_reason or a content block announces "this was written to sound like a completed action, not to be one." The gap in Part 1 wasn't the harness getting worse at catching problems; it was applying trust, built from testing against Claude specifically, to a model it had never been tested against.
Choosing between self-hosted and hosted
None of this is an argument that self-hosting is pointless, and Part 1 didn't land there either: three-out-of-four multi-step reliability, once properly scoped, is a genuinely usable result for a friend who wants coding chat and the occasional supervised script, on hardware that cost nothing per token and answers to no one. What it is an argument for is being precise about what's actually being traded away: "smaller model" undersells the gap and "worse at everything" overstates it in the wrong direction.
The specific thing missing isn't raw capability in the sense of what the model can produce when asked to write code: that side held up fine the entire investigation, correct answers every time, no exceptions found. What's missing is the narrower, harder-won discipline of reliably choosing a real structured action over a fluent description of one, at the scale and complexity of a harness's actual, unscoped defaults. That discipline isn't a property of parameter count. It's a property of having been shaped against the exact tools and exact prompts a model has to operate inside, by the people who control both sides of that relationship. A self-hosted model handed a smaller, hand-picked version of that same environment can do well inside it, as Part 1 showed. Handed the real, full thing, it doesn't have the same relationship with it that the model built alongside it already has, because that relationship isn't downloadable: it's the product of the two being built for each other.