StrideLabs/Writing/What bytecode can tell an agent that grep can't
What bytecode can tell an agent that grep can't
The story behind Codelens: watching an agent unzip jars to read dependency source, why a language server was not the first answer, and what a persistent bytecode index might give a coding agent that grep can't.
The moment I knew I wanted Codelens, I was watching Claude Code try to read the source of a dependency. It dug into my Gradle cache, found the jar, and unzipped it, just to see how a library method worked. It got there, but it was slow and a little absurd, the kind of thing an IDE answers instantly. There had to be a better way to give an agent some of the structural view of a JVM project I get for free in IntelliJ.
Not a language server
My first instinct was a language server. Kotlin and Java both have one. In practice that path was less useful and more awkward than I’d hoped, and wrapping one in an MCP server or a CLI for an agent to call felt like fighting the design. Whatever I tried, I kept hitting the same wall: latency. Running analysis fresh for every question was too slow to be pleasant in a loop.
That’s the shape the tool took. A small Go CLI talks to a persistent Kotlin server that stays resident for the project, so the expensive work (scanning bytecode, resolving the classpath) happens once and every later question is cheap. The server uses the Gradle Tooling API, the same machinery IDEs lean on, to run the project’s own Gradle and read the classpath the build actually produced, which is also what lets it track across Gradle and JDK versions instead of guessing.
What bytecode adds that grep doesn’t
Consider “what implements PaymentHandler?” In source, an implementer might be a class three modules away that says implements PaymentHandler, easy. But it might also implement a sub-interface, or extend a base class that implements it, or live in a dependency you only have as a jar. Grep sees text; the compiler resolved relationships and wrote a lot of that structure into class files.
Same story for calls. A method foo() that calls bar() is obvious in source until bar is reached through an interface, a lambda, an overload resolved by argument types, or a method on a type that’s only present transitively on the classpath. Source-only tooling has to approximate from text; bytecode gives the tool a resolved view to work from.
The load-bearing word is resolved: Codelens reads the classpath the build actually produced, so xref finds references that cross module and library boundaries instead of stopping at the edge of your own source tree.
codelens xref com.example.PaymentHandler # everything that references the type
codelens calls com.example.Checkout#submit # method calls from compiled bytecode
codelens classes list --package "com.example.*" --json
Every command speaks --json, which matters more than it sounds. The consumer of these answers is usually an agent, not a human reading a table, and structured output is what lets it chain xref into calls into a hierarchy query without re-parsing prose.
Framework knowledge stays in skills
There’s a temptation to bake framework smarts straight into a tool like this: special cases for Spring, for Ratpack, for whatever you’re migrating off. I deliberately didn’t. The binary ships general primitives only; framework-specific analysis lives in installable agent skills that compose them. The migration that started all this (moving a Ratpack service to Spring Boot, for fewer CVEs and a healthier support story) is a skill, not a flag. The skills are still early: an agent can often work out the CLI on its own, but the skill is what lets it reach for these lookups in conversation without me spelling it out. Keeping framework knowledge out of the binary keeps the core small and lets that knowledge evolve on its own cadence, installed into whichever agent you already use.
Who it’s for
Not me, directly: when I’m reading code myself I open IntelliJ. Codelens is for the agent working the repo when I’m not. That’s also the honest caveat: it’s early, and agent harnesses are starting to ship language-server support of their own. Whether a persistent bytecode index gives an agent something better is the open question Codelens exists to answer. The first time xref surfaces a caller living somewhere you would not have grepped, though, the idea feels worth exploring.