Self-Hosting a Coding Agent, Part 2: What Tight Integration Actually Means


Two matte clay-render puzzle pieces interlocked with a glowing seam, a third mismatched piece off to the side

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.

Here's the detail that makes this worth a second post instead of an asterisk on the first one: the session that found all of that, the one 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 actually different.

The toolset was never bloat, it was calibration

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 completely. 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, because nobody trained it against this particular set of twenty-five tools with this particular set of descriptions in this particular arrangement. The tools that looked like noise to it are load-bearing structure to the model they were actually built around.

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, deliberately, against the actual harness it will run inside, on the actual prompts that harness actually sends, at the actual scale those prompts actually reach. Claude Code's toolset works with Claude the way a well-fitted tool works in a hand that trained specifically on that tool, not because either one is impressive alone, but because they were shaped against each other.

The trust runs in both directions

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 a real amount of 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 the harness's well-earned trust in Claude being pointed, unchanged, at a model that had never earned it.

What this actually means for choosing between the two

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, because "smaller model" undersells the real 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 just asked to write code, that side held up fine the entire investigation, correct answers every time, no exceptions found. What's missing is the much 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, because that discipline isn't a property of parameter count. It's a property of having been shaped, specifically, against the exact tools and exact prompts it's going to have to operate inside, by the people who control both sides of that relationship. A self-hosted model can be handed a smaller, hand-picked version of that same environment and do genuinely well inside it, which is exactly what Part 1 ended up proving. It cannot be handed the real, full thing and expected to have the same relationship with it that the model built alongside it already has, because that relationship isn't downloadable. It's the actual product of the two being built for each other, which is the whole reason a tool list that would drown one model reads, to the other, as an ordinary Tuesday.