Jarvis · chapter 1 of 4

How one class grew to 271 methods while I wasn't reading PRs

A prompt told every agent to keep modules small, and one class still grew from 15 methods to 271 in 16 days because nothing checked it.

On October 10, 2026, an architecture audit of Jarvis v1 found a class called RunnerStore with 271 methods in a 10,818-line file, and 226 modules importing it. It had been born on September 24 with 15. In 16 days, 186 PRs touched it. I didn’t read any of them.

methods in RunnerStore 0 100 200 300 Sep 14 Sep 21 Sep 28 Oct 5 Oct 10 2026, last commit of each day the file changed Sep 14: “Prefer small modules” written into the rules file, no check born Sep 24, 15 +68 on Sep 27 271 on Oct 9 Oct 10 audit and v2 reset
Fig. 01 — Methods in one class, RunnerStore, at the last commit of each day it changed. Born with 15 on September 24, it had 49 when the babysitter took over shipping, and 271 by October 9. “Prefer small modules” was in every agent's brief the whole time. The audit on October 10 ended that codebase.

Fig. 01 · pinch or scroll to zoom · drag to pan

What I believed

On September 14 a conventions file went into every brief an agent received: “Prefer small modules with one responsibility and explicit dependencies.” I believed a rule every agent reads is a rule every agent follows. In v1 I ran the fleet that way. “I was really spending a lot of time allowing the LLMs to manage things before and not having a lot of deterministic capabilities.”

Three days later I asked whether the code being generated for Jarvis was actually good. The agent’s answer that night was that it had “no evidence either way,” and it proposed auditing recent merges. No architecture audit ran until October 10. RunnerStore didn’t exist yet when I asked. It was born a week later and grew under the same blind spot.

What broke

RunnerStore was the card engine’s store, and every card that needed one more thing from it added one more method. It had 49 methods when the babysitter, the agent session that ran shipping from outside, took over on September 26. 222 of the 271 came after that. On September 27 alone the file took 75 commits from 29 PRs and gained 68 methods.

No single diff was unreasonable. Size is a property of the sum, and nothing owned the sum. By the freeze, 108 of the 271 methods wrote to more than one kind of record, and the proposed split was 18 repositories. The database driver had spread the same way. Files under the daemon that named sqlite3 went from 0 on September 13 to 223, with 578 except sqlite3 clauses across 178 files and 20 separate database files.

The audit named the cause in one line: “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 had been running the fleet described the mechanism: “A 271-method ‘repository’ isn’t a repository. It’s what you get when every card adds ‘one more method’ to the nearest object and nobody steps back.” Then its own part: “I optimized for merge count and never put architecture rules in the gate.” v1’s best day, October 8, merged 320 PRs.

Mine is simpler. “The idea that there was a repository class that had 271 methods in it is just insane to me. And I definitely take ownership of not looking at all the PRs or quite frankly, any of the PRs that led up to that.”

What I do now

I reset the codebase that day, and the next chapter covers what survived. The audit “opened my eyes to needing to be smarter on how I’m building this out and not expecting that the agents are going to build things with best practices in mind.” v2 starts from the patterns instead of hoping to arrive at them. “It’s obviously a lot easier to fix it from when you’re trying to build from scratch than try to retroactively go fix everything.”

Two checks answer RunnerStore directly. One fails any port in the daemon with more than 10 methods. The other fails any database driver import outside an adapter. Both list their escape hatch as “none.” The port check counts methods on interfaces, and counted the same way, RunnerStore was over the limit the night it was born. Every outside call in v2 goes through a port with a fake and one contract suite that runs against both fake and real.

“Now I’ve got a lot of mechanical capabilities that are built in that are forcing things to work the way that I want them to, such as linting that don’t get overlooked by an LLM.” Where the rules live is that gate, and what it costs.

What’s still open

Lint catches the shapes I’ve already seen. It doesn’t catch the next one. “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. … But you’re right, I should be doing more comprehensive audits on a interval basis.” The audit that found RunnerStore ran once, 16 days in, and no audit is on a schedule yet. The prose rules carried over from v1 are still being converted into checks.

The same commit that wrote “Prefer small modules” also gave the fleet a rule a script did check, and that one held. A check for every rule my agents follow tells it from that side.

I don’t put a structural rule in a brief anymore without the check that fails it. The brief says what I want. The gate decides what ships.

How this was counted

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. The PR count is merges on v1’s main that touched the file. The sqlite3 count is files under the daemon’s source tree that name it; the audit itself counted 222 under a slightly different rule. The quotes are mine from an interview on October 29, 2026, except where the text credits the agent or the audit.

This project

This project, in order

Start

Tell me what’s stuck

I’ll tell you in about a day whether I’m the right person. The first conversation is fit, not a free architecture review.