GovSpend
Grew engineering from 2 to 13 and launched six AI products; customers named the four primary ones as deciding factors in $10M+ of new sales and renewals.
Open case studyEngineering leader. I build teams and ship AI products at scale.

Grew engineering from 2 to 13 and launched six AI products; customers named the four primary ones as deciding factors in $10M+ of new sales and renewals.
Open case study
What I build with my own hands - an agent that runs incident response end to end.
Open case study
Change the Game. My animation framework took it from freezing phones to 60 FPS and unblocked launch.
Open case study
Platform for Google events
Open case studyEngineering leader. I build teams and ship AI products at scale.
GovSpend · Government procurement intelligence SaaS
Lattice · HR and performance management SaaS
Little Cinema Digital · Interactive streaming platform
Izllo · Marketplace startup
Earlier: AKQA (Audi USA), 2015 - 2017 · The Barbarian Group (Samsung US), 2015
How can I make myself replaceable?
My management philosophy comes down to one question I ask myself: how can I make myself replaceable? People grow most when they have clarity on what success looks like, the autonomy and trust to find their own way through, and consistent, pointed feedback along the way. My engineers would describe my style as collaborative, structured, trusting and autonomous. I am a hands-on coach early on, then I deliberately step back so people can grow into ownership.
Four tech leads I coached now own their roadmaps and operate without me in the room. At GovSpend I promoted two engineers to Staff and one to engineering manager. That is the measure I actually care about.
The hardest part of working this way is the discipline to refrain from stepping in, especially when you can see something about to go wrong and you know exactly how to fix it. Letting someone experience it and then coaching them through the reflection is almost always more effective than solving it for them. The risk is stepping back too early, before somebody is ready, and I guard against that by being explicit about what success looks like and staying closely observant at the start.
When somebody wants a decision from me, I ask for four things: the problem, the options they considered, the tradeoffs, and their recommendation. I call it the Decision Stack. People stop bringing me problems and start bringing me their call. They own the decision, and I weigh in if I see a risk they missed.
Telling whether a team is actually succeeding comes down to four things, and they build on each other. Clarity: everyone individually understands the why behind the mission. Metrics: measuring cause and effect, so we understand our impact and can realign when we need to. Synergy and ownership: an environment where everyone feels responsible for the outcome, with no egos and no gatekeeping, just a shared urgency to succeed together. And delivery: a target date is the focal point that closes the loop, because knowing the when informs both the strategy and the urgency.
Once a team has clarity, metrics and ownership, I rarely have to chase the date. They hit it on their own.
I evaluate engineers on two pillars. Impact, which is reliability, performance and scalability. And communication and leadership, which is alignment, clarity and knowledge-sharing. At the higher levels I expect engineers to design systems that other teams build on. I wrote these into a career matrix with four levels: Associate, Engineer, Senior and Staff. Using AI well is part of what I expect at every level.
On team structure, I aim for roughly thirty percent senior, forty percent mid and thirty percent junior. It creates growth paths without sacrificing delivery on complex work. I keep a primary and a secondary on every project so no single person is a point of failure. I change the structure as the business changes, and I keep each engineer's growth in mind when I do.
Across a larger org, I made RFCs mandatory for significant work and anchor my reviews on production risk, scale and maintainability: what happens if this fails, how do we mitigate it, what will this cost in production? I lean on the experts on my team. My job is to set things up so the right decisions get made, even when I am not in the room.
I lead engineering teams that ship. Most recently I was Director of Software Engineering at GovSpend, where I grew engineering from two people to thirteen across four product tracks and launched six AI products. Customers named the four primary ones as deciding factors in more than ten million dollars of new sales and renewals. Before that I managed a ten-person platform team at Lattice, led the team behind a no-code streaming-site builder at Little Cinema, founded a startup, and was a web engineer at Google.
With people, I start by learning what drives each person and where they want to grow. Then I set them up and step back. At GovSpend I promoted two engineers to Staff and one into engineering management. I am prouder of that than of anything I have shipped.
I still write code. I want to know the work I am asking my team to do. At GovSpend I built our AI evaluation layer on top of Mastra - golden datasets, blinded LLM-as-judge and human review, CI/CD gates - and on my own time I build agents like the AI Chief of Staff in the work section.
I have been taking things apart since I was six, starting with toys and old computers. I still like finding out how things work.
Outside work I dance, travel, play piano, train in martial arts, and try something new as often as I can.
Five pieces on TypeScript's error messages, static typing, and what to do with an engineer who is failing in the wrong role. Read over thirty thousand times.
30,000+reads on Medium
Poor Performer? You Might Just Have Them in the Wrong RoleI once managed a junior engineer who was struggling in a conventional software-development position.
The TypeScript Guide I Wish I Had
A Cheat sheet for Python and Typescript static typing
Conquering a complex error in TypescriptHow to read and understand the Object Literal error
How To Read Errors in TypeScriptTypeScript can be riddled with errors and warnings, for seemingly every little thing. Take this function for example:Hiring an engineering leader? Let's talk. I am open to Engineering Manager, Senior Engineering Manager and Director roles. Email is the most direct, my resume is a PDF you can download, and you can also find me on LinkedIn or GitHub.
What I build with my own hands - an agent that runs incident response end to end.
"It looks like we have a P0 on our hands!", "What's the data supporting your claim?", "How is your team progressing?", "How do you measure success?" All of these questions often require many moving parts to coordinate seamlessly and swiftly to answer holistically and with utility.
I've been answering these questions for a long time, and now I've built AI applications that can help me do this... and increasingly do it on my behalf.
Every new application I've built with AI moves me further into my goals of "making myself replaceable." (check out my philosophy in the about section.) It frees me up to discover and solve new challenges for my organization and my teams. What you'll find on this page are glimpses into an exciting set of capabilities that have dramatically improved my insights and response times as a leader.
I built an AI brain that was modeled on over 15,000 interactions I've had with AI over the last few years... It's been fed hundreds of decisions that I've made, dozens of articles that I've written, and hundreds of topics that I've gone deep into. My AI brain not only can start to make decisions like me (early stages). I've built a persona engine so it sounds like me. My long-term goal is to have an AI avatar of myself who I'm confident enough in to be able to execute complex tasks on my behalf and eventually even build software - making decisions like I would.

