On September 14, 2026, one commit gave my agent fleet two rules about code structure, in the same paragraph. One said the authority package never imports the daemon. A script checked it on every change, and at the freeze it had 0 violations. The other said “Prefer small modules.” Nothing checked it, and one class grew from 15 methods to 271 in 16 days. A rule an agent only reads decays. A rule a machine checks holds.
Two rules, one check
I knew this before it bit me. The plan for Jarvis v1, the successor to
Omnigent, was confirmed on September 14. It said the codebase has one
voice because the formatter and the rule files decide it, and that “a CLAUDE.md is a
reminder, never the mechanism.” Structural drift the tools couldn’t see went to a weekly architecture
sweep — fan-in, fan-out, and import boundaries per module — scheduled for a later milestone.
That afternoon’s commit wrote the conventions file. Every brief an agent got carried the file’s exact bytes and its SHA-256. By the freeze the sentence sat in 27 files.
# CONVENTIONS.md, commit e6b8377e78, 2026-09-14
Prefer small modules with one responsibility and explicit dependencies. The
standalone authority package never imports the daemon. Shared native protocol
models belong in JarvisKit; UI belongs in the apps.
The same commit turned the second sentence into an AST walk in tools/checks.py that fails any
daemon import under the authority package. The first sentence got nothing. The check that did ship for
conventions proved every brief carried the rule, byte for byte. Nothing checked whether the code followed
it. Delivery isn’t compliance.
What 16 days did to one class
RunnerStore was born on September 24 with 15 methods and a docstring that kept it small: “their lifecycle transitions belong to later runner modules.” By October 9 it had 271 methods in a 10,818-line file. 186 PRs touched it. On September 27 alone it gained 68.
v1 wasn’t checkless. Its check script grew from 14 failure sites to 105 between September 14 and October 10. Those checks covered secrets, test names, migration numbers, and an engine boundary added on September 29. None measured size, and none looked at the runner. The checks guarded what had already broken, and the runner hadn’t broken yet.
Every diff was reasonable on its own. Size is a property of the sum, and no agent owned the sum. I didn’t either. “I definitely take ownership of not looking at all the PRs or quite frankly, any of the PRs that led up to that.”
The audit on October 10 named the root cause in its heading: “zero architecture fitness functions.” In its
words, “There is no import-linter, no tach, no ruff banned-api/TID
rule, and no layer or boundary test in the gate.” The agent that ran my shipping put its own line on it
that morning: “I optimized for merge count and never put architecture rules in the gate.” My reaction was
simpler. “The idea that there was a repository class that had 271 methods in it is just insane to me.”
Why the sweep never ran
The weekly sweep from the September 14 plan was never built. No sweep code exists in the v1 tree. The plan had the idea and the check was deferred, and nobody came back for it.
I don’t have a clean reason. “I’m not sure why it never got built.” The pattern I see is one I still fall into: “I often have conversations with an agent and I believe that the agent has a great impeccable memory and will do all the things we talked about. But the reality is that they don’t.” It agrees, we make a plan, and “things don’t end up going that way because there’s constantly more and more things that happen.”
The other half is mine. “My desire is faster than what the agent can build and provide, and I need to find ways to either taper that or you know, I just need to do a better job of that.” At the pace the fleet merged, a weekly sweep would already have been a week late.
Gates first in v2
I reset Jarvis on October 10. The next day an audit checked 208 lessons from v1 against the new pipeline, and 8 were honoured. The rules hadn’t survived the rewrite. The ones that carried were the ones that had been scripts.
So v2’s daemon started with the gate. Its first commit, on October 12, installed import-linting, domain boundaries, and capability shape on a package with no code in it yet, each with planted violations that have to fail.
# commit f864e879e0, 2026-10-12
BE1-0.1 Scaffold the Python daemon and enforce architecture
Add daemon-lint/types/imports/shape to GATE
C2: IMPORT-LINT, DOMAIN-BOUNDARY, CAPABILITY-SHAPE and TYPES run in make gate;
actual-tool planted-violation checks fail as expected
C4: Empty daemon runtime/test suite
The direct answer to RunnerStore came on October 17. Any port with more than 10 methods fails the gate. Its doc lists the escape hatch as “none.”
# scripts/gate/lib/port-checks.py, commit 25f1020c2c, 2026-10-17
LIMIT = 10
PORTS = ("platform/*/port.py", "platform/*/public.py", "domains/*/port.py", "domains/*/public.py")
def methods(cls: ast.ClassDef) -> list[Fn]:
return [n for n in cls.body if isinstance(n, Fn)]
def port_size(root: Path) -> list[str]:
return [
f"PORT-SIZE: {where}: {len(methods(cls))} methods (max {LIMIT}); split it per aggregate"
for where, cls in protocols(root)
if len(methods(cls)) > LIMIT
]
RunnerStore was a concrete class, not a port, so this exact check wouldn’t have scanned it. Counted the same way, it was over the limit the night it was born. On October 29 the v2 daemon has 154 Protocols, its port interfaces. The largest has 9 methods, and the median has 2.
On October 18 I put it the way I’d put it now: “Everything possible needs to be deterministic and mechanical.” Counted checks went from 5 on October 10 to 126 by October 28. The agent had written its own version into its rules file on October 10: “A principle without a check is a wish.” That line is the agent’s, not mine.
Then the rules needed a check
The lesson moved up a level within a week. On October 13 I asked, “How do we ensure that agents actually
follow the bake rules? What more do I need to do?” The bake list is where every lesson from the fleet gets
written down. My first instinct was more instructions. The answer that stuck was a gate on the list
itself. Since October 17, a new entry without Enforced-by:, naming a test or script that has
to exist and run, or JUDGEMENT:, saying why no check is possible, fails the pipeline. Entries
naming an enforcer went from 6 of 386 on October 16 to 214 of 558 on October 29.
Presence isn’t quality. The resolver proves the enforcer exists and runs, not that it guards the rule well.
Checks have a cost too. Weekly merge rates fell from 11.2 an hour in v2’s first, UI-only days to between 2.5 and 3.2 an hour while the checks went from 5 to 126. They bought reliability, not speed. The shared exception lists, the files that let old code skip a new rule, turned into the most-edited files in the repo.
Where it still leaks
A person with a terminal, a good rules file, and a strong model can get a long way. “To get off the ground sure use just pure agents to do things but then once you want to start to scale you’re going to want to become more efficient and the only way you can be more efficient is to do things deterministically.” Past a few agents, the rules file stops being the mechanism.
The leak I haven’t closed is the promise. When the agent and I agree on a plan, it goes into a commitments ledger, because the agent’s memory isn’t one. The ledger’s rows went stale within two days. There’s meant to be a process that reads it and checks we’re on track: “whether or not that actually runs, I know that it’s not running, but that is the idea. I don’t know, still haven’t figured out the right solution for that.” A ledger the agent has to remember to check is still a prompt.
I also owe audits of the checks themselves. “If there is something that gets a little off track, then I think adding more linting rules is the right way to do it for right now.” More lint catches drift, but nothing yet asks whether the lint is still the right lint.
The rule
A rule enters the system with its check, or it enters labelled as judgement. The check goes in before the code it guards, it proves it can fail, and it has no escape hatch unless the hatch can only shrink. The rules a check can’t decide get counted and routed to review, not trusted to memory. Mine go to a reviewer on the other vendor. The same move took the fleet’s orchestration off the model, in where code replaces the model.
Underneath
The commits, the RunnerStore counts, and the gate scripts are from my own repositories for Jarvis, which
aren’t public. SHAs are given as the repositories record them. The method count is every
def and async def directly in the class body, parsed with Python’s
ast module at the last commit of
each day the file changed. v2’s import rules run through
import-linter. Gate counts add gate scripts, layout
rules, and web lint rules. The quotes are mine, from agent sessions and an interview on October 29, 2026,
except where the text credits the agent.
More field notes
Start
I’ll tell you in about a day whether I’m the right person. The first conversation is fit, not a free architecture review.