Operational Tier Architecture
Permission Based Engineering for the Agent Era Software has always evolved by moving into higher layers of abstraction. From punch cards to assembly, compiled languages, frameworks, and declarative UI, every new abstrac
Permission Based Engineering for the Agent Era
Software has always evolved by moving into higher layers of abstraction. From punch cards to assembly, compiled languages, frameworks, and declarative UI, every new abstraction makes the layer below cheaper to produce. And every time that happens, we adapt our architecture to fit that change.
Even programming fundamentals are not safe from this shift, but one thing that stays consistent in a good architecture is proper labeling and categorizing.
Good architecture has always been about managing complexity while yelding clarity and velocity.
Whether it's interfaces, inheritance, grouping code into functions, microservices, modularization, domain driven design, or functional programming, these are all, at a high level, ways of labeling and categorizing software.
Code Is Cheap. Maintaining It Isn't.
Today, code generation is cheap. But we've seen this cycle before, albeit never at this pace or scale.
What once took months or years to implement eventually became the foundation of another abstraction. Software takes less and less time to produce, but code without structure has always been expensive to maintain.
But If agents can generate, refactor, and rewrite code in minutes, why should we care so much about how that code is organized?
Because agents are much better at consistently following explicit instructions. They still make mistakes, but they don't get tired of applying the same rules across hundreds of changes.
In the past, no matter how good our architecture guidelines were, we rarely got the most out of them.
Humans are lazy. Programmers, even more so.
A significant portion of any codebase eventually drifts away from its intended architecture and it doesn't matter how many lint rules you add, how many documents you write, or how many meetings you sit through to refine and redefine the architecture.
Someone will eventually take a shortcut.
Today, if those instructions are clear, concise, and enforceable, we have an opportunity to achieve a level of architectural consistency that was difficult to maintain with humans alone.
But that opportunity comes with a few headaches.
The New Engineering Bottlenecks
PR Reviews
I've seen senior engineers spend what feels like half their week buried in PR queues.
Sometimes those PRs contain nothing more than a simple UI tweak. Yet they get treated with the same level of caution as a payment gateway update.
We have an enormous influx of PRs, and the review process has become one of the biggest bottlenecks in software engineering.
And no, despite how often I've seen it happen, rubber stamping isn't the solution.
Context Confusion
We're expecting AI to navigate codebases containing millions of lines of code and somehow understand which parts are relevant to the task.
It doesn't matter how capable your LLM of choice is or how much context it can process at once. Large codebases are notorious for providing conflicting information.
Agents read old scratchpads, get distracted by irrelevant files, and sometimes generate code against obsolete logic.
How many times have you seen an agent build its own UI component, completely ignoring your design system because it never picked up on it?
Or use a deprecated feature instead of the upgraded implementation?
You could argue this isn't a new problem. Humans struggled with the same thing.
And you'd be right.
But not at this scale.
Humans had a way of solving context confusion. They took their sweet time.
Meetings, technical reviews, design reviews, PRDs, and weeklong conversations with opinionated engineers who had years of institutional knowledge.
And somehow, through enough conversations, that context made its way into the implementation.
It took ages, and thankfully we're moving away from some of that.
But it did serve a purpose. It helped resolve ambiguity.
We no longer have the luxury of relying on that process for every change, especially when agents are producing code faster than humans can review it.
And God help you if your codebase has two features with similar names.
I have a good example of this from my time at Square.
We owned a platform feature called Add Ons. It also had a web based integration that allowed third party developers to build their own add ons and connect them to our platform.
These integrations could call our APIs, add items or rewards to a cart, complete payments, and interact with external services.
As the platform gained traction and more sellers and partners became interested, we decided to build a more capable native solution.
And what did we call it?
"3P Add On Integrations".
At the time, AI was mostly a chat interface, so naming collisions weren't something we were particularly concerned about.
Fast forward to agents navigating the codebase, and they started mixing the two implementations like peanut butter and jelly.
You'd ask an agent to debug an issue in the new solution, and it might point you to the old web implementation. Sometimes it would combine logic from both because the names were similar enough to confuse them.
Anthropic's research on effective context engineering for AI agents [https://www.anthropic.com/engineering/effective-context-engineering-for-ai-agents] discusses how larger context windows can introduce noise and reduce a model's ability to focus on relevant information.
The goal is to give agents the right context with little noise.
We do have a tool that helps thats called skills, but we often use it incorrectly, when agents get confused, we respond by creating another custom skill.
Massive prompt files, bloated instructions, and knowledge bases filled with raw data that somebody compiled using AI and never bothered to read.
Skills should follow the same discipline as a well designed function or microservice. A clear responsibility, a narrow scope, and a predictable purpose.
Otherwise, we're just adding more noise to an already confused agent.
Slop
We hear this buzzword a lot nowdays.
When we allow agents to touch everything without guardrails, we slowly weaken our moat.
The codebase starts drifting away from its original architecture until we're left maintaining what is essentially a fully vibe coded app.
The Operational Tier Architecture
My proposal is what I'm calling the Operational Tier Architecture.
The idea is to organize the codebase into a permission based system, where the way we arrange software also determines how agents interact with it.
None of the underlying concepts are entirely new. We've had code ownership, modular boundaries, access control, and policy based approvals for years.
What I'm proposing is bringing these concepts together at the architectural level, where the structure of the codebase determines not only how software is organized, but also what context an agent receives, what it can modify, and how those modifications are governed.
The software module becomes the common unit for agent context, execution authority, and change approval.
This gives us two mechanisms built around the same architectural structure.
Context routing:
Organize modules so agents can start with a narrow, relevant context and expand only when necessary.
Permission enforcement:
Use deterministic CI checks to validate which modules were modified and whether those changes are permitted under the defined operational tiers.
Each module and submodule has an explicit owner, and a defined operational tier.
The modules are organized into categories based on the team's structure, the business domain, and the responsibilities that matter to the project.
I'm deliberately keeping this high level because I don't think there's a universal blueprint that works for every company.
The idea is to establish guiding principles, tailor them to each team, and benchmark what works.
Structure and Instructions
At the root of the project, we'd have an "agents.yaml" file.
This would be a central, maintained source of truth, containing project specific instructions, architectural guidelines, and the definitions of the operational tiers.
Individual modules may have their own "agents.yaml", but those files should be extremely short. One paragraph at most. Preferably just a few declarative rules, or nothing at all.
For example, a security module might have an instruction that requires a particular validation script whenever its implementation changes.
What we don't want is lengthy instructions scattered across the codebase, becoming outdated, conflicting with one another, and creating the very context confusion we're trying to avoid.
This principle is also reflected in OpenAI's custom code review rules for Codex [https://developers.openai.com/blog/custom-code-review-rules-for-codex], which recommends keeping repository guidance at the root and placing concise, specific rules in the directories where they apply.
Ideally, most modules wouldn't need additional instructions. Their structure, ownership, and operational tier should communicate most of what an agent needs to know.
Organizing Modules by Responsibility
Let's say we have a checkout feature.
Instead of treating checkout as one large area of code, or distributing its responsibilities across packages organized primarily around data flow, we organize it into submodules based on responsibility.
We might have "features/checkout/ui" for presentation, "features/checkout/contracts" for schemas, protocols, and event definitions, and "features/checkout/domain" for business logic.
Other submodules could cover documentation, content, security, testing, and analytics.
These are just examples. The actual categories should reflect how a team operates and what matters to the business.
The important part is that related code is easy to locate, and each module has a clear responsibility.
If an agent needs to modify the checkout UI, it should find the relevant components in one place, rather than searching through unrelated packages to piece together how the feature works.
This also makes existing components easier to discover and reuse instead of generating new implementations unnecessarily.
In the example of the UI, we get a benefit of naturally encouraging declarative UI, because it forces us to keep it as dumb as possible, pasing simple stable state and hoisting events out.
More importantly, it creates clear boundaries for agents and saves precious tokens!!!
An agent working on a UI change shouldn't normally need to understand how payment authorization works. And an agent changing telemetry shouldn't need to explore the entire checkout implementation.
That doesn't mean we prevent agents from reading other modules. But, we organize the codebase so they don't have to unless the task requires it.
The less irrelevant context an agent has to navigate, the less opportunity there is for outdated implementations, unrelated logic, and conflicting patterns to influence its work.
Operational Tiers
Now we assign operational tiers to those modules.
The tiers define the baseline level of risk and the authority required to approve changes.
For example, we could have:
Tier 0: Autonomous
This is the lowest risk tier.
Depending on the project, it could include documentation, content, certain UI changes, or telemetry.
If an agent is updating a simple telemetry event, and the change passes the required checks, why should a senior engineer have to review and approve it?
In this tier, an agent could implement the change and have the PR approved and merged automatically, provided all the required validations pass.
That doesn't mean bypassing engineering safeguards.
UI tests, screenshot tests, integration tests, AI code reviews, security checks, and other applicable CI validations still run as usual.
Tier 1: Team Approved
This might include testing, support tooling, or other changes that require some oversight without involving the owners of more sensitive systems.
A QA engineer or another designated team could approve these changes.
Tier 2: Governed
This tier could include domain logic, contracts, and changes that affect behavior across multiple modules.
These changes would require approval from the relevant engineering owners.
Note:
Risk isn't always determined by the module alone. The tiers should be treated as baseline classifications, with room to escalate when the scope or consequences of a change require it.
Approval routing based on code ownership isn't a new invention.
We have similar concepts in conventional engineering workflows. GitHub CODEOWNERS [https://docs.github.com/en/repositories/managing-your-repositorys-settings-and-features/customizing-your-repository/about-code-owners], for example, allows teams to assign ownership and require approvals based on the files being modified.
What changes here is that we're combining that mechanism with context organization and agent autonomy.
Context Is Also Part of the Architecture
An operational tier determines the authority required to modify and approve a module. But the module's organization also helps determine what context the agent starts with.
Let's go back to the checkout example.
A designer, engineer, or agent needs to change the color of a checkout button.
Why should that agent read the entire checkout implementation?
It should start with the relevant UI module, the contracts it depends on, and the design system.
It doesn't need the payment logic, the user database, or unrelated network and security implementations.
When everything needed for a particular responsibility is grouped together, the agent can begin with a much smaller set of files and expand outward if necessary.
Permission Enforcement
When an agent opens a PR, the pipeline can inspect which modules have changed, determine their operational tiers, and apply the appropriate validation and approval requirements.
For a Tier 0 change, that might mean running the required tests, AI PR reviews, and security checks before allowing an automatic merge.
For a Tier 2 or Tier 3 change, those checks would still run, but additional human approval would be required.
You seriously think I'll read all of that?
- Maybe not, and maybe neither should your agent have to.
That's partly the problem I'm trying to solve. We've spent years teaching agents how to navigate our codebases by giving them more instructions, more context, and more things to read.
As models improve, we're beginning to see diminishing returns from some of the techniques that made earlier agents useful.
Custom instructions, elaborate prompt structures, and other workarounds that once improved performance can become barriers as the underlying models get more capable.
We say we want to be model agnostic, and in theory, we are, but in practice, many codebases have accumulated instructions and conventions designed around the limitations of older models.
We continue carrying those constraints forward, even when the newer models no longer need them.
Then we pay top dollar for more capable models and wonder why they're performing the same as, or sometimes worse than, the previous generation.
It's like driving a Lamborghini through New York traffic.
It may no longer matter as much how much boilerplate we generate. Even some of our older conventions around class encapsulation and code organization may become less important as code generation gets cheaper.
But the way we separate responsibilities, define boundaries, and govern changes becomes increasingly important.
This is a concept that needs iteration, experimentation, and benchmarks. Different teams will organize their modules differently, assign different risk levels, and establish different approval requirements.
The goal is to make the most of increasingly capable models while keeping our codebases safe, consistent, and maintainable.
Humans are lazy. Programmers, even more so.
And now that agents write the code, we finally have more time to argue about the architecture.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes β full credit and traffic to the original publisher.