I've always loved working with computers. From simple Batch scripts in Pre-K to running one of the world's most popular Minecraft servers, I've had a deep passion for the crossroads of computing, creativity, and community.
Software engineering became my way to translate these ideas in my head to reality—from glue to keep game servers running to early machine-learning concepts for community moderation.
Nowadays, I work at Broadcom (née VMware, Pivotal) helping clients solve all sorts of technical and human challenges. From cloud platform buildouts, to solving megascale hurdles, or even starting training bootcamps, nothing is 'not my job'.
I practice Extreme Programming, focusing with User-Centered Design, and implementing with pair programming and test-driven development. I have strong opinions about platforms as products.
Also: a lot of bad bird & astro pictures, bike rides, and a Cannonball attempt.
Can't say I'm a good photographer, but I do find it a lot of fun :)
I read every email, usually within a day or two. Happy to talk about any technical issue you're facing, or if you just wanted to chat about something.
My friends and I really like the card game Dominion. There are already official online offerings of Dominion, but we like playing with house rules, custom printed cards, and competitive matches with rewards. In addition, I thought the UX of the existing online Dominion services were a bit difficult. So, I set out to make my own.
I built a verb-based engine where every Dominion card is expressed as a composition of low-level primitives (called verbs for common actions, like gain_card or trash_card. Cards register themselves via decorators into a central dispatch registry, which are looked up by the core engine loop as events come in. Branching, multi-step interactions (i.e. "trash a card, then gain a card costing $2 more than it") collapse into an atomic state machine installed as a "pending action", pausing the game loop while the nested action completes.
The payoff of this architecture is its' extensibility; adding a new card, a full custom expansion, or a house rule is a single decorated function leveraging these API-like verbs—no changes to the engine required.
The first internal platform I ever loved had a logo. Someone on the team had drawn it in Sketch over a long weekend—a little mascot with a name, and printed stickers you could put on your laptop. There was a Slack channel where the team triaged feature requests like a customer support queue. There was a roadmap. There were weekly reviews with users.
Technically, nothing about it was remarkable. But every product engineer in the building knew its name, knew who built it, and spoke about it as if it were a thing. They didn’t know, nor care, that it was a Concourse pipeline that pushes .jars to VMs. The technical details didn’t matter: it was a thing, with a personality, a team behind it, and a Slack channel where you could ask questions or submit feedback and someone would actually answer.
In short, this team did what we always do and built an abstraction layer. But, rather than it being a technical one, it was more of a human one: a brand. This was, and is, the whole trick.
The pitch for treating your internal platform like a product is not complicated. You have engineers who write applications, and you have engineers who provide the runtime those applications execute on. The runtime team’s customers are the application team. Their job is to make those customers’ lives easy enough that they’d choose your platform if it were one of three options on a procurement spreadsheet—usually against some GCP or AWS solutions. They’d want your platform, because word-of-mouth has told them how easy ACME Roadrunner (dumb example, sorry) is to use, or how cost-effective it is. Running it as a product increases user satifaction, and giving it a brand gives users something to tie that satisfaction to.
So: run it like a product. Give the team a name. Let them brand it. Let them solicit feedback. Let them say no to feature requests that don’t fit (who wants an omelette at a steakhouse?). Let them measure things—adoption, time-to-first-deploy, p99 of the time it takes to get a change into production, and report those numbers up the chain the way a product team would report DAU. Let SRE handle reliability the way a product team handles uptime. Let the platform team be proud of the thing they’ve made.
The symbiotic relationship is the point. Product engineers get to stop thinking about the runtime, which frees them to do the work they were hired for. Platform engineers get a sense of ownership—this is mine, I made this, my name is on it—which is the single most reliable motivator I’ve ever seen in a software team. Everybody gets to be good at their job because everybody gets to do their job.
I spent my formative years at Pivotal, a deeply XP shop: pair programming all day, TDD, pizza-box-size teams, and user-centered design applied to literally everything, including the internal tooling. The thing that impacted me the most is that “user-centered design” doesn’t stop at the external boundary of your product. Your colleagues are users. Your platform team’s users are the application engineers two desks over. The same discipline applies: talk to them, watch them work, find out where the friction is, and ship something that removes it.
The other thing Pivotal taught me—and I am still trying to articulate this properly a decade later—is that engagement matters. Engineers are creatives that want to build. The ones who feel like their work has a name, an audience, and a reputation are engaged and outperform the engineers who feel like they are filling in a Jira board, every single time. The internal platform team that has a logo and a roadmap and a Slack channel full of opinionated users is the one that ships. The team that is the “infrastructure org, second floor” is the one that’s constantly underwater.
You hired great engineers. The bet has already been placed. The only remaining question is whether the structure around them lets them do great work, or whether it grinds them down into ticket-closers.
The opposite of “platform as product” is “platform as cost center.” A cost center has no users. It has tickets: soulless, agency-robbing, eyes-glazed-over tickets.
I’ve spent so much time with under-resourced and under-empowered platform teams. They have all the responsibilities of running production but none of the agency to fix the things they know are broken. They cannot say no to feature requests. They cannot prioritize. They are measured on uptime but not on the experience of the people they serve. They are, in effect, an internal vendor whose customers can’t fire them. And in turn, they’ve become deeply cynical, disengaged, and burned out.
This is a particular kind of misery, but it’s solvable. Not by hiring more people, or buying another vendor product; but by giving the team a product to own. By letting them say no. By giving them KPIs that reflect the experience of their users, not the number of tickets in the backlog. By letting them put a logo on a sticker and hand it out at all-hands.
I completely get how silly this sounds. A logo? Stickers? The vibe of a startup-within-a-startup? Yes. All of it. It works. I have watched it work at countless companies now, and the variance in outcomes between “the platform team has a brand” and “the platform team has a Confluence page” is, no exaggeration, the difference between a company that can pivot when the wind changes and one that cannot.
That’s the real prize: a company whose platform engineers are proud of what they’ve built is a company whose application engineers can focus on the application. Which is to say: a company that can move. When the market shifts, when the customers shift, when somebody at the top says “we need to do X by Q3”—the company with the good internal platform can. The company without one is going to spend the first two months of Q3 arguing about whose Kubernetes cluster the new service goes on.
The platform is the substrate of the company’s ability to change. Treat it like a product. Give it a name. Let the team be proud.
# ─── 1. CARD EFFECT: runs when the card is played ───────────────────────────
@card_effect("Avatar of Francesco")
def _effect_avatar_of_francesco(cur_player, cur_pid):
options = supply_options()
if options:
# set_pending halts the turn here. The engine won't advance to the
# next phase until a matching @action_choice fires and clears it.
# The "gain" tag is the discriminator the choice handler dispatches on.
set_pending("Avatar of Francesco", "gain", options=sorted(options))
else:
add_log(f"No cards to gain")
# ─── 2. ACTION CHOICE: resolves the pending the effect installed ────────────
@action_choice("Avatar of Francesco", "gain")
def _handle_avatar_francesco_gain(cur_player, cur_pid, data, pending):
# `data` is the client's response payload; `pending` is the dict we set above.
card_name = data.get("card_name")
options = pending.get("options", [])
clear_pending(pending) # release the engine; we'll re-block below if needed
if not card_name or card_name not in options:
return # invalid selection -> silently no-op; engine continues
# gain_from_supply runs the full gain pipeline (on-gain hooks, triggers, etc.)
if not gain_from_supply(cur_player, card_name, destination="hand", pid=cur_pid):
return
add_log(f"{cur_player['name']} gains {card_name} to hand")
# Fan out to every opponent. enqueue_opponent registers a pending
# attack-response keyed by pid; each opponent will independently
# answer via the @attack_response handler below.
for pid in state.game["player_order"]:
if pid != cur_pid:
gain.enqueue_opponent(pid, {
"card": "Avatar of Francesco",
"chosen_card": card_name,
})
# The gain above may have triggered on-gain reactions (Watchtower, Hostelry,
# Border Village...) that installed their own pending. If so, set_pending
# is identity-aware and defers via a thunk so OUR pending fires only after
# those resolve. Either way, we install a "waiting" pending so the turn
# player stays blocked until every opponent has answered.
if gain.any_opponent_pending():
set_pending("Avatar of Francesco", "waiting")
# ─── 3. ATTACK RESPONSE: one invocation per opponent, accept or decline ─────
# (attack_response is a bit of a misnomer, but the engine started out as only
# "attacks" being the thing off-turn players responded to (i.e. trash a card),
# but as cards grew in complexity, attack_response became the pipeline to handle
# ALL off-turn action responses. I could rename this; but that's a significant
# burden for very little pay off.)
@attack_response("Avatar of Francesco")
def _respond_avatar_francesco(player, pid, data, attack):
# `player`/`pid` here are the OPPONENT, not the turn player.
# `attack` is the dict we enqueued via gain.enqueue_opponent.
chosen_card = attack.get("chosen_card")
if data.get("accept"):
# off_turn_actor swaps the "current actor" for the duration of the block.
# Without it, any on-gain hook (e.g. Watchtower trashing the Curse)
# would fire against the turn player's state instead of this opponent's.
with off_turn_actor(pid):
if gain_from_supply(player, "Curse", pid=pid):
gain_from_supply(player, chosen_card, pid=pid)
add_log(f"{player['name']} gains a Curse and {chosen_card}")
else:
add_log(f"{player['name']} accepts but no Curses available")
else:
add_log(f"{player['name']} declines")
# pop_opponent clears this opponent's queued attack-response. When the
# last one pops, the turn player's "waiting" pending unblocks and the
# turn resumes.
gain.pop_opponent(pid) {
"Avatar of Matthew": {
"type": "avatar-treasure-curse",
"cost": 7,
"coins": 5,
"vp": -3,
"desc": "+$5. −3 VP.",
"expansion": "Aspects",
"tags": [
"+Coins"
]
}
}