<?xml version="1.0" encoding="utf-8" standalone="yes"?><?xml-stylesheet href="/feed.css?v=208f42b55cff" type="text/css"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:site="https://lalitm.com/feed/ns#"><channel><title>Lalit Maganti (Tag: Ai)</title><link>https://lalitm.com/tags/ai/</link><description>Recent content tagged Ai on Lalit Maganti</description><site:notice>This is a feed.
Feeds let you subscribe to updates from this site using a feed reader. Copy this page's URL from your address bar and paste it into your reader.
New to feeds? Read: https://aboutfeeds.com</site:notice><docs>https://aboutfeeds.com</docs><generator>Hugo -- gohugo.io</generator><language>en</language><lastBuildDate>Mon, 27 Jul 2026 10:43:00 +0100</lastBuildDate><atom:link href="https://lalitm.com/tags/ai/index.xml" rel="self" type="application/rss+xml"/><item><title>AI agents are not subsystem maintainers</title><link>https://lalitm.com/post/ai-subsystem-maintainer/</link><pubDate>Mon, 27 Jul 2026 10:43:00 +0100</pubDate><guid>https://lalitm.com/post/ai-subsystem-maintainer/</guid><description>Antirez, the creator of Redis, recently argued that expert programmers using AI should think of themselves as being Linus Torvalds.
The key part of his analogy is this:
Automatic programming, instead, in the hands of people that are expert technicians, or expert programmers, expert designers, expert software architects, is to assume the role of Linus, with the agents and the LLMs assuming the role of the different maintainers of the different subsystems.
This analogy does not work for me. As they exist today, I cannot be Linus and an LLM cannot be my subsystem maintainer.
Why? In one word: trust. Linus trusts his subsystem maintainers. He is able to focus on the big picture, safe in the knowledge that his lieutenants will consistently exercise good judgment in the long-term maintenance of their subsystems. He knows this because they have earned his trust by proving themselves over and over again.
Every time I have tried to give current LLMs the opportunity to prove themselves in this way, I’ve ended up regretting it. At a high level, I simply dislike too many of the decisions they make. This has happened even in areas (performance, databases, developer tool UX) where I feel qualified to judge how well an AI is doing. I’ve written about this in depth in my account of building Syntaqlite with AI, but beyond this, I’ve used LLMs in many different ways both in my job and in other side projects.</description><content:encoded>&lt;p&gt;Antirez, the creator of Redis, recently argued that expert programmers using AI
should think of themselves as
&lt;a href="https://antirez.com/news/171"&gt;being Linus Torvalds&lt;/a&gt;.&lt;/p&gt;
&lt;p&gt;The key part of his analogy is this:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Automatic programming, instead, in the hands of people that are expert
technicians, or expert programmers, expert designers, expert software
architects, is to assume the role of Linus, with the agents and the LLMs
assuming the role of the different maintainers of the different subsystems.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This analogy does not work for me. As they exist today, I cannot be Linus and an
LLM cannot be my subsystem maintainer.&lt;/p&gt;
&lt;p&gt;Why? In one word: &lt;em&gt;trust&lt;/em&gt;. Linus trusts his subsystem maintainers. He is able to
focus on the big picture, safe in the knowledge that his lieutenants will
consistently exercise good judgment in the long-term maintenance of their
subsystems. He knows this because they have earned his trust by proving
themselves &lt;em&gt;over and over again&lt;/em&gt;.&lt;/p&gt;
&lt;p&gt;Every time I have tried to give current LLMs the opportunity to prove themselves
in this way, I&amp;rsquo;ve ended up regretting it. At a high level, I simply dislike too
many of the decisions they make. This has happened even in areas (performance,
databases, developer tool UX) where I feel qualified to judge how well an AI is
doing. I&amp;rsquo;ve written about this in depth in
&lt;a href="https://lalitm.com/post/building-syntaqlite-ai/"&gt;my account of building Syntaqlite with AI&lt;/a&gt;,
but beyond this, I&amp;rsquo;ve used LLMs in many different ways both in my job and in
other side projects.&lt;/p&gt;
&lt;p&gt;If a human behaved like this repeatedly, I would not give them ownership of any
part of a project I own. Trust has to be earned, and current agents have not
come close to earning enough of it for me to step back. It is much cheaper for
me to stay involved while the design is taking shape than to discover at the end
that a sequence of plausible local decisions added up to the wrong system.&lt;/p&gt;
&lt;p&gt;And no, just passing tests or static verification is not enough for me. The
decisions I most often dislike are precisely the ones that cannot be easily
checked. Tests cannot tell me that an agent chose the wrong abstraction or
produced an API that is unpleasant to use. They cannot tell me that the code
will be difficult to change in three years, and they certainly cannot tell me
that I have solved the wrong problem.&lt;/p&gt;
&lt;p&gt;None of this makes AI a bad tool. With clear parameters and enough steering, it
is an extremely capable bounded implementer. AI has substantially changed what I
can build, and I use it extensively every day.&lt;/p&gt;
&lt;p&gt;But calling it a maintainer is a step too far. If I cannot trust it to exercise
independent judgment, then I still have to own the code. At best, today&amp;rsquo;s AI is
helping me implement my decisions; it is not freeing me to be Linus.&lt;/p&gt;</content:encoded></item><item><title>Perfetto v57: fixing PyTorch traces, plus journald logs and an AI skill</title><link>https://lalitm.com/post/perfetto-v57/</link><pubDate>Fri, 03 Jul 2026 00:23:00 +0100</pubDate><guid>https://lalitm.com/post/perfetto-v57/</guid><description>We just released Perfetto v57 and I wanted to share the new things I’m most excited about. This is something I wanted to do for past releases but I just never quite got round to it. It’s also something I plan on doing more of going forward: there might even be dedicated pieces if I think the feature deserves it!
PyTorch traces reliably work!# What I’m most excited about in this release isn’t a feature but a bugfix. If you used the PyTorch profiler and opened the resulting trace in Perfetto, there was a decent chance some of your events would just not show up; specifically this would happen when these events overlapped each other on a single track.
Technically, PyTorch is in the wrong here. The Chrome Trace Event (JSON) format says duration events on a track have to nest and can’t overlap; if you need overlap, you’re supposed to use async events. chrome://tracing appears to handle them, but its rendering is actually buggy as soon as a trace has real overlaps; people just learned to live with it. So when a bug came in January about overlapping events being broken, I closed it as working-as-intended because I couldn’t see any easy fix from our end.</description><content:encoded>&lt;p&gt;We just
&lt;a href="https://github.com/google/perfetto/releases/tag/v57.1"&gt;released Perfetto v57&lt;/a&gt;
and I wanted to share the new things I&amp;rsquo;m most excited about. This is something I
wanted to do for past releases but I just never quite got round to it. It&amp;rsquo;s also
something I plan on doing more of going forward: there might even be dedicated
pieces if I think the feature deserves it!&lt;/p&gt;
&lt;h2 id="pytorch-traces-reliably-work"&gt;PyTorch traces reliably work!&lt;a class="heading-anchor" href="#pytorch-traces-reliably-work" aria-label="Permalink to PyTorch traces reliably work!"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;What I&amp;rsquo;m most excited about in this release isn&amp;rsquo;t a feature but a bugfix. If you
used the
&lt;a href="https://docs.pytorch.org/tutorials/recipes/recipes/profiler_recipe.html"&gt;PyTorch profiler&lt;/a&gt;
and opened the resulting trace in Perfetto, there was a decent chance some of
your events would just not show up; specifically this would happen when these
events overlapped each other on a single track.&lt;/p&gt;
&lt;p&gt;Technically, PyTorch is in the wrong here. The Chrome Trace Event (JSON) format
says duration events on a track have to nest and can&amp;rsquo;t overlap; if you need
overlap, you&amp;rsquo;re supposed to use async events. &lt;code&gt;chrome://tracing&lt;/code&gt; appears to
handle them, but its rendering is actually buggy as soon as a trace has real
overlaps; people just learned to live with it. So when a
&lt;a href="https://github.com/google/perfetto/issues/4280"&gt;bug&lt;/a&gt; came in January about
overlapping events being broken, I closed it as working-as-intended because I
couldn&amp;rsquo;t see any easy fix from our end.&lt;/p&gt;
&lt;p&gt;Then over time, I realized it wasn&amp;rsquo;t just one person with a weird trace. The bug
had affected many different PyTorch traces, and even
&lt;a href="https://github.com/google/perfetto/issues/6277"&gt;JAX traces&lt;/a&gt;. I even discovered
that people were maintaining forks and hosting old versions of Perfetto just to
deal with this issue. This is basically
&lt;a href="https://www.hyrumslaw.com/"&gt;Hyrum&amp;rsquo;s Law&lt;/a&gt;: it doesn&amp;rsquo;t much matter what the spec
says, once enough people rely on some behaviour, that behaviour is your
contract.&lt;/p&gt;
&lt;p&gt;This is also a very similar situation to what I discussed in
&lt;a href="https://lalitm.com/perfetto-oss-company-prio/"&gt;Perfetto and open source&lt;/a&gt;: it
was genuinely hard for me to tell just how many people it was affecting at the
beginning: if I had understood, I would have dealt with this very differently.&lt;/p&gt;
&lt;p&gt;In any case, in v57, Perfetto now imports the overlapping events instead of
dropping them, overflowing the extras onto a different line in the same UI
track. I do have to credit &lt;a href="https://github.com/i-komarov"&gt;@i_komarov&lt;/a&gt; who helped
me understand how big this issue was and to converge on the fix.&lt;/p&gt;
&lt;h2 id="recording-systemd-journald-logs"&gt;Recording systemd journald logs&lt;a class="heading-anchor" href="#recording-systemd-journald-logs" aria-label="Permalink to Recording systemd journald logs"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Perfetto has always tried to give you as much context as possible about what the
system is doing and put it all on a single timeline. One missing piece of that
on Linux has been in logs from journald; there&amp;rsquo;s a ton of useful information
there but until now it was hard to correlate that with e.g. scheduling or other
app level information.&lt;/p&gt;
&lt;p&gt;With v57, there&amp;rsquo;s now a &lt;code&gt;linux.systemd_journald&lt;/code&gt; data source that records
journald entries straight into the trace, on the same timeline as everything
else. You can filter by minimum syslog priority, by &lt;code&gt;SYSLOG_IDENTIFIER&lt;/code&gt;, or by
systemd unit, so you are not pulling in the entire journal when you only care
about one service.&lt;/p&gt;
&lt;p&gt;To show it in action, here&amp;rsquo;s
&lt;a href="https://ui.perfetto.dev/#!/?url=https://lalitm.com/traces/journald-demo.pftrace"&gt;a trace I recorded on my own machine&lt;/a&gt;.
Alongside a few log lines I generated myself, it happened to capture a real
udisks SMART error that fired while I was recording; exactly the kind of thing
you&amp;rsquo;d otherwise miss.&lt;/p&gt;
&lt;p&gt;&lt;img src="https://lalitm.com/img/perfetto-v57/journald-logs.png" alt="Journald logs on the Perfetto timeline, with the log panel showing the captured entries including a udisks SMART error"&gt;&lt;/p&gt;
&lt;p&gt;I have to shout out &lt;a href="https://github.com/staticfloat"&gt;@staticfloat&lt;/a&gt; who
contributed this feature; thanks a lot for doing this!&lt;/p&gt;
&lt;h2 id="an-ai-skill-for-recording-and-analysis"&gt;An AI skill for recording and analysis&lt;a class="heading-anchor" href="#an-ai-skill-for-recording-and-analysis" aria-label="Permalink to An AI skill for recording and analysis"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;For a while now, people have been building their own AI tooling around Perfetto.
&lt;a href="https://github.com/Gracker/SmartPerfetto"&gt;SmartPerfetto&lt;/a&gt; and
&lt;a href="https://github.com/antarikshc/perfetto-mcp"&gt;perfetto-mcp&lt;/a&gt; are two public
examples I&amp;rsquo;ve come across but many teams inside Google have built their versions
of these which each handle a subset of Perfetto&amp;rsquo;s functionality. To us, this is
a sign that there&amp;rsquo;s demand out there to build something upstream so that
everyone can benefit.&lt;/p&gt;
&lt;p&gt;v57 includes an installable AI skill that works with Claude Code, Codex, Pi,
Opencode, Antigravity, and anything else that speaks the Agent Skills format. It
explains to the agent how to record traces (Android only for now!) and then
query them. It also includes dedicated workflows for debugging Android memory, a
specific area of focus for us; we plan on expanding these workflows over time
including to Linux.&lt;/p&gt;
&lt;p&gt;The skill is just one piece of a much larger AI plan, which I wrote up as
&lt;a href="https://github.com/google/perfetto/discussions/5763"&gt;an RFC&lt;/a&gt;. We will be doing
much more in this space in the coming months.&lt;/p&gt;
&lt;h2 id="state-tracks"&gt;State tracks&lt;a class="heading-anchor" href="#state-tracks" aria-label="Permalink to State tracks"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;State tracks are a new kind of primitive in Perfetto, sitting alongside slices
and counters. They represent something that is always in exactly one state: a
connection that is open or closed, a device that is on or off, a worker that is
idle or busy. You could already model this with slices, by emitting a slice that
starts when a state begins and ends when it changes. This works but you have to
be careful to emit events &lt;em&gt;exactly&lt;/em&gt; at the same timestamps or you end up having
gaps.&lt;/p&gt;
&lt;p&gt;With a state track you don&amp;rsquo;t emit start and end events at all. At each point
where the state changes, you emit one event that says what the state is now. The
previous state ends automatically. Clearing the track marks it idle. This just
makes it a lot simpler to manage.&lt;/p&gt;
&lt;p&gt;While the simpler emission model is the obvious win, the reason we made it a
first-class primitive goes further than that. Because a state track holds
exactly one value at any moment, it can never overlap itself, and the trace
processor can lean on that guarantee to treat it specially. You can attach
arguments that change as the state does. And in ring-buffer traces, where old
events get overwritten as the buffer wraps, you can periodically re-emit the
current state, so a trace that starts mid-stream still knows what state
everything is in.&lt;/p&gt;
&lt;p&gt;State tracks are still quite new so there are likely to be some rough edges
here, but we think they&amp;rsquo;ll become an important primitive: because they&amp;rsquo;re
guaranteed never to overlap, you can do things with them you simply couldn&amp;rsquo;t
before.&lt;/p&gt;
&lt;h2 id="querying-traces-over-https"&gt;Querying traces over HTTPS&lt;a class="heading-anchor" href="#querying-traces-over-https" aria-label="Permalink to Querying traces over HTTPS"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I recently discovered that quite a few folks have written scripts which download
a trace and then run queries against it with the trace_processor CLI. Which made
me think: why don&amp;rsquo;t we just support that natively inside the CLI itself?&lt;/p&gt;
&lt;p&gt;So in v57, &lt;code&gt;trace_processor&lt;/code&gt; takes an &lt;code&gt;http(s)&lt;/code&gt; URL or a &lt;code&gt;ui.perfetto.dev&lt;/code&gt; share
link directly. It streams the trace and runs your query with no local copy. Here
it is against the journald trace from the previous section:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;$ trace_processor query \
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;https://lalitm.com/traces/journald-demo.pftrace&amp;#34; \
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &amp;#34;select log_source, count(*) as n from logs where msg is not null group by log_source&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#34;log_source&amp;#34;,&amp;#34;n&amp;#34;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&amp;#34;systemd_journald&amp;#34;,30
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;It&amp;rsquo;s one of those small polish features which you wouldn&amp;rsquo;t necessarily think of
immediately but when you have it, you&amp;rsquo;d never want to go back.&lt;/p&gt;
&lt;h2 id="lightning-round"&gt;Lightning round&lt;a class="heading-anchor" href="#lightning-round" aria-label="Permalink to Lightning round"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;A few smaller things also landed:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Shift + mouse wheel scrolls the timeline horizontally.&lt;/strong&gt; A tiny change that
removes a daily papercut when you are panning around a wide trace.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;The query results grid got a real overhaul.&lt;/strong&gt; You can sort, reorder, and
hide columns, the sidebar collapses, and SQL errors now show up in the grid
instead of disappearing.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Adjacent slices that share a boundary render with a 1px gap,&lt;/strong&gt; so a run of
back-to-back slices reads as separate slices rather than one solid block.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Saved textproto record configs are editable,&lt;/strong&gt; saving the trouble of
downloading the config, deleting it, editing it locally, and then reuploading.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="wrap-up"&gt;Wrap up&lt;a class="heading-anchor" href="#wrap-up" aria-label="Permalink to Wrap up"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Overall this was a pretty solid release of Perfetto; it&amp;rsquo;s always satisfying to
fix a bug blocking a lot of people. AI skills and state tracks are both first
steps in a much longer journey, and journald logs are an important new
capability that improves our Linux support. The rest are mostly a bunch of small
quality of life improvements which are always nice to see!&lt;/p&gt;</content:encoded></item><item><title>Security analysis is finally reaching software's long tail</title><link>https://lalitm.com/post/perfetto-security-bugs-ai/</link><pubDate>Sun, 07 Jun 2026 16:26:00 +0100</pubDate><guid>https://lalitm.com/post/perfetto-security-bugs-ai/</guid><description>This post was formerly titled “17 bugs in 10 weeks from AI security scanning”. It was changed upon feedback from trusted readers who suggested, while accurate, it did not give a good sense of what I’m actually arguing in the post.
Over the last several weeks, I’ve been receiving more security bug reports for Perfetto’s trace processor than I ever have before, all of them found by AI. And I’m very happy about it! These are bugs that would almost certainly not have been found a year ago and it feels good to close these loopholes even though trace processor is by no means security critical.
For years, security researchers concentrated their time on the highest-stakes targets: kernels, cryptography libraries, password managers. But there’s a lot of code out there which is security-relevant but not truly security-critical. In my experience, these sorts of projects didn’t draw much attention. Now systems in the long tail can get that attention which they wouldn’t have before.</description><content:encoded>&lt;p&gt;&lt;em&gt;This post was formerly titled &amp;ldquo;17 bugs in 10 weeks from AI security scanning&amp;rdquo;.
It was changed upon feedback from trusted readers who suggested, while accurate,
it did not give a good sense of what I&amp;rsquo;m &lt;em&gt;actually&lt;/em&gt; arguing in the post.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;Over the last several weeks, I&amp;rsquo;ve been receiving more security bug reports for
Perfetto&amp;rsquo;s trace processor than I ever have before, all of them found by AI. And
I&amp;rsquo;m very happy about it! These are bugs that would almost certainly not have
been found a year ago and it feels good to close these loopholes even though
trace processor is by no means security critical.&lt;/p&gt;
&lt;p&gt;For years, security researchers concentrated their time on the highest-stakes
targets: kernels, cryptography libraries, password managers. But there&amp;rsquo;s a lot
of code out there which is security-relevant but not truly security-critical. In
my experience, these sorts of projects didn&amp;rsquo;t draw much attention. Now systems
in the long tail can get that attention which they wouldn&amp;rsquo;t have before.&lt;/p&gt;
&lt;h2 id="why-is-this-happening"&gt;Why is this happening&lt;a class="heading-anchor" href="#why-is-this-happening" aria-label="Permalink to Why is this happening"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Trace processor is a project which sits squarely in that long tail. It&amp;rsquo;s a C++
library (yes, Rust would be the obvious choice today but it&amp;rsquo;s not practical to
rewrite, see footnote &lt;sup id="fnref:1"&gt;&lt;a href="#fn:1" class="footnote-ref" role="doc-noteref"&gt;1&lt;/a&gt;&lt;/sup&gt;) for processing recorded traces of various
formats. These are typically traces you collected yourself or in your test infra
and process offline so &amp;ldquo;untrusted input&amp;rdquo; isn&amp;rsquo;t much of a concern.&lt;/p&gt;
&lt;p&gt;However, some people &lt;em&gt;do&lt;/em&gt; process traces they didn&amp;rsquo;t collect themselves (e.g.
user bug reports, automated collection from dogfood users). For those cases
we&amp;rsquo;ve strongly recommended sandboxing trace processor (e.g.
&lt;a href="https://gvisor.dev/"&gt;gvisor&lt;/a&gt;,
&lt;a href="https://developers.google.com/code-sandboxing/sandbox2"&gt;sandbox2&lt;/a&gt;, or
&lt;a href="https://google.github.io/minijail/"&gt;minijail&lt;/a&gt;) or, for even more sensitive use
cases, a VM.&lt;/p&gt;
&lt;p&gt;Beyond sandboxing, for catching issues proactively, we mainly relied on fuzzing
running internally in Google. These fuzzers occasionally surfaced real,
actionable bugs: we set them up to pass in arbitrary trace bytes (as this is the
main &amp;ldquo;attack surface&amp;rdquo;) but over time these became quite rare as they discovered
much of the low hanging fruit, which we quickly fixed. The bugs that remained
tend to live deep in the internals, reachable only with a very precisely crafted
sequence of bytes that a fuzzer is unlikely to hit by mutation alone.&lt;/p&gt;
&lt;p&gt;Apart from this, there has rarely been any bandwidth or resources for a human,
either a security expert or someone from my team, to spend lots of time finding
security issues in trace processor. There were always other parts of Perfetto
more worth spending security time on (e.g. the tracing service, on-device
profilers) as they&amp;rsquo;re actively running in production systems.&lt;/p&gt;
&lt;p&gt;All of this changed as of a couple of months ago. We started receiving bug
reports filed by some central team which appears to be running AI-based security
scanning against various projects throughout Google. Unfortunately, I have to be
hand wavy about what exactly they&amp;rsquo;re doing as their work doesn&amp;rsquo;t appear to be
public.&lt;/p&gt;
&lt;p&gt;Starting in early April, we had a slow drip of 1 bug a week, but since the end
of April this increased to a rate of several a week, with some days having 3 or
4 being opened in quick succession. This lasted until mid-May, at which point it
started tapering back to 1-2 a week with some weeks having none.&lt;/p&gt;
&lt;p&gt;I also want to say that the quality of the bugs is high. They&amp;rsquo;re well-described,
often with the relevant attacker model already worked out and even minimal fixes
proposed: basically everything I could ask for from a bug report. This matches
what both &lt;a href="https://daniel.haxx.se/blog/2026/04/22/high-quality-chaos/"&gt;curl&lt;/a&gt; and
&lt;a href="https://www.theregister.com/2026/03/26/greg_kroahhartman_ai_kernel/"&gt;Linux kernel&lt;/a&gt;
maintainers have noted about security bugs they&amp;rsquo;ve received, especially how
sharply quality has improved in the last few months.&lt;/p&gt;
&lt;p&gt;As I can only see the bugs that get filed against me, not the raw output of the
AI scanner, I don&amp;rsquo;t know exactly how much triage happens upstream. My guess is
there&amp;rsquo;s a human doing a light pass to drop obvious noise before reports reach
client teams, but judging from the rate at which bugs are opened and the way
they&amp;rsquo;re filed, I doubt anyone is deeply triaging each one.&lt;/p&gt;
&lt;p&gt;In total, we&amp;rsquo;ve received &lt;strong&gt;21&lt;/strong&gt; bugs (17 real issues and 4 not actionable),
which can be broken down into the following categories:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;10 bounds checking&lt;/strong&gt;: arbitrary trace data flowing into fixed-size buffers
or unchecked array indices, leading to out-of-bounds reads or writes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;5 use-after-free&lt;/strong&gt;: back-pointers, pointer snapshots, or hashmap keys
outliving the object they refer to.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1 stack overflow&lt;/strong&gt;: unbounded recursion when input is deeply nested.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;1 access control&lt;/strong&gt;: not enforcing allowlists on some rare codepaths.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;4 closed as not actionable&lt;/strong&gt;: either where the chance of exploit was purely
hypothetical or where fixing would have required fundamental design changes
which were not worth the tiny security risk.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;All 17 real issues have been fixed, almost all shipping in Perfetto v56.0
&lt;sup id="fnref:2"&gt;&lt;a href="#fn:2" class="footnote-ref" role="doc-noteref"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id="how-it-feels-to-get-a-report"&gt;How it feels to get a report&lt;a class="heading-anchor" href="#how-it-feels-to-get-a-report" aria-label="Permalink to How it feels to get a report"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;How does receiving one of these reports actually feel? Well not as bad as you&amp;rsquo;d
think. Unlike a security-critical application like OpenSSL or curl, in trace
processor, a security issue is very unlikely to be a P0 I have to drop
everything to fix. Don&amp;rsquo;t get me wrong, it&amp;rsquo;s still a priority but one where I
have the luxury of taking a few days to figure out the right answer and can
release fixes according to our normal schedule, instead of trying to rush out a
CVE and get everyone to patch immediately.&lt;/p&gt;
&lt;p&gt;Also thankfully, because the majority of the issues are mechanical, the fixes
are generally quite straightforward.
&lt;a href="https://github.com/google/perfetto/pull/5586"&gt;Take this PR&lt;/a&gt;, for example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;We build a key string into a fixed-size stack buffer while parsing some
metadata.&lt;/li&gt;
&lt;li&gt;The bounds check only runs in debug builds, and the metadata name comes
straight from the trace. Putting a long enough name means you would escape the
buffer.&lt;/li&gt;
&lt;li&gt;The fix is a simple matter of swapping the stack buffer for a std::string. The
code path is very cold (only once or twice in a trace) so the extra heap
allocation doesn&amp;rsquo;t matter.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;In fact, these sorts of issues are &lt;em&gt;so&lt;/em&gt; mechanical that I trust a coding agent
to just fix them with minimal guidance: take the well-written report, feed it to
the agent, and within ~10 minutes there&amp;rsquo;s a 10-20 line PR which fixes it. I
review every line thoroughly and make sure I understand it, but these tasks are
not difficult and firmly &lt;em&gt;inside&lt;/em&gt; the &amp;ldquo;jagged frontier&amp;rdquo; of what AI can do.&lt;/p&gt;
&lt;p&gt;I want to stress though that not every issue is mechanical or can be left to AI;
a few reports actually point more to design problems than incorrect function
implementations.
&lt;a href="https://github.com/google/perfetto/pull/5593"&gt;This use after free&lt;/a&gt; is a good
example:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The problem was a state object held a back-pointer that could end up pointing
to freed memory given certain data appearing in the trace.&lt;/li&gt;
&lt;li&gt;The immediate dangling case was easy to patch by just having a callback which
invalidated the back-pointer on free. But this is a horrible hack which makes
the lifetimes of the objects involved impossible to reason about.&lt;/li&gt;
&lt;li&gt;The real problem here is that you had a child object whose parent could go
away before it, which really shouldn&amp;rsquo;t happen if this code is properly
architected.&lt;/li&gt;
&lt;li&gt;Fixing it properly meant restructuring the ownership model so the lifetime was
correct by construction.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The interesting thing was that this was a problem I was aware of and that I had
been meaning to clean up for close to a year but never got round to: the
security bug just gave me the push and justification to do it. This applied in a
couple of other bugs as well and made me internalize that security issues can
sometimes be correlated with deeper design flaws or hacky code so there are
wider benefits to &amp;ldquo;security scanning&amp;rdquo; than just the direct bugs they find.&lt;/p&gt;
&lt;h2 id="will-this-last"&gt;Will this last?&lt;a class="heading-anchor" href="#will-this-last" aria-label="Permalink to Will this last?"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;One thing I am wary of is how long this stream of bugs will keep up; I&amp;rsquo;m feeling
good about it given it&amp;rsquo;s only been going on for a couple of months, but I can
easily imagine that if this goes on for several more months, it might become
mentally exhausting.&lt;/p&gt;
&lt;p&gt;But my suspicion is that this will go to zero. Why? It&amp;rsquo;s to do with the pattern
of how these bugs are being filed. Each part of the codebase seems like it&amp;rsquo;s
getting a day or two of attention (and associated bugs) before moving on to a
different part. Repeats are rare, and the pace of bugs has slowed especially in
the last couple of weeks: we had a lot more in the start of May (several a week)
but now we&amp;rsquo;re down to 1-2 a week. There are a finite number of files, so
eventually my gut tells me they will run out.&lt;/p&gt;
&lt;p&gt;An important consideration is whether we&amp;rsquo;ll add new bugs faster than the scanner
can find old ones. My suspicion is no; the 17 real issues so far are from
scanning across 9 years of development. Even if that number triples before
things settle, the scanner is still working through years of accumulated code.
And we wrote a lot more code, a lot faster, in the earlier years of the project,
so the rate of new code being added now is lower than it once was.&lt;/p&gt;
&lt;p&gt;The other question is whether new model releases will find more complex design
issues rather than the simple issues we&amp;rsquo;re finding today. Those take
significantly more time and effort to fix and so would be a lot more painful if
we were to get many of those. I&amp;rsquo;m very unsure on this so we&amp;rsquo;ll just have to wait
and see!&lt;/p&gt;
&lt;h2 id="where-this-leaves-us"&gt;Where this leaves us&lt;a class="heading-anchor" href="#where-this-leaves-us" aria-label="Permalink to Where this leaves us"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I feel Daniel Stenberg (curl maintainer) phrased it well in this
&lt;a href="https://daniel.haxx.se/blog/2026/05/11/mythos-finds-a-curl-vulnerability/"&gt;post&lt;/a&gt;:&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;Any project that has not scanned their source code with AI powered tooling
will likely find huge number of flaws, bugs and possible vulnerabilities with
this new generation of tools.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;This rings very true to me. More broadly, I think folks will have one of three
experiences:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;em&gt;Untrusted input + security critical&lt;/em&gt; (e.g. curl, kernel, OpenSSL): many
complex reports, with a higher false positive rate than the other categories,
because there&amp;rsquo;s a lot of attention on the project and much of the low hanging
fruit would already have been picked in the critical codepaths. Though
codepaths for lesser-used functionality (e.g. legacy drivers) could end up in
category 2 instead.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;Untrusted input + not previously audited&lt;/em&gt; (e.g. trace processor): a wave of
mechanical bugs at a manageable pace and low individual stress because the
project is not on a security critical code path. This is where both Daniel
and I expect AI security scanners to have the most impact.&lt;/li&gt;
&lt;li&gt;&lt;em&gt;No untrusted input&lt;/em&gt; (internal tools, math libs, anything operating only on
trusted data): you probably won&amp;rsquo;t notice this shift at all.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;My own case sits squarely in that second bucket. But I don&amp;rsquo;t want to
over-generalize from my experience, because there are three things that make
this manageable for me that wouldn&amp;rsquo;t be true for everyone: a) I&amp;rsquo;m paid to
maintain trace processor as part of my full time job; b) someone else is taking
the effort to run the AI scans and discover the bugs in the first place; c) the
reports appear to be lightly filtered by an upstream human reviewer, enough to
strip obvious noise but probably not a deep triage.&lt;/p&gt;
&lt;p&gt;To me, this points to a gap in the ecosystem: most open-source projects cannot
afford to have a dedicated team doing security scanning for them, and telling a
maintainer to stand up their own pipeline when their security risk is marginal
will restrict this to only the most motivated projects. I would guess we&amp;rsquo;re
going to see a lot more innovation in this space, including from the big AI
labs.&lt;/p&gt;
&lt;p&gt;All in all, I&amp;rsquo;m cautiously positive about my own experience: most of the bugs
are mechanical, a few have nudged long-overdue design cleanups, and the pace is
manageable. There&amp;rsquo;s plenty I don&amp;rsquo;t know about how this evolves: whether the pace
holds, whether future models start finding harder design issues. So this should
very much be treated as a snapshot, not a forecast!&lt;/p&gt;
&lt;div class="footnotes" role="doc-endnotes"&gt;
&lt;hr&gt;
&lt;ol&gt;
&lt;li id="fn:1"&gt;
&lt;p&gt;A common response I expect is: &amp;ldquo;if it&amp;rsquo;s parsing arbitrary binary data like
traces, it should be written in Rust.&amp;rdquo; In a vacuum I agree and if I was
writing trace processor from scratch today, I would definitely use Rust. But
&lt;em&gt;switching&lt;/em&gt; to Rust is unfortunately quite impractical; the library is a
significant amount of code and is embedded in hundreds of downstream tools,
many in environments that don&amp;rsquo;t have a Rust toolchain. Asking all our
embedders to start using Rust would be a significant burden and one I don&amp;rsquo;t
want to impose. Not to mention that our team doesn&amp;rsquo;t actually have any Rust
expertise so reviewing this code at a standard I want from trace processor
would be a significant productivity hit until folks got up to speed. And
unlike others in the industry, I don&amp;rsquo;t feel comfortable just rewriting the
whole project in one shot and calling it a day&amp;hellip;&amp;#160;&lt;a href="#fnref:1" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;li id="fn:2"&gt;
&lt;p&gt;The couple of remaining bugs were found after the v56.0 release was cut and
are low-priority enough that it&amp;rsquo;s not worth rushing out a release for them.
They have already been fixed on main and will be fixed in an upcoming point
release.&amp;#160;&lt;a href="#fnref:2" class="footnote-backref" role="doc-backlink"&gt;&amp;#x21a9;&amp;#xfe0e;&lt;/a&gt;&lt;/p&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/div&gt;</content:encoded></item><item><title>Eight years of wanting, three months of building with AI</title><link>https://lalitm.com/post/building-syntaqlite-ai/</link><pubDate>Sun, 05 Apr 2026 13:00:00 +0100</pubDate><guid>https://lalitm.com/post/building-syntaqlite-ai/</guid><description>For eight years, I’ve wanted a high-quality set of devtools for working with SQLite. Given how important SQLite is to the industry1, I’ve long been puzzled that no one has invested in building a really good developer experience for it2.
A couple of weeks ago, after ~250 hours of effort over three months3 on evenings, weekends, and vacation days, I finally released syntaqlite (GitHub), fulfilling this long-held wish. And I believe the main reason this happened was because of AI coding agents4.
Of course, there’s no shortage of posts claiming that AI one-shot their project or pushing back and declaring that AI is all slop. I’m going to take a very different approach and, instead, systematically break down my experience building syntaqlite with AI, both where it helped and where it was detrimental.
I’ll do this while contextualizing the project and my background so you can independently assess how generalizable this experience was. And whenever I make a claim, I’ll try to back it up with evidence from my project journal, coding transcripts, or commit history5.</description><content:encoded>&lt;p&gt;For eight years, I&amp;rsquo;ve wanted a high-quality set of devtools for working with
SQLite. Given how important SQLite is to the industry&lt;sup class="sn-ref" id="sn-ref-sqlite-industry"&gt;&lt;a href="#sn-sqlite-industry"&gt;1&lt;/a&gt;&lt;/sup&gt;, I&amp;rsquo;ve long been puzzled that no one has invested in building
a really good developer experience for it&lt;sup class="sn-ref" id="sn-ref-devtools"&gt;&lt;a href="#sn-devtools"&gt;2&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;A couple of weeks ago, after ~250 hours of effort over three months&lt;sup class="sn-ref" id="sn-ref-hours"&gt;&lt;a href="#sn-hours"&gt;3&lt;/a&gt;&lt;/sup&gt; on evenings, weekends, and vacation days, I finally
&lt;a href="https://lalitm.com/post/syntaqlite/"&gt;released syntaqlite&lt;/a&gt;
(&lt;a href="https://github.com/LalitMaganti/syntaqlite"&gt;GitHub&lt;/a&gt;), fulfilling this
long-held wish. And I believe the main reason this happened was because of AI
coding agents&lt;sup class="sn-ref" id="sn-ref-codingtools"&gt;&lt;a href="#sn-codingtools"&gt;4&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;Of course, there&amp;rsquo;s no shortage of posts claiming that AI one-shot their project
or pushing back and declaring that AI is all slop. I&amp;rsquo;m going to take a very
different approach and, instead, systematically break down my experience
building syntaqlite with AI, both where it helped &lt;em&gt;and&lt;/em&gt; where it was
detrimental.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ll do this while contextualizing the project and my background so you can
independently assess how generalizable this experience was. And whenever I make
a claim, I&amp;rsquo;ll try to back it up with evidence from my project journal, coding
transcripts, or commit history&lt;sup class="sn-ref" id="sn-ref-evidence"&gt;&lt;a href="#sn-evidence"&gt;5&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h2 id="why-i-wanted-it"&gt;Why I wanted it&lt;a class="heading-anchor" href="#why-i-wanted-it" aria-label="Permalink to Why I wanted it"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;In my work on &lt;a href="https://docs.perfetto.dev"&gt;Perfetto&lt;/a&gt;, I maintain a SQLite-based
language for querying performance traces called
&lt;a href="https://perfetto.dev/docs/analysis/perfetto-sql-getting-started"&gt;PerfettoSQL&lt;/a&gt;.
It&amp;rsquo;s basically the same as SQLite but with a few extensions to make the trace
querying experience better. There are ~100K lines of PerfettoSQL internally in
Google and it&amp;rsquo;s used by a wide range of teams.&lt;/p&gt;
&lt;p&gt;Having a language which gets traction means your users also start expecting
things like formatters, linters, and editor extensions. I&amp;rsquo;d hoped that we could
adapt some SQLite tools from open source but the more I looked into it, the more
disappointed I was. What I found either wasn&amp;rsquo;t reliable enough, fast
enough&lt;sup class="sn-ref" id="sn-ref-speed-comparison"&gt;&lt;a href="#sn-speed-comparison"&gt;6&lt;/a&gt;&lt;/sup&gt;, or flexible enough to adapt to PerfettoSQL. There was
clearly an opportunity to build something from scratch, but it was never the
&amp;ldquo;most important thing we could work on&amp;rdquo;. We&amp;rsquo;ve been reluctantly making do with
the tools out there but always wishing for better.&lt;/p&gt;
&lt;p&gt;On the other hand, there &lt;em&gt;was&lt;/em&gt; the option to do something in my spare time. I
had built lots of open source projects in my teens&lt;sup class="sn-ref" id="sn-ref-holoirc"&gt;&lt;a href="#sn-holoirc"&gt;7&lt;/a&gt;&lt;/sup&gt; but this
had faded away during university when I felt that I just didn&amp;rsquo;t have the
motivation anymore. Being a maintainer is much more than just &amp;ldquo;throwing the code
out there&amp;rdquo; and seeing what happens. It&amp;rsquo;s triaging bugs, investigating crashes,
writing documentation, building a community, and, most importantly, having a
direction for the project.&lt;/p&gt;
&lt;p&gt;But the itch of open source (specifically freedom to work on what I wanted while
helping others) had never gone away. The SQLite devtools project was eternally
in my mind as &amp;ldquo;something I&amp;rsquo;d like to work on&amp;rdquo;. But there was another reason why
I kept putting it off: it sits at the intersection of being both hard &lt;em&gt;and&lt;/em&gt;
tedious.&lt;/p&gt;
&lt;h2 id="what-makes-it-hard-and-tedious"&gt;What makes it hard and tedious&lt;a class="heading-anchor" href="#what-makes-it-hard-and-tedious" aria-label="Permalink to What makes it hard and tedious"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;If I was going to invest my personal time working on this project, I didn&amp;rsquo;t want
to build something that only helped Perfetto: I wanted to make it work for &lt;em&gt;any&lt;/em&gt;
SQLite user out there&lt;sup class="sn-ref" id="sn-ref-ambition"&gt;&lt;a href="#sn-ambition"&gt;8&lt;/a&gt;&lt;/sup&gt;. And this means parsing SQL &lt;em&gt;exactly&lt;/em&gt;
like SQLite.&lt;/p&gt;
&lt;p&gt;The heart of any language-oriented devtool is the parser. This is responsible
for turning the source code into a &amp;ldquo;parse tree&amp;rdquo; which acts as the central data
structure anything else is built on top of. If your parser isn&amp;rsquo;t accurate, then
your formatters and linters will inevitably inherit those inaccuracies; many of
the tools I found suffered from having parsers which approximated the SQLite
language rather than representing it precisely.&lt;/p&gt;
&lt;p&gt;Unfortunately, unlike many other languages, SQLite has no formal specification
describing how it should be parsed. It doesn&amp;rsquo;t expose a stable API for its
parser either. In fact, quite uniquely, in its implementation it doesn&amp;rsquo;t even
build a parse tree at all&lt;sup class="sn-ref" id="sn-ref-no-parse-tree"&gt;&lt;a href="#sn-no-parse-tree"&gt;9&lt;/a&gt;&lt;/sup&gt;! The only reasonable approach
left in my opinion is to carefully extract the relevant parts of SQLite&amp;rsquo;s source
code and adapt it to build the parser I wanted&lt;sup class="sn-ref" id="sn-ref-extraction"&gt;&lt;a href="#sn-extraction"&gt;10&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;This means getting into the weeds of SQLite source code, a fiendishly difficult
codebase to understand. The whole project is written in C in an
&lt;a href="https://sqlite.org/src/file?name=src/vdbe.c&amp;amp;ci=trunk"&gt;incredibly dense style&lt;/a&gt;;
I&amp;rsquo;ve spent days just understanding the virtual table
&lt;a href="https://www.sqlite.org/vtab.html"&gt;API&lt;/a&gt;&lt;sup class="sn-ref" id="sn-ref-vtab-nuance"&gt;&lt;a href="#sn-vtab-nuance"&gt;11&lt;/a&gt;&lt;/sup&gt; and
&lt;a href="https://sqlite.org/src/file?name=src/vtab.c&amp;amp;ci=trunk"&gt;implementation&lt;/a&gt;. Trying
to grasp the full parser stack was daunting.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also the fact that there are &amp;gt;400 rules in SQLite which capture the full
surface area of its language. I&amp;rsquo;d have to specify in each of these &amp;ldquo;grammar
rules&amp;rdquo; how that part of the syntax maps to the matching node in the parse tree.
It&amp;rsquo;s extremely repetitive work; each rule is similar to all the ones around it
but also, by definition, different.&lt;/p&gt;
&lt;p&gt;And it&amp;rsquo;s not just the rules but also coming up with and writing tests to make
sure it&amp;rsquo;s correct, debugging if something is wrong, triaging and fixing the
inevitable bugs people filed when I got something wrong&amp;hellip;&lt;/p&gt;
&lt;p&gt;For years, this was where the idea died. Too hard for a side project&lt;sup class="sn-ref" id="sn-ref-complexity"&gt;&lt;a href="#sn-complexity"&gt;12&lt;/a&gt;&lt;/sup&gt;, too tedious to sustain motivation, too risky to invest months
into something that might not work.&lt;/p&gt;
&lt;h2 id="how-it-happened"&gt;How it happened&lt;a class="heading-anchor" href="#how-it-happened" aria-label="Permalink to How it happened"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;I&amp;rsquo;ve been using coding agents since early 2025 (Aider, Roo Code, then Claude
Code since July) and they&amp;rsquo;d definitely been useful but never something I felt I
could trust a serious project to. But towards the end of 2025, the models seemed
to make a significant step forward in quality&lt;sup class="sn-ref" id="sn-ref-agents-got-good"&gt;&lt;a href="#sn-agents-got-good"&gt;13&lt;/a&gt;&lt;/sup&gt;. At the
same time, I kept hitting problems in Perfetto which would have been trivially
solved by having a reliable parser. Each workaround left the same thought in the
back of my mind: maybe it&amp;rsquo;s finally time to build it for real.&lt;/p&gt;
&lt;p&gt;I got some space to think and reflect over Christmas and decided to really
stress test the most maximalist version of AI: could I vibe-code the whole thing
using just Claude Code on the Max plan (£200/month)?&lt;/p&gt;
&lt;p&gt;Through most of January, I iterated, acting as semi-technical manager and
delegating almost all the design and all the implementation to Claude.
Functionally, I ended up in a reasonable place: a parser in C extracted from
SQLite sources using a bunch of Python scripts, a formatter built on top,
support for both the SQLite language and the PerfettoSQL extensions, all exposed
in a web playground.&lt;/p&gt;
&lt;p&gt;But when I reviewed the codebase in detail in late January, the downside was
obvious: the codebase was complete spaghetti&lt;sup class="sn-ref" id="sn-ref-spaghetti"&gt;&lt;a href="#sn-spaghetti"&gt;14&lt;/a&gt;&lt;/sup&gt;. I didn&amp;rsquo;t
understand large parts of the Python source extraction pipeline, functions were
scattered in random files without a clear shape, and a few files had grown to
several thousand lines. It was &lt;em&gt;extremely&lt;/em&gt; fragile; it solved the immediate
problem &lt;em&gt;but&lt;/em&gt; it was never going to cope with my larger vision, never mind
integrating it into the Perfetto tools. The saving grace was that it had proved
the approach was viable and generated more than 500 tests, many of which I felt
I could reuse.&lt;/p&gt;
&lt;p&gt;I decided to throw away everything and start from scratch while also switching
most of the codebase to Rust&lt;sup class="sn-ref" id="sn-ref-rust-not-c"&gt;&lt;a href="#sn-rust-not-c"&gt;15&lt;/a&gt;&lt;/sup&gt;. I could see that C was going
to make it difficult to build the higher level components like the validator and
the language server implementation. And as a bonus, it would also let me use the
same language for both the extraction and runtime instead of splitting it across
C and Python.&lt;/p&gt;
&lt;p&gt;More importantly, I completely changed my role in the project. I took ownership
of all decisions&lt;sup class="sn-ref" id="sn-ref-took-control"&gt;&lt;a href="#sn-took-control"&gt;16&lt;/a&gt;&lt;/sup&gt; and used it more as &amp;ldquo;autocomplete on
steroids&amp;rdquo; inside a much tighter process: opinionated design upfront, reviewing
every change thoroughly, fixing problems eagerly as I spotted them, and
investing in scaffolding (like linting, validation, and non-trivial
testing&lt;sup class="sn-ref" id="sn-ref-scaffolding"&gt;&lt;a href="#sn-scaffolding"&gt;17&lt;/a&gt;&lt;/sup&gt;) to check AI output automatically.&lt;/p&gt;
&lt;p&gt;The core features came together through February and the final stretch (upstream
test validation, editor extensions, packaging, docs) led to a 0.1 launch in
mid-March.&lt;/p&gt;
&lt;p&gt;But in my opinion, this timeline is the least interesting part of this story.
What I really want to talk about is what wouldn&amp;rsquo;t have happened without AI and
also the toll it took on me as I used it.&lt;/p&gt;
&lt;h2 id="ai-is-why-this-project-exists-and-why-its-as-complete-as-it-is"&gt;AI is why this project exists, and why it&amp;rsquo;s as complete as it is&lt;a class="heading-anchor" href="#ai-is-why-this-project-exists-and-why-its-as-complete-as-it-is" aria-label="Permalink to AI is why this project exists, and why it’s as complete as it is"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id="overcoming-inertia"&gt;Overcoming inertia&lt;a class="heading-anchor" href="#overcoming-inertia" aria-label="Permalink to Overcoming inertia"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;I&amp;rsquo;ve &lt;a href="https://lalitm.com/llm-motivation-via-emotions/"&gt;written in the past&lt;/a&gt;
about how one of my biggest weaknesses as a software engineer is my tendency to
procrastinate when facing a big new project. Though I didn&amp;rsquo;t realize it at the
time, it could not have applied more perfectly to building syntaqlite.&lt;/p&gt;
&lt;p&gt;AI basically let me put aside all my doubts on technical calls, my uncertainty
of building the right thing and my reluctance to get started by giving me very
concrete problems to work on. Instead of &amp;ldquo;I need to understand how SQLite&amp;rsquo;s
parsing works&amp;rdquo;, it was &amp;ldquo;I need to get AI to suggest an approach for me so I can
tear it up and build something better&amp;quot;&lt;sup class="sn-ref" id="sn-ref-inertia-journal"&gt;&lt;a href="#sn-inertia-journal"&gt;18&lt;/a&gt;&lt;/sup&gt;. I work so much
better with concrete prototypes to play with and code to look at than endlessly
thinking about designs in my head, and AI lets me get to that point at a pace I
could not have dreamed about before. Once I took the first step, every step
after that was so much easier.&lt;/p&gt;
&lt;h3 id="faster-at-churning-code"&gt;Faster at churning code&lt;a class="heading-anchor" href="#faster-at-churning-code" aria-label="Permalink to Faster at churning code"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;AI turned out to be better than me at the act of writing code itself, assuming
that code is obvious. If I can break a problem down to &amp;ldquo;write a function with
this behaviour and parameters&amp;rdquo; or &amp;ldquo;write a class matching this interface,&amp;rdquo; AI
will build it faster than I would and, crucially, in a style that might well be
more intuitive to a future reader. It documents things I&amp;rsquo;d skip, lays out code
consistently with the rest of the project, and sticks to what you might call the
&amp;ldquo;standard dialect&amp;rdquo; of whatever language you&amp;rsquo;re working
in&lt;sup class="sn-ref" id="sn-ref-standard-dialect"&gt;&lt;a href="#sn-standard-dialect"&gt;19&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;p&gt;That standardness is a double-edged sword. For the vast majority of code in any
project, standard is exactly what you want: predictable, readable, unsurprising.
But every project has pieces that are its edge, the parts where the value comes
from doing something non-obvious. For syntaqlite, that was the extraction
pipeline and the parser architecture. AI&amp;rsquo;s instinct to normalize was actively
harmful there, and those were the parts I had to design in depth and often
resorted to just writing myself.&lt;/p&gt;
&lt;p&gt;But here&amp;rsquo;s the flip side: the same speed that makes AI great at obvious code
also makes it great at refactoring. If you&amp;rsquo;re using AI to generate code at
industrial scale, you &lt;em&gt;have&lt;/em&gt; to refactor constantly and
continuously&lt;sup class="sn-ref" id="sn-ref-refactoring-journal"&gt;&lt;a href="#sn-refactoring-journal"&gt;20&lt;/a&gt;&lt;/sup&gt;. If you don&amp;rsquo;t, things immediately get
out of hand. This was the central lesson of the vibe-coding month: I didn&amp;rsquo;t
refactor enough, the codebase became something I couldn&amp;rsquo;t reason about, and I
had to throw it all away. In the rewrite, refactoring became the core of my
workflow. After every large batch of generated code, I&amp;rsquo;d step back and ask &amp;ldquo;is
this ugly?&amp;rdquo; Sometimes AI could clean it up. Other times there was a large-scale
abstraction that AI couldn&amp;rsquo;t see but I could; I&amp;rsquo;d give it the direction and let
it execute&lt;sup class="sn-ref" id="sn-ref-refactor-pattern"&gt;&lt;a href="#sn-refactor-pattern"&gt;21&lt;/a&gt;&lt;/sup&gt;. If you have taste, the cost of a wrong
approach drops dramatically because you can restructure
quickly&lt;sup class="sn-ref" id="sn-ref-refactor-taste"&gt;&lt;a href="#sn-refactor-taste"&gt;22&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id="teaching-assistant"&gt;Teaching assistant&lt;a class="heading-anchor" href="#teaching-assistant" aria-label="Permalink to Teaching assistant"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Of all the ways I used AI, research had by far the highest ratio of value
delivered to time spent.&lt;/p&gt;
&lt;p&gt;I&amp;rsquo;ve worked with interpreters and parsers before but I had never heard of
Wadler-Lindig pretty printing&lt;sup class="sn-ref" id="sn-ref-wadler-lindig"&gt;&lt;a href="#sn-wadler-lindig"&gt;23&lt;/a&gt;&lt;/sup&gt;. When I needed to build
the formatter, AI gave me a concrete and actionable lesson from a point of view
I could understand and pointed me to the papers to learn more. I could have
found this myself eventually, but AI compressed what might have been a day or
two of reading into a focused conversation where I could ask &amp;ldquo;but why does this
work?&amp;rdquo; until I actually got it.&lt;/p&gt;
&lt;p&gt;This extended to entire domains I&amp;rsquo;d never worked in. I have deep C++ and Android
performance expertise but had barely touched Rust tooling or editor extension
APIs. With AI, it wasn&amp;rsquo;t a problem: the fundamentals are the same, the
terminology is similar, and AI bridges the gap&lt;sup class="sn-ref" id="sn-ref-lateral-moves"&gt;&lt;a href="#sn-lateral-moves"&gt;24&lt;/a&gt;&lt;/sup&gt;. The VS
Code extension would have taken me a day or two of learning the API before I
could even start. With AI, I had a working extension within an hour.&lt;/p&gt;
&lt;p&gt;It was also invaluable for reacquainting myself with parts of the project I
hadn&amp;rsquo;t looked at for a few days&lt;sup class="sn-ref" id="sn-ref-context-reacquisition"&gt;&lt;a href="#sn-context-reacquisition"&gt;25&lt;/a&gt;&lt;/sup&gt;. I could control
how deep to go: &amp;ldquo;tell me about this component&amp;rdquo; for a surface-level refresher,
&amp;ldquo;give me a detailed linear walkthrough&amp;rdquo; for a deeper dive, &amp;ldquo;audit unsafe usages
in this repo&amp;rdquo; to go hunting for problems. When you&amp;rsquo;re context switching a lot,
you lose context fast. AI let me reacquire it on demand.&lt;/p&gt;
&lt;h3 id="more-than-id-have-built-alone"&gt;More than I&amp;rsquo;d have built alone&lt;a class="heading-anchor" href="#more-than-id-have-built-alone" aria-label="Permalink to More than I’d have built alone"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Beyond making the project exist at all, AI is also the reason it shipped as
complete as it did. Every open source project has a long tail of features that
are important but not critical: the things you know theoretically how to do but
keep deprioritizing because the core work is more pressing. For syntaqlite, that
list was long: editor extensions, Python bindings, a WASM playground, a docs
site, packaging for multiple ecosystems&lt;sup class="sn-ref" id="sn-ref-last-mile-list"&gt;&lt;a href="#sn-last-mile-list"&gt;26&lt;/a&gt;&lt;/sup&gt;. AI made these
cheap enough that skipping them felt like the wrong trade-off.&lt;/p&gt;
&lt;p&gt;It also freed up mental energy for UX&lt;sup class="sn-ref" id="sn-ref-ux-focus"&gt;&lt;a href="#sn-ux-focus"&gt;27&lt;/a&gt;&lt;/sup&gt;. Instead of spending
all my time on implementation, I could think about what a user&amp;rsquo;s first
experience should feel like: what error messages would actually help them fix
their SQL, how the formatter output should look by default, whether the CLI
flags were intuitive. These are the things that separate a tool people try once
from one they keep using, and AI gave me the headroom to care about them.
Without AI, I would have built something much smaller, probably no editor
extensions or docs site. AI didn&amp;rsquo;t just make the same project faster. It changed
what the project &lt;em&gt;was&lt;/em&gt;.&lt;/p&gt;
&lt;h2 id="where-ai-had-its-costs"&gt;Where AI had its costs&lt;a class="heading-anchor" href="#where-ai-had-its-costs" aria-label="Permalink to Where AI had its costs"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;h3 id="the-addiction"&gt;The addiction&lt;a class="heading-anchor" href="#the-addiction" aria-label="Permalink to The addiction"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;There&amp;rsquo;s an uncomfortable parallel between using AI coding tools and playing slot
machines&lt;sup class="sn-ref" id="sn-ref-addiction"&gt;&lt;a href="#sn-addiction"&gt;28&lt;/a&gt;&lt;/sup&gt;. You send a prompt, wait, and either get something
great or something useless. I found myself up late at night wanting to do &amp;ldquo;just
one more prompt,&amp;rdquo; constantly trying AI just to see what would happen even when I
knew it probably wouldn&amp;rsquo;t work. The sunk cost fallacy kicked in too: I&amp;rsquo;d keep at
it even in tasks it was clearly ill-suited for, telling myself &amp;ldquo;maybe if I
phrase it differently this time.&amp;rdquo;&lt;/p&gt;
&lt;p&gt;The tiredness feedback loop made it worse&lt;sup class="sn-ref" id="sn-ref-tiredness-loop"&gt;&lt;a href="#sn-tiredness-loop"&gt;29&lt;/a&gt;&lt;/sup&gt;. When I had
energy, I could write precise, well-scoped prompts and be genuinely productive.
But when I was tired, my prompts became vague, the output got worse, and I&amp;rsquo;d try
again, getting more tired in the process. In these cases, AI was probably slower
than just implementing something myself, but it was too hard to break out of the
loop&lt;sup class="sn-ref" id="sn-ref-ai-slower"&gt;&lt;a href="#sn-ai-slower"&gt;30&lt;/a&gt;&lt;/sup&gt;.&lt;/p&gt;
&lt;h3 id="losing-touch"&gt;Losing touch&lt;a class="heading-anchor" href="#losing-touch" aria-label="Permalink to Losing touch"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Several times during the project, I lost my mental model of the
codebase&lt;sup class="sn-ref" id="sn-ref-losing-touch"&gt;&lt;a href="#sn-losing-touch"&gt;31&lt;/a&gt;&lt;/sup&gt;. Not the overall architecture or how things
fitted together. But the day-to-day details of what lived where, which functions
called which, the small decisions that accumulate into a working system. When
that happened, surprising issues would appear and I&amp;rsquo;d find myself at a total
loss to understand what was going wrong. I hated that feeling.&lt;/p&gt;
&lt;p&gt;The deeper problem was that losing touch created a communication
breakdown&lt;sup class="sn-ref" id="sn-ref-communication-breakdown"&gt;&lt;a href="#sn-communication-breakdown"&gt;32&lt;/a&gt;&lt;/sup&gt;. When you don&amp;rsquo;t have the mental
thread of what&amp;rsquo;s going on, it becomes impossible to communicate meaningfully
with the agent. Every exchange gets longer and more verbose. Instead of &amp;ldquo;change
FooClass to do X,&amp;rdquo; you end up saying &amp;ldquo;change the thing which does Bar to do X&amp;rdquo;.
Then the agent has to figure out what Bar is, how that maps to FooClass, and
sometimes it gets it wrong&lt;sup class="sn-ref" id="sn-ref-manager-analogy"&gt;&lt;a href="#sn-manager-analogy"&gt;33&lt;/a&gt;&lt;/sup&gt;. It&amp;rsquo;s exactly the same
complaint engineers have always had about managers who don&amp;rsquo;t understand the code
asking for fanciful or impossible things. Except now you&amp;rsquo;ve become that manager.&lt;/p&gt;
&lt;p&gt;The fix was deliberate: I made it a habit to read through the code immediately
after it was implemented and actively engage to see &amp;ldquo;how would I have done this
differently?&amp;rdquo;.&lt;/p&gt;
&lt;p&gt;Of course, in some sense all of the above is also true of code I wrote a few
months ago (hence the
&lt;a href="https://text-incubation.com/AI+code+is+legacy+code+from+day+one"&gt;sentiment that AI code is legacy code&lt;/a&gt;),
but AI makes the drift happen faster because you&amp;rsquo;re not building the same muscle
memory that comes from originally typing it out.&lt;/p&gt;
&lt;h3 id="the-slow-corrosion"&gt;The slow corrosion&lt;a class="heading-anchor" href="#the-slow-corrosion" aria-label="Permalink to The slow corrosion"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;There were some other problems I only discovered incrementally over the three
months.&lt;/p&gt;
&lt;p&gt;I found that AI made me procrastinate on key design
decisions&lt;sup class="sn-ref" id="sn-ref-procrastination"&gt;&lt;a href="#sn-procrastination"&gt;34&lt;/a&gt;&lt;/sup&gt;. Because refactoring was cheap, I could
always say &amp;ldquo;I&amp;rsquo;ll deal with this later.&amp;rdquo; And because AI could refactor at the
same industrial scale it generated code, the cost of deferring felt low. But it
wasn&amp;rsquo;t: deferring decisions corroded my ability to think clearly because the
codebase stayed confusing in the meantime. The vibe-coding month was the most
extreme version of this. Yes, I understood the problem, but if I had been more
disciplined about making hard design calls earlier, I could have converged on
the right architecture much faster.&lt;/p&gt;
&lt;p&gt;Tests created a similar false comfort&lt;sup class="sn-ref" id="sn-ref-tests-insufficient"&gt;&lt;a href="#sn-tests-insufficient"&gt;35&lt;/a&gt;&lt;/sup&gt;. Having 500+
tests felt reassuring, and AI made it easy to generate more. But neither humans
nor AI are creative enough to foresee every edge case you&amp;rsquo;ll hit in the future;
there are several times in the vibe-coding phase where I&amp;rsquo;d come up with a test
case and realise the design of some component was completely wrong and needed to
be totally reworked. This was a significant contributor to my lack of trust and
the decision to scrap everything and start from scratch.&lt;/p&gt;
&lt;p&gt;Basically, I learned that the &amp;ldquo;normal rules&amp;rdquo; of software still apply in the AI
age: if you don&amp;rsquo;t have a fundamental foundation (clear architecture,
well-defined boundaries) you&amp;rsquo;ll be left eternally chasing bugs as they appear.&lt;/p&gt;
&lt;h3 id="no-sense-of-time"&gt;No sense of time&lt;a class="heading-anchor" href="#no-sense-of-time" aria-label="Permalink to No sense of time"&gt;#&lt;/a&gt;
&lt;/h3&gt;
&lt;p&gt;Something I kept coming back to was how little AI understood about the passage
of time&lt;sup class="sn-ref" id="sn-ref-no-sense-of-time"&gt;&lt;a href="#sn-no-sense-of-time"&gt;36&lt;/a&gt;&lt;/sup&gt;. It sees a codebase in a certain state but
doesn&amp;rsquo;t &lt;em&gt;feel&lt;/em&gt; time the way humans do. I can tell you what it feels like to use
an API, how it evolved over months or years, why certain decisions were made and
later reversed.&lt;/p&gt;
&lt;p&gt;The natural problem from this lack of understanding is that you either make the
same mistakes you made in the past and have to relearn the lessons &lt;em&gt;or&lt;/em&gt; you fall
into new traps which were successfully avoided the first time, slowing you down
in the long run. In my opinion, this is a similar problem to why losing a
high-quality senior engineer hurts a team so much: they carry history and
context that doesn&amp;rsquo;t exist anywhere else and act as a guide for others around
them.&lt;/p&gt;
&lt;p&gt;In theory, you can try to preserve this context by keeping specs and docs up to
date. But there&amp;rsquo;s a reason we didn&amp;rsquo;t do this before AI: capturing implicit
design decisions exhaustively is incredibly expensive and time-consuming to
write down. AI can help draft these docs, but because there&amp;rsquo;s no way to
automatically verify that it accurately captured what matters, a human still has
to manually audit the result. And that&amp;rsquo;s still time-consuming.&lt;/p&gt;
&lt;p&gt;There&amp;rsquo;s also the context pollution problem. You never know when a design note
about API A will echo in API B. Consistency is a huge part of what makes
codebases work, and for that you don&amp;rsquo;t just need context about what you&amp;rsquo;re
working on right now but also about other things which were designed in a
similar way. Deciding what&amp;rsquo;s relevant requires exactly the kind of judgement
that institutional knowledge provides in the first place.&lt;/p&gt;
&lt;h2 id="relativity"&gt;Relativity&lt;a class="heading-anchor" href="#relativity" aria-label="Permalink to Relativity"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Reflecting on the above, the pattern of when AI helped and when it hurt was
fairly consistent.&lt;/p&gt;
&lt;p&gt;When I was working on something I already understood deeply, AI was excellent. I
could review its output instantly, catch mistakes before they landed and move at
a pace I&amp;rsquo;d never have managed alone. The parser rule generation is the clearest
example&lt;sup class="sn-ref" id="sn-ref-parser-rules"&gt;&lt;a href="#sn-parser-rules"&gt;37&lt;/a&gt;&lt;/sup&gt;: I knew exactly what each rule should produce, so
I could review AI&amp;rsquo;s output within a minute or two and iterate fast.&lt;/p&gt;
&lt;p&gt;When I was working on something I could describe but didn&amp;rsquo;t yet know, AI was
good but required more care. Learning Wadler-Lindig for the formatter was like
this: I could articulate what I wanted, evaluate whether the output was heading
in the right direction, and learn from what AI explained. But I had to stay
engaged and couldn&amp;rsquo;t just accept what it gave me.&lt;/p&gt;
&lt;p&gt;When I was working on something where I didn&amp;rsquo;t even know what I wanted, AI was
somewhere between unhelpful and harmful. The architecture of the project was the
clearest case: I spent weeks in the early days following AI down dead ends,
exploring designs that felt productive in the moment but collapsed under
scrutiny. In hindsight, I have to wonder if it would have been faster just
thinking it through without AI in the loop at all.&lt;/p&gt;
&lt;p&gt;But expertise alone isn&amp;rsquo;t enough. Even when I understood a problem deeply, AI
still struggled if the task had no objectively checkable answer&lt;sup class="sn-ref" id="sn-ref-verifiability"&gt;&lt;a href="#sn-verifiability"&gt;38&lt;/a&gt;&lt;/sup&gt;. Implementation has a right answer, at least at a local level:
the code compiles, the tests pass, the output matches what you asked for. Design
doesn&amp;rsquo;t. We&amp;rsquo;re still arguing about OOP decades after it first took off.&lt;/p&gt;
&lt;p&gt;Concretely, I found that designing the public API of syntaqlite was where this
hit home the hardest. I spent several days in early March doing nothing but API
refactoring, manually fixing things any experienced engineer would have
instinctively avoided but AI made a total mess of. There&amp;rsquo;s no test or objective
metric for &amp;ldquo;is this API pleasant to use&amp;rdquo; and &amp;ldquo;will this API help users solve
the problems they have&amp;rdquo; and that&amp;rsquo;s exactly why the coding agents did &lt;em&gt;so badly&lt;/em&gt;
at it.&lt;/p&gt;
&lt;p&gt;This takes me back to the days I was obsessed with physics and, specifically,
relativity. The laws of physics look simple and Newtonian in any small local
area, but zoom out and spacetime curves in ways you can&amp;rsquo;t predict from the local
picture alone. Code is the same: at the level of a function or a class, there&amp;rsquo;s
usually a clear right answer, and AI is excellent there. But architecture is
what happens when all those local pieces interact, and you can&amp;rsquo;t get good global
behaviour by stitching together locally correct components.&lt;/p&gt;
&lt;p&gt;Knowing where you are on these axes at any given moment is, I think, the core
skill of working with AI effectively.&lt;/p&gt;
&lt;h2 id="wrap-up"&gt;Wrap-up&lt;a class="heading-anchor" href="#wrap-up" aria-label="Permalink to Wrap-up"&gt;#&lt;/a&gt;
&lt;/h2&gt;
&lt;p&gt;Eight years is a long time to carry a project in your head. Seeing these SQLite
tools actually exist and function after only three months of work is a massive
win, and I’m fully aware they wouldn&amp;rsquo;t be here without AI.&lt;/p&gt;
&lt;p&gt;But the process wasn&amp;rsquo;t the clean, linear success story people usually post. I
lost an entire month to vibe-coding. I fell into the trap of managing a codebase
I didn&amp;rsquo;t actually understand, and I paid for that with a total rewrite.&lt;/p&gt;
&lt;p&gt;The takeaway for me is simple: AI is an incredible force multiplier for
implementation, but it’s a dangerous substitute for design. It’s brilliant at
giving you the right answer to a specific technical question, but it has no
sense of history, taste, or how a human will actually feel using your API. If
you rely on it for the &amp;ldquo;soul&amp;rdquo; of your software, you’ll just end up hitting a
wall faster than you ever have before.&lt;/p&gt;
&lt;p&gt;What I&amp;rsquo;d like to see more of from others is exactly what I&amp;rsquo;ve tried to do here:
honest, detailed accounts of building real software with these tools; not
weekend toys or one-off scripts but the kind of software that has to survive
contact with users, bug reports, and your own changing mind.&lt;/p&gt;</content:encoded></item><item><title>What Makes a Good Tool for Claude Code</title><link>https://lalitm.com/writing-tools-for-claude-code/</link><pubDate>Sun, 12 Oct 2025 00:00:00 +0000</pubDate><guid>https://lalitm.com/writing-tools-for-claude-code/</guid><description>I’ve been using Claude Code extensively for personal projects, and similar AI coding tools at work. Recently I came across this excellent blog post that resonated with a lot of my experience.
One part stuck with me though: Noah emphasizes that tools fail with LLMs when they’re “overly complex,” with the Unix philosophy being particularly well-suited for tool calling. But then I thought about git.
Git breaks the Unix philosophy completely. It’s sprawling, stateful, and complex. And yet Claude Code handles it effortlessly. It composes commands that, even after 10+ years of daily git usage, I wouldn’t think to use. It handles rebasing, cherry-picking, complex resets—stuff that trips up experienced developers regularly.
So if simplicity and the Unix philosophy aren’t the whole story, what else matters?
I’ve come up with three “hallmarks” of a good tool for tool calling with LLMs.
1. It’s been around for a long time and/or is used by lots of people</description><content:encoded>&lt;p&gt;I&amp;rsquo;ve been using Claude Code extensively for personal projects, and similar AI
coding tools at work. Recently I came across
&lt;a href="https://www.alephic.com/writing/the-magic-of-claude-code"&gt;this excellent blog post&lt;/a&gt;
that resonated with a lot of my experience.&lt;/p&gt;
&lt;p&gt;One part stuck with me though: Noah emphasizes that tools fail with LLMs when
they&amp;rsquo;re &amp;ldquo;overly complex,&amp;rdquo; with the Unix philosophy being particularly
well-suited for tool calling. But then I thought about &lt;code&gt;git&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;Git breaks the Unix philosophy completely. It&amp;rsquo;s sprawling, stateful, and
complex. And yet Claude Code handles it effortlessly. It composes commands that,
even after 10+ years of daily git usage, I wouldn&amp;rsquo;t think to use. It handles
rebasing, cherry-picking, complex resets—stuff that trips up experienced
developers regularly.&lt;/p&gt;
&lt;p&gt;So if simplicity and the Unix philosophy aren&amp;rsquo;t the whole story, what else
matters?&lt;/p&gt;
&lt;p&gt;I’ve come up with three “hallmarks” of a good tool for tool calling with LLMs.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;1. It’s been around for a long time and/or is used by lots of people&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Examples: Unix tools like &lt;code&gt;cat&lt;/code&gt;, &lt;code&gt;sed&lt;/code&gt;, &lt;code&gt;awk&lt;/code&gt;, &lt;code&gt;grep&lt;/code&gt;, &lt;code&gt;find&lt;/code&gt;—but also &lt;code&gt;git&lt;/code&gt;,
&lt;code&gt;npm&lt;/code&gt;, &lt;code&gt;docker&lt;/code&gt;, &lt;code&gt;kubectl&lt;/code&gt;. Every Stack Overflow thread, blog post, and tutorial
using these tools has likely ended up in the training data. Claude isn’t
reasoning from first principles—it’s drawing on millions of examples.&lt;/p&gt;
&lt;p&gt;This is why git works despite its complexity: Claude has effectively memorized
decades of collective wisdom.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;2. It has really good documentation (built-in help or external docs)&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Even if a tool isn’t widely used, great documentation can bridge the gap. I’ve
been building a finance system on top of
&lt;a href="https://beancount.github.io/docs/"&gt;Beancount&lt;/a&gt;—a double-entry accounting system
that’s definitely not mainstream (maybe I’ll write a post about this in the
future). Claude Code handles it surprisingly well because Beancount has
exceptional documentation. When I point Claude at the docs, it can figure out
the directive syntax, transaction formats, and account structures without
necessarily having seen millions of examples in its training data.&lt;/p&gt;
&lt;p&gt;Good &lt;code&gt;--help&lt;/code&gt; text matters. Clear external documentation matters. If Claude can
discover how your tool works, it can use it effectively.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;3. Good error messages&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;Error messages, especially those with suggestions like “you used X, did you mean
Y?”, can be tremendously helpful for LLMs. The best example is the Rust
compiler: it gives errors like “you typed foobar, did you mean foobaz?” and
Claude Code can actually use that feedback to correct itself.&lt;/p&gt;
&lt;p&gt;This might be one reason why people feel Claude Code is particularly good at
Rust programming—the compiler is essentially coaching it through mistakes in
real time.&lt;/p&gt;
&lt;p&gt;This isn’t to say the Unix philosophy is wrong — it’s that Unix tools work well
with Claude Code for different reasons than we might think. Tools like sed and
awk nail hallmarks 1 and 2: they’ve been around for decades (massive training
data) and have extensive man pages (great documentation). The fact that they
follow “do one thing well” is almost incidental to their success with LLMs.&lt;/p&gt;
&lt;p&gt;So if I’m building a tool today, I can’t make it instantly popular, but I can
make it understandable. That means good documentation, clear error messages, and
a few solid examples of how it’s used. Those aren’t new ideas — they’ve always
mattered. The difference now is that there’s much higher ROI in doing them than
there used to be.&lt;/p&gt;
&lt;p&gt;In other words, we used to write docs for humans. Now, we’re also writing for
AIs that help humans. That shift changes the economics of good documentation in
a big way.&lt;/p&gt;</content:encoded></item><item><title>Harnessing Frustration: Using LLMs to Overcome Activation Energy</title><link>https://lalitm.com/llm-motivation-via-emotions/</link><pubDate>Mon, 14 Jul 2025 00:00:00 +0000</pubDate><guid>https://lalitm.com/llm-motivation-via-emotions/</guid><description>One of my biggest weaknesses as a software engineer is procrastination when facing a new project. When the scope is unclear, I have a tendency to wait until I feel I’ve “felt out” the problem to start doing anything. I know I’ll feel better and work much faster when I get “stuck in” but I still struggle with that first step, overcoming the “activation energy” required to engage with the details.
LLMs have been a game-changer for me in this respect: I can just throw a couple of sentences at them with the shape of the problem. This leads to one of two outcomes:
The LLM comes up with a good solution, usually in a slightly different way than what I was thinking. I realize “oh wow the solution is much simpler than I thought”. Straight away I start thinking about the consequences of implementing and improving what the LLM suggested. The LLM comes up with a solution that I intuitively recognize as “wrong”. My immediate reaction is frustration (“How could it get it so wrong”) which leads me to go back and forth with the model, explaining to it why its solution could not possibly work. But in the process of arguing with the model, my brain is churning away and generating variations or different approaches that could work. After a while, even if the AI is still on the wrong track, the debate will trigger a moment of inspiration where suddenly the solution will come to me. I’ll excitedly start up a new conversation and start working through it with the model. The key is the emotional reaction I have immediately to the LLM’s response, either excitement or frustration. By harnessing this immediate feedback loop, I get my brain out of its passive, procrastination mode. It’s almost like a jolt: either I’m thrilled because it’s simpler than I thought, or I’m spurred to action by the urge to correct a perceived ‘wrong’ answer. This forces me to engage with the problem in a meaningful way.</description><content:encoded>&lt;p&gt;One of my biggest weaknesses as a software engineer is procrastination when
facing a new project. When the scope is unclear, I have a tendency to wait until
I feel I&amp;rsquo;ve &amp;ldquo;felt out&amp;rdquo; the problem to start doing anything. I know I&amp;rsquo;ll feel
better and work much faster when I get &amp;ldquo;stuck in&amp;rdquo; but I still struggle with that
first step, overcoming the &amp;ldquo;activation energy&amp;rdquo; required to engage with the
details.&lt;/p&gt;
&lt;p&gt;LLMs have been a game-changer for me in this respect: I can just throw a couple
of sentences at them with the shape of the problem. This leads to one of two
outcomes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;The LLM comes up with a good solution, usually in a slightly different way
than what I was thinking. I realize &amp;ldquo;oh wow the solution is much simpler than
I thought&amp;rdquo;. Straight away I start thinking about the consequences of
implementing and improving what the LLM suggested.&lt;/li&gt;
&lt;li&gt;The LLM comes up with a solution that I intuitively recognize as &amp;ldquo;wrong&amp;rdquo;. My
immediate reaction is frustration (&amp;ldquo;How could it get it so wrong&amp;rdquo;) which
leads me to go back and forth with the model, explaining to it why its
solution could not &lt;em&gt;possibly&lt;/em&gt; work. But in the process of arguing with the
model, my brain is churning away and generating variations or different
approaches that &lt;em&gt;could&lt;/em&gt; work. After a while, even if the AI is still on the
wrong track, the debate will trigger a moment of inspiration where suddenly
the solution will come to me. I&amp;rsquo;ll excitedly start up a new conversation and
start working through it with the model.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The key is the emotional reaction I have immediately to the LLM&amp;rsquo;s response,
either &lt;strong&gt;excitement or frustration&lt;/strong&gt;. By harnessing this immediate feedback
loop, I get my brain out of its passive, procrastination mode. It&amp;rsquo;s almost like
a jolt: either I&amp;rsquo;m thrilled because it&amp;rsquo;s simpler than I thought, or I&amp;rsquo;m spurred
to action by the urge to correct a perceived &amp;lsquo;wrong&amp;rsquo; answer. This forces me to
engage with the problem in a meaningful way.&lt;/p&gt;
&lt;p&gt;For what it&amp;rsquo;s worth, this experience is very similar to talking through a
problem with another engineer: the advantage of LLMs is that it&amp;rsquo;s available 24/7
and I never have to worry about my problem being &amp;ldquo;too insignificant&amp;rdquo; to bother
someone with. The simple act of articulating my thoughts and hearing a response
seems sufficient, regardless of the response&amp;rsquo;s quality.&lt;/p&gt;
&lt;p&gt;So next time you&amp;rsquo;re procrastinating a problem don&amp;rsquo;t wait around for inspiration.
Outsource the first step. Throw the problem at an LLM and see what happens.
Whether excitement or frustration, by channeling that emotion you can solve your
problem faster.&lt;/p&gt;</content:encoded></item></channel></rss>