HAOL 1.0

In May I wrote about HAOL learning to watch itself — cascade traces on every routing decision, an observability endpoint, a tuner that promotes its own rules. I ended that post on a deliberately unfinished note: the system was measurable, and once a system is measurable, its problems become tractable. As of today, HAOL is tagged 1.0.
1.0 doesn't ship a marquee feature. In fact, almost nothing obvious changed between 0.5.0 and 1.0. What changed is everything around the features — and that, it turns out, is what the version number was waiting on.
I’ve shipped enough side projects to know my own weakness: I call something done when the interesting part works. The interesting part of HAOL worked in May. So I made myself write down what would have to be true before I’d put it in front of real, untrusted traffic. The list came back as five hard blockers — none of them fun or interesting, but HAOL could never be a trusted service without addressing them:
- The rate limiter’s trust-proxy configuration
- A PII retention policy for the
promptandinput_textfields - An exactly-one-active constraint on routing policy
- A
claimQueuedrace that could strand rows and corrupt task state - A Zod issue-tree that leaked internal structure in error responses
Version 1.0 is the release where that list reached zero.
Blockers#
Two are worth walking through, because they’re the exact category of bug the happy path will never catch.
The rate limiter trusted attacker-supplied input. Per-IP rate limiting is only as good as your idea of what the client’s IP is. Behind a load balancer the source address is the proxy, and the real client lives in X-Forwarded-For — but only the hops you control are trustworthy. Anything past that can be forged to mint unlimited buckets. So RATE_LIMIT_TRUSTED_PROXY_HOPS now has to be set explicitly.
Every prompt HAOL routes is somebody’s input, and HAOL stores its routing decisions in Dolt — a version-controlled database whose whole point is that history is immutable and diffable. That’s a feature for routing rules and a liability for user content. A real retention policy for prompt and input_text had to exist.
The remaining three are the same shape: an exactly-one-active constraint to stop the router resolving two active policies nondeterministically, and fixes for the claimQueued race and the Zod information-disclosure leak — the latter two the same category as the bugs the security-middleware tests revealed in May. None of these is easy to expose with a unit test.
Infrastructure#
Type safety and linting are now CI gates: npm run typecheck and npm run lint run across source, tests, and scripts, so the next time I haphazardly refactor a connection helper at 11:00 at night, the build provides an early warning. The escalation model, previously hardcoded in four places, is now a single META_MODEL_ID constant. And withBranchConnection had been resetting branch state on every release whether or not anything touched it, roughly 40 extra queries per task; cutting them is invisible to users.
The 1.0 Promise#
HAOL has been backward compatible since 0.7.0. Releasing 1.0 is a declaration that the public surface is stable enough to build against. I’m now on the hook for not breaking it. A version under 1.0 is a license to experiment. Going to 1.0 is relinquishing that license for stability.
I think the architecture is right. The five-stage pipeline — intake, cascade router, agent selection, execution, outcome capture — hasn’t needed a structural change in months. The interesting churn now happens inside the layers, not in the seams between them. That stability is the real precondition for a 1.0.
None of which means finished. There’s still no real-time traffic dashboard sitting on top of the observability endpoints, the migration runner’s parsing is more fragile than I’d like, and a handful of routing edge cases still fall through to T3 by default. 1.0 means the foundation is stable, not that the building is done.
But I’ve learned the same lesson here that I did shipping GrindLab a month ago: the distance between a project and a product is mostly that last, least interesting twenty percent — the retention rules, the trust-proxy config, the CI gates nobody will ever thank you for. The fun part earns you a prototype. The boring part earns you the version number.
HAOL 1.0 is the boring part, finished. That’s exactly why I’m proud of it.