Scaling agentic coding past the human bottleneck
Agentic coding today scales only as far as the person approving every step, so each agent you add makes one review queue longer. This post shows how a deterministic Risk Engine, using thresholds the team sets, decides which changes an agent can finish on its own and which need a person, so review time no longer limits how many agents you can run.
Most agentic coding today is driven by a human. A developer points the agent at a task, reviews the diff it produces, and approves each action before it runs. That is not a bad thing, and for high-stakes changes it is exactly what you want. It does have a cost that is easy to miss, though. The human sits in the loop of everything, so the human becomes the bottleneck.

Figure 1: Work from every agent waits in one developer's review queue.
Ask how many parallel agents one engineer can realistically supervise. A handful, before review load cancels out the gains. At that point, the person stops being the reviewer and becomes the queue. Every agent waits on the same pair of eyes, and the throughput of the whole system collapses back to the throughput of one human. We keep adding horsepower to the agents and then bottlenecking them behind a single approver.
So, we asked a different question. What if an agent could pick up work by itself, and a separate, deterministic engine decided which actions it was allowed to take without asking anyone? Not the model deciding it feels safe. A rules engine, configured by the team in advance, answering that question before any action runs.
We built a working demo of this idea and showed it at AWS Cloud & AI Day in Auckland. This post walks through the approach, describes the Risk Engine in enough detail that you could build your own, and covers two ways to wire it up.
The human is the bottleneck, not the agent
What caps agentic development is supervision bandwidth, not model quality. A single developer reviewing agent output can only read so many diffs an hour, and every agent you add competes for that same attention. You scale the fleet and the review queue grows with it.
The fix is not a smarter model that needs less review. It is deciding, up front and mechanically, which work never needed a review in the first place. A one-line copy fix in a well-tested file with no downstream dependencies does not warrant the same scrutiny as a change to your billing logic. Today both usually wait in the same queue behind the same person.
What bounded autonomy looks like
Picture the agent as a junior team member rather than a tool you operate. A good junior engineer knows which work they can take all the way to staging on their own, and which work they should stop and ask about. They fix the small, low-risk bugs, raise the pull request, and move on. They escalate the ambiguous, high-blast-radius changes with a recommendation instead of guessing.
That is the behaviour we wanted. An agent that clears the safe, boring backlog work end to end, and hands the risky or unclear work to a person with context already attached. The team defines where that line sits, and the agent operates inside it. The small bug fixes and minor features that sit in a backlog for months because nobody has time for them become work the team can hand off, and people spend their time on the changes that need a person.
Making autonomy safe with a deterministic engine
Ask an experienced engineer whether a change is risky and they will not guess. They look at what it touches. Is the code well tested, how much else depends on it, how tangled is the logic, how far could a mistake spread, how big is the change. Much of that is a reading of properties the code actually has, and every one of those properties can be measured directly from the repository. The same logic can be written into an engine that reads the change and works out its risk. The engine asks the same questions a good reviewer would, and answers them the same way every time.
A handful of attributes carry most of the signal. The five below are a starting set rather than a universal list. What counts as risk looks different in every organisation, so ideally they are extended to fit yours.
- Test coverage on the affected code. Well-covered code fails loudly when a change breaks it, so the same edit is safer in a tested file than in an untested one.
- Dependencies into and out of the changed modules. Code that many other parts import carries its breakage further than code nothing depends on.
- Complexity of the changed logic. More branches and paths mean more ways for a change to be subtly wrong.
- Blast radius, the set of files in the repository that transitively depend on what changed. The wider that set, the further a mistake can travel. **
- Size of the change in lines touched. A larger diff is a larger surface for error.
Each of these is a fact about the code rather than an opinion about the agent. You can write logic that reads the repository and computes all five for a proposed change, then compares them against thresholds the team has set. Coverage under the line the team draws counts against a change. A blast radius past the number they choose pushes it up a level. Those thresholds belong to the team that owns the code, because only they know how cautious it needs to be. The kind of code a change touches also sets its starting point. The team maps path and filename patterns to categories such as documentation, styling, UI, or core classes, and each category starts at its own tier, so a fix to billing logic begins more cautiously than a documentation edit. The measured attributes raise it from there.
Run a change through that logic and it lands in one bucket, a risk tier, decided by measured attributes and the team's own limits. That logic is what we call the Risk Engine.

Figure 2: How the Risk Engine scores incoming work.
Each risk tier maps to exactly one permitted action, and that action declares the precise set of tool calls it is allowed to make. The defaults look like this, and every threshold is configurable per project.