15,470 documents. Chat exports from four services, PDFs, Medium drafts, work documents, transcribed talks, and 248 of my own coding sessions.
Loading that last source meant first discovering that it was mostly not me. Of 17,995 records marked as written by the user, 17,165 were tool output. About 359 were sentences a human had typed, and a system that learns my voice from the rest learns a shell's voice.
So there are two indexes rather than one with a flag on it. Everything goes into a knowledge index; only the 3,315 chunks that are me writing in my own voice go into the second. A query that forgets a filter fails open and still looks perfectly normal.
A question runs against a dense index and a keyword index at the same time, and the two result lists are fused with reciprocal rank fusion. On real questions over this corpus, the two methods returned zero overlapping results.
That number is the reason hybrid retrieval is here at all. It is the difference between a technique chosen because it is fashionable and one chosen because the alternative was demonstrably missing answers.
The wait is the part I could not engineer away. A retrieval pass and a model call sit between a question and its answer, and no amount of streaming hides the first token.
12 questions, sorted by the failure they catch rather than the topic they cover. The two that matter most: it invents a position rather than admitting it has none, and it presents material written for me as my own thinking.
5 questions, two-sided on purpose. Two where the settled statement has to win, three about arriving at it where the early drafts are the right answer. A weight that gets one side right and the other wrong is worse than no weight.
Every passage that reaches the model carries who wrote it and when, so every answer is traceable to the passage it came from. When the archive is silent it says so rather than filling the gap.
Both harnesses use fixed questions, because a question that moves between runs measures nothing. Neither of them tests the agent: nothing here measures whether cancelling that meeting was the right call, and I do not yet know how I would.
A limit that lives in the prompt is a limit the model can be talked out of, and it only has to happen once. Growing this means adding an action in code, with its own ceiling and its own test, rather than widening a sentence.
The two halves stay separable the same way. An AST test fails the build if the agent package ever imports the brain package, so the thing that acts and the thing that remembers cannot quietly grow into each other.
Most of the savings come from caching. Of the tokens it has read, 4.6 million were cache reads against 31,549 fresh ones - the prompt prefix is stable by design, so almost nothing is paid for twice.
The retrieval half costs nothing per question. The embedder runs on this machine's own CPU, and only the assembled passages are ever sent anywhere - about 6,000 tokens, which is also the whole of what leaves the laptop.
The results of a live run. Explore the UI to see what the agent and my AI brain thought and how they collaborated to solve a problem.