I started writing code in 1985.
I was twelve, on a Commodore 64, copying BASIC programs out of magazines and wondering why changing one line made the whole thing stop working. The screen was green. The storage was a cassette tape. The concept of "the cloud" would have sounded like science fiction.
I started getting paid for it in 1996.
That first paycheck felt like a mistake. Someone was going to give me money to do what I did for free on weekends? The answer, I learned, was yes — but only because what I did for free on weekends turned out to be rarer than I thought. Not the coding; the caring. The willingness to sit with a broken system until you understood why it broke.
Thirty years later, the tools have changed beyond recognition. The human patterns haven't.
The Arc
My career didn't follow a ladder. It followed a river — meandering, sometimes doubling back, occasionally plunging through rapids I didn't choose.
I started in QA. Not because I planned to; because that's where there was an opening. I learned something important there that engineers who skip QA never learn: the difference between "it works" and "it's correct." Working is observable; correctness requires thinking about what isn't there. Edge cases, failure modes, the user's actual intent versus the spec's assumed intent.
From QA I moved into development, then into architecture, then into the fuzzy space where technical decisions and organizational decisions become the same thing. The title that eventually stuck was "principal engineer," but the work was never about the title. It was about the pattern: seeing systems, human and technical, whole enough to make choices that don't solve today's problem at tomorrow's expense.
Along the way I built things that shipped and things that didn't. I worked on teams that hummed and teams that imploded. I learned Nix, Kubernetes, compliance-as-code, and a dozen other technologies that will sound dated by the time you read this. And I kept noticing that the successes and failures had less to do with the stack than with the soil underneath it.
What Doesn't Change
Here are the human patterns that repeat regardless of whether you're writing COBOL or Rust, deploying to mainframes or serverless edge functions:
People optimize for what gets measured.
This is the oldest pattern in software and the hardest to escape. You measure velocity, you get fast code that breaks in production. You measure test coverage, you get tests that cover lines but not intent. You measure commits, you get small meaningless changes that game the metric.
I've watched this pattern destroy quality in every decade. The fix isn't better metrics; it's better judgment about which metrics matter and the courage to ignore the ones that don't. That's a people problem, not a tooling problem.
Complexity is a loan with compounding interest.
Every shortcut, every abstraction leak, every "we'll fix this later" is a loan. The interest rate is higher than you think, and it compounds. I've inherited systems where the technical debt was so deep that adding a simple feature required touching fourteen files in seven services. The original authors weren't stupid; they were under pressure. The pressure won.
What I've learned: the teams that survive are the ones where someone has the authority and the stubbornness to say no to complexity before it becomes structural. Usually that person is unpopular until everyone else realizes they were right.
Communication bandwidth is the real bottleneck.
Not CPU. Not network. Not database throughput. The limit on every project I've ever worked on was how fast the right information got to the right person.
A requirement misunderstood for three weeks is more expensive than a slow query. An architectural decision made in isolation and reversed two months later is more expensive than a server outage. A team that doesn't know what the adjacent team is building is more expensive than any technical constraint.
The senior engineers I respect most aren't the fastest coders. They're the ones who know when to stop coding and start talking.
Authority without accountability corrupts; accountability without authority paralyzes.
I've been in organizations where engineers had no say in what they built. I've been in organizations where they had say but no ownership of the result. Both are broken in different directions.
The healthiest teams I've worked on had clear authority boundaries and clear accountability. You owned this; you decided that; you were responsible for the outcome. Ambiguity in either direction created the politics that technical people pretend to hate but are actually terrible at.
The best engineers are gardeners, not builders.
This took me years to see. The engineers who leave lasting value don't just write code; they cultivate the conditions for other people to write better code. They clean up the codebase incrementally. They write the documentation that saves the next person six hours. They refactor the module that everyone else is afraid to touch.
This work is invisible in sprint reviews. It shows up in velocity six months later when the team is suddenly moving faster and nobody can point to why. The engineer who did it is usually working on something else by then.
Beyond the Prompt
Lately I've been working on AI systems — not using them, but thinking about how to build with them. The beyond-the-prompt repo is my attempt to treat AI skills as software modules: composable, testable, versioned.
What I've learned there connects back to everything else. The prompt is the interface, but the architecture is what determines whether the system works. Context management is memory. Evaluation is quality assurance. Progressive disclosure is user experience design.
The tools are new; the patterns are ancient. Every generation of technologists thinks they're the first to face integration complexity, context management, or the gap between what the user asked for and what they actually need. We're not. We're just using different vocabulary.
The containerless work — deploying Nix closures instead of containers — is another example. The specific technology (Nix, Kubernetes, registries) is 2020s. The underlying question (how do we package and deploy software reliably?) is 1960s. The human challenge (getting teams to change how they work) is timeless.
What I Got Wrong
Thirty years includes a lot of wrong turns. Here are some I can name:
I once optimized a system for performance metrics that turned out to matter to no actual user. The numbers were beautiful; the experience was worse. I learned that metrics require validation against reality, not just internal consistency.
I once pushed a technical decision through despite team resistance because I was convinced I was right. I was right about the technology and wrong about the timing. The implementation worked; the adoption failed. I learned that correctness without timing is incomplete.
I once stayed too long in a role because the title was flattering and the work was familiar. I stopped growing; I started extracting. I learned that the best signal it's time to leave is when you can do the job without thinking.
I once believed that better tools would solve organizational problems. They don't. Tools amplify existing patterns. Healthy organizations get healthier with good tools; dysfunctional ones get more efficiently dysfunctional.
What I Still Believe
Despite the wrong turns, some convictions have deepened:
Software is a team sport played by people who often wish it wasn't. The best code I've written was improved by someone else's eyes. The worst code I've written was written alone, late at night, under pressure.
The user is not stupid; the user is busy. Every interface that assumes user stupidity is actually hiding designer laziness. Every system that requires the user to "just" do something complex is pushing cost onto the wrong side of the boundary.
Technical debt is a moral problem, not just a technical one. When we leave messes for others to clean, we're choosing our convenience over their time. The codebase is a shared space; leaving it worse than you found it is a small act of extraction.
Learning compounds. The engineer who reads broadly — outside software, outside their specialty — makes connections that the narrowly focused engineer can't. The domain knowledge, the historical context, the adjacent discipline: these aren't distractions. They're the nutrients.
The Stack Is Temporary; the Patterns Are Permanent
If you're early in your career, here's what I'd tell you: don't chase the stack. Chase the patterns.
The frameworks you're learning today will be obsolete in a decade. The cloud provider you're certifying on will change its API, its pricing model, its entire approach. The language that's hot now will be legacy or dead in fifteen years.
But the patterns — how complexity grows, how communication breaks down, how authority and accountability need to match, how good work is invisible and bad work is loud — these don't change. Learn them once and you'll recognize them in every new technology.
The senior engineer's real job isn't knowing more syntax. It's seeing the pattern earlier and naming it clearly enough that others can see it too.
Still Walking
I'm not done. The Nix work, the AI skills, the multi-agent system, the book, the talks — these aren't a retirement portfolio. They're the current crop. The soil underneath them is thirty years of patterns, failures, and the occasional insight that held up longer than I expected.
The technology will keep changing. The human patterns will keep repeating. The work is to notice which is which, and to cultivate the conditions where good work can grow regardless of what stack it happens in.
That's what thirty years teaches you. Not how to code better; how to see more clearly. The code is just the medium.