Figure 3: The default action each risk tier allows the agent to take on its own.
An approval gate checks every tool call the agent attempts against the set its tier authorised, and denies anything outside it by default. A change authorised only to raise a pull request cannot merge one, because the merge call is refused at the gate. If a decision cannot be written to the audit log, the tool does not run. An approval nobody answers becomes a denial rather than a default yes.
Two independent layers have to fail for a bad change to ship. The agent re-checks its own work before writing, and the gate holds the allowlist regardless of what the agent believes.
This is also what defends against a poisoned ticket. A malicious or badly worded ticket can influence what the agent tries to do, but it cannot widen what the agent is allowed to do, because the permitted tool set comes from the risk tier and the policy rather than from anything in the prompt. The worst it can achieve at the boundary is a denied tool call, recorded in the audit log. The gate limits which kinds of action the agent can take, not whether a change inside that scope is benign. A Low-tier change still relies on tests and required checks to catch a subtly harmful edit.
How the Risk Engine works
We have not released the engine's code, so here is the mechanism in enough detail to reproduce. It works rules first. A base level is set by the kind of code a change touches, and the attributes can raise that level but never lower it. Scoring a change runs in four steps.
- Classify the change. Resolve the ticket or diff to the files it touches, and map those paths to a category the project has defined, such as docs, test-only, api-surface, or payments. If no code scope resolves, the change is unscoreable and goes to a human. A project may not configure its way out of that.
- Look up a base tier. A versioned rule table maps each category to a starting tier. This table is data, not code, and it is per project. docs might start at Low, payments at High.
- Measure the attributes from the repository. These are the five attributes introduced above, computed as real static analysis rather than estimated.
- Apply threshold rules that can only raise the tier. Each rule is a small piece of data of the form attribute, comparator, threshold, target tier. The final tier is the highest of the base tier and every rule that fired. Nothing can lower a tier below its base.
Here is how you compute each of the five, and a sample threshold rule for each.
| Attribute | How to measure it | Example rule |
|---|---|---|
| Test coverage | Read the LCOV report for the changed files. A file absent from the report counts as 0, not the project average, so an untested file is a gap rather than a neutral. Coverage measures execution, not assertion quality, so treat it as one signal, not a guarantee | coverage < 0.5 raises to Medium |
| Dependencies | Count imports into and out of the changed modules | dependencies out >= 10 raises to Medium |
| Cyclomatic complexity | Compute complexity over the changed functions | complexity >= 15 raises to High |
| Blast radius | Build an import graph from the repository's own sources and take the transitive reverse-dependency closure of the changed files. The number equals a list of files you can name | blast radius >= 20 raises to High |
| Diff size | Added plus removed lines from the actual diff | size >= 400 raises to Medium |
Thresholds beat weights because a threshold can explain itself. The engine tells you "blast radius 25 >= 20 raised this to High" instead of handing you a number nobody can argue with. Some attributes cannot be measured exactly until the code exists. Diff size is the clear case. Before the change is written the engine estimates it, and a second pass checks that estimate against the real diff once there is one.
The engine scores twice, and the two scores are what stop a small-looking change from growing into an unauthorised one. It scores the ticket before coding as a cheap pre-filter to authorise an initial action, where coverage, dependencies, complexity, and blast radius are measured facts and size is only an estimate. This early score is deliberately provisional, since the ticket often understates the true scope of the change. Then it scores the actual diff once the code exists, where everything is a fact. If a change that scored Low from the ticket turns out to touch more code than the ticket implied and re-scores Medium, the second score catches it. The earlier authority is discarded, and the run follows the newly measured tier, raising a pull request for review rather than merging on a stale estimate.

Figure 4: Risk assessment before and after the change
What we showed at AWS Cloud & AI Day
We built the demo around a custom control plane, a console that showed the list of tickets the agents had picked up and, for each run, a full audit record. The record held the risk tier and the rationale behind it, the stages the run moved through, and every action the agent took along the way. It made the engine's decisions legible at a glance rather than buried in logs.
The demo ran the full loop live. An agent pulled a list of tickets from the backlog, scored each one, and then acted on each according to the action its tier authorised. A low-risk ticket went the whole way on its own, code read, fix written, pull request raised and merged, ticket updated, with nobody touching a keyboard. An ambiguous, high-risk one scored Extra-High, so instead of attempting it the agent stopped and handed it back with a recommendation.

