The VPN-shaped hole in our pair programming workflow¶
At ShitOps, pair programming is not a ritual, it is a production control. Two engineers open the same Model-View-Controller workspace, one drives and one navigates, and together they ship. Then we opened a second office. The VPN tunnel between regions introduced 180 ms of jitter and the MVC monolith began to disagree with itself. The driver would update a model, the navigator's view would still show the old controller state, and both would try to save. We saw duplicate order status updates, stale form fields, and an incident where a pair spent 40 minutes reviewing a view that no longer existed. The root cause was clear: the controller was bound to local session state, the view was rendered from a different cache, and the model was updated through a tunnel that had the consistency guarantees of a paper straw. We needed to make pair programming across the VPN deterministic at a global scale.
The architecture: a Model-View-Controller orchestration mesh¶
We introduced the MVC Orchestration Mesh. Each letter of Model-View-Controller becomes an independently deployable service. The Model service owns domain state and publishes every change as an immutable event to Kafka. The View service renders a canonical projection through a federated GraphQL gateway. The Controller service executes commands as Temporal workflows and issues driver leases. No service talks directly to another; everything passes through Envoy sidecars on a WireGuard mesh that runs over the corporate VPN. We call this the zero-trust pair programming substrate.
The flow is intentionally explicit. A keystroke in the IDE becomes a controller command. The Controller service validates the driver lease, writes a command event to Kafka through the outbox pattern, and starts a Temporal saga. The Model service consumes the event, applies it to Postgres, and emits a domain event. Debezium tails the database and sends the change to the View service. The View service projects the canonical state and pushes it to both developers through WebRTC data channels. The CRDT Collaboration Service handles cursor positions, selections, and form drafts so neither developer can overwrite the other's intent. Redis holds active session heartbeats with a 4-second TTL. If a heartbeat expires, the controller lease is revoked and the navigator automatically becomes the driver. This is all observed with OpenTelemetry spans and eBPF probes at the kernel level.
Every event is versioned with Protobuf and validated against a schema registry. Backward compatibility is enforced in CI. We provision Kafka topics as Kubernetes custom resources through Crossplane. The Session Orchestrator is a Go service with a Rust WASM filter for request shaping. The View service uses React Server Components. The Controller service exposes gRPC endpoints. The Model service uses Postgres logical replication. Redis Enterprise Active-Active backs the CRDT layer. WebRTC data channels use SCTP, with a WebSocket fallback through the VPN if a network policy blocks the direct path. Feature flags in LaunchDarkly control the rollout of every handoff rule. Terraform, Helm, and Argo CD manage the deployment. Argo Rollouts runs canaries in three regions.
Pair programming as a finite-state protocol¶
We modeled the pair programming relationship as a finite-state machine: Idle, DriverActive, NavigatorSuggesting, HandoffRequested, HandoffApproved, and Reconciling. Each transition is a Temporal workflow with retries, compensation, and a dead-letter queue. When the driver saves a file, the Controller service emits FileSaved, which triggers a Model service projection update, which triggers a View service re-render, which triggers a CRDT diff broadcast. The navigator sees the change before the local IDE does, which is how we know the VPN is no longer the source of truth. Handoffs require a two-phase commit across both developer sessions: the current driver yields the lease, the CRDT service takes a snapshot, the View service confirms convergence, and the new driver receives a signed lease token. We use feature flags to roll out new handoff rules to 1 percent of pairs, then 10 percent, then all pairs. Argo Rollouts canaries each service in three regions. The pair programming session itself becomes a Temporal workflow with a durable event history, so a VPN reconnect resumes exactly where the last controller command committed.
The result¶
We now run 47 services across three regions. Session convergence p99 is 190 ms, event delivery is 99.999 percent, and duplicate model writes are down to zero in the last 30 days. The VPN is still there, but it is just transport now. Pair programming feels like a single deterministic MVC session, even when the two engineers are on different continents and the tunnel is having a bad day. The platform team has a Grafana dashboard for driver lease churn, CRDT conflict rates, and Temporal saga latency. We also run Litmus chaos experiments that kill a random Envoy sidecar every hour to prove the handoff protocol is resilient. Next quarter we are adding a reinforcement-learning scheduler that predicts which developer should drive based on typing cadence and calendar pressure. The MVC Orchestration Mesh is the foundation of how ShitOps pairs now. If you are still pairing through a shared screen and a VPN client, you are not doing pair programming; you are doing packet loss with extra steps.
Comments 7
SkepticalSRE
This has to be a parody, right? 47 services, a Temporal workflow for pair programming, and a CRDT layer just so two people can edit the same file? The operational overhead alone would crush most teams. You could have fixed the VPN jitter with a shared screen and a decent IDE plugin.
Bram W. Tiddlywinks Author
Not a parody. We measured duplicate order statuses, stale form fields, and a 40-minute review of a view that no longer existed. The mesh gives us deterministic handoffs. But yes, it is a lot—we have a platform team of 12 dedicated to it.
TemporalTina
I love Temporal, but using it for driver leases is wild. How do you handle workflow history growth when every keystroke is a command event?
Bram W. Tiddlywinks Author
We batch keystrokes into 250ms windows at the Controller service and use Temporal's continue-as-new after 10k events. The history stays manageable.
KafkaFanboy
The outbox pattern plus Debezium plus Kafka is solid. But why GraphQL federation for the View service? That feels like you added a second distributed system to debug on top of the first one.
Bram W. Tiddlywinks Author
The view is composed from six domain services. Federation was the only way to keep p99 under 200ms without building a custom BFF. We tried REST aggregation and it added 80ms of waterfall latency.
GraphQLHater
Federation is never the answer. You just moved the complexity from the client to the gateway and called it a day.
PairProgrammingPurist
I've been pairing for 15 years. This is the most absurd thing I've ever read. You've turned a social practice into a distributed systems nightmare. The best pair programming happens when you trust your partner, not when you have signed lease tokens and a two-phase commit for handoffs.
Bram W. Tiddlywinks Author
Trust doesn't survive 180ms of jitter. We had duplicate order status updates and stale form fields. The lease tokens are not about trust; they are about making the controller state unambiguous across regions.
LiveShareLarry
Couldn't you just use VS Code Live Share or a CRDT editor? Why build an entire orchestration mesh?
Bram W. Tiddlywinks Author
Live Share doesn't give us controller leases, deterministic handoffs, or a durable event history for session resumption after a VPN reconnect. We needed the protocol, not just the shared buffer.
PairProgrammingPurist
That's exactly my point. You didn't need a protocol. You needed a better VPN or a co-located pair. This is engineering as therapy.
CostConscious
What's the cloud bill for this? 47 services across three regions, Kafka, Temporal, Redis Enterprise Active-Active, WebRTC infra, eBPF probes... I'm guessing $50k/month minimum.
Bram W. Tiddlywinks Author
Roughly $38k/month, but we saved an estimated 200 engineer-hours of duplicate work last quarter. The platform team costs more than the infrastructure, honestly.
JuniorDev
This is inspiring but terrifying. How do I learn to build something like this? I'm still trying to understand the outbox pattern.
Bram W. Tiddlywinks Author
Start with a monolith. Please. Learn the outbox pattern in a single service first. This mesh is a response to a specific failure mode at scale, not a starting point.
SeniorDev
Don't. This is a cautionary tale dressed up as a success story. If you need a Temporal saga to pair program, your organization has bigger problems.
CRDT_Crusader
The CRDT collaboration service for cursor positions and form drafts is the only part of this that makes sense to me. But why Redis Enterprise Active-Active instead of something like Yjs with a small sync server?
Bram W. Tiddlywinks Author
We needed multi-region active-active with conflict-free merging at the session level. Yjs is great for document text, but we also needed presence, selections, and form drafts to survive region failover. Redis Enterprise gave us that with operational maturity.
AgileAndy
I can't tell if this is a joke or if ShitOps is actually a real company. The name suggests the former, but the architecture diagram suggests the latter. Either way, I'm forwarding this to my platform team as a warning.
Bram W. Tiddlywinks Author
ShitOps is real enough. The name is a long story. Forward it as a warning or a blueprint—both are valid.