AI Engineer Europe 2026
The Friction Is Your Judgment
Read the talk
The Friction Is Your Judgment
Agents can produce code faster than teams can understand it. Explicit boundaries, mechanical checks, and focused human review help preserve judgment where speed creates risk.
From a talk by Armin Ronacher and Cristina Poncela Cubeiro
Before you start: Familiarity with pull requests, configuration files, and basic application architecture will help; the TypeScript example requires no agent framework knowledge.
Shipping without friction
A forum post about a security incident acquired an unfortunate social preview. An accidentally deployed configuration change had caused the problem; beneath the incident headline, the company’s marketing image promised: “Ship without friction.” The juxtaposition exposes a practical question for software teams: which obstacles should disappear, and which ones make us stop before a consequential mistake?
Armin Ronacher approaches that question after two decades in software, much of it in open source, including creating Flask. He jokes that the Python framework is so familiar to models that their output now introduces people to it. After leaving Sentry, he had time to explore Claude Code, write about agentic engineering, and hear from other developers experimenting with it. He then started Earendil with a friend in October.
Cristina Poncela Cubeiro, his colleague at Earendil and previously an engineer at Bending Spoons, comes from the other direction. AI tools predate her software engineering career: they helped her learn the work as well as perform it. Together, their experience building with and on agents has brought substantial gains and disappointments. They present an evolving practice, not a settled solution. Two problems recur: psychologically, why is it so difficult to stop prompting and think; technically, why do agents improve some people’s code and worsen others’? Even the prospect of perfect generated code leaves a question about how much judgment humans would still need to exercise.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
When saved time becomes expected output
The shift begins pleasantly. After enough unsuccessful prompts, something clicks: the tools become useful, work becomes more enjoyable, and tasks leave extra time. As adoption spreads, however, that advantage becomes a baseline expectation. Everyone is assumed to have the tools, so everyone is expected to ship faster. The time saved is consumed by additional production, leaving less room for review and deliberation.
Poncela Cubeiro describes two parts of the resulting trap. First, the next prompt has an uncertain payoff: it might finish a feature or add the change that destabilizes the product. That uncertainty encourages another attempt. Second, the volume of visible output makes activity feel like efficiency. The stream of changes crowds out the question of whether this is the right implementation—or even the right work. Once the loop is running, stopping becomes difficult for the person, while the agent may keep exploring files it never needed to read. The human has to retain the agency to interrupt the loop.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Production grows faster than responsibility
At team scale, the same shift changes the balance between creating code and reviewing it. When code creation was the constraint, writing and review capacity were more closely matched. Agents amplify each engineer’s production without an equivalent increase in the engineer’s ability to inspect and understand changes. Pull requests accumulate. At the same time, more people participate: marketing staff can submit code, and CEOs who once worked as engineers can return to coding.
The responsibility does not expand as readily. Engineering teams still have to maintain and operate the result, including changes produced by people whose primary roles lie elsewhere. Machines cannot take responsibility for those changes. When the number of producers outgrows the people able to carry that responsibility, reviews are skipped or rubber-stamped. Small pull requests remain desirable, but the amplification pushes in the opposite direction. Ronacher uses a hypothetical 5,000-line pull request to illustrate the mismatch: it demands careful judgment precisely when its size makes disengagement tempting.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Runnable code can conceal an invalid system
When generating code feels cheap, large changes become easy to request. Ronacher connects the resulting behavior to agents’ optimization for immediate progress: write code, run tests, and unblock the task. A configuration reader illustrates the danger. If reading the required file fails, an agent may add defaults so the program can continue. That looks like recovery locally, but the application may now be operating with settings nobody intended.
Consider a TypeScript configuration loader for a required database URL. Catching a failed read and returning a default URL would let subsequent writes proceed against an unintended configuration. A loader that preserves the failure instead makes the missing or invalid configuration visible before those writes begin:
typescript
import { readFile } from "node:fs/promises";
type AppConfig = {
databaseUrl: string;
};
export async function loadRequiredAppConfig(
path: string,
): Promise<AppConfig> {
const parsed: unknown = JSON.parse(await readFile(path, "utf8"));
if (
typeof parsed !== "object" ||
parsed === null ||
!("databaseUrl" in parsed) ||
typeof parsed.databaseUrl !== "string" ||
parsed.databaseUrl.trim() === ""
) {
throw new Error("Configuration requires a nonempty databaseUrl");
}
return { databaseUrl: parsed.databaseUrl };
}
In Ronacher’s illustrative scenario, the configuration mistake is discovered two hours later, after incorrect database records have already been written. The problem is not merely that a read failed; it is that a recovery path hid the failure and allowed consequential work to continue.
In his experience, these attempts to recover from local failures can produce brittle services. The codebase then grows beyond what the agent can navigate reliably: it misses relevant files and creates a new implementation of something that already exists elsewhere. Each local workaround adds complexity that makes the next task harder. A human engineer may feel discomfort while adding this sort of code; an agent does not provide an equivalent emotional brake. That concern has to enter the process through people and explicit constraints.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Libraries expose a smaller problem
Poncela Cubeiro reports an encouraging correlation: agents tend to do better in libraries than in products. A library usually has a defined problem, features that map onto an API surface, and constraints that bound the work. Its core is often simple enough to understand and extend. Products combine more interacting concerns.
| Concern | Library | Product |
|---|---|---|
| Scope | Defined problem | Interacting requirements |
| Surface | Constrained API | UI, API, permissions, billing |
| Structure | Simple, extensible core | Coupled components and states |
| Agent reasoning | More locally bounded | Requires broader context |
For example, a product’s UI and API responses may depend on permissions, feature flags, and billing state. Those relationships can exceed what the agent can hold in context. An edit can be reasonable in the file being changed and incoherent across the application. The design response is to treat the codebase as infrastructure for the agent: its structure determines how much of the system the agent can see and reason about while working.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Make the flow between components explicit
Modular components help an agent add a feature in one place without disturbing everything else. But module boundaries alone are insufficient: the execution flow also needs clear stages. In an assistant refactor, Poncela Cubeiro identifies three main steps:
- Receive the user’s message.
- Pass the message to the agent loop.
- Handle the output.
These landmarks were clear to her, and the code at each landmark was relatively orderly.
The trouble accumulated between the stages. Agents introduced conversions between types and added information to state that did not belong there. These additions quietly expanded the set of behaviors the application supported, including unexpected and potentially dangerous ones. Making the transitions explicit matters as much as naming the major components: otherwise, the connecting code becomes a place to accommodate conditions that the design never intended to allow.
The rest of the approach follows familiar engineering principles. Use established patterns that align with what models have learned. Keep the core simple and put necessary complexity in explicit layers. Avoid abstractions that conceal intent the agent needs to respect. Poncela Cubeiro cites React Server Actions and ORMs, compared with raw SQL, as examples of where that concealment can occur. This is her argument for inspectable behavior, not a universal prohibition on those tools: an abstraction becomes a problem when it hides the relationship the agent must understand to change the system correctly.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Turn design constraints into mechanical checks
At Earendil, much of this discipline is enforced with linting rules. The first example is a ban on bare catch-alls. There is a small irony in the presentation: the agent had already found and edited the catch-all intended for the demonstration. The larger point is to make undesirable patterns detectable without relying on someone remembering every convention during review.
- One SQL query interface. Keep query access in one place so an agent does not have to hunt across the repository and risk missing a site that also needs to change.
- One UI primitives library. Avoid raw input elements outside the shared primitives so styling and behavior stay consistent.
- No dynamic imports. Keep this form of runtime indirection out of their codebase.
- Unique function names. Make a search identify the intended function unambiguously. If an agent’s grep returns one relevant result, it spends less context on disambiguation and can continue its work more directly.
They are also exploring TypeScript’s erasableSyntaxOnly option. It restricts TypeScript constructs that need runtime transformation, such as enums and parameter properties, favoring JavaScript with erasable type annotations. That does not itself remove every build or transpilation step. The attraction is a closer relationship between the source the agent reads and the behavior being debugged, with less transformation-related indirection when locating an error.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Separate repairable defects from human decisions
Mechanical checks improve what the agent produces, but a person still needs a signal that restores attention at consequential moments. Ronacher’s team built a review extension for Pi around that distinction. Mechanical bugs and clear violations of AGENTS.md belong in feedback the agent can act on. Other changes need an explicit human call-out because understanding their consequences requires judgment beyond the patch.
- Database migrations. A migration’s operational risk depends on locks and the amount of data in production. Ronacher wants a human judgment call before it goes in.
- Permission changes. The intended authorization rules may be under-documented. A plausible implementation is not enough to establish that the new access is appropriate.
An agent can help identify these changes even when it cannot own the decision. The call-out makes the person notice that this is the point to engage.
In the demonstrated interface, actionable findings appear above a separate group of human call-outs. Selecting the action to fix issues would send the first two categories—mechanical bugs and instruction violations—back to the agent. It would not resolve the human decisions below. The linked extension is a mutable implementation, and its current human call-outs are non-blocking attention signals, not an enforced approval gate. A new dependency, for example, raises questions about whether it belongs in the codebase and whether its maintainers are people the team wants to trust. Fast generation does not answer either question.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Use speed where it creates a useful starting point
Issue reproduction is one of Ronacher’s preferred uses of agents. A customer report can become a concrete reproduction case, giving an engineer a much better starting point. Exploring product directions is another useful application, provided the team accepts responsibility for the generated code. His assessment is less favorable for system architecture and reliability, where interactions and consequences matter more than producing a first working version.
Ronacher warns that months of technical debt can accumulate in weeks or days; this is an experiential warning, not a measured production rate. As the mess grows, understanding falls, and honest assessment becomes harder both technically and psychologically. He describes finding code that failed in production, then realizing that he had committed it himself with an agent’s help. The disappointment came with recognizing that the responsibility was still his. Slowing down is necessary to see the actual state of the codebase, including mistakes hidden by the satisfaction of having shipped.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Keep the friction that lets you steer
Some shipping friction is merely annoying and should be removed. Other friction is deliberately designed to make a team consider whether a service is reliable enough, how critical it is, and whether enough people are available to operate it. Ronacher points to service-level objectives, or SLOs, as an established example. More precisely, the operational constraint comes from the policy tied to the objective, such as an error-budget policy. Agent speed does not eliminate those reliability and staffing decisions.
The closing analogy is physical: steering requires friction. Software teams also need points of resistance where experience can affect the direction of travel. A review that prompts a real decision about permissions, a migration, or operational risk is not wasted time simply because the code arrived quickly. Useful friction is where human judgment enters the process.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Resources
From the talk
Official guides, tutorials and API reference for the Flask Python web framework.
Explains the syntax restrictions used to keep TypeScript compatible with type stripping.
Ronacher's review extension separates actionable defects from changes needing human attention, including migrations and permission changes.
Installation and documentation for the extensible coding agent used in the review workflow.
Further reading
- Mario and EarendilArticle
Ronacher discusses Pi, deliberate software design and why quality requires more than faster output.
- Example Error Budget PolicyDocumentation
A concrete policy for slowing feature releases when reliability falls below an agreed objective.
Read the transcript and explore slides
Loading transcript and slides…