Reach for a skill before middleware
When I started building my agent, my bias was to cage it. Wrap the model in a tool with a fixed shape. Add middleware that watches its output and steps in. Write a guardrail that catches the bad case before it reaches the user. This is how the industry made services reliable: structure around the unreliable part. With a small local model, the one I run at home, this reflex is stronger because the model is weaker.
Most of what I have learned in the last few months is that the reflex is wrong. Reach for a skill before middleware. Cage the model last, only at the backstop, and only in a generic way.
The cost of the cage
Two failures taught me the price.
The first was a loop detector. I built six patterns to stop a small-model agent from running away, each with a real incident behind it. In production it bounced a perfectly good research turn between three of those patterns, stripped its tool calls, and dropped the user into a canned "I couldn't settle on one, how would you like to proceed?" The guardrail meant to prevent a runaway had created a worse failure than the runaway. The fix was to delete four of the six patterns, and a thousand lines with them, and let the agent run to a coarse recursion limit instead.
The second was an org format editing bug. Who would have thought that org wasn't a large part of Qwen's training set? The model kept anchoring an edit on the wrong line and splitting a file. My instinct was a deterministic guard that rejects bad edits. I spent days trying to "lightly" detect and surface a bad edit, and landed on doing the agent's job for it before realizing that fights the whole point of the project. What actually worked was a checkable rule on the tool's arguments, written into the instruction, not a wrapper bolted around it.
Both times my reflex was structure, and both times it was worse than what it replaced.
Reach for the skill
The alternative is a skill. On the phone, "call Ana" has to turn a name into a number, maybe disambiguate, confirm, and dial. The first design added a tool to look up contacts and a server event to drive the confirmation. Hand-holding the whole way. The dial is a dumb, deterministic command: a number in, a call out, no thinking. Everything fuzzy lives in a skill, a plain-English recipe the agent follows: search my notes for a name near a number, pick, confirm, call. No new tool. It runs on the small local model and it actually works.
A skill is the opposite of a commitment. It is text. I can read it, change it, and watch the behavior change, the same way I change anything else on the phone. A tool is a fixed shape with code behind it and a contract that drifts from the recipe that uses it. Given the choice, put the judgment in the text and let the model do the thing it is actually good at.
The backstop
Leaning in has a backstop, the thin layer of hard structure you cannot skill your way out of. A weak model under pressure falls back to its strongest prior, and prose does not reliably outweigh it. I told an agent eight ways never to answer from its own memory. When search failed it answered anyway and invented a version number. It had not missed the rule, it had no other move. A "stop" handed back in a tool result reads the same way, as one more piece of data, not a command.
So the backstop needs structure, and the craft is keeping it narrow. Guard only the case that is never legitimate work: the same tool, the same arguments, over and over, ended by a counter. "Many distinct searches" is not safe to guard, that is also what thorough work looks like. For anything fuzzier reach for a coarse bound, a timeout or a recursion limit, not a clever per-behavior heuristic.
The rule
- Skill
- Checkable instruction
- The narrowest structure that makes the bad state unreachable
Middleware is what you add once you have proven a recipe cannot do the job, not what you start with.
It is also why the functionality on this phone is skills and not apps. An app is someone else's code, a fixed shape I cannot open. A skill is a paragraph of plain English describing how I want my device to work. If the model is good enough to follow the paragraph, and most of the time it is, then the paragraph is the better place to keep the behavior.