TL;DR: A year ago, my company handed me a project bigger than anything I’d worked on before. The project was OpenGraph, the new extensibility foundation for a cybersecurity tool called BloodHound, and this essay is partly about building it. Mostly it’s about what happens when an engineer leaves the comfortable bounds of individual ownership and starts participating in the conversations that decide what gets built. We’ve shipped the first version – it’ll be a long-running thing. I’m still in the middle of what it taught me. I couldn’t explain any of it on a hike last weekend, so I sat down and tried here instead. Maybe some of it will sound familiar.
I. Introduction
April 2026
I was on a hike last weekend, trying to do the thing that you’re supposed to do on hikes in Northern California, which is to revel in the doing and thinking of nothing all day while the world keeps going. A creek gushed beside the trail as I followed it down through redwood shade. Then, someone in our group asked me the question that follows you everywhere in the Bay Area: what do you do?
I didn’t want to be back at work. I wanted to be back with the creek, which had asked nothing of me. But he was waiting, and I owed him an answer. I searched through my work thoughts for the material I needed, only to struggle when it came time to articulate.
I started saying something about graphs and cybersecurity. He stared at me with the blankness of a man who has received no useful information. I couldn’t help him. I asked if he knew any graph theory. He did not. I tried again: nodes and edges, like the way Google Maps finds the shortest path between two places. A frown spread across his face, and he told me he thought he was going to be sick.
All of a sudden, another member of our group bellowed at us to keep up, and the record of that conversation was lost to the void of time. I returned to the scenery, but my thoughts wouldn’t obey my will. They kept replaying the conversation, writing new transcripts for the parts that could have gone more smoothly.
Communicating my work in casual settings has never been the easy part of the job. On many occasions I’ve watched enthusiasm leave the face of the other person in the conversation, as I ramble into details and bury the higher-level stories that I live in, the stories that surround the activity of programming. I think this is the result of spending too much time alone with the laptop, just trying to get something impossible to work, and not enough time practicing the human art of salesmanship, of how to explain the meaning of these matters to whoever’s listening. What follows is my attempt to rectify that longstanding neglect. Here is my story of building OpenGraph – a year of work that I could not, standing on that trail, explain to a man who just wanted to make conversation.
II. A Formative Conversation
July 2024
For the first year and a half at this job, I was shy to be excited about anything I did. Some quiet tribunal in my head kept measuring my work against the work of people a decade ahead of me, and the verdict was always the same. I didn’t know I was running the comparison. I just knew the result: that whatever I’d done didn’t really count. I worked at a startup – the kind of company still deciding what it would be. The job posting had warned me: “highly ambiguous problems,” “limited direction,” “many hats.” I’d read those lines as flattery, the kind of language startups use to attract ambitious people. They were not flattery. They were a serious warning, and my time in the role so far had been a study of exactly what they meant.
I had been more of a watcher than anything, listening to the conversations of people who had been there longer. Remote work removes the opportunity to experience the small signals you’d normally use to orient yourself: the hallway aside, the in-person sense of whether you’re keeping up. My engineering work was one long series of tickets handed down from somewhere I couldn’t see. The work grew in difficulty always, which gave me a sense of growth for a while. But I kept wondering: is this it? What comes before tickets? Where did the ideas come from? How do ideas become a plan?
People were around me who generated ideas in places I wouldn’t have known to look. They led with a naturalness I didn’t feel. I questioned if they were operating on a faculty I didn’t have, the kind you couldn’t create or acquire, only be born with. That feeling reached its peak at an offsite meeting in that earthly paradise known as the Seattle summer.
On Wednesday I consumed a gallon of coffee in a single 8-hour sitting and listened to presentations all day. When it was over, all the various departments that support the BloodHound product arranged transit to Lake Union. An old magnificent vessel awaited us. The assortment of people I indirectly worked with filed onto the boat like a parade of misfit toys, settling on the open-air upper deck. As we moved through that dark blue Seattle water, I imagined us hitting an iceberg and sinking like the Titanic. Who would pick up the mantle of attack path management if we went down? In our hats and sunglasses we sat around under the falling sun, finding it easier there to talk about life than it had been back in the conference room. The chemical burn of coffee churned against my stomach lining.
Late in the cruise, I walked over to our director, who at six and a half feet, towered over everybody else. He was a completely honest man, and loquacious on the topic of computers; one time he went on a maniacal coding binge and hand built a compiler to translate the Cypher query language into SQL, just so we didn’t have to run a Neo4J database in production anymore. We believe in him.
Afraid to be vulnerable, I approached him anyway. I told him I was inspired by the week’s talks, that I longed to increase my contribution one day. He told me to hold that thought. Come get a beer. He plodded over to the bar like some huge Texan outlaw and returned with two perspiring Bud Lights. Let’s go to the front of the boat. I followed him there.
He looked out at the green shaggy hills piling up behind the shoreline. “What did you like about the talks today?”
I answered too fast. “I liked that one of the speakers showed something the room didn’t know yet. It was exciting.”
He didn’t say anything. The silence got longer. At first I thought he was disappointed; later I would realize he was just waiting to see if I had more.
“Did I answer the question the way you meant it?” I said.
“You tell me. Have you exhausted the topic?”
I didn’t have an answer.
“Thinking requires silence,” he said. “Some people are afraid of silence. Take your time.”
My heart was loud for thirty seconds, each one longer than the last.
“It was interesting to watch theory become practical,” I said. “There’s a moment when an idea stops being an idea, and things happen, and it becomes a list of things to build. I’d want to be in that room.”
“Good.” He nodded. He was still watching the water.
He took his time with the Bud Light. He had something he wanted to say and was deciding how to say it. “You must be able to describe what you want to work on. Don’t focus on why you want it. Why is a trap. You can chase it forever and never actually do anything.”
He paused.
“What is the interesting question. What leads to action. Your career growth will follow the work you choose. You are at the point where you must decide what you work on next. You need to tell your manager what you want. And if you can’t find it here, leave the company.”
“I’m happy to mentor you sometimes, like this. But find other people in the organization who are ahead of you. Talk to them. Ask them what they wanted. Ask them what they did to get it.”
The boat drifted forward in that impossible Seattle water. The moment settled into memory.
Until then, I hadn’t realized that initiating conversations about advancement was my responsibility. I assumed that was my manager’s job.
His question stayed with me. What did I want to work on. I turned it over on my runs that Autumn, because running is a good time to think. It came to me slowly. My time at the company so far had been like the training you do for a race – the 3, 5, 8, 13 mile workouts, delivered week after week, the mileage that accumulates into something without your noticing. That’s where 99% of the miles live. Most of running is that. Most of work, I was starting to see, is like that too.
But I wasn’t training for nothing. You don’t pile up mileage for its own sake. Somewhere underneath the weekly grind was the thing the grind was for, and on one of those runs the word came to me: marathon. That was what I wanted. Not more tickets, not harder tickets, but the 26.2 – the true test of talent, focus, and endurance that asks whether the training added up to something, whether you can go farther than you thought you could. I wanted to see what was within my limits. I wanted to see if I could transcend whatever limits I thought I had.
No matter how much you’ve run, until you run the marathon, you have some doubt you can finish. You carry that doubt every minute of the race until you cross the finish line. That was the feeling I was chasing at work. It had been there all along, I just hadn’t been asked to name it.
Naming it had taken months. Asking for it was its own kind of hard. I started talking to my manager — a bald-headed Louisianan who burned through his vacation days driving cars at significant speeds. I had met him before he was my manager, in Seattle, the night before an Azure fundamentals course we’d both signed up for. We were the only two engineers there, so we found each other in the hotel lobby. Over an admirable bourbon, the conversation strayed, and somehow we ended up debating whether we had free will. Neither of us settled it.
Months later he became my manager. Our 1:1s opened the same way every time: a courtly good day, sir in some ghastly old-English accent, followed by a long sooo, how are the things. Then he’d settle in and let me ramble until I’d said something worth following up on. Nearby, an orange cat tested his patience.
He sat through my speech about my career and pulled the big theoretical ideas out of it, the way he always did. He took it away and thought about it. A few weeks later, he opened with the usual good day, sir and told me he’d been thinking. He knew I’d been looking for more architecture and design work. There was something coming up: an experimental project, still without a name, that they were calling Generic Ingest. Was I interested?
I said yes the way you say yes to something you don’t fully understand. The project was bigger than anything I’d worked on, and I wanted to try.
The director had told me on that boat to figure out what I wanted. My manager helped me ask for it. I’ve thought about this moment since, and the lesson isn’t simply that I asked and more appeared. A different environment, and the conversation on the boat leads nowhere. A less prepared version of me, and the same conversation leads nowhere too.
I said yes.
III. The Problem Worth Solving
February 2025
Some time later, I was on a call with the same man who rage-coded the compiler. A shared document was open between us, the cursor blinking at the top of an empty page. He was expounding to me his philosophy of design. Scope it way down, he said. He explained that as the technical voice in your negotiations, you must be explicit about what you are leaving out, and aggressively defend yourself from having to commit to those things. Then he demonstrated: under an “In Scope” heading, two requirements. Under “Out of Scope,” a longer list, full of the graph-related words that would take a long time to learn – reconciliation, post-processing, analysis. He moved things to the second list with the calm of someone who called the shots. A cup of rich brown, life-giving coffee steamed encouragingly on my desk. Utterly green, I took notes on everything.
At the beginning of this story I was wondering where ideas came from, and once born, how they become a plan. Here I was inside that process, and it required no faculty I didn’t have. It just required a question I hadn’t thought to ask. Before you could decide what to build, you had to understand what hurt. What pain deserves to be addressed first?
The product we were designing for is named BloodHound. It is a defensive security tool built to reduce the attack surface of Active Directory – Microsoft’s Identity and Access Management service, and the door through which any Windows-centric digital environment in the world can be breached.
Picture a map of our handsome country, crisscrossed by every road that connects every city. BloodHound is that map for enterprise security: it shows how identities and permissions link together, and how an attacker can connect those links into a route (an “attack path”) from the front door to whatever they want.
My forebears at the company mapped all of the attack paths presented by Active Directory, and through some eye-glazing graph theory, built a product that identified the chokepoints an organization needed to close. The goal was to make the Active Directory “door” not worth picking. There is colorful vocabulary in cyber security; the whole field talks like an Ocean’s 11 movie. Attackers and adversaries, breaches and exploits, payloads and kill chains. Everyone is a degenerate criminal, every server is a vault, every login is a lock to pick. In your first month you feel like you’ve been cast in something.
The world that BloodHound models does not stop changing. New attack techniques emerge constantly, and every one that BloodHound doesn’t know about is a path missing from the map. Keeping the map current required expert knowledge of two fields at once: software engineering and security research. The engineers knew the codebase. The researchers knew the tradecraft. Neither could proceed without the other, and every graph expansion was an expensive project between two departments who spoke wildly different languages.
The direction the product was naturally headed made this problem more urgent. The same graph theory that made BloodHound valuable for Active Directory could apply to a wider ecosystem of technologies. SaaS applications, cloud providers, and other identity systems all contribute to the attack surface of an enterprise. The attack surface grows the way a city does; suburbs sprawling outward, built haphazardly around the original grid, until the planners, exhausted, can’t keep up. Like a city, this expanding attack topography provides ample opportunities for threats to abound. But if expanding the graph for one platform was already painful, expanding it for many was untenable. Something had to change structurally.
Extensibility — a term I’d picked up from a crusty software architecture book — is the quality that lets a system grow without having to change its structure. OpenGraph was our bet on it. If we built the right substrate, security researchers could bring new attack patterns into the product without writing a line of application code. Engineering would build the platform once. Everyone else would use it indefinitely. The wall between the two worlds would come down, and the full picture of risk could finally show up in one place.
It was at this inflection point in the company’s history that I joined the work. At the beginning of this story I was wondering what came before tickets, and now I was helping decide.
IV. Phase One – A Contract for the Graph
April 2025
If pain provided the why, and the engineering director – through a series of tremendous exhortations – provided the what, it was my job to figure out the how.
We wouldn’t have to build a generic ingestion system entirely from scratch. The foundation came from excavating a body of dense legacy code, where the underlying primitives were already there, just buried. There was a suite of file-upload endpoints that already accepted data from the existing collectors. There was a pattern for writing those files to disk and processing them asynchronously. There was a library that took nodes and edges and flushed them to Postgres, in a way that nobody alive could fully explain. The work wasn’t to invent these things; it was to find them, pry them loose from the Active-Directory-specific assumptions tangled around them, and turn them into something general enough to handle any graph data. This practice is called code archaeology, but I’ve described this period in my life as one of spiritual archaeology. This is because the inanimate horrors I discovered in that code cultivated a fortitude of character that the phrase “code archaeology” is not sturdy enough to convey.
The central concept we landed on was the graph data file: a serialized representation of a graph snapshot. We pushed toward minimalism. Think of how shipping worked before the container, that great unsung leveler of global commerce. Every port had its own crates, its own conventions, its own opinions about how cargo got on and off a ship. Every transfer required repacking. Standardizing the container didn’t simplify the cargo – it made the translation interfaces disappear.
I had seen the contracts for the existing data collectors, and they were the cautionary tale we were designing against. Those formats described bespoke objects specific to each platform: the cargo, in different packaging, at every port. Mapping what was in those files to actual nodes and edges in the graph required a jumble of custom ingest logic that was upsetting to follow.
Our format would be like the standardized shipping container. It would describe a graph directly: a collection of nodes and edges. Everything else in the system exists to make that simple definition operational.
Once a user provides a graph snapshot, the ingestion pipeline takes responsibility for the hard parts: validating large datasets without loading them entirely into memory, translating the serialized snapshot into database mutations, linking edges to their corresponding nodes, allowing users to define new node and edge types without touching application code, and surfacing errors in a way that lets users correct their data.
A deadline was looming. OpenGraph was going to be the big new capability announced at Black Hat, and as the conference approached, a gap surfaced that nobody had gotten to yet: deletion. Getting data into the graph was solved. Getting data out – clearing a specific platform’s nodes and edges when a user needed to remove it – had been an afterthought. In an unstructured graph database, deletion is not trivial. You can’t just say “delete everything belonging to platform X” without a reliable way to identify what belongs to platform X in the first place.
I thought about this problem. I acquired a terrific, big cup of coffee, hotter than a meteorite slamming into the earth. Somewhere in the second half of the cup, the answer came to me. Source kinds: a label applied to every node a platform ingested. One platform, one label, stamped onto every node it brought into the graph. When a user wanted to remove that platform, the deletion logic could find every node carrying the label and remove them all in a single sweep. I knew before I presented it that it wasn’t elegant. The principal engineer, who knows this product the way a monastic contemplative knows every line of scripture, looked at the approach and validated it. That was good enough for me, just confirmation that the call is defensible. We shipped it, because the alternative was showing up to Black Hat empty handed.
Establishing this contract – a graph is nodes and edges, source kinds give us a handle on deletion, and BloodHound handles the rest – was the foundational work of Phase One. But a contract for ingestion raised an immediate question: once the data was in, what could the system do with it? How could it analyze an arbitrary graph with the same rigor it applied to Active Directory? That question was Phase Two.
V. Phase Two – Full Steam, No Map
October 2025
Phase One had delivered something real but narrow: a way to get arbitrary graph data into the product and make it searchable. It was a proof of concept with a clean contract. Phase Two was the broader build, extending that contract into more parts of the application. For the first time, a user with a custom identity graph wouldn’t just be able to store it. They’d generate findings from it – the choke points an attacker would have to pass through, the same kind of analysis BloodHound had always done for Active Directory.
The difference in scale was immediate. Phase One had been two engineers moving fast with limited scope. Phase Two was eight, working as soon as scope was defined and tickets were cut. The handoffs were beautiful – each stream of work passing into the next like a track and field team of All-Americans, each engineer a graceful athlete, handing their baton over in a narrow window at full speed.
This phase was announced before anyone knew what was going on. Several leaders were involved, requirements were still being gathered, and the question of who would own what hung in the air. I started with the search and pathfinding work, the more tractable piece – opening more of the application to OpenGraph-formatted data so a user with a custom graph could actually find things in it. The work moved forward and I handed the beginnings of a design to one of the engineering teams, who broke it into tickets that could ship. Then I carried that mental model into the next problem.
The next problem was analysis. How could the analysis engine that produced findings for Active Directory do the same for arbitrary graph data? It was the most complex part of the product, and nobody on the team had been inside it in a while. I told the project manager I’d take the research.
I noticed myself volunteering. A year earlier I would have waited for the ticket. The team I was on assumed engineers would pick their own hard problems, and I had started picking mine. I went out and purchased a nice big coffee in a stout glass jar and started reading.
When I first opened the unknowable analysis engine, I knew where to look – I had been in that area of the codebase once before. But at least a year had passed, and so much complexity had been piled on top to support other features that I barely recognized it. A few sins from my earlier self were still visible, like finding 100 eggs splashed against the side of your own car, and slowly remembering you had egged it. I scrolled for a long time, jumping between files, filling my head until it hurt. I went on runs to clear it, but my thoughts were held hostage. There was no brain left for anything else. I wrote things down in my notebook, talking to myself on paper like a madman.
For a while, I was breaking things and adding things, trying to get the analysis engine to produce a finding on OpenGraph data. The reading had become burglary: I was inside the house, knocking things over in the dark, trying to find what I came for.
What I found was that the engine knew things it shouldn’t have had to know. It had hardcoded lists of node types to look at, edge types to follow, exposure metrics to count – all of them populated with the Active Directory and Azure types the engine had been built around. OpenGraph data lived in the database without the engine ever seeing it.
The deeper problem was hybrid relationships – edges that connect identities across different platforms. The engine had two of them hardcoded, the only two that existed at the time: edges that linked an AD user to their Azure counterpart and vice versa. Two hand-written cases were fine when there were only two. But identity sprawl is the actual problem OpenGraph was built to model – the same person scattered across an AD account, an Okta identity, a Salesforce profile, a GitHub login, with permissions and trust relationships connecting all of them in ways nobody has fully mapped. The most valuable findings OpenGraph could surface were going to be the hybrid ones. The engine had no machinery for them.
The fix was a loop that ran across every registered platform, asking each platform to declare which of its edges reached into which others. The engine stopped imposing its assumptions on the graph and started listening to it. That contract – the shape of what the analysis required – was a primary influence on the extension schemas we were designing.
What I had not expected, going into the work, was how much of it would happen in rooms with other people. As I broke things and put them back together, I hosted meetings where I talked to other engineers about the changes I was making. A year and a half earlier I had been asked to do something similar and struggled to articulate what I had built, leaving everyone as hopelessly lost as I was. That deficiency stuck with me and bothered me. This time I prepared differently. I led with the story before the code: the users, the value, the feature, the region of the codebase, all of it before a single line was on screen.
I am generally a chill person, but I have learned in these moments where I have to present that I am more nervous than I realize. I’ve been like this since a poetry recital that was forced on me in 5th grade. I don’t have tactics for it besides compulsively planning, and I suspect addressing it properly would require an obscene amount of therapy. I white-knuckle through the panic of the first minute or so until my exhales return.
Some months later, deeper into the work, some gaps showed themselves. There were things we had missed in planning that I could have caught earlier. I had never pushed a deadline back before. At standup I told the team we’d need more time, and braced myself for a hard conversation that didn’t come. The project manager and engineering manager have seen it all. They told me to dive in, write bare-bones tickets for the gaps, and we’d refine as a team. They would communicate the timeline upward. No one looked for a person to blame. The conversation moved on to the work itself within minutes, as if my admission had been the boring part.
I never doubted I could do the work, because I knew the people around me would help me when I needed it. That meeting was the first time I needed them to, and they did, the way I had hoped they would.
This phase of work has ended the way intense things end: not with a ceremony, but more gradually. Tickets have closed. Slack is quieter. Six months of a singular focus on one problem, the same problem, approached from every angle, at every layer of the application, has loosened its grip. The platform can now receive arbitrary graph data, analyze it, search it, and find paths through it. For now, the only question is what we want to build next. After six months of full steam ahead with no map, that feels like an extraordinary luxury.
VI. Reflection
April 2026
San Francisco, as I look up, has given me a familiar spectacle. Five hippies of varying sobriety have formed a drum circle in Golden Gate Park. One man in flip flops has found something like a rhythm on a bongo drum. Beside him, his unkempt friend leans into a metal spray paint can, working it with a drumstick. Whatever it is they’re doing, it has begun to work on me.
The knowledge that made OpenGraph possible was not the kind that transfers into conversation on a hiking trail. It was the silence on the boat before I found my answer. It was the runs where the problems ran with me, and were still running when I got home. I spent a year accumulating that knowledge. I learned to put it into language other people could use, and by the end of the year I’d convinced myself I had it down. Then someone asked me what I did on a hike, and I stood there like an idiot talking about nodes and edges.
The work taught me things I didn’t have the words for while I was doing it. Writing made me figure them out. Sitting down forced me to gather all of the scattered moments into one place and ask what they amounted to, and only then did the conversation on the boat in Seattle reveal itself as the thread it had been all along, running through every place where the work asked me to stop watching and start deciding.
He wanted a simple answer. The city I live in is built to produce simple answers. Every conversation is a chance to pitch, every walk a small auditioning of your work into a sentence. The year I just had does not fit in a sentence. It took the time it took, and the only thing that was ever going to teach me what it taught me was the going-through of it.
I’m narrating it now, imperfect and after the fact. Across the lawn, those hippie drummers have somehow figured it out. Whatever they were chasing has arrived. The spray paint can is keeping time, and they have something that sounds, against all expectations, like a song.