Figure 5: A ticket in the Cloud Day demo, from pickup through scoring, the fix, and the re-score.
The most interesting example we showed was a ticket that scored Low up front, which authorised the agent to fix and merge on its own. Once the fix was written, the post-fix score came back Medium, because the real change touched more than the ticket had implied. The agent did not merge on the Low authority it started with. It raised the pull request and handed it to a human for review, exactly what a Medium tier calls for.
Different ways to build it
The same pattern fits several architectures, and where the engine runs matters less than the rule that it, not the model, decides. Two shapes sit at opposite ends, and the demo used the heavier one.

Figure 6: Two ways to build the pattern: with a standalone control plane, or entirely on Kiro Crew.
- The heavier shape is the custom control plane behind that demo. The control plane consists of an orchestrator which discovers eligible tickets, the Risk Engine, the policy resolver, and the approval gate. Agent execution runs on Kiro through Kiro Crew, which calls back to the gate for every tool call. This shape suits a team that wants durable governance records, a console, and an audit trail that outlive any single agent run and sit outside the agent harness. The cost is that you build and operate that control plane.
- You do not need a separate control plane, though. Kiro Crew can host the whole loop itself. It runs remotely and has a built-in scheduler, so it can spawn an agent to pick up backlog work on a cadence with no external trigger. An orchestrator agent claims a ticket and delegates it to a worker sub-agent, and the Risk Engine sits behind a tool the orchestrator calls before it hands the work down, returning the tier, the authorised action, and the exact tool set the worker may use. Enforcement reuses the tool-approval callback Kiro Crew already exposes. Every tool call the worker attempts is checked against that set before it runs, so the deny-by-default gate lives inside the harness rather than in a separate service.
This shape has no standalone console, but it keeps the audit record. Write the risk tier, the attribute scores behind it, and each tool approval or denial back to the ticket, and the issue tracker the team already uses becomes the audit trail. Apart from Kiro Crew and the Risk Engine, there is nothing new to host.
The Risk Engine is the common thread between the two shapes. In both, it is ordinary deterministic code rather than an agent, and it does the same two jobs. It scores the work before an agent picks it up, and it answers the approval gate each time the agent asks to call a tool. What changes is where it runs. In the first shape it is a service inside the control plane. In the second it is packaged as a tool the orchestrator calls and registered with Kiro Crew's approval callback, running in-process with the harness or as a small local service. Either way, it sits outside the model.
Where your existing controls fit
Many teams already have controls that look adjacent to this, and a question we heard at Cloud Day was how the workflow fits alongside them. It replaces none of them. AGENTS.md steers how the agent writes code, so an autonomous run follows the same conventions as a developer running an agent locally. CODEOWNERS routes the review when a tier calls for a human, the same as it would for a person's pull request. Branch protection and required checks still gate every merge. The Risk Engine adds the one decision none of these make, how much autonomy a given change has earned, then either lets the agent finish it or hands it to the review path you already run.
Where to go next
In this post we showed how bounded autonomy lets coding agents work a backlog on their own while a deterministic Risk Engine, not the model, decides what each change is allowed to do. We described the engine closely enough to reproduce, and covered building it as a standalone control plane or entirely on Kiro Crew, with the engine as a tool the orchestrator calls and the gate hooked into Kiro Crew's tool-approval callback. The pattern generalises well past bug fixing, to any high-volume, mostly-low-risk stream of work where the risky exceptions need a human, including dependency upgrades, documentation drift, flaky-test cleanup, and routine refactors.
If you are exploring agentic development in your own environment, the useful first step is to separate two questions that usually get tangled together. What can the agent attempt, and what is it permitted to complete on its own? Enforce the answer to the second one deterministically and outside the prompt. The estimate of risk can be as smart as you like, but the authority to act cannot live in the model. Do that, and autonomy stops being a leap of faith.
Your own environment may be less straightforward, with many repositories, sometimes more than one issue tracker, and institutional context scattered across wikis, design docs, and team conventions. In a follow-up post we will show how the same bounded-autonomy pattern extends there, with the Risk Engine resolving context across repositories and trackers and an agent given the onboarding guidance a human would need to work safely across them. Keep an eye out.
Have you tried giving agents autonomy over part of your backlog, or decided against it? We would like to hear where you drew the line and why. Let us know in the comments.
Blog authored by: Mohannad Hammadeh (Senior SA at AWS), Daniel Chitralla (Senior SA at AWS), Andre Bezuidenhout (SA at AWS)
Enjoyed reading this content? Let the author know!
Your likes, comments, shares, and saves help creators reach more builders.
Loading recommendations
Loading article