# Why My Local Coding Agent Could Act but Couldn't Finish

> Source: <https://dev.to/gyu07/why-my-local-coding-agent-could-act-but-couldnt-finish-81d>
> Published: 2026-07-29 07:53:24+00:00

There was a month where I blew through my token budget without noticing. Claude Code and Codex, running most of the day, on a codebase I was exploring more than editing. The ceiling I'd set turned out to be a number I'd passed weeks earlier, and I found out by getting the bill.

So I did the thing I'd been putting off, and ran a coding agent on my own machine to see what happened.

The short version:

None of this is a benchmark. These are notes on where my thinking has drifted, written down mostly so I can find out where it's wrong.

The story I'd been telling myself was that token prices are going up. I can't really support that. Unit prices haven't broadly risen; my consumption did, and the shape of agentic coding is why. A turn in these loops can carry the system prompt, the tool definitions, the files already read, and the history along with it, so usage climbs with your setup as much as your effort.

I [wrote on Medium](https://medium.com/@takafumi.endo/token-allocation-the-new-capital-discipline-of-ai-native-companies-15a9183fe083) that token spend only becomes an *asset* when you redesign the work around it. My first response to the bill wasn't to switch models. It was to notice that most of the tokens went into *finding things*, and to build a terminal emulator with tree-sitter and LSP folded in, so the agent could get deterministic answers to "who calls this." That became [the graphs argument](https://dev.to/gyu07/rigor-compresses-why-ai-agents-need-graphs-not-more-context-5404), and it comes back at the end meaning something I hadn't intended.

**Cost.** Frontier APIs charge per token; local open-weight inference doesn't. This is what got me looking, and on my hardware it doesn't hold: what I stop paying in dollars comes back as wall-clock and retries, which are scarcer than money.

**Locality.** Some work wants to be near the actor, being private, high-churn, latency-sensitive, or offline-capable, and that holds regardless of what a token costs. This one I believe, and it's the same [state-gravity](https://dev.to/gyu07/why-ai-agents-make-me-reach-for-sqlite-4dh0) reasoning I keep landing on elsewhere.

Same hardware, different conclusions. I'd rather not borrow the second to prop up the first.

[Qwen3-Coder-Next](https://qwen.ai/blog?id=qwen3-coder-next) is described by its team as being for coding agents and *local development*, with emphasis on long-horizon runs, tool use, and recovering from failed execution. That last item is a loop property rather than a generation property, and a telling thing to optimise for. Apache 2.0 throughout, which for anything pointed at a real codebase is a gate that comes before capability.

I'd also been repeating that [Qwen Code](https://github.com/QwenLM/qwen-code) is a Gemini CLI fork. It did start as one, and in lineage terms still is, but it stopped syncing upstream early and has grown its own subagents, hooks, and MCP support. What matters here is that it's backend-agnostic: the same harness points either at a local endpoint or a commercial API, which is as close to a controlled comparison as I get on one laptop. Qwen also describes it as reaching feature parity with Claude Code, which is worth holding loosely, since feature parity and outcome parity are separate claims.

`3B active`

is a compute number, not a memory number
The correction that most changed how I read model announcements. Activated parameters govern compute per token; total parameters govern the memory to hold the weights. Qwen3-Coder-Next activates around 3B and still has 80B in total, which at 4-bit is on the order of 40 GB for weights alone, before runtime overhead, KV cache, and the context you actually wanted. On a 64 GB machine that makes it a candidate rather than a comfortable one, and the margin thins exactly where I'd want it. It cuts the other way too: I'd filed myself under "small quantized models only," and the quantized 30B-odd class in fact runs with real headroom at this size.

Loading a model and running it comfortably over a long context are two different capabilities, and only the first appears in a requirements table.

Which is why I care about this tier rather than the top of the range. What decides whether local inference becomes *normal* is what runs on a machine a working developer already bought.

Two sittings in April, an evening and then the following afternoon. They weren't the same task: the first picked up a half-finished short-video feed clone and carried on, the second built a video site from scratch. Different tasks, different days, different models, so nothing here is controlled. It's a log rather than an experiment.

| Setup | |
|---|---|
| Machine | MacBook Pro · M2 Max · 64 GB unified memory · macOS 26 |
| Runtime |
`llama-server` , llama.cpp b8680, Metal |
| Agent | Qwen Code 0.14.5, over the OpenAI-compatible endpoint |
| Models | Qwen3-Coder-Next (80B total, IQ4_XS) · Qwen3.6-35B-A3B (unsloth dynamic Q4_K_M GGUF, ~21 GB) |
| Context | 131,072, compaction at 50% |
| Flags | `--jinja -ngl 99 -c 131072 -b 512 -ub 256` |

Two notes on that. In this setup `--jinja`

was needed for Qwen Code's tool calls to work at all, and depending on GGUF metadata llama.cpp may also want an explicit tool-aware chat template. The prompt batch is small, 512 with a micro-batch of 256, because anything larger died with a Metal compute error. That one matters later: batching governs how fast context gets ingested, and I had throttled it before I started measuring.

| Session | Task | wall-clock | tool calls | reissued |
|---|---|---|---|---|
| Evening, day 1 | feed clone, resuming | ~1h25m | 151 | 31 |
| Day 2, first | video site, from scratch | ~3h20m | 154 | 11 |
| Day 2, second | same, continued | ~1h20m | 240 | 8 |

"Reissued" just means the agent sent the call again. The logs don't separate malformed arguments from non-zero exits from failing tests, and a command that fails and then gets fixed is arguably the harness working, so the column reads better as friction than as an error rate. Day one is muddier still, with dozens of server-side compute errors that belong to the runtime rather than the model.

The second session is the one I keep thinking about. The UI came out, and came out well: header, sidebar with the full nav, category chips, a video grid with channel names and view counts and duration badges, and a watch page with player, title, subscriber count, like and dislike, share, description. All of it correct.

What never loaded was the contents of `public/`

. Every thumbnail in the grid was a broken-image icon with its alt text showing through, and the player sat on a black frame with a spinner reading `0:00 / 0:00`

. A complete site with nothing in the slots.

The last hour or so of that session is the agent chasing that one 404, and the shape of it is the part worth looking at:

```
observed: thumbnails return 404
  │
  ├─▶ restart the dev server ....... call ok · 404 remains
  ├─▶ try a production build ....... call ok · 404 remains
  ├─▶ flatten the directory layout . call ok · 404 remains
  ├─▶ rewrite the database paths ... call ok · 404 remains
  └─▶ drop the SQL approach ........ call ok · 404 remains
                                     │
                                     └─ each call accepted and run,
                                        none of them establishing
                                        which path was being served
```

Much of that final session went into that loop. My last message in the log is a flat "this isn't working."

So the pair of numbers I keep returning to is this: of 240 tool calls, 232 went through without needing to be reissued, and the bug was still there at the end. That's a per-call rate, not a measure of how finished anything was. It had the competence to reproduce a large, fiddly interface from scratch, then couldn't work out why a static file wasn't being served, a smaller and much more boring problem than the one it had just solved. It kept generating plausible fixes for the wrong layer.

I'd call that a **diagnosis** problem rather than a generation one: forming and discarding hypotheses about a system it built itself, with no cheap way to check any of them. Which is the honest answer to whether a quantized local coder can ship a non-trivial front end. It can produce a lot of good code and still not close the last inch.

My prior experience makes me suspect Claude Code would have got out of that hole faster, and this run offers no evidence for that comparison: no matched run, nothing held constant, no recording, so there's no row for it in the table. It's a narrow suspicion anyway. Not about code quality, but about how quickly the loop notices it's fixing the wrong thing. I mention it because leaving it out would flatter the local run.

"It's slower" turned out to be doing no work at all. Any of these could be the culprit:

Published write-ups on agentic coding point at the same place. In a loop like this, each turn can require the server to process the system prompt, the tool definitions, the retained file context, and the conversation so far all over again, so the same material gets worked through dozens of times in one task, and prompt processing can eventually dominate generation.

```
call 1   [ system · tools · files read · history ] ──▶ one short action
call 2   [ system · tools · files read · history ] ──▶ one short action
call 3   [ system · tools · files read · history ] ──▶ one short action
  ⋮               re-read in full, longer each time
×240

   writing   a few hundred tokens per call
   reading   the entire transcript, per call
```

Meanwhile decode speed on my hardware class keeps improving, going by [the numbers runtimes are publishing for Apple Silicon](https://ollama.com/blog/mlx-performance). The axis being optimised hardest is the one I'd already stopped waiting on.

**The bottleneck may be less about the model writing than the model re-reading.**

I want to be exact about that sentence's status. All I have is wall-clock, call counts, and reissues. What would settle it is cumulative prompt time against decode time, prompt tokens against tokens generated, and how much of each prompt came from cache, all of which `llama-server`

reports and none of which I collected. So: leading suspect, consistent with how these harnesses behave, not demonstrated here. If prompt time turns out to be a minority of the total, the explanation doesn't hold.

If it does hold, the tooling detour means something I hadn't intended. Vague context is charged twice locally, once in tokens and once in wall-clock, every loop. Handing an agent short deterministic facts instead of files to re-read is a cost optimisation against a metered API; on hardware I own it becomes a latency optimisation, and I'm the one sitting there for it. The way that second session ended is the same argument from the other side: an agent that could cheaply ask which file was being served wouldn't have spent an hour fixing one that wasn't.

Behaviour was less stable than I'm used to, with tool calls going sideways and loops not recovering cleanly, and my instinct was to file it under quantization. That instinct was mostly a wish for a tidy story.

Quantization does affect quality, and published comparisons show that two schemes at the same bit width can behave measurably differently, which is why I named both of mine. But the candidates I can't separate go well past it. The base models' tool-use ability, chat-template fit, sampling settings, context bloat over a long run, the runtime, the throttled batch size, the quantization recipe. That's a list of things to check rather than a cause, and most of it I can change without swapping models.

The same caution applies across the two sessions. Qwen3-Coder-Next is the model my own arithmetic said would fit but not comfortably, and it reissued roughly one call in five while throwing repeated Metal errors, where the smaller one ran much more cleanly. Memory pressure is my leading explanation, but it was a different model on a different task with a different 4-bit recipe, so I'd read it as suggestive. What survives is that bit width alone explains nothing.

Fixing a local OpenAI-compatible server as the execution layer is what made this tractable: the runtime stays put and I recombine around it. It may also be why it broke.

Look at those candidates again and notice how many are interface problems rather than capability problems. Template fit, tool-call formatting, sampling defaults, context compaction. Those are seams. A stack where one organisation post-trains the model and ships the agent doesn't have seams there, because the model was trained against that harness's tool loop, on its own tool schema, with its own recovery behaviour. The interface has been tested as one product.

So the view I've drifted to, an impression from use rather than something measured, is that model and harness are better evaluated as a single unit, and that the OpenAI × Codex and Anthropic × Claude Code advantage may come not only from model quality but from having no seam to misfit.

Cursor is the interesting exception, having stayed model-flexible while also training its own model against its own editor and tools. Notably it built that on someone else's open checkpoint, which suggests the load-bearing part is the co-design rather than owning the pretraining.

Either the open tier keeps compressing until a long local loop is comfortable, or the coupled stacks keep pulling ahead in ways a swappable arrangement can't track. I'd prefer the first, and I'm not confident enough to plan around it. Meanwhile the on-device direction is already being built into Apple's and Google's frameworks as a split rather than a migration, with private, low-latency work on the device and the heavy inference elsewhere, which makes the question *which parts route where* rather than *local or hosted*. Doing all of this by hand reads like the phase before that.

So I keep reinstalling. Every few months, same test, same laptop, and I write down where it broke. It hasn't changed what I open on a Tuesday. It has changed what I think I'm buying when I do, which is less about tokens and more about something that can work out which file is actually being served. Next time I'll log the prompt timings, and then I'll know whether any of this is true.
