no. 14 · the browser-native stack · harness

The entire agent is a fenced code block.

A 4B model running on your GPU has no function-calling API, no constrained-decoding mode and no vendor SDK. So the harness that makes it act — read files, edit code, generate whole apps, schedule tasks — is about as small as a harness can be: one fenced block, a six-step loop, and a context manager that is ruthless about what the model gets to remember. Here's the whole thing, and why every part of it is small on purpose.

The protocol: one block, identical for every provider

The model is told: if you want to use a tool, reply with only this.

```tool
{"tool": "code.search", "args": {"q": "def promedio"}}
```

That's it. That's the protocol. The harness looks for that block, JSON.parses it, runs the tool, and feeds the result back as the next turn. The same block works with Gemma on WebGPU, with the 1.2B ONNX fallback, with OpenAI, with Anthropic and with a local Ollama — because it doesn't depend on anybody's function-calling implementation. It depends on a model's ability to emit fenced JSON, which is the one thing every instruction-tuned model on Earth has been trained to do.

There is a second block type, and it is the reason Claw exists at all: if the answer is an app, the model replies with a complete HTML document, and the app appears in a sandboxed iframe next to the chat. Generating a program and rendering it is the same code path as answering a question.

Ignoring the vendor tool APIs was the right call. One protocol across five backends means a bug fixed for the tiny local model is fixed for Claude too — and the app keeps working on a machine that has never talked to an API in its life.

The loop, in full

The whole agentic core is: model → is there a tool block? → run it → append the result → ask again. With one guard rail that matters more than any of the rest:

const MAX_STEPS = 6;

Six. A small model that has taken six actions without producing an answer is not one step from success — it's in a loop, usually re-reading the same file or re-running the same search. Capping the loop turns "the tab hangs and the fans spin up" into "here's what I found, and here's where I stopped." The system prompt reinforces it in plain words: don't repeat the same tool; if the app is created, it's done — answer and stop.

The context manager: ACE-lite

Here's the constraint that shapes everything above it. Depending on the machine, the model gets between 4k and 32k tokens of context — and the small fallback works best with a 5,000-token budget, roughly a fifth of what a hosted model would casually accept. Long histories don't just cost money here; they make the model measurably dumber and slower.

So history isn't a list you append to. It's a budget you spend, re-decided on every turn:

RuleWhy
The last 6 messages always surviveRecency is never wrong in a chat. This is the floor, not the strategy.
Older messages compete for what's left, scored BM25-lite + IDF against the current queryThe message about the vault from twenty turns ago should come back when you say "vault" — and stay evicted when you don't.
No single message may exceed 12,000 characters, truncated in the middleOne pasted README shouldn't consume the whole budget. Head and tail matter; the middle of a file rarely does.
Evicted messages leave a visible marker ("…N older messages omitted")A model that knows something is missing asks. A model that thinks it saw everything invents.

This is a port of an attention-based context-eviction technique down to a place where the attentions aren't reachable: in a browser, you can't inspect what the model attended to, so relevance is approximated lexically at the message level. Cruder — and it fits in a file you can read in one sitting.

Why the agent lives in the prompt and not in the weights

We did heal our own agentic Gemma-4 E4B. It doesn't load in the browser: the runtime expects the artisan packaging and our export is prefill_decode. Rather than block the product on a conversion, the decision was written into the code as a dated comment: the brain is Google's base build, the agentic behaviour is the system prompt.

That turned out to be more than a workaround. Prompt-level agency is portable: the same instructions make a 1.2B fallback, a 4B Gemma and a frontier API model behave like the same product, with the same tools and the same protocol. Weight-level agency would have been better in exactly one configuration and useless in the other four.

What else the harness carries

The honest edges

Harness source: js/agent.js, js/context.js, js/permissions.js, js/skills.js in elffuss-claw and elffuss-code. Previous: the runtime. Next: your machine.

Update (Aug 2026): I finally measured this limitation instead of just naming it. The engine described on this page recovered 15 of every 100 facts it was later asked for; textbook BM25 recovers 65. The rules table above was the problem, not the fix. → no. 16.