Notes on Building, Leading, and Figuring Things Out

Lessons from 15 years in tax tech, building AI tooling, and leading teams through impossible-sounding projects.

July 2026 6 min read

Virginia Doesn't Care What You Think of It

I moved to Virginia thinking it was a placeholder — a sensible, boring address between bigger lives. Six years later I'm pretty sure I was wrong, and I'm not sure what to do with that. This is not a love letter. It's more of a grudging acknowledgment.

The State That Refuses to Have a Brand

Every state in America has a bit. Florida is unhinged. Texas is loud. California is expensive and convinced it's saving the world. New York is New York. These are lazy stereotypes, sure, but they're sticky because they have enough truth to function as shorthand. Virginia doesn't have a bit. Virginia is just... there. Present. Competent. Offering neither chaos nor spectacle. When I told people I was moving here from out of state, the most common response was a thoughtful pause followed by, 'Oh, cool.' That pause is doing a lot of work.

The Geography Is Quietly Insane

Here's what nobody puts in the brochure: Virginia is three states wearing a trench coat. You have Northern Virginia, which is effectively a DC suburb that happens to have a different governor, populated by defense contractors, federal employees, and data centers. Then you have the middle — Richmond and its orbit, which has actual culture, weird restaurants, and a chip on its shoulder about being overlooked. Then you have western and southwestern Virginia, which is mountains and small towns and an entirely different relationship with time. I live in a part of the state where I can be in DC traffic in forty minutes or standing next to a cow in twenty. This should not be possible. It is possible.

The Traffic Is a Character Study

I work in tax technology. I think about systems, bottlenecks, and failure modes for a living. Northern Virginia traffic is the most elegant demonstration of cascading system failure I have ever witnessed in the physical world. One fender-bender on I-66 propagates backward through the network with a precision that would make any distributed systems engineer both impressed and furious. I have sat unmoving on the Beltway for forty-five minutes because someone's bumper fell off six miles ahead. The locals have made their peace with it. They have podcasts queued. They have snacks. They have accepted that the road is not a road but a waiting room with lane markings.

'Virginia is the only place I've lived where you can have a genuinely good conversation about Civil War fortifications, data center power consumption, and crab pot regulations in the same week, with different people who each think their topic is the obvious one.'

The History Is Not Optional

I grew up in a place where history was something that happened somewhere else. Virginia does not offer you that comfort. You cannot drive twenty minutes in most directions without running into a battlefield, a marker, a cemetery, or a town that is in several history textbooks. My kids have absorbed more American history by proximity than I ever did in a classroom. The oldest can tell you about the significance of Fredericksburg with a matter-of-factness that I find both impressive and slightly unsettling. The state treats history like infrastructure — it's just part of the landscape, maintained and present, whether you engage with it or not.

The Weather Is Genuinely Adversarial

Virginia gets four seasons, which sounds like a feature. It is a bug. Summer is humid in a way that feels personal — the kind of humid where you step outside at 7am and immediately understand that the air has already won. Winter is inconsistent enough to be maddening: two inches of snow produces full societal shutdown, and then three weeks later it's sixty degrees and people are on patios. Spring is genuinely beautiful and lasts approximately eleven days. Fall is long and excellent and makes you feel like the weather is apologizing for everything else it did. I have a complicated relationship with fall here. I look forward to it with an intensity that would embarrass my former self.

It Made Me a Different Kind of Distracted

I build things for a living. I think about AI tooling, system design, and engineering team dynamics most of my waking hours. Virginia hasn't changed that. But it has added noise — good noise. There are trails near my house where I've solved more architecture problems than I have in any conference room. The state has this texture to it, topographically and culturally, that rewards going outside and being mildly uncomfortable. I don't know how to quantify the value of walking through fog on a November morning before a long engineering review, but I'd put it somewhere above 'useful' and below 'essential.' Which is basically how I'd describe Virginia itself.

The Grudging Part

I still don't tell people they should move here. That would be too much. Virginia doesn't need my advocacy and frankly wouldn't want it. But I've stopped treating it as a temporary address, a responsible choice, a sensible middle option. It's a real place with a real texture, and it suits me in ways I didn't plan for and can't fully explain. That might be the most Virginia thing about my relationship with Virginia: it works, I'm not entirely sure why, and I've decided to stop questioning it.

