<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>architecture on Saurabh Kumar</title><link>https://saurabh-kumar.com/tags/architecture/</link><description>Recent content in architecture on Saurabh Kumar</description><generator>Hugo</generator><language>en-US</language><copyright>Copyright © 2025, Saurabh Kumar.</copyright><lastBuildDate>Sat, 17 Oct 2026 12:00:00 +0000</lastBuildDate><atom:link href="https://saurabh-kumar.com/tags/architecture/index.xml" rel="self" type="application/rss+xml"/><item><title>How the Claude Code team does code review</title><link>https://saurabh-kumar.com/til/1792238400/</link><pubDate>Sat, 17 Oct 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1792238400/</guid><description>&lt;p&gt;&lt;a href="https://claude.dev/blog/solving-code-review-at-anthropic/" rel="noopener external"&gt;How the Claude Code team does code review&lt;/a&gt;. Claude writes ~90% of Claude Code, so nobody reads every diff. They swapped &amp;ldquo;did a human read it?&amp;rdquo; for &amp;ldquo;which check, automated or human, should catch what could go wrong here?&amp;rdquo;, and optimise for the least human time per change, with revert and incident rates as the guardrail.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The bit I missed on first read: review noise costs more when the author is an agent. A person waves off a nit in seconds; an agent fixes every one, which is a new commit and another full review. One PR ran 50+ rounds overnight, and ~59% of later findings were on code already there at round one. Fix: cap at three rounds, re-review only the new diff, flag the class of bug, never repeat a finding.&lt;/li&gt;&#10;&lt;li&gt;The bot posts findings, never approvals. Human approvals (zero, one or two) are set by the change&amp;rsquo;s risk, not by who wrote it. Whoever prompted the agent counts as a reviewer, and an approval covers one commit, so an agent&amp;rsquo;s next push dismisses it.&lt;/li&gt;&#10;&lt;li&gt;Their reviewer&amp;rsquo;s eval is their own past incidents and reverts, run daily. Noise is measured by what authors did with each comment: fixed it, said it was wrong, intentional, or not worth it.&lt;/li&gt;&#10;&lt;li&gt;Versus &lt;a href="https://saurabh-kumar.com/til/weekly/2026-04-26.html#ankit-jain" rel="noopener external"&gt;Ankit Jain&amp;rsquo;s review layers I noted in April&lt;/a&gt;: this is the production version, with no spec-review layer and fast rollback under everything.&lt;/li&gt;&#10;&lt;li&gt;Read the numbers carefully: the share of PRs reverted fell 40% while PR volume doubled, so the count of reverts still rose (~1.2x). Also: the authors sell the reviewer.&lt;/li&gt;&#10;&lt;li&gt;Where I push back: &amp;ldquo;nobody needs a full mental model, ask Claude for an explainer of the slice you&amp;rsquo;re changing.&amp;rdquo; An explainer recovers what the code does, not why it was built that way, which is the &lt;a href="https://addyosmani.com/blog/comprehension-debt/" rel="noopener external"&gt;comprehension debt&lt;/a&gt; Addy Osmani (a co-author here) wrote about. People should spend review on whether to ship it and on direction, not line-level correctness.&lt;/li&gt;&#10;&lt;li&gt;How I read it: had Claude explain it against my notes, then quiz me. The question I got wrong taught me the most.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Pi Pocket's subagent implementation</title><link>https://saurabh-kumar.com/til/1791547202/</link><pubDate>Fri, 09 Oct 2026 12:00:02 +0000</pubDate><guid>https://saurabh-kumar.com/til/1791547202/</guid><description>&lt;p&gt;&lt;a href="https://github.com/TannerMidd/pi-pocket/blob/3e6046612db4358b599da302d5c96bd407d9de5c/src/server/extensions/subagents.ts" rel="noopener external"&gt;Pi Pocket&amp;rsquo;s subagent implementation&lt;/a&gt; gives each worker a retained conversation and uses durable tasks to deliver messages and report answers. Request IDs and recorded answer IDs prevent duplicate delivery and reporting after restart. The idea worth stealing: make the handoff durable, not just the worker.&lt;/p&gt;&#10;</description></item><item><title>Pi Durable</title><link>https://saurabh-kumar.com/til/1791374400/</link><pubDate>Wed, 07 Oct 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1791374400/</guid><description>&lt;p&gt;&lt;a href="https://earendil.com/posts/pi-durable/" rel="noopener external"&gt;Pi Durable&lt;/a&gt; is Earendil&amp;rsquo;s experimental agent harness, shipped alongside Pi 1.0. &lt;a href="https://github.com/TannerMidd/pi-pocket" rel="noopener external"&gt;TannerMidd/pi-pocket&lt;/a&gt; is a mobile-first, multiplayer web app for Pi built on it.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Every model call and tool call is a task that checkpoints to storage (SQLite, JSONL, or memory) before moving on. Kill the process and a new one opens the same file and resumes.&lt;/li&gt;&#10;&lt;li&gt;Recovery is per step: a cut-off model request is resent with the partial answer kept and marked aborted. A cut-off tool call reruns only if it is replay-safe; otherwise the model is told it was interrupted.&lt;/li&gt;&#10;&lt;li&gt;A &lt;code&gt;requestId&lt;/code&gt; makes a submission exactly-once, so a client retrying after a crash gets the original back.&lt;/li&gt;&#10;&lt;li&gt;Forks see the parent transcript up to the fork point without copying it.&lt;/li&gt;&#10;&lt;li&gt;~15k lines total, small enough for an agent to read end to end.&lt;/li&gt;&#10;&lt;li&gt;pi-pocket: restart the server mid-run and the work continues. Several people can watch, steer, or queue messages on the same session.&lt;/li&gt;&#10;&lt;li&gt;&lt;code&gt;npm start -- --access tailscale&lt;/code&gt; binds only to the tailnet address, so my phone gets it with no tunnel.&lt;/li&gt;&#10;&lt;li&gt;The shift: the session lives in a SQLite file, not a terminal process, so the phone is just another client. Could replace my Moshi + tmux setup.&lt;/li&gt;&#10;&lt;li&gt;Caveat: anyone who can steer runs shell commands as you.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>jetify-com/typeid</title><link>https://saurabh-kumar.com/til/1790424000/</link><pubDate>Sat, 26 Sep 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1790424000/</guid><description>&lt;p&gt;&lt;a href="https://github.com/jetify-com/typeid" rel="noopener external"&gt;jetify-com/typeid&lt;/a&gt; is a spec for Stripe-style IDs: &lt;code&gt;user_01h2xcejqtf2nbrexx3vqjhp41&lt;/code&gt; is a snake_case type prefix, &lt;code&gt;_&lt;/code&gt;, then a UUIDv7 in 26 chars of Crockford base32 (hex UUIDs take 36). Time-ordered, so B-tree inserts stay local, unlike UUIDv4. Double-click selects the whole ID, and a serializer expecting &lt;code&gt;user_&lt;/code&gt; rejects a pasted &lt;code&gt;post_&lt;/code&gt; ID. Strip the prefix and you get a valid UUIDv7 back, so the database never has to know. Cheap way to adopt: keep the column a native 16-byte &lt;code&gt;uuid&lt;/code&gt; (&lt;code&gt;DEFAULT uuidv7()&lt;/code&gt; on Postgres 18, &lt;code&gt;uuid.uuid7()&lt;/code&gt; on Python 3.14) and encode/decode only at the API edge, e.g. a DRF serializer field on top of &lt;a href="https://github.com/akhundMurad/typeid-python" rel="noopener external"&gt;typeid-python&lt;/a&gt;. If PKs are already &lt;code&gt;uuid&lt;/code&gt;, no schema change, and IDs explain themselves in logs and support tickets.&lt;/p&gt;&#10;</description></item><item><title>Three Things That Bite You When You Ship gRPC to Production</title><link>https://saurabh-kumar.com/articles/2026/07/three-things-that-bite-you-when-you-ship-grpc-to-production/</link><pubDate>Fri, 24 Jul 2026 00:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/articles/2026/07/three-things-that-bite-you-when-you-ship-grpc-to-production/</guid><description>&lt;p&gt;I recently spent some time going deeper into gRPC, past the quickstart and into HTTP/2 framing, Protobuf encoding, and the parts of the system that sit around the service itself. I came away liking it. Contract-first APIs are useful, generated clients remove an entire class of mistakes, and streaming is part of the protocol rather than something attached later.&lt;/p&gt;&#10;&lt;p&gt;I also came away with a list of things I wish the quickstart had made harder to miss. They are not obscure bugs, and they are not arguments against gRPC. They are direct consequences of the choices that make it good.&lt;/p&gt;&#10;&lt;h2 id="connections-are-not-requests"&gt;Connections Are Not Requests&lt;/h2&gt;&#10;&lt;p&gt;For a long time I carried a simple model of load balancing: a request arrives, the load balancer picks a backend, and the backend handles it. That model is often close enough for HTTP/1.1 systems, even though the machinery underneath is balancing TCP connections rather than individual requests.&lt;/p&gt;&#10;&lt;p&gt;gRPC makes the difference visible. It usually keeps an HTTP/2 connection open and carries many concurrent RPCs over that connection as streams. Reusing the connection avoids handshakes and lets several calls make progress at once. This is one of the reasons gRPC performs well.&lt;/p&gt;&#10;&lt;p&gt;An L4 load balancer cannot see those streams. It sees one TCP connection, chooses one backend when that connection opens, and keeps sending the connection there for its lifetime. If a client resolves a single virtual IP and holds one long-lived connection to it, hundreds or thousands of calls can remain pinned to one backend while other instances have very little to do.&lt;/p&gt;&#10;&lt;p&gt;The metrics look absurd: one pod is hot, several pods are quiet, and adding more pods changes almost nothing. The cluster has capacity, but the client has no path to it.&lt;/p&gt;&#10;&lt;p&gt;It is like assigning a delivery truck to a warehouse when it leaves the depot, then insisting that every package on that truck go to the same warehouse. Adding warehouses does not help. The decision was already made at the wrong level.&lt;/p&gt;&#10;&lt;p&gt;The fix is to balance one level higher. A gRPC client can resolve individual backends, keep subchannels to them, and choose one for each RPC with a policy such as &lt;code&gt;round_robin&lt;/code&gt;. In Kubernetes that often means a headless service rather than a normal ClusterIP service. An HTTP/2-aware proxy such as Envoy, or a service mesh built around one, can also distribute streams across backend connections.&lt;/p&gt;&#10;&lt;p&gt;Both approaches work, and both add something I now have to operate: endpoint discovery and health checking in the client, or another proxy in the request path. That may be a good trade. It is still a trade.&lt;/p&gt;&#10;&lt;p&gt;A load balancer that understands connections does not necessarily understand gRPC.&lt;/p&gt;&#10;&lt;h2 id="browser-http2-is-not-grpc"&gt;Browser HTTP/2 Is Not gRPC&lt;/h2&gt;&#10;&lt;p&gt;I made what seemed like an obvious inference: gRPC uses HTTP/2, browsers support HTTP/2, therefore browser JavaScript can call a gRPC service.&lt;/p&gt;&#10;&lt;p&gt;It cannot, at least not as native gRPC through the browser APIs available to application code.&lt;/p&gt;&#10;&lt;p&gt;The browser may speak HTTP/2 on the network, but JavaScript does not control that connection as an HTTP/2 peer. gRPC places its final status and error details in trailing headers after the response body, and &lt;code&gt;fetch&lt;/code&gt; and &lt;code&gt;XMLHttpRequest&lt;/code&gt; do not give application code the trailer behavior native gRPC expects. Client streaming and bidirectional streaming also need the request and response to remain active together, with the client writing while it reads. Browser request APIs have historically not offered that form of full-duplex transport in a portable way.&lt;/p&gt;&#10;&lt;p&gt;The browser owns the connection. My code gets a window into it, not the keys.&lt;/p&gt;&#10;&lt;p&gt;grpc-web exists to bridge that boundary. It adapts the protocol so trailers can travel inside the response body as a marked frame that browser code can read. A proxy such as Envoy commonly translates between grpc-web on the browser side and native gRPC on the service side, although some servers support grpc-web directly. Unary calls work, and server streaming can work, but client and bidirectional streaming remain constrained by the browser transport.&lt;/p&gt;&#10;&lt;p&gt;Connect takes a different approach. A Connect server can expose browser-friendly HTTP alongside gRPC and grpc-web, with JSON or Protobuf payloads, and the browser can call it with ordinary &lt;code&gt;fetch&lt;/code&gt;. That removes a dedicated translation proxy in many deployments and makes a service much easier to inspect with tools such as &lt;code&gt;curl&lt;/code&gt;. It does not grant JavaScript arbitrary control over the browser&amp;rsquo;s HTTP/2 connection; available streaming modes still depend on the browser transport.&lt;/p&gt;&#10;&lt;p&gt;I do not consider this a flaw in gRPC. It was designed primarily for environments where both ends are programs with control over their network stack, and server-to-server RPC is where its tradeoffs make the most sense. A browser is someone else&amp;rsquo;s runtime, with someone else&amp;rsquo;s security model and someone else&amp;rsquo;s API surface.&lt;/p&gt;&#10;&lt;p&gt;The mistake is not that gRPC has a boundary. The mistake is drawing an architecture diagram that pretends the boundary is not there.&lt;/p&gt;&#10;&lt;h2 id="field-numbers-never-forget"&gt;Field Numbers Never Forget&lt;/h2&gt;&#10;&lt;p&gt;Protobuf field names are for people. Field numbers are for the wire.&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;email&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;The encoded message does not contain the words &lt;code&gt;name&lt;/code&gt; or &lt;code&gt;email&lt;/code&gt;. It contains field numbers, wire types, and values. Renaming &lt;code&gt;name&lt;/code&gt; to &lt;code&gt;full_name&lt;/code&gt; while keeping field number 1 does not change the wire contract. Changing the number does.&lt;/p&gt;&#10;&lt;p&gt;This is easy to understand while looking at one version of a &lt;code&gt;.proto&lt;/code&gt; file. It becomes harder after field 2 was deleted two years ago, the person who deleted it has moved to another team, and the current file makes number 2 look pleasantly unused.&lt;/p&gt;&#10;&lt;p&gt;It is not unused. It is retired.&lt;/p&gt;&#10;&lt;p&gt;Suppose a later version reuses field 2 for another length-delimited value:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;bytes&lt;/span&gt; &lt;span class="n"&gt;avatar_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="c1"&gt;// reused field number&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;An old client can still send an email address in field 2. The new server sees the same field number and a compatible wire type, and accepts those bytes as &lt;code&gt;avatar_hash&lt;/code&gt;. Protobuf cannot object because the old meaning is not present on the wire. Unless validation catches it, perfectly valid bytes can acquire the wrong meaning and travel further into the system.&lt;/p&gt;&#10;&lt;p&gt;That is worse than a failed decode.&lt;/p&gt;&#10;&lt;p&gt;The fix is a tombstone:&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-protobuf" data-lang="protobuf"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;message&lt;/span&gt; &lt;span class="nc"&gt;User&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;reserved&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;reserved&lt;/span&gt; &lt;span class="s"&gt;&amp;#34;email&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;string&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="kt"&gt;bytes&lt;/span&gt; &lt;span class="n"&gt;avatar_hash&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;reserved&lt;/code&gt; tells the compiler that the number and name may not be used again. It turns historical knowledge into a rule the tooling can enforce. Without it, the only thing protecting the schema is memory, and organizational memory is not a compatibility mechanism.&lt;/p&gt;&#10;&lt;p&gt;The rest of the discipline follows from the same fact. I do not renumber fields. I reserve every removed number and name. I prefer additive changes while old and new binaries must coexist, and I introduce a new message or RPC when the meaning genuinely needs to break. Proto3&amp;rsquo;s removal of &lt;code&gt;required&lt;/code&gt; fields came from the same hard-earned lesson: one service should not make every older peer invalid merely by evolving its schema.&lt;/p&gt;&#10;&lt;p&gt;A field number is small, but it is permanent.&lt;/p&gt;&#10;&lt;h2 id="the-abstraction-ends-somewhere"&gt;The Abstraction Ends Somewhere&lt;/h2&gt;&#10;&lt;p&gt;These three problems looked unrelated to me at first. One was about Kubernetes, one about browsers, and one about schema files. The common thread is that gRPC&amp;rsquo;s guarantees stop at the layer that understands them.&lt;/p&gt;&#10;&lt;p&gt;A TCP load balancer does not understand HTTP/2 streams. Browser JavaScript does not control the browser&amp;rsquo;s HTTP/2 implementation. Protobuf understands field numbers and wire types, but it does not understand that some bytes used to mean an email address and now mean an avatar hash.&lt;/p&gt;&#10;&lt;p&gt;Put those boundaries together and gRPC becomes less forgiving than its clean interface suggests. The generated method call may look like a local function, but it still crosses a network, a load balancer, a runtime, and a schema history. Each layer has constraints the service design needs to account for.&lt;/p&gt;&#10;&lt;p&gt;I still like gRPC, especially for internal communication across services written in different languages. The contract is real, the tooling is mature, and native streaming is valuable. I do not want to give those things up merely because the surrounding system needs care.&lt;/p&gt;&#10;&lt;p&gt;I need to account for those boundaries when choosing clients, load balancers, and schema changes.&lt;/p&gt;&#10;</description></item><item><title>Go interfaces are implicit: a type satisfies an interface by having the right…</title><link>https://saurabh-kumar.com/til/1784462400/</link><pubDate>Sun, 19 Jul 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1784462400/</guid><description>&lt;p&gt;Go interfaces are implicit: a type satisfies an interface by having the right methods, no &lt;code&gt;implements&lt;/code&gt; keyword.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Closest Python equivalent is &lt;code&gt;typing.Protocol&lt;/code&gt; (structural subtyping, checked by mypy), but Go enforces it at compile time, not via a separate type checker.&lt;/li&gt;&#10;&lt;li&gt;Sharp edge: a nil concrete pointer wrapped in an interface value is not &lt;code&gt;nil&lt;/code&gt;. &lt;code&gt;var dog *Dog = nil; var s Speaker = dog&lt;/code&gt; gives &lt;code&gt;s != nil&lt;/code&gt; because the interface holds &lt;code&gt;(*Dog, nil)&lt;/code&gt;, a typed pair. Bites hardest when returning &lt;code&gt;error&lt;/code&gt; interfaces.&lt;/li&gt;&#10;&lt;li&gt;Go proverb: &amp;ldquo;the bigger the interface, the weaker the abstraction.&amp;rdquo; Keep them to 1-3 methods, define where consumed not where implemented.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Ankit Jain on automated AI code review</title><link>https://saurabh-kumar.com/til/1776859200/</link><pubDate>Wed, 22 Apr 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1776859200/</guid><description>&lt;p&gt;A good collection of practices on automated AI code reviews by &lt;a href="https://www.latent.space/p/reviews-dead" rel="noopener external"&gt;Ankit Jain&lt;/a&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;The Scalability Crisis: Manual post-PR review is no longer viable. AI agents have nearly doubled code output, causing human review time to spike by 91%, creating a bottleneck that traditional workflows cannot solve.&lt;/li&gt;&#10;&lt;li&gt;The Upstream Pivot: Human value must shift from reviewing implementation to defining intent. Instead of checking syntax, humans spend their energy writing rigorous specs and acceptance criteria before the code is written, which the machine then uses to self-verify.&lt;/li&gt;&#10;&lt;li&gt;The Swiss-Cheese Defense: Rather than one &amp;ldquo;perfect&amp;rdquo; human gate, the model uses a stack of imperfect automated layers. By layering signals like agent competition, deterministic guardrails, and adversarial &amp;ldquo;red-team&amp;rdquo; agents, the system catches errors where their individual failure modes don&amp;rsquo;t overlap.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Django Modern REST</title><link>https://saurabh-kumar.com/til/1775131200/</link><pubDate>Thu, 02 Apr 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1775131200/</guid><description>&lt;p&gt;&lt;a href="https://django-modern-rest.readthedocs.io" rel="noopener external"&gt;Django Modern REST&lt;/a&gt; seems to be an emerging REST API library with focus on performance, scalability and pluggable serializers. Supporting msgpack, CBV and less of sync_to_async. Might evaluate it against the my current preferred &lt;code&gt;django-ninja&lt;/code&gt;. &lt;a href="https://gemini.google.com/app/1aa113e0b21dd18f" rel="noopener external"&gt;gemini&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>Every layer of review makes you 10x slower</title><link>https://saurabh-kumar.com/til/1773748800/</link><pubDate>Tue, 17 Mar 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1773748800/</guid><description>&lt;p&gt;Every layer of review makes you 10x slower via &lt;a href="https://apenwarr.ca/log/20260316" rel="noopener external"&gt;apenwarr&lt;/a&gt;&lt;/p&gt;&#10;</description></item><item><title>UIPC and SIPC on blockchain</title><link>https://saurabh-kumar.com/til/1770292802/</link><pubDate>Thu, 05 Feb 2026 12:00:02 +0000</pubDate><guid>https://saurabh-kumar.com/til/1770292802/</guid><description>&lt;p&gt;On blockchain, UIPC (Universal IPC) and SIPC (Secure IPC) are frameworks that allows different network to talk to each other using a standard protocol. While UIPC acts as the universal courier, SIPC acts as the security guard. Together, they create &amp;ldquo;Internet of Blockchains&amp;rdquo;. &lt;a href="https://gemini.google.com/app/b8339d20bf7f33cf" rel="noopener external"&gt;Gemini&lt;/a&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;a href="https://docs.cosmos.network/ibc/v10.1.x/intro" rel="noopener external"&gt;Cosmos IBC&lt;/a&gt; &amp;amp; &lt;a href="https://wiki.polkadot.network/docs/learn-xcm" rel="noopener external"&gt;Polkadot - XCM&lt;/a&gt; are two industry leading protocols for UIPC&lt;/li&gt;&#10;&lt;li&gt;For the SIPC, &lt;strong&gt;Zero-Knowledge Proofs&lt;/strong&gt; and &lt;strong&gt;Rollup architecture&lt;/strong&gt; also &amp;ldquo;Trustless bridges&amp;rdquo; or &amp;ldquo;Cross-rollup communication&amp;rdquo; are often more frequently used terms in blockchain literatures.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Designing Data-Intensive Applications</title><link>https://saurabh-kumar.com/til/1770292800/</link><pubDate>Thu, 05 Feb 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1770292800/</guid><description>&lt;p&gt;&lt;strong&gt;Designing Data-Intensive Applications: The Big Ideas Behind Reliable, Scalable, and Maintainable Systems&lt;/strong&gt; by Martin Kleppmann manages to be both rigorous technical manual and something approaching a philosophical treatise on the nature of truth, consistency, and trust in distributed systems. [Claude][https://claude.ai/chat/0f58f2f6-bd56-41a1-a785-8267afa5a3d1]&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Foundation. He starts by asking the questions &lt;em&gt;&amp;ldquo;What do we actually want in our data systems&amp;rdquo;&lt;/em&gt;, answers them as - reliability, scalability and maintainability.&#10;&lt;ul&gt;&#10;&lt;li&gt;Reliability. The question isn&amp;rsquo;t whether failure will happen, it will, but the whether the systems can survive them.&lt;/li&gt;&#10;&lt;li&gt;Scalability. It&amp;rsquo;s not a binary question of whether a system is &amp;ldquo;scalable&amp;rdquo; or &amp;ldquo;not scalable&amp;rdquo;. Ask - &amp;ldquo;What happens when specific load parameter increase?&amp;rdquo;&lt;/li&gt;&#10;&lt;li&gt;Maintainability - most underappreciated of the three, he argues the majority of cost isn&amp;rsquo;t in initial development but in ongoing maintainence.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;The data model wars - the skill isn&amp;rsquo;t in choosing the &amp;ldquo;best&amp;rdquo; database but in understanding which tradeoffs matter for your specific problem.&lt;/li&gt;&#10;&lt;li&gt;Storage engines. Two major approaches to read &amp;amp; write data from disk, neither is universally better.&#10;&lt;ul&gt;&#10;&lt;li&gt;&lt;strong&gt;Log-structured storage&lt;/strong&gt; (like LSM-trees) optimizes for writes, every write is appended and the merged/compacted later.&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Page-oriented storage&lt;/strong&gt; (like B-trees) optimizes for reads, data is stored in fixed-sized block, which then get updated in-place, more like filing cabinet with each document has a designated slot.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;Instead of asking &amp;ldquo;how do I build this?&amp;rdquo; ask &amp;ldquo;what does it mean for this to work correctly?&amp;rdquo;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>How AI assistance impacts the formation of coding skills</title><link>https://saurabh-kumar.com/til/1769860800/</link><pubDate>Sat, 31 Jan 2026 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1769860800/</guid><description>&lt;p&gt;Notes on &amp;ldquo;How AI assistance impacts the formation of coding skills&amp;rdquo; &lt;a href="https://www.anthropic.com/research/AI-assistance-coding-skills" rel="noopener external"&gt;Article&lt;/a&gt; &lt;a href="https://news.ycombinator.com/item?id=46820924" rel="noopener external"&gt;HN&lt;/a&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;AI speeds up coding but reduces deep understanding and mastery&lt;/li&gt;&#10;&lt;li&gt;Juniors (1-3 years experience) showed speed improvements with AI, but 4+ year developers showed no difference&lt;/li&gt;&#10;&lt;li&gt;Modern software work is more about requirements, specs, documentation, and communication than raw coding skill&lt;/li&gt;&#10;&lt;li&gt;Small sample size (n&amp;lt;8) and study design limitations make results questionable&lt;/li&gt;&#10;&lt;li&gt;Takeaways:&#10;&lt;ol&gt;&#10;&lt;li&gt;&lt;strong&gt;Use AI for high-scoring interaction patterns&lt;/strong&gt;: Ask conceptual questions and request explanations rather than just code generation&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Adopt AI for documentation and specs&lt;/strong&gt;: Multiple developers report dramatic improvements in tickets, PRs, and documentation quality&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Be deliberate about learning&lt;/strong&gt;: If using AI, actively practice explaining concepts and avoid pure copy-paste workflows&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;Use AI to reduce grunt work&lt;/strong&gt;: Let it handle boilerplate, test writing, and repetitive tasks while focusing on architecture and requirements&lt;/li&gt;&#10;&lt;/ol&gt;&#10;&lt;/li&gt;&#10;&lt;li&gt;&lt;strong&gt;The research confirms what many suspected: AI coding assistants create a real trade-off between speed and skill development, but the practical significance is hotly contested.&lt;/strong&gt; The critical question isn&amp;rsquo;t whether AI reduces learning (it does), but whether deep coding skill remains as valuable as expressing requirements clearly—and whether we&amp;rsquo;re comfortable with a generation of developers who can&amp;rsquo;t function without AI assistance.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Thoughtworks - Technology Radar vol 33</title><link>https://saurabh-kumar.com/til/1766404802/</link><pubDate>Mon, 22 Dec 2025 12:00:02 +0000</pubDate><guid>https://saurabh-kumar.com/til/1766404802/</guid><description>&lt;p&gt;Notes from &lt;a href="https://www.thoughtworks.com/content/dam/thoughtworks/documents/radar/2025/11/tr_technology_radar_vol_33_en.pdf" rel="noopener external"&gt;Thoughtworks - Technology Radar vol 33&lt;/a&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;text-to-sql solutions aren&amp;rsquo;t working as expected&lt;/li&gt;&#10;&lt;li&gt;pnpm, langGraph, and pydantic recommended for adoption&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Inversion of Control</title><link>https://saurabh-kumar.com/til/1766404800/</link><pubDate>Mon, 22 Dec 2025 12:00:00 +0000</pubDate><guid>https://saurabh-kumar.com/til/1766404800/</guid><description>&lt;p&gt;&lt;a href="https://en.wikipedia.org/wiki/Inversion_of_control" rel="noopener external"&gt;Inversion of Control&lt;/a&gt; principle &amp;ndash; &amp;ldquo;Don&amp;rsquo;t call us, we&amp;rsquo;ll call you&amp;rdquo;.&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;This design principle is used widely in Web Frameworks, GUI programs, etc in the form of event listeners, dependency injections, callbacks to avoid boilerplate code, resuability and testability.&lt;/li&gt;&#10;&lt;li&gt;Some popular examples are DOM, Flask, &lt;a href="https://docs.nestjs.com/providers" rel="noopener external"&gt;NestJS&lt;/a&gt;, Angular, etc.&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Choose Boring Tech</title><link>https://saurabh-kumar.com/til/1766145602/</link><pubDate>Fri, 19 Dec 2025 12:00:02 +0000</pubDate><guid>https://saurabh-kumar.com/til/1766145602/</guid><description>&lt;p&gt;&amp;ldquo;Choose Boring Tech&amp;rdquo; by &lt;a href="https://boringtechnology.club/" rel="noopener external"&gt;Dan McKinley&lt;/a&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;&amp;ldquo;The failure mode of boring tech are well-understood&amp;rdquo;&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item><item><title>Django rapid architecture</title><link>https://saurabh-kumar.com/til/1766145601/</link><pubDate>Fri, 19 Dec 2025 12:00:01 +0000</pubDate><guid>https://saurabh-kumar.com/til/1766145601/</guid><description>&lt;p&gt;Notes from &lt;a href="https://www.django-rapid-architecture.org/" rel="noopener external"&gt;&amp;ldquo;Django rapid architecture&amp;rdquo;&lt;/a&gt;&lt;/p&gt;&#10;&lt;ul&gt;&#10;&lt;li&gt;Recommends organizing the codebase with interfaces at the top layer followed by readers and &amp;ldquo;actions&amp;rdquo;, Finally, &amp;lsquo;data&amp;rsquo; as the bottom layer to organize Django codebase to allow for a simple code structure that allows for scaling&lt;/li&gt;&#10;&lt;/ul&gt;&#10;</description></item></channel></rss>