Ship fast, think later

I started freelancing this year. This has meant jumping between unfamiliar codebases and getting productive as quickly as possible. A year ago this process entailed scouring through READMEs and directly pairing with a more veteran engineer to further familiarize myself with the codebase. Today, the process couldn’t be more different. I prompt an agent to walk me through general architecture, set up my local build environments and I’m often opening a PR within the first few hours of work. ...

June 29, 2026

Infra has an intent problem

In a past job, I broke networking within my first week of starting. The change? A single line commit that added a customer domain to a reverse proxy control loop that directed traffic to a specific server. api.somecompany.com, api.anotherfancynewcompany.ai { reverse_proxy http://localhost:4200 } At a glance, this code is legible, it should work; we’re manually proxying traffic from two domains to a locally running instance. A passing caddy validate run further verifies this. ...

March 24, 2026

Whatever you do, don't lose the plot

The other day, I was working on translating data from a file into json to visualize the results. Easy enough, given that this was an operation I’d done countless times in the past. Still, I blanked. After spending months delegating all coding related tasks to an agent, I’d completely lost the muscle memory of a simple fs.readFileSync call. I ended up sheepishly reaching for Claude, which wrote the function in more time than past me would have taken. ...

March 20, 2026

2025; Welcome Home

Who am I, if not what I do? This was the question that haunted me at the start of the year. I began 2025 willingly unemployed, which was more uncomfortable than I’d imagined. Growing up in Asia, I’d been conditioned to believe that “a life well-lived” followed a clear rubric: education, career, family, being a “good person”. But who am I, bereft of that plan? If not in relation to someone else, and to a job? I work, therefore I am. ...

December 31, 2025

Infra is stacks of turtles

In Pulumi, stacks can separate an infrastructure build by environment or by more granular concern. In a recent setup I worked on, we went with the latter approach. One stack handled compute, another handled networking and a third managed global artifacts like container images. Given that Pulumi stacks are all about independent interoperable units, the stack differentiation helped us reason about our infrastructure as separate areas of concern. Until it didn’t. ...

December 22, 2025

The Journey to Otel Collector

In a previous role, I worked on establishing an observability stack using OpenTelemetry (OTel) which mostly involved setting up instances of OTel collector to run across a distributed network. The main goal of this effort was to decouple data collection from data export, to more reliably gather data from various services for export. In the simplest of setups, logs, and metrics can be sent directly to Grafana Cloud via an OTLP (OTel Protocol) exporter. An OTLP exporter is the component that sends your telemetry data from the OTel Collector to a backend like Grafana. Exporters generally handle authentication, and batching and can be configured for HTTP or gRPC transport. Essentially, they act as the bridge between your application’s telemetry and your monitoring platform. ...

December 19, 2025

You might not need an orchestrator

Across the startups that I’ve worked at, a recurring theme has been using Nomad and eventually migrating off of it. In one of them, which you might already be fairly acquainted with, Nomad was an initial iteration at constraint-based deployments enabling regional rollouts and seamless rescheduling across a fleet during maintenance events. For a company like Fly.io that gives customers the ability to schedule apps in different regions, an orchestrator is fundamental to the user experience. That said, many companies (at least several that I’ve worked at) rely on orchestrators like Nomad for deployments. In these instances, Nomad is overkill and often complicate the deployment experience. ...

December 17, 2025

There's an 'A' in failure

I was recently let go from my role. This wasn’t my first experience with an unexpected termination. In fact, this time last year I found myself in a similar situation albeit under different circumstances and with far more surprise. Yet this one felt like an especially low point in my career; unexpected in a similar way, but predictable given the circumstances. I was technically on an unofficial PIP prior to receiving the news, which was somewhat reasonable given that I wasn’t shipping and showing progress fast enough. I had taken on the substantial task of reconfiguring our infra from the ground up, all while maintaining the current one single handedly. I’d written about this in a previous blogpost, but this job stretched my skills well past my comfort zone and I was firing on all cylinders. With little to no onboarding (as is often common in every early-stage startup) I had to learn fast, make decisions confidently, and move at breakneck pace. In previous roles, I frequently had the support of a team or a more senior coworker when I pushed significantly past a growth edge. This time, however, I was on my own, operating in a completely asynchronous remote environment, building greenfield infra I had no prior experience with. I thought I could manage (I am no stranger to working through uncertainty) but this work was arduous and there was barely enough room or time for a beginner’s mindset. ...

December 16, 2025

Sync the local-first powerhouse

One of the hardest problems in local-first software is sync. In previous posts, I talked about OTs/CRDTs, the role of the cloud relative to the client, and general conflict resolution. But I never quite landed on the core theme behind those ideas; sync is the bread and butter of data reconciliation in local-first software. Briefly, a sync engine is responsible for collecting changes, resolving conflicts and propagating these changes across all clients. Considering that every client operates on their own copy of the data, and can go offline for long, unknown stretches of time, a good sync engine must be designed for availability, partition tolerance, and eventual consistency the classic trifecta of trade-offs in distributed systems. ...

December 15, 2025

What Makes a Good Deterministic Merge

The goal of every data reconciliation algorithm is to ensure all replicas converge on the same state of the world, in spite of concurrent changes. The best way to achieve this is through a deterministic merge where the same result can be guaranteed regardless of the order or timing of updates. The backbone of a deterministic merge is one that is associative (grouping doesn’t matter), commutative (order doesn’t matter) and idempotent (reapplying changes yields the same result). ...

December 12, 2025