What a Month of Portfolio-Wide Engineering Looks Like

I’ve written about the pieces of my setup separately: Swamp as the automation backbone, the Open Knowledge Format as the memory, and distilling skills out of that memory. This post is the output side. For the last month I’ve had a loop running continuously across every repository I own, and I want to describe what came out of it, because the shape of the work surprised me more than the volume did.
The numbers
In one month the loop opened and I merged 651 pull requests across 75 repositories. At peak it produces about a dozen per five-hour cycle. My involvement is two gates: I approve which proposed work gets built, and I merge the result. Everything between those two clicks happens without me.
The distribution is lopsided in a way I find informative. The top of the list looks like this:
| Project | Merged PRs |
|---|---|
| my newsletter product | 58 |
| python-skills (shared agent skills) | 34 |
| elote (rating systems) | 19 |
| git-pandas (git analytics) | 17 |
| ParGA (parallel genetic algorithms) | 17 |
Then a long tail of 50-odd projects with one to twelve each. The active products get the most attention because they have the most open questions, and the dormant libraries get a handful each, which turns out to be exactly what they need.
What the work actually was
I expected features. What I got, in rough order of volume:
- Correctness fixes in library code. The single largest category, and the one I care most about. Not crashes, mostly: quiet wrongness in math, in cache keys, in ordering. The kind of thing that returns a plausible number.
- Tests and CI. Eighteen repositories went from having no test suite or no CI to having both. Thirty-five got their GitHub Actions configuration tuned in the same pass.
- Security and hygiene. Credentials moved out of source and into the environment, state-changing endpoints given CSRF protection, client IP derivation fixed behind proxies.
- Modernization. Fourteen repositories got some flavor of it, ranging from dropping Python 2 constructs and
distutilsto declaring a supported Python range and moving ontouvwith a committed lockfile. - Features, last and smallest, concentrated almost entirely in the two products I’m actively selling.
That ordering is the thing I’d want someone else to take away. Given a portfolio and unlimited patience, the highest-value available work is not new capability. It is making the existing capability correct, tested, and buildable.
The capability that’s actually new
I keep coming back to one number: 35 repositories got the same CI change in the same week. Not 35 similar changes, one change applied 35 times, from a single finding.
Nobody hand-writes that. Not because it’s hard, but because it’s 35 tedious pull requests that each save a few dollars a month, and no reasonable person spends a week that way. Same story with porting five dormant applications to modern Python, or adding a first test suite to eighteen repositories that never had one. These were always worth doing and never worth doing by me.
So the interesting question stopped being “how do I improve this codebase” and became “what convention is missing everywhere.” That reframing is the whole shift. A single clever abstraction in one repo is now worth less to me than a mediocre convention applied consistently across seventy, because the second one is finally cheap.
Where the bottleneck went
It went straight to me, which I should have predicted.
Writing code is no longer the constraint. Reviewing it is. A dozen pull requests every five hours is more than I can read closely, so I triage by blast radius: anything touching math, money, authorization, or a data migration gets read line by line, and dependency bumps and CI configuration get skimmed. The failure I actually watch for is not a bad diff. It’s a well-tested, plausible fix to a misdiagnosed problem, which passes every check and solves the wrong thing.
The other place my time goes is direction, and that one I asked for. The loop proposes work, and I steer it per repository through a written strategy document that says what each project is for and what it should become. Steering fifteen projects by writing paragraphs is a genuinely different job from steering them by writing code, and I’m still learning to do it well.
What I don’t delegate
Worth being explicit, since “AI wrote 651 pull requests” invites a specific suspicion. The loop is instructed to fix technical blockers on its own and to escalate anything that is a product decision. Scope, pricing, packaging, and direction come to me as a question, never as a diff. When an agent hits something that requires deciding what the product should be, the correct output is a question in the strategy channel, and I answer it.
That boundary is not there for safety theater. It’s there because those are the decisions where I have information the codebase does not contain, and where a plausible guess is expensive to undo.
The uncomfortable part
Two-thirds of that 651 was work I had been meaning to do for years. The dormant Flask app that no longer imported on modern Python. The library whose cache silently returned wrong results. The eleven repositories tracking files that should never have been committed. None of it was mysterious. All of it was known and shelved, because the cost of context-switching into an old project exceeded the value of the fix.
That cost is what changed. The fixes were always cheap. Getting to them was expensive.
Stay in the loop
Get notified when I publish new posts. No spam, unsubscribe anytime.