Your AI Agent Is Only as Good as Your Types

2 hours ago by David Grudl  

The moment you start coding with an AI agent, you notice something odd: the value of a top-tier IDE like PhpStorm suddenly drops, while the value of solid type checking, say with PHPStan, shoots up. Two years ago that would've sounded like heresy. Today it's closer to practical advice on how to write better code.

The IDE was for humans, analysis is for machines

We loved the IDE for its autocomplete, for the type that pops up on hover, for jump-to-definition and safe refactoring. But all of that is help for the human eye and hand. An agent doesn't read your screen. It doesn't need a tooltip that appears when you hover. It needs information it can read as text and, above all, verify on its own.

And that's exactly what static analysis is: not a hint for a human, but a verdict for a machine. PhpStorm discreetly underlines a spot where you might be passing null. PHPStan writes the same thing to the console as an error that the agent reads, understands and fixes without you sitting there.

It doesn't mean the IDE is dying, it's still a great tool. It's just that where the useful value comes from has shifted. What the editor used to give one person at the keyboard, you now need in a form that a tool running without you can swallow too.

It's all about the loop

The first experience with an agent is usually a letdown, and almost always for one reason:

  • you run it without conventions
  • you run it without an environment

An agent is no wizard that guesses how your code should look. You have to describe it: conventions, style, linter. For Nette we wrote exactly this down into a set of rules for Claude Code, thanks to which the agent generates code just the way Nette does it.

It's also a tool that's only as good as the feedback it gets. And the best feedback is a loop. The agent writes something, runs static analysis and tests, reads the result, fixes the error and tries again. Write, check, fix, repeat. This cycle is called the agentic loop, and it's the moment a text generator turns into someone who actually tunes the code into working shape.

Two sources of feedback are at hand right away. Static analysis and tests. If you don't write tests, that's the very first thing to have the agent generate. Without decent coverage you can't close the loop, the agent has no way to know it broke something.

A head start that's only now paying off

This is where Nette comes in, because it's typed to the bone. And it's not a thing of the past year.

When PHP 7.1 brought scalar and return types, Nette was the very first full-stack framework to adopt them fully, including declare(strict_types=1) in every file. And it kept going: as soon as PHP 7.4 added typed properties, Nette took them on. Whatever the language could express natively, Nette used it, usually before anyone else.

For years this was mainly a matter of quality and clean code. Nice, but a detail only the purists cared about. With the arrival of agents it turned into a competitive advantage. The better the foundation is typed, the less the agent has to guess and the more precisely it generates your code.

What types can do when PHP alone falls short

Native types have a ceiling. PHP can't write “an array of strings”, “a non-empty list” or “the return value depends on the argument”. Yet PHPStan can express exactly these things through PHPDoc annotations, and Nette uses them to the fullest.

The best place to see it is a common pattern: a form hands you data straight as your own DTO class. Just ask for it by name and getValues() returns it fully typed:

$data = $form->getValues(RegistrationData::class);

echo $data->email;   // $data is RegistrationData, fully typed

It works thanks to a conditional return type that says “if I pass you a class name, you return me an instance of that class”. No native type can do this:

/**
 * @template T of object
 * @param class-string<T>|T|'array'|true|null  $returnType
 * @return ($returnType is class-string<T>|T ? T : ($returnType is 'array'|true ? mixed[] : ArrayHash<mixed>))
 */
public function getValues(string|object|bool|null $returnType = null): object|array

So static analysis knows that $data is RegistrationData, and so does the agent. It doesn't have to guess, it reads it straight from the type, and when you write $data->emial, it gets an error before the code even runs.

Across the framework you'll find array shapes like array{absolute: bool, path: string, signal: bool, ...}, plus class-string<T>, positive-int, non-empty-string or @param-out. There are dozens upon dozens of places where the type carries more information than bare PHP could hold.

The result is an API that documents itself. And that's exactly what the agent needs inside the loop.

New: all of Nette passes PHPStan at level 8

Now the main thing. With the latest versions we've taken typing all the way. All Nette packages are now fitted with exhaustive type annotations, and every single one passes static analysis at full level 8. Not just parts, not with a pile of exceptions, but the whole framework.

The essential takeaway for you: you build on a foundation typed as rigorously as makes sense. And on a well-typed foundation it's easy to build a well-typed application. Your own types have something to lean on, and an agent building on Nette has firm ground underfoot.

And why “only” level 8, when PHPStan has eleven levels (0 to 10)? Because everything above it is just about strict handling of the mixed type, which a framework legitimately works with (configuration, the DI container, arbitrary callbacks), so level 8 is the highest tier that still makes practical sense here.

And typing doesn't stop at the framework's edge. Nette also ships a PHPStan extension that teaches the analyzer Nette specifics: it recognizes $this['menu'] as your MenuControl, infers form control types from addText() or addSelect(), understands properties with #[Inject]. So static analysis sees into your code too, not just the framework's, and the agent in the loop has something to read.

Code is no longer read by humans alone

The IDE won't vanish, it's still a great tool for humans. But the center of gravity has shifted. The value we used to expect from the editor is now carried by types and tests, because a machine reads them too. Nette has been accumulating it for years, back when it still looked like a detail.

If you're tempted to get into coding with agents properly, it's worth learning to do it right. And then just close the agent into the loop and let it work.

David Grudl An artificial intelligence and web technology specialist, creator of the Nette Framework and other popular open-source projects. He writes for Uměligence, phpFashion, and La Trine blogs. He conducts AI training workshops and hosts the Tech Guys show. He's passionate about making artificial intelligence accessible through clear, practical explanations. Creative and pragmatic, he has a keen eye for real-world technology applications.