Picture yourself watching your agent work in a Crystal project. It needs a folder created, so it writes File.mkdir_p("reports/2026"). It reads naturally. It is also not a real method in Crystal; the real one is Dir.mkdir_p.
The agent doesn't know that yet, so it keeps going. It writes the report builder that uses that folder, then the controller that calls the builder, then a test. At the end it runs the build, and the compiler stops on the first line it wrote, four files back. Now it has to unwind its own work, or you do.
The mistake took one line. Where should it have been caught?
Two timelines for one line
Caught at the end: the method is wrong on line one, and nobody finds out until the build. By then three more files assume it works. The fix is small; finding it, and checking everything built on top of it, is not.
Caught at the line: before the agent writes the call, it asks the project what methods really exist. The answer comes back in a moment, and the wrong name never gets written.
That second timeline is what amber-lsp does for Crystal projects. It ships with Amber CLI, and it answers from the project's real code: its own files, every library it depends on, and Crystal's standard library.
What the agent sees
Asked about the real method, it returns the exact signature and where it lives (paths shortened here):
$ amber-lsp lookup 'Dir.mkdir_p'
amber-lsp lookup: found (fresh)
Dir.mkdir_p(path : Path | String, mode : Int32 = 511) : Nil — src/dir.cr:294 [crystal]
Creates a new directory at the given path, including any non-existing
Asked about the one that sounds right, it can compile a tiny test against the project to be sure:
$ amber-lsp lookup 'File.mkdir_p' --verify
amber-lsp lookup: absent (fresh)
amber-lsp lookup: verified absent in 204 ms
That check, which compiles a small test, took a fifth of a second. In the example above, catching it there means the agent never builds three more files on a call that doesn't exist.
In an Amber app, it also checks each Crystal file against the framework's rules right after the agent edits it. Here it is on a controller whose class got the wrong name:
$ amber-lsp --check src/controllers/invoices_controller.cr
amber-lsp: covered 5 diagnostics, 1 error
src/controllers/invoices_controller.cr:1: error: amber/controller-naming: Class 'InvoicesHandler' in controllers/ directory should end with 'Controller'
src/controllers/invoices_controller.cr:1: information: amber/spec-existence: Missing spec file: expected 'spec/controllers/invoices_controller_spec.cr' to exist
src/controllers/invoices_controller.cr:1: information: fsdd/doc-block-required: Class 'InvoicesHandler' is missing a doc comment
src/controllers/invoices_controller.cr:2: information: fsdd/doc-block-required: Method 'index' is missing a doc comment
src/controllers/invoices_controller.cr:2: warning: fsdd/method-type-signature: Method 'index' has incomplete type signature: missing return type
The error is the framework's own naming rule. The rest are rules we build with: a spec file for every controller, a comment on every class and method, a declared return type. All of it reaches the agent in the same turn as the edit, while the file is still open in front of it.
Did looking things up help?
When we made the agent look methods up before calling them, its code compiled on the first try more often. In the 40-task evaluation reported with Amber CLI 2.0.7, Claude Sonnet went from about 6 tasks in 40 failing to compile on the first try to about 1. The smaller Claude Haiku went from about 15 failures to about 7. The details and limits are below.
What happens as the agent works
amber setup:agent connects Claude Code and Codex to the checker in any Crystal project with four hooks:
- When a session starts, the agent learns whether the checker is ready and is told to look methods up before calling them.
- Before a Crystal edit, the edit is refused if the checker is missing, out of date or fails its checksum, and the agent is told to ask you to run
amber setup:agent. - After a Crystal edit, the file is formatted and, in an Amber app, checked against the rules.
- When the agent tries to finish, the project is built and any compiler errors go back to it.
Stopping the edit makes a missing checker visible right away, instead of letting the agent keep writing Crystal without its way to look things up.
Try this today
You don't need Crystal for the habit. The next time your agent is about to use a function or method it hasn't used before in your project, ask it: "Before you call that, show me where it is defined and what it takes." If it can't find it, it was about to guess. Step 7 of the lesson sets up the full checker for a Crystal project and shows the two checks to set up with your language's tools.
The cheapest correction is the one the agent gets while it is still writing the line.
How we tested it
- The tool output: Amber CLI 2.0.7 with
amber-lsp1.0.0, run on November 1, 2026 on two small example projects made for this post. TheFile.mkdir_pstory above is an illustration built around that real lookup, not a recorded session. - The 40-task result: from the Amber CLI 2.0.7 release notes: requiring lookup raised first-try compile rates from 85.0% to 97.5% with Sonnet and from 62.5% to 82.5% with Haiku. The task counts above are those rates applied to 40 tasks. The evaluation's raw data is not published, so treat these as reported figures from our own team.
- Limits: one framework, two models, one task set. The lookup result and the checker example are separate: the evaluation measured looking methods up, not the after-edit checks.
Next in the series: To the builder who is worn out from reviewing their agent's code