Having worked at Glean and now at WisdomAI, I have seen the LLM transformation very closely. In this post, I have captured my learnings over the last few years of managing an AI obsessed engineering team. To convey the point, I will try a simple experiment and then capture the lessons on how to do efficient software development with LLM agents. Spoiler alert: answer is more nuanced than adopt agents everywhere.
Preface
Unless you have been living under a rock, you know the way software is built has changed significantly. At a high level the process for building a software has been gathering product requirements, defining the design/architecture, and finally, coding and implementing it.
All of this can now be done by an LLM. However, this still doesn’t mean you can ask Claude to build the full software. In this post we will take an example of a well-known system SQLite and single shot Codex to build it.
MiniSQL experiment
I try to recreate SQLite using Codex, and name this recreation MiniSQL. Here’s the prompt I used for development: “Mini SQL is a self-contained, serverless relational database engine that stores entire databases as single, cross-platform files. Build this fully featured, make your best judgement on everything, take your time and come back with a finished software.”
The session completed in ~15m and what it generated (https://github.com/sharvanath/MiniSQL) is simply amazing. Most engineers would take much longer to build the first version of this quality. However, note that this is still first version and has to go through several iterations before getting to a state where it can be used as production grade database.
Feature Incompleteness
I created a super basic test script which passes on SQLite and tried to transform it to test on MiniSQL. Codex runs into the following issues. “MiniSQL doesn’t have SQLite’s parameter binding, AUTOINCREMENT, COUNT(*), or real indexes yet, so I’m going to write the test in the same spirit but against the current MiniSQL surface: create, insert, select, update, delete, uniqueness, and an explicit assertion that CREATE INDEX is currently unsupported.”
Architectural gaps
MiniSQL’s core architectural gap is that it currently behaves like an in-memory SQL interpreter with whole-file JSON persistence, while SQLite is a layered database engine built around paged storage, B-tree tables and indexes, a pager-managed transaction system, crash recovery, a query planner, and a bytecode virtual machine. In short: MiniSQL can execute simple relational operations, but it does not yet have the storage, transaction, indexing, and execution architecture that makes SQLite durable, scalable, concurrent. Again you only realize these when you dig a level deeper.
Correctness bugs
I asked Codex and Claude Code to find bugs in this and they found several interesting bugs. Here are the top few:
- Transactional semantics (found by both Codex/Claude Code): minisql/engine.py#L143 appends each inserted row as it validates. If a later row violates a constraint, earlier rows stay in memory even though the statement failed. A later successful write can persist that partial failed insert. For instance,
INSERT INTO users VALUES (1, 'a'), (2, 'a');fails on duplicate email, but row(1, 'a')remains visible and can later be committed. - Join bug (only by Claude Code):
SELECT *silently drops/overwrites columns on JOINs when both tables share a column name (e.g.id). The merged row context uses the same flat key for both sides, so the right-hand table's value clobbers the left-hand table's — data loss, not just a display quirk. Qualified selects (a.id, b.id) are unaffected, so the underlying join data is fine; only the*expansion is broken. - Conditional parsing bug (only by Claude Code): Three-valued NULL logic isn’t implemented correctly. Comparisons (
=,<, etc.) short-circuit toFalsewhenever either side isNULL, rather thanUNKNOWN. This mostly avoids weirdness in plainWHERE/ORclauses, but breaksNOT: e.g.NOT (a = b)evaluatesTRUEwhen bothaandbareNULL, where standard SQL would exclude that row (UNKNOWN, notTRUE).
The essence of Iteration loop
This experiment is not the best example since a suitably designed harness/agent can easily build a much better first version of SQLite. However, that is precisely because a well written, serverless local SQL engine like SQLite already exists for the agent to learn from and iterate against. In practice, you don’t have these lessons already captured and you learn them via usage based iterations.
The lesson is simple, software building is iterative by nature. We build and launch something, get feedback (feature requirements, bugs, or performance gaps), iterate, and the repeat this loop.
You don’t know all product requirements upfront. You need real users to give you this feedback. No USERS means bad quality SOFTWARE. The architecture will most likely be missing lots of core aspects because that also evolves as you find performance/scaling issues. Bugs are identified and fixed as you uncover them.
Effective software engineering with AI
Instead of focussing on the details. Focus on the fast iteration loop. While development is part of it, LLMs are quite good at development. What this means is your focus as an engineering team should be on proactively finding user feedback, product gaps, architectural flaws, and bugs.
Faster iteration loop
- Simulate users with LLM: Even before you get real human users on any new feature, you should constantly hammer the system and ask LLM to judge the usability of the software. You can build a custom script (e.g. using playwright for web apps) to simulate user behavior. Also, Claude browser extension with a suitable prompt is a great tool for this.
- Proactive product analytics: After you put the product in the hands of real users. Don’t rely on a user to reachout or provide explicit feedback, actively watch for usage patterns and gaps. Additionally, human touch and relationship/community building is always indispensable for a high quality feedback loop (and AI can’t do this well). One more tip here is that Instrumenting all parts of the software is typically hard and not complete. A preferable approach is to just log everything as a raw stream of event and then use a modern analytics tool (At Wisdom.ai I work on exactly this problem, reach out if you need help :-)).
- Loads of testing: Generate tests both using the usage patterns that you learn from the above two exercises, additionally let LLM write blackbox tests as a developer would. There a bunch of new QA tools which can help here, e.g. momentic.ai.
Faster development/deployment loops
- Custom development agents: Vanilla agents (e.g. claude code) are still handicapped since they have no access to our learnings, internal tools or systems mostly because we don’t trust them. Build internal custom coding agents which have all the context you come across, access to internal tools and systems in a safe way. Here is a great write up which collects examples from various companies that have built this.
- Code review agents: Using Codex review or Claude review to find bugs/gaps in the code is a great tool. We have found anecdotally reviewing code using an alternate LLM (e.g. Claude when Codex wrote it or vice verse) offers better results.
- Dev/Test tools: Last but not the least. Investment deployment and testing toolkit for your developers and agents is more urgent than ever since this often ends up being a bottleneck in this new world. Think about assigning a few engineers on this problem fulltime.