Key Takeaways

  • Virginia resists easy categorization — it's three distinct regions sharing a border and not much else, which is either a flaw or what makes it livable depending on your tolerance for ambiguity.
  • The Northern Virginia traffic system is the best real-world model of cascading distributed failure I've encountered outside a production incident.
  • History here isn't curated or optional — it's infrastructure, and it changes how your kids think whether you intend it to or not.
  • Living somewhere that refuses to perform for you turns out to be underrated. Virginia doesn't need to be exciting. It just needs to work.
July 2026 5 min read

What Two Years of Bedtimes Taught Me That Fifteen Years of Engineering Didn't

I spent fifteen years learning to make systems predictable — to remove variance, to write the test before the bug, to design so that the same input always produces the same output. Then I had a kid. A toddler is the most beautifully non-deterministic system I have ever tried to operate, and two years of bedtimes have quietly rewired how I think about control, patience, and what "done" actually means.

My entire career has been an exercise in making things predictable. You write the failing test first. You design the API so the same input always returns the same output. You add logging so that when something breaks at 2 A.M., the system can tell you exactly why. Fifteen years of that trains a certain kind of brain: cause, effect, control, repeat.

Then a small human arrives, and none of it applies.

A toddler is the most gloriously non-deterministic system I have ever tried to operate. The same input — same dinner, same bath, same three books in the same order — produces a peaceful sleep one night and a full-scale negotiation the next. There is no stack trace. There is no changelog. There is just a very small person who has, overnight, decided that socks are now unacceptable.

1. You cannot debug a feeling

My first instinct, the engineer's instinct, was to treat the meltdowns as bugs. Isolate the variable. What changed? Was it the nap, the snack, the weather? I'd run the whole diagnostic tree in my head while my daughter cried on the kitchen floor.

It took me embarrassingly long to realize the crying wasn't a defect to be fixed. It was just information — a two-year-old with a two-year-old's vocabulary trying to tell me something big with very small tools. The job was not to resolve the ticket. The job was to sit on the floor with her until the storm passed. Presence, it turns out, is not a patch. It's the whole feature.

2. "Done" is a lie I told myself at work

In software, "done" is a comforting fiction we all agree to. The story is closed, the PR is merged, we move on. Parenting has no merge. There is no state where the work is finished and you get to walk away from the repo. Every night resets. Every phase you finally understand is immediately replaced by a new one you don't.

Fifteen years of engineering taught me to remove variance from a system. Two years of fatherhood taught me that the variance was the point.

Strangely, this has made me better at my actual job. I've stopped treating "done" as the goal and started treating "still standing, still showing up" as the metric that matters. Teams are like that too. There's no version of leadership where you finish caring about your people and get to close the tab.

3. Patience is not a virtue, it's a runtime

I used to think of patience as a personality trait — something you either had or didn't. Fatherhood reframed it as a runtime environment. Patience is the amount of latency you're willing to tolerate between an action and the result you wanted, without throwing an exception.

A toddler will test that latency tolerance the way a load test hammers a server. Putting on shoes can take twenty minutes. Twenty minutes! For an operation that should take forty seconds. And the only two options are: raise your voice and poison the whole morning, or breathe, expand your timeout threshold, and let the small human do the small human thing. I am, slowly, learning to expand the timeout.

Here's the thing nobody told me: the skills that made me good at building systems actively worked against me as a father, at least at first. The impulse to optimize, to control, to reduce everything to inputs and outputs — that impulse is a liability when the system you're caring for is a person who mostly needs you to slow down and be there.

Two years in, I've stopped trying to debug my kid. I sit on the floor. I expand the timeout. I show up again the next night knowing the work is never done, and that this is the good news, not the bad. It's the best uptime commitment I've ever made.

Key Takeaways

  • The engineer's instinct to make everything deterministic actively backfires when the "system" is a small human.
  • Not every hard moment is a bug to fix — sometimes presence is the entire solution.
  • "Done" is a useful fiction in software and a harmful one in parenting and leadership; showing up consistently is the real metric.
  • Patience is less a personality trait than a timeout threshold you can deliberately widen.
June 2026 6 min read

The US Tax Code is the World's Worst-Designed Programming Language

Software engineers love complaining about legacy spaghetti code. But if you want to see a codebase that is truly on the brink of collapse, look at the Internal Revenue Code. It has circular dependencies, expired functions that are never deleted, and a parser that takes twelve months to return a compiler error. Here is why the tax code is the ultimate legacy system.

We software engineers think we have it hard. We whine about microservices that should have been monoliths, complain about Javascript framework churn, and write thinkpieces about the technical debt in our 5-year-old React applications.

