Alex Casquete
(He/Him)
CMDO-Chief Market Development Officer
This month I shared an internal note with the entire Plain Concepts team. I chose the headline “we no longer review code” on purpose so it would get attention. But the headline is the least important part. What matters are the conversations this kind of statement opens, and that we finally said out loud something everyone had seen coming for a year, even if no one was quite sure how it would arrive.
Maybe all the headline did was make visible something that was already distributed among all of us, waiting for someone to say it: a new way of building software has arrived, and it doesn’t look exactly like what we imagined.
Until recently, reviewing a PR meant the usual: someone read the changes, left comments, approved it, and only then did the code reach production. Today, in eight internal applications we share across the company, that step is disappearing. Not because we lowered the bar, but because we raised it somewhere else.
In those applications, a large part of the cycle (reading the issue, writing the code, running the tests, opening the PR) is no longer done by a person, but by an AI agent with its own tools and permissions, within the limits we set. The steps are the same; what has changed is who takes them. And that forces us to rethink where we put human attention.
The decision we made was this: human review stops being a gate before merge and becomes a later layer. A PR is only integrated if all its gates (tests, spec, audit, contracts) are green. The person reviews the result, not every line that produced it.
The reason is not to go faster. The reason is that when a person reads agent-generated changes at the rate agents generate them, that reading stops being a quality signal. It becomes theater.
Robert C. Martin, Uncle Bob, the author of Clean Code, the one who spent twenty years teaching us to read code with a magnifying glass, recently replied to a developer who said he couldn’t let an agent touch code he didn’t understand. His response went viral:
“I started programming in the late 60s. My current strategy is not to read any of the code my agents write. It’s the only way to take advantage of their productivity. What I do instead is surround the agents with extreme constraints. Unit tests, Gherkin tests, QA procedures, quality metrics, mutation testing, test coverage and a lot more. In the end, I have a great deal of confidence in the code they produce because they have had to pass all my constraints and tests.”
— Robert C. Martin, July 23, 2026
He had already said it in April, in a reply that got far less attention: he doesn’t review the agents’ code; he measures coverage, dependency structure, cyclomatic complexity and mutation score, because “humans are slow with code.” The July one, on the other hand, generated a lot of discussion.
Coming from him, it is uncomfortable. But it points exactly at what we were already building: instead of reading the code, design the test cage that code has to pass and let the agent write inside it. If the set of constraints is well built, quality no longer depends on someone looking at every line.
This is a distinction that took me a while to see.
There are two things we call a “cage” and they are not the same. The first is the set of checks a change has to pass before it ships: the tests, the mutation score, the security scans, the spec it was built against. That checks the result. It doesn’t care how good the model is, because it would have to exist even if a person had written the code.
The second is the process we force the agent through to get there: which step comes first, what artifact it has to produce before we let it move to the next, how much of the work is scaffolding so a weak model doesn’t leave the lane. That checks the path. It is what in English is called the agent’s harness: the wrap of tools, permissions, steps and templates that surrounds the model and tells it where it can move. And with every new model version, part of it stops making sense.
The cage that checks the result does not age with the models. The one that checks the path does.
Ours is mostly of the first type, though not entirely: requiring every change to touch a specification is a path constraint, and we keep it because the spec is what the result is checked against. Honestly separating our gates is work we still haven’t finished.
It is tempting to read all this as “less engineering, more automation.” It is exactly the opposite.
Someone has to draw the bounded contexts, decide the invariants (what can never stop being true), write the specification the agent builds against, and design the test suite that has to catch the regression before anyone looks at the code. That is considerably harder than writing the code by hand. It is, in fact, the engineering we used to skip, because there was always a reviewer behind acting as a safety net.
Now the safety net is the system. And that means the system has to be well built, or no one will catch the error. Right now, when we have more engineering capacity than ever, is when we most need engineering discipline.
All of this sounds abstract until you open a cage. So, in the application where we made the decision, the cage is roughly like this.
Every change starts with a written spec. Before a single line is generated, the change has to be described: what it does, what it cannot break, how we will know it works. If it is missing or poorly formed, the build fails. A vague spec does not produce bad code; it produces good code for the wrong problem.
Thousands of tests on every change. About four thousand between the API and the web, and those that touch the database run against a real database. This is what replaces the reviewer’s eye, and it needs that size because it has to catch what a person used to catch by reading.
We break our own code on purpose to see if the tests notice. It is called mutation testing. It is not enough for a line to be covered by a test: we introduce small deliberate errors and check that at least one test fails. A test that runs but never fails is decoration. That way we know the net actually works, which is the only thing that matters when no one reads the code.
Security on every build, without having to ask for it. TruffleHog for leaked secrets, Trivy for known vulnerabilities in dependencies and infrastructure, and Semgrep for insecure patterns in the code. All three block. None of this depends on someone noticing, nor should it.
Every build knows what it contains. Every build generates with Syft a software bill of materials, an SBOM: a complete, machine-readable inventory of every package and component the application contains, with its license. A package with a license we cannot distribute is caught before it reaches a deliverable. Besides, it is the first thing a security team or an auditor asks for, which is why it used to be done by hand, once, the week before the audit.
What is deployed is tested again with the system running. After merge, the code goes to a staging environment and ZAP probes it automatically the way an attacker would, before a person confirms the move to production. The cage does not end at merge.
The engineering that was always postponed is now done. Every team knows the list: restore a backup to prove it works, rotate secrets, review who still has access, check that what is running matches what is documented, keep the runbook up to date. Always important, always the first thing the auditor asks for, always for next quarter. A backup no one has restored is a hypothesis, and an access list no one reviews is a risk. Audit evidence, which used to be a last-minute scramble, becomes a byproduct of the build.
The same cage, as a list you can copy and adapt. Each item blocks the merge.
After merge there is one more before production: ZAP probes the staging deployment the way an attacker would, and only then does a person confirm the step.
None of this arrived all at once and none of it is free. Almost every protection on that list exists because something slipped through, or because we saw how it could slip through. That is the deal, said honestly: you do not stop reading code and then build the cage. You build the cage first, you keep maintaining it, and the day it stops catching things, you go back to reading code. Skipping the first half and keeping only the second is not what I am describing. That is simply not reviewing code.
None of this means letting go of the wheel. There is still a non-negotiable entry gate. No change is born from a loose idea or an improvised prompt, but from a described, analyzed problem with explicit acceptance criteria. That is where we put human judgment: in what to build and why, not in the syntax of how it is built.
And there are red lines the agent does not cross alone: before issuing or rotating a credential or a key, rewriting shared history, or touching production, it stops and asks a person for confirmation.
Responsibility does not move either. No one explains a production incident by saying the agent wrote it. Deciding not to read every line is deciding where it is worth putting attention, not giving up authorship. Whoever integrated the change still answers for it.
It would be easy to end with “and that’s why you need a cage” and leave it there. It would also be a mistake. We built the cage thinking about the models we had at the time, and models improve faster than the tools that contain them. Part of what we put in so an agent would not leave the lane is already surplus, and more will be. Pretending otherwise would be the same theater I complained about at the beginning.
So let’s not lose our heads in either direction. Engineering does not disappear: someone has to write the spec, decide the invariants, and build the checks that tell you whether what came out is what was asked for. That part gains importance as models improve, because it is the only thing left between a very capable model and production.
What gets cheaper is the harness, everything we built to keep the model on the path: the scaffolding, the ceremony, the extra steps because a previous model got lost without them. Each of them is a bar it makes sense to remove the day it stops catching anything, and the sooner that day comes, the better: every bar you remove is effort that returns to the engineering that matters.
A gate earns its place by catching something. If it has gone months without catching anything, it is a cost, not a protection.
The way to manage it is the same way we built the cage: by measuring. Result gates will keep earning their place for a long time. Many of the path ones will not, and that is fine. That is what happens when models are good enough that the bottleneck shifts back to the engineering that surrounds them.
Review has moved; responsibility has not. You can stop reading every diff, but you cannot stop answering for what goes to production. That is why building the cage is real engineering, and deciding which bars to remove as models improve is the part I am least willing to delegate.
Note on AI use: This article was developed mainly by the author, using artificial intelligence tools in a limited way for content structuring. All opinions, analysis, and reflections are the author’s.
Enrique Fernandez Guerra
Director of Engineering Operations
| Cookie | Duration | Description |
|---|---|---|
| __cfduid | 1 year | The cookie is used by cdn services like CloudFare to identify individual clients behind a shared IP address and apply security settings on a per-client basis. It does not correspond to any user ID in the web application and does not store any personally identifiable information. |
| __cfduid | 29 days 23 hours 59 minutes | The cookie is used by cdn services like CloudFare to identify individual clients behind a shared IP address and apply security settings on a per-client basis. It does not correspond to any user ID in the web application and does not store any personally identifiable information. |
| __cfduid | 1 year | The cookie is used by cdn services like CloudFare to identify individual clients behind a shared IP address and apply security settings on a per-client basis. It does not correspond to any user ID in the web application and does not store any personally identifiable information. |
| __cfduid | 29 days 23 hours 59 minutes | The cookie is used by cdn services like CloudFare to identify individual clients behind a shared IP address and apply security settings on a per-client basis. It does not correspond to any user ID in the web application and does not store any personally identifiable information. |
| _ga | 1 year | This cookie is installed by Google Analytics. The cookie is used to calculate visitor, session, campaign data and keep track of site usage for the site's analytics report. The cookies store information anonymously and assign a randomly generated number to identify unique visitors. |
| _ga | 1 year | This cookie is installed by Google Analytics. The cookie is used to calculate visitor, session, campaign data and keep track of site usage for the site's analytics report. The cookies store information anonymously and assign a randomly generated number to identify unique visitors. |
| _ga | 1 year | This cookie is installed by Google Analytics. The cookie is used to calculate visitor, session, campaign data and keep track of site usage for the site's analytics report. The cookies store information anonymously and assign a randomly generated number to identify unique visitors. |
| _ga | 1 year | This cookie is installed by Google Analytics. The cookie is used to calculate visitor, session, campaign data and keep track of site usage for the site's analytics report. The cookies store information anonymously and assign a randomly generated number to identify unique visitors. |
| _gat_UA-326213-2 | 1 year | No description |
| _gat_UA-326213-2 | 1 year | No description |
| _gat_UA-326213-2 | 1 year | No description |
| _gat_UA-326213-2 | 1 year | No description |
| _gid | 1 year | This cookie is installed by Google Analytics. The cookie is used to store information of how visitors use a website and helps in creating an analytics report of how the wbsite is doing. The data collected including the number visitors, the source where they have come from, and the pages viisted in an anonymous form. |
| _gid | 1 year | This cookie is installed by Google Analytics. The cookie is used to store information of how visitors use a website and helps in creating an analytics report of how the wbsite is doing. The data collected including the number visitors, the source where they have come from, and the pages viisted in an anonymous form. |
| _gid | 1 year | This cookie is installed by Google Analytics. The cookie is used to store information of how visitors use a website and helps in creating an analytics report of how the wbsite is doing. The data collected including the number visitors, the source where they have come from, and the pages viisted in an anonymous form. |
| _gid | 1 year | This cookie is installed by Google Analytics. The cookie is used to store information of how visitors use a website and helps in creating an analytics report of how the wbsite is doing. The data collected including the number visitors, the source where they have come from, and the pages viisted in an anonymous form. |
| attributionCookie | session | No description |
| cookielawinfo-checkbox-analytics | 1 year | Set by the GDPR Cookie Consent plugin, this cookie is used to record the user consent for the cookies in the "Analytics" category . |
| cookielawinfo-checkbox-necessary | 1 year | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". |
| cookielawinfo-checkbox-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". |
| cookielawinfo-checkbox-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". |
| cookielawinfo-checkbox-necessary | 1 year | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Necessary". |
| cookielawinfo-checkbox-non-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Non Necessary". |
| cookielawinfo-checkbox-non-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Non Necessary". |
| cookielawinfo-checkbox-non-necessary | 11 months | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Non Necessary". |
| cookielawinfo-checkbox-non-necessary | 1 year | This cookie is set by GDPR Cookie Consent plugin. The cookies is used to store the user consent for the cookies in the category "Non Necessary". |
| cookielawinfo-checkbox-performance | 1 year | Set by the GDPR Cookie Consent plugin, this cookie is used to store the user consent for cookies in the category "Performance". |
| cppro-ft | 1 year | No description |
| cppro-ft | 7 years 1 months 12 days 23 hours 59 minutes | No description |
| cppro-ft | 7 years 1 months 12 days 23 hours 59 minutes | No description |
| cppro-ft | 1 year | No description |
| cppro-ft-style | 1 year | No description |
| cppro-ft-style | 1 year | No description |
| cppro-ft-style | session | No description |
| cppro-ft-style | session | No description |
| cppro-ft-style-temp | 23 hours 59 minutes | No description |
| cppro-ft-style-temp | 23 hours 59 minutes | No description |
| cppro-ft-style-temp | 23 hours 59 minutes | No description |
| cppro-ft-style-temp | 1 year | No description |
| i18n | 10 years | No description available. |
| IE-jwt | 62 years 6 months 9 days 9 hours | No description |
| IE-LANG_CODE | 62 years 6 months 9 days 9 hours | No description |
| IE-set_country | 62 years 6 months 9 days 9 hours | No description |
| JSESSIONID | session | The JSESSIONID cookie is used by New Relic to store a session identifier so that New Relic can monitor session counts for an application. |
| viewed_cookie_policy | 11 months | The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. |
| viewed_cookie_policy | 1 year | The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. |
| viewed_cookie_policy | 1 year | The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. |
| viewed_cookie_policy | 11 months | The cookie is set by the GDPR Cookie Consent plugin and is used to store whether or not user has consented to the use of cookies. It does not store any personal data. |
| VISITOR_INFO1_LIVE | 5 months 27 days | A cookie set by YouTube to measure bandwidth that determines whether the user gets the new or old player interface. |
| wmc | 9 years 11 months 30 days 11 hours 59 minutes | No description |
| Cookie | Duration | Description |
|---|---|---|
| __cf_bm | 30 minutes | This cookie, set by Cloudflare, is used to support Cloudflare Bot Management. |
| sp_landing | 1 day | The sp_landing is set by Spotify to implement audio content from Spotify on the website and also registers information on user interaction related to the audio content. |
| sp_t | 1 year | The sp_t cookie is set by Spotify to implement audio content from Spotify on the website and also registers information on user interaction related to the audio content. |
| Cookie | Duration | Description |
|---|---|---|
| _hjAbsoluteSessionInProgress | 1 year | No description |
| _hjAbsoluteSessionInProgress | 1 year | No description |
| _hjAbsoluteSessionInProgress | 1 year | No description |
| _hjAbsoluteSessionInProgress | 1 year | No description |
| _hjFirstSeen | 29 minutes | No description |
| _hjFirstSeen | 29 minutes | No description |
| _hjFirstSeen | 29 minutes | No description |
| _hjFirstSeen | 1 year | No description |
| _hjid | 11 months 29 days 23 hours 59 minutes | This cookie is set by Hotjar. This cookie is set when the customer first lands on a page with the Hotjar script. It is used to persist the random user ID, unique to that site on the browser. This ensures that behavior in subsequent visits to the same site will be attributed to the same user ID. |
| _hjid | 11 months 29 days 23 hours 59 minutes | This cookie is set by Hotjar. This cookie is set when the customer first lands on a page with the Hotjar script. It is used to persist the random user ID, unique to that site on the browser. This ensures that behavior in subsequent visits to the same site will be attributed to the same user ID. |
| _hjid | 1 year | This cookie is set by Hotjar. This cookie is set when the customer first lands on a page with the Hotjar script. It is used to persist the random user ID, unique to that site on the browser. This ensures that behavior in subsequent visits to the same site will be attributed to the same user ID. |
| _hjid | 1 year | This cookie is set by Hotjar. This cookie is set when the customer first lands on a page with the Hotjar script. It is used to persist the random user ID, unique to that site on the browser. This ensures that behavior in subsequent visits to the same site will be attributed to the same user ID. |
| _hjIncludedInPageviewSample | 1 year | No description |
| _hjIncludedInPageviewSample | 1 year | No description |
| _hjIncludedInPageviewSample | 1 year | No description |
| _hjIncludedInPageviewSample | 1 year | No description |
| _hjSession_1776154 | session | No description |
| _hjSessionUser_1776154 | session | No description |
| _hjTLDTest | 1 year | No description |
| _hjTLDTest | 1 year | No description |
| _hjTLDTest | session | No description |
| _hjTLDTest | session | No description |
| _lfa_test_cookie_stored | past | No description |
| Cookie | Duration | Description |
|---|---|---|
| loglevel | never | No description available. |
| prism_90878714 | 1 month | No description |
| redirectFacebook | 2 minutes | No description |
| YSC | session | YSC cookie is set by Youtube and is used to track the views of embedded videos on Youtube pages. |
| yt-remote-connected-devices | never | YouTube sets this cookie to store the video preferences of the user using embedded YouTube video. |
| yt-remote-device-id | never | YouTube sets this cookie to store the video preferences of the user using embedded YouTube video. |
| yt.innertube::nextId | never | This cookie, set by YouTube, registers a unique ID to store data on what videos from YouTube the user has seen. |
| yt.innertube::requests | never | This cookie, set by YouTube, registers a unique ID to store data on what videos from YouTube the user has seen. |