I deleted thousands of lines of code from Acolyte this week. Not dead code. Not a rewrite. Working features that I built, tested, and shipped. I removed them because the assumptions they were built on no longer hold.

Most projects accumulate complexity. Features get added, rarely removed. Deleting working code feels like admitting you were wrong. That is not quite what happened here. The code was right when I wrote it. The models and the product have moved past it.

This is the third time. I dropped an explore phase before launch. Then I removed the mode system. Now I am removing behavioral guards. Each time, the same pattern: a feature that once solved a real problem became the problem.

## Modes

[Acolyte](https://acolyte.sh) originally had four modes: chat, explore, work, and verify. I built this to simulate how developers work: explore the problem, implement, and review. Each mode had its own tool permissions and instructions. Chat and explore were dropped before launch. Work and verify stayed.

The intent was reasonable. The modes added complexity and made the product worse. They created rigid boundaries where the model needed fluidity. Real developers move between reading, writing, and reviewing constantly. Forcing the agent into sequential phases was my assumption about how work should flow, not how it actually flows.

So I removed it. Twenty-two commits. If any of this needs to come back, it will come back when I know what is needed instead of assuming.

## Guards

Acolyte has eleven behavioral guards that run before every tool call. They block patterns that look degenerate: duplicate calls, ping-pong loops, file churn, redundant searches. I built them because I assumed the model needed policing. It did not.

In a test session this week, two tasks failed because of guards, not because of the model. In one, the model tried to run the linter after creating a file. The guard blocked it. In another, the model tried a follow-up edit with new information. The guard said no. Both times the model was doing something reasonable.

The guards were not solving a model problem. They were solving a trust problem. I did not trust the model to manage its own tool usage, so I built runtime checks to override it.

## Evaluators

Evaluators run after generation. When a guard blocks or a tool fails, they decide whether to force the model to retry by injecting feedback and triggering a regeneration loop.

Same assumption, different direction. The model already sees tool errors in its output. It can read a failure message and decide what to do. But instead of trusting that, I built a system that forced retries the model did not ask for. In practice, the forced retry was often worse than what the model would have done on its own.

Removing evaluators means removing regeneration entirely. One pass, side-effects run, done.

## The principle

The host should support the model. It should not constrain it.

The host provides capability: tools, caching, workspace awareness, formatting, linting. The model provides judgment: what to do, in what order, when to stop. When the host overrides the model's judgment, you get false positives. When you trust it, the host becomes simpler and the agent becomes better.

A prompt instruction like "avoid repeating tool calls without new information" achieves the same goal as a behavioral guard without preventing the model from making an informed exception. A guard is a hard block. A prompt instruction is guidance.

## What stays

Not everything goes. Step budgets stay because they protect resources, not behavior. Two hundred tool calls on a one-line fix means something is wrong regardless of model quality. That is a cost boundary, not a judgment call.

The tool cache stays. It makes the model faster without constraining decisions.

Format and lint stay as automatic effects. The host should handle deterministic execution, and that has not changed. Format and lint still run after every write. The difference is that lint errors no longer force a regeneration. They surface in the tool result and the model decides what to do. The host provides information. The model makes the call.

## The uncomfortable part

This only works with capable models. If you run Acolyte on a local 7B model, it will probably loop. The guards I am removing would have caught that.

I am okay with that tradeoff. Acolyte targets developers who run frontier models. Building for the weakest possible model means holding back the best ones. If smaller models catch up, they get the same benefits automatically.

I also looked at how other open-source agents handle this. Most of them do not have behavioral guards at all. They give the model tools, a prompt, and get out of the way. That was a useful data point. The direction is clear from both the benchmarks and daily use: the scaffolding is becoming the bottleneck, not the safety net.

## Wrong abstractions

This is what wrong abstractions look like. They start reasonable, attract more code, and become load-bearing before you realize the foundation was wrong. Removing them touches dozens of files. That is the real cost: not building the abstraction, but untangling everything that grew around it.

These ones lived for weeks, not months. In a larger codebase, this kind of removal becomes a multi-sprint project that nobody wants to start.

The mode system, the behavioral guards, and the evaluators were built on assumptions about what the model needed. Those assumptions were wrong. Recognizing that and acting on it is what keeps the product honest.