But if you want to see a codebase that is truly, catastrophically broken; a system that handles trillions of dollars of throughput while running on logic that violates every known principle of computer science; you need to look at the US Internal Revenue Code (Title 26).

The IRC is not just a set of laws. It is a massive, Turing-complete codebase written in natural language, compiled by accountants, and executed on the brains and spreadsheets of 150 million taxpayers.

And as a software system, it is an absolute disaster. Here is why.

1. The Logic Rules are Patched by Non-Technical PMs

In a healthy software project, the people defining the business logic collaborate with engineers to ensure feasibility.

In the tax industry, the logic is defined by 535 product managers (Congress) who have never written a line of code in their lives. Every December, they push a massive, 4,000-page pull request called an "omnibus bill" directly to the production branch with zero unit tests, zero regression planning, and an absolute disregard for edge cases. If you think your company's product requirements are vague, try coding a system based on rules that were literally drafted in a smoke-filled room at 3:00 AM to secure a senator's vote.

2. Circular Dependencies are Built into the Architecture

One of the first rules of software engineering is to avoid circular dependencies: Component A should not require Component B if Component B also requires Component A.

The tax code has circular dependencies baked into the core engine. Consider the interaction between state income tax deductions and federal tax liability. To calculate your federal taxable income, you deduct your state tax paid. But to calculate your state tax liability, you need your federal adjusted gross income (AGI).

In computer science, this is a deadlock. In the tax industry, we solve this by having accountants run iterative calculations until the numbers stop moving; a manual, human-compiled loop that exists purely because the architects did not know how to design a clean API schema.

taxc — Internal Revenue Compiler v10.40
$ taxc --compile --optimize -f form1040.xml
[INFO] Initializing IRC parser engine...
[INFO] Loaded 74,000 pages of logic guidelines.
[WARN] Sec. 163(h): Deprecated mortgage interest deduction contains obsolete logic gates.
[WARN] Sec. 199A: Qualified Business Income calculation pattern is highly unstable.
[ERR] Compilation failed: Circular dependency detected in state_federal_loop.o.
[ERR] Stack overflow. Please run manual iterative reconciliation or hire a CPA.

3. "Dead Code" is Never Deleted

When a feature is no longer needed in software, you deprecate it and eventually delete the code.

In the tax code, code is almost never deleted. Instead, Congress uses "phase-outs." If they want to eliminate a deduction, they do not remove the section. They add a clause saying the deduction is phased out by 2% for every $1,000 of income above a certain limit, subject to inflation adjustments, unless you are a qualifying farm owner in a disaster zone.

The tax code is a codebase where the logic rules are written by a committee of 535 non-technical product managers with zero unit tests.

This leaves millions of lines of "dead logic" active in the system, forcing compilers (both human and machine) to evaluate thousands of conditional branches that apply to almost nobody. It is the legislative equivalent of leaving a decade of commented-out code in your repository "just in case we need it later."

4. Twelve-Month Compiler Error Latency

If you write bad code, your compiler tells you in milliseconds. If you deploy a bad build, your monitoring alerts trigger in seconds.

If you compile your tax return incorrectly, the compiler (the IRS) takes up to twelve months to send you an error message. And it does not come with a stack trace or a line number. It comes as a paper letter (the CP2000 notice) written in legal prose that demands payment within 30 days or it will trigger a manual debugger (an audit).

So the next time you are staring at a messy codebase and cursing the developer who wrote it, take a deep breath. Look at your clean IDE, your instant compiler feedback, and your automated test suite.

It could be worse. You could be compiling the tax code.

Key Takeaways

  • The tax code behaves exactly like an unmaintainable legacy software system with decades of technical debt.
  • Phase-outs and sunset clauses act as messy conditional patches instead of clean logic deletions.
  • The lack of automated testing or compile-time linting results in high error rates and delayed compiler feedback.
  • Tax complexity exists because we use the tax code as a policy steering framework, making clean architecture politically impossible.
February 2026 8 min read

Why I Built a Code Intelligence Platform in 6 Days (And What MCP Servers Actually Solve)

Everyone's talking about AI coding tools. But here's the problem nobody mentions: LLMs don't understand your codebase. They understand code in general. When your company has 63 repositories, a proprietary DSL, and calculation chains that span 15 files — "code in general" isn't enough.

At Taxwell, our tax calculation engine lives across two very different codebases: PowerBASIC (Drake) and MathMaster DSL (TaxAct). When we started using Claude Code for development work, we hit a wall immediately. The AI would confidently generate code using syntax that didn't exist. It would trace a calculation chain halfway and then fabricate the rest. It would suggest changes to functions without understanding that 47 other functions depended on them.

