AI Workflow/Usage in Kairo
Kairo is being built by a small core team of university students. I (Dhruvan) do most of the compiler and language design work, so the AI usage described here is mine. I want to be upfront about where and how AI is used in this project.
This was originally a snapshot as of May 2026. My usage has changed since then, so this post was updated in October 2026. See Integration Work in the Compiler, Tests, Audits, and Git Commit Messages for what changed and why.
Why I'm Writing This
I know the systems programming community has a lot of opinions about AI in development. I'd rather be straightforward about exactly where it's used than have someone find out later and wonder what else I didn't tell them.
What Is Not AI-Assisted
To be clear: the language design, compiler architecture (Stage 0 and Stage 1), memory model, type system, ABI decisions, codegen pipeline, and build system are all designed by me. The core logic is written by hand. The one exception is mechanical integration work in Stage 1, explained below.
Compiler
The compiler is hand-written. The lexer, parser, AST, arena allocator, diagnostics engine, preprocessor, and codegen pipeline were all written by hand. Stage 0 contains no AI-generated code.
The original version of this post said the same was true for Stage 1. That's no longer accurate. AI is now used in Stage 1, but not the way most people would assume. It doesn't design or write compiler logic; it does plumbing. Details in the next section.
I don't use inline code completion for compiler code. Kairo is a new language, completion models are bad at predicting it, and the code I'm writing is novel logic, not boilerplate. There's nothing for autocomplete to autocomplete.
Integration Work in the Compiler (October 2026 Update)
The Stage 1 compiler has passed 130,000 lines of Kairo, and with two people maintaining it, sweeping changes have become a real problem.
A concrete example: say primitive types are hardcoded in seven different places across the compiler, and I want to pull that logic into a single shared helper. Writing the helper is the easy part, and I still do that myself. The hard part is the integration: finding every call site and every slightly different copy of the same logic, and rewiring all of it to the helper without breaking anything.
In a mature language, that's a few minutes of work. Your editor's LSP gives you find-all-references, go-to-definition, and rename, and the refactor is mostly mechanical. Kairo doesn't have that yet. Stage 1 is compiled by Stage 0, and Stage 0's tooling gives me error squiggles and not much else. I added a form of jump-to-definition, but it guesses rather than actually resolving symbols, and it's right about half the time. Without real tooling, a change that should take minutes takes days of grepping and manually tracing code across 130k lines. That gets old fast.
So that's where Claude Code comes in. The split looks like this:
- I do: the design, the decision to make the change, and the new code itself (the helper, the new abstraction, the actual logic).
- Claude Code does: the plumbing. It finds the places that need to change and rewires them to the new code.
- Then I review everything. Every change it makes is read, checked against what I intended, and tested before it lands. Anything that's wrong gets fixed or thrown out.
It isn't writing the compiler. It's doing the job an LSP and a refactoring tool would normally do, which Kairo doesn't have yet. Once Stage 1 has a proper LSP, I expect most of this to go back to being done by hand.
Tests
Most of the compiler's lit tests are written by AI (Claude Code), under a strict process. The rule behind all of it: I decide what correct behavior is. The AI only expresses that behavior as a test.
- Ask first. Before writing a test, it asks me what the expected behavior is, unless it's 100% sure that's the behavior the compiler is meant to have. "Probably" doesn't count.
- Get the test right. It can iterate on the test itself: write it, watch it fail, read the diagnostic, and fix the test. The compiler is stable enough and the diagnostics are clear enough for that loop to work. This is about getting the Kairo source and the lit/FileCheck structure right, not about changing what the test expects.
- Check it against my criteria. Once the structure is in place, it runs the test through lit. I have strict criteria for what a test should look like, and if anything seems off, it flags it and stops.
- Pass or stop. If the test passes, it moves on to the next one. If it fails, it stops and brings it to me. It never works around the compiler to make a test pass. A failing test is either a compiler bug or a wrong expectation, and deciding which is my call.
That last rule is the important one. The usual way AI-written tests go wrong is by quietly encoding a bug as expected behavior. Stopping on every failure is what prevents that.
Audits
I also use Claude Code, with subagents running in parallel, for codebase-wide audits. For example, checking every CLI flag against what the compiler actually does with it. Every finding points at a file and line, so I can verify it. I review the findings and decide what gets fixed. The audit reports stay internal, but the tests that come out of them (like the CLI tests in Tests/CLI/) are public, and they're written under the same process as above: anything flagged as a bug doesn't get a test that treats the current behavior as correct.
Doing this by hand would take weeks of tracing. With subagents it takes minutes, and my time goes into reviewing the findings instead of finding them.
Debugging
When I hit deep bugs in the compiler, I use Claude as a debugging partner. Stage 0 compiles Kairo through C++, so every LLDB symbol is a mangled C++ name, and reading them is miserable. My process: I walk through the trace in LLDB and try to find and fix the issue myself. If, and only if, I can't, I give Claude the output with the relevant source context. It tells me what commands to run or what state to look at, I run them, feed the results back, and we figure out what's really going on. Then I write the fix.
Recent example: empty source files crashed the compiler. The debugging session traced it to a file.size() - 1 on a zero-length file. The subtraction underflowed a usize into usize(-1), which blew out everything downstream, nulled a buffer, and segfaulted in a completely unrelated file that I thought was isolated. Nothing about the crash pointed back to the underflow. Fixing it took the compiler from crashing to working but slow (a spin-wait), which turned out to be another underflow, this time in SourceLineTable. After both fixes, it went to immediate execution.
The bug itself was trivial. Finding it through mangled symbols and threaded execution was not. AI helped me find it faster. It did not write the fix.
AI is a second pair of eyes on diagnostic output, not a code author.
Doc Comments Within the Compiler Codebase
This is the one place I use GitHub Copilot: drafting doc comments in the compiler source. The code itself is hand-written, but the prose explaining what it does is AI-assisted, and I review it. Honestly, it means far more of the codebase is documented than it would be if I wrote every comment myself, and future-me (and new contributors) will be grateful for that. I plan to replace these with hand-written comments eventually, ideally in Stage 2.
Docs
Every page on kairolang.org started as a raw technical dump: syntax examples, ABI lowering information, generated C++ output, invariants, edge cases. The kind of notes that make perfect sense to me and nobody else.
I handed those dumps to Claude to turn them into something a human other than me can actually read. It structures sections, writes prose transitions, formats tables and code blocks, and smooths out phrasing. I do all the technical writing, review every page for correctness, and change anything that misrepresents the language semantics.
The technical substance (the language itself, how it works, the invariants) is mine. AI helps with the prose. If you see a technical error in the docs, that's on me.
Website
I'm not a web developer. Web dev has never interested me, and I'm not going to pretend otherwise. Astro, CSS, HTML, and JS are not languages I work with. The kairolang.org website was built by Claude. I explained the theme, the layout, what the site should include, and what it's for. The AI wrote the Astro components, CSS, and front-end code. I looked over the output and corrected errors wherever I found them.
The website source is publicly available at github.com/kairolang/kairo-web.
Git Commit Messages
Commit messages are AI-written. The commits are mine; the messages are generated from my diffs.
I originally used GitLens AI for this. I still use it, but I now also use Claude Code, because GitLens gives an overview and Claude Code gives a detailed, exact message: what changed, where, and why. I keep commits small enough to stay bisectable, and the message records enough detail that when someone else is tracing a bug through the history, the log tells them what each commit actually did without having to reverse-engineer the diff. The verbosity is on purpose.
LSP and VS Code Extension (Stage 0)
The Stage 0 compiler is written entirely by hand, but the LSP integration and VS Code extension were AI-assisted. I implemented a pseudo-LSP mode in the compiler: pass it --lsp-mode and it returns diagnostics as JSON. Then I gave Claude the LSP protocol spec, and it built the Python (pygls) server that connects the compiler output to VS Code, plus the extension code to wire it up.
It's held together with tape, but it unblocked Stage 1 development without me spending weeks on the LSP protocol and the VS Code extension API. The Stage 1 LSP server and extension will be written from scratch.
If you have questions, open an issue on GitHub.