The answer wasn't better prompts. The answer was giving the AI a structured knowledge layer it could query. That's what Model Context Protocol (MCP) servers do — they give LLMs tools to look things up instead of guessing.

I built our code intelligence MCP server over a long weekend that turned into 6 days. The architecture is straightforward: an offline indexer parses all 63 repos and builds a SQLite database with FTS5 full-text search. Function definitions, call relationships, DSL field dependencies, cross-form references, include chains — all pre-computed and queryable. The MCP server exposes 37+ tools that Claude Code can call: search_code, get_call_chain, get_calculation_flow, get_form_dependencies, find_references.

The result? Questions that used to take hours of manual file-hopping now take seconds. "What breaks if I change this function?" has a real answer. "Trace the Earned Income Credit calculation end to end" returns the complete chain instead of a hallucinated approximation.

The lesson for other technical leaders

If your team is using AI coding tools and getting mediocre results, the problem probably isn't the AI. The problem is that the AI doesn't have access to the relationships and context that live in your engineers' heads. An MCP server — even a simple one backed by SQLite — can be the difference between an AI that wastes time and an AI that multiplies your team's output.

You don't need a vector database. You don't need embeddings. You need a well-indexed relational model of your codebase and a way for the AI to query it. Start there.

January 2026 6 min read

What Nobody Tells You About Merging Two Engineering Teams

When Cinven acquired TaxAct and Drake Tax, someone had to figure out how to make two very different tax development teams work as one. Here's what I learned about the human side of M&A integration that no playbook covers.

The tech was the easy part. Different codebases? Fine — we can build abstraction layers. Different deployment processes? Fine — we can standardize. Different testing philosophies? Fine — we can align on quality gates.

The hard part was that people had built their identities around their team. Drake developers were proud of being Drake developers. TaxAct developers had their own culture, their own inside jokes, their own way of doing things. Telling both groups "you're one team now" is about as effective as telling two families "you're one family now" at a forced reunion.

What actually worked

Shared problems, not shared mandates. Instead of reorganizing and hoping for the best, I gave both teams a shared problem to solve together. HappyFox ticket triage was the first one — everyone could see the customer pain, and fixing it required knowledge from both sides. Nothing bonds a team faster than a shared enemy, and "customer suffering" is a pretty compelling enemy.

Transparent career architecture. One of the first things I did was build a unified career ladder — Analyst I through Principal — with written job descriptions for every level. When people can see where they're going and what it takes to get there, a lot of the political anxiety disappears.

Let people grieve the old team. This sounds dramatic, but it's real. People had built careers on Team A or Team B. Acknowledging that something was being lost — not just something being gained — made the whole transition smoother.

Two years in, the team operates as a genuine unit. But it didn't happen because of a reorg slide deck. It happened because we focused on the work, built trust through shared wins, and treated the humans like humans.

November 2025 5 min read

From 38% SLA Breach Rate to 5.6%: A Dashboard Is Not a Dashboard

We had zero visibility into our support operations. No one could tell you how many tickets were open, who was handling them, or whether we were meeting our SLAs. Six dashboards later, our breach rate dropped by 85%. Here's the framework.

When I took over federal tax development, I asked a simple question: "How are we doing on support ticket response times?" The answer was a combination of shrugs and someone pulling up an Excel file from three weeks ago. That was the moment I knew dashboards weren't optional — they were the foundation.

But here's the thing about dashboards that most people get wrong: a dashboard that nobody looks at is worse than no dashboard at all, because it gives you the illusion of visibility. I've seen teams build beautiful Power BI reports that get opened twice and then forgotten.

The framework that worked

Start with one question, not one dataset. Our first dashboard answered exactly one question: "Which tickets are about to breach SLA?" Not a comprehensive ticket overview. Not a historical analysis. Just: what's on fire right now? That dashboard got used every single morning because it was immediately actionable.

Automate the delivery, not just the data. This is where Power Automate changed the game. I built a flow that sends an AI-generated daily recap to our Teams channel every morning before anyone starts work. Nobody has to remember to check the dashboard. The insights come to you.

Make it embarrassingly specific. Our dashboard shows which individual analyst handled which tickets and how long each one took. That felt uncomfortable at first. But when the top 5 analysts were handling 69% of tickets, that specificity led to redistribution that made the entire team healthier.

The 38% to 5.6% SLA improvement didn't come from working harder. It came from knowing where to look. That's what dashboards actually do when they're built right.