I’ve been experimenting with taking AI coding agents beyond the IDE. The project is still in development, but the workflow is fairly simple.
A Jira card moves into a specific status. An agent notices it and picks up the task. A worker agent checks out the code, understands the change, makes the required modifications and runs the tests. Another agent prepares the pull request. A human reviews the result and decides whether to approve it.
On the surface, this looks reassuringly familiar. There is still a ticket. There is still source control. There are still tests. There is still a pull request. And, importantly, there is still a human approval before the change gets through.
But while building it, I found myself thinking less about what the agent could do and more about what it should be allowed to do.
- If the agent only needs to change one module, why can it modify the entire repository?
- If it needs a credential for five minutes, why should it keep that credential for the lifetime of the worker?
- If it finds two possible interpretations of a requirement, should it choose one or stop and ask me?
- And if I approve the pull request at the end, how much do I actually know about everything the agent did before that pull request existed?
That led me to a broader conclusion.
Our SDLC was designed around a largely human actor. AI agents change that assumption.
The agent is no longer just suggesting code
We are already moving beyond AI that waits for a developer to ask a question and returns a code snippet.
Coding agents can inspect repositories, edit files, run tools and tests, and prepare pull requests for review. GitHub, for example, now describes coding agents that can work asynchronously on an issue and create a pull request for a developer to review. GitHub’s documentation reflects how quickly this model is becoming part of mainstream development workflows.
The interesting change isn’t simply that AI can generate more code. It is that AI is becoming an actor inside the engineering system.
That distinction matters. Our existing controls evolved around developers. Developers authenticate themselves. They receive repository permissions. They work on branches. They access development environments. They make decisions when requirements are unclear. They submit code for review.
We can put an agent into that same workflow. But that doesn’t mean we should automatically give it the same boundaries.
Human approval at the end isn’t enough
My first instinct was similar to what I suspect many engineering teams will do. Keep a human in the loop .The agent can do the work, but a human approves the pull request. That’s still a useful control. But I’ve become less convinced that it is sufficient on its own.
Consider everything an agent might do before the pull request appears.
It may read hundreds of files, modify files outside the ticket’s scope, execute shell commands, download dependencies, call tools or APIs, use credentials, read content that contains instructions, or make assumptions when requirements are ambiguous.
By the time I see the final diff, a lot has already happened. So the security boundary cannot exist only at the merge button.
The boundaries need to follow the agent through the entire task.
Give the agent a task, not your access
Imagine we’re working on an ecommerce storefront. The codebase contains everything from the product catalogue and shopping cart to checkout, payments and the infrastructure used to deploy them.
A Jira card asks for a small change to the validation performed during checkout. The agent needs to understand the checkout flow, update the relevant code and tests, and verify that the change works.The easiest setup is to give the agent access to the repository and tell it:
“Only modify the checkout code.”
That is an instruction. It is not a security control. If the agent needs to work in:
/src/checkout/** and: /tests/checkout/** then perhaps those are the only locations it should be able to modify. It might need to read shared libraries to understand how the application works, but read access doesn’t automatically have to mean write access. And there may be parts of the repository it has no reason to touch at all:
/infrastructure/**
/security/**
/.github/workflows/**
/database/migrations/**
The exact boundaries will vary by application. The principle doesn’t.
Prompts communicate intent. Controls enforce boundaries. The safest agent is not necessarily the one with the best instruction.It’s the agent that cannot exceed its intended blast radius, even if the instruction fails.This applies least privilege to software engineering agents. OWASP’s guidance recommends privilege minimization to reduce impact when an agent encounters malicious or untrusted content.
A developer may need long-lived access to multiple systems because they work across them every day. An agent usually has a much narrower purpose. If a worker needs access to a development database to complete one task, give it access for that task. If it needs another repository, grant that access when required. When the task finishes, revoke it. That suggests a different model:
Task starts → identity created → scoped access granted → work performed → access revoked
Move toward task-scoped identities, short-lived tokens, and just-in-time access. I think of this as leasing access to the agent rather than giving the agent access. The distinction sounds small but can be operationally significant.
Give the agent somewhere safe to make mistakes
There is another difference between an AI assistant and an AI agent. An assistant suggests a command. An agent may execute it. Once agents can run tests, install packages, call APIs and invoke engineering tools, their execution environment becomes part of the security architecture. A worker doesn’t necessarily need my complete development environment. It can have an isolated workspace created for the task, with a restricted filesystem, controlled network access, approved tools and no production credentials. The agent can experiment inside that boundary. If it gets something wrong, discard the environment. If the task succeeds, preserve the code changes and evidence we care about. Then destroy the workspace.This leads to a principle I think is useful beyond AI:
Give agents freedom inside a small, reversible environment rather than limited freedom across a large, shared one.
When the agent doesn’t know, guessing shouldn’t be the default
Permissions solve one class of problem. Ambiguity creates another.
Imagine a Jira card says:
“Return the correct error when an account has expired.”
The agent searches the codebase and discovers two existing behaviours:
ACCOUNT_EXPIRED
and:
ACCOUNT_INACTIVE
Both look plausible. A developer would probably ask someone. An autonomous workflow needs the equivalent. This is something I’ve come to see as an important part of agent design: asking for clarification should be a first-class capability, not a failure state. The worker should be able to pause the task, explain what is ambiguous and route a question back to the appropriate human. Once answered, it continues. The alternative is surprisingly dangerous. A capable model will often be able to produce a plausible interpretation. Plausible is not necessarily correct. So I would rather design for this:
Uncertainty → pause → escalate → clarify → continue
than:
Uncertainty → infer → implement → hope the reviewer notices
In other words:
Uncertainty should trigger escalation, not invention.
Not every action deserves the same level of autonomy
This doesn’t mean a human needs to approve every action an agent performs. If we do that, we may simply replace a coding bottleneck with an approval bottleneck. The better question is: what is the consequence if this action is wrong?
Generating unit tests is different from modifying authentication. Updating application code is different from changing production infrastructure. Reading a development database is different from deleting from one. That gives us a risk-based model for autonomy.
| Action | Possible control |
|---|---|
| Generate or update unit tests | Autonomous within workspace |
| Modify application code inside task scope | Automated validation |
| Add a new dependency | Policy check or approval |
| Modify authentication/authorization | Human approval |
| Change CI/CD configuration | Human approval |
| Modify infrastructure | Restricted permissions + approval |
| Perform any production action | Strong human control |
The exact table will be different for every organisation. The principle is more important:
The goal isn’t maximum autonomy. It is appropriate autonomy.
AWS published a control framework for AI coding agents focused on balancing autonomy with system impact. AWS’s security guidance recommends least privilege and human approval for irreversible actions.
Verification and accountability need to catch up
If an agent generates the implementation, runs the tests and then tells me the implementation is correct, how much independent assurance have I gained? Having confidence in changes becomes more important as code generation gets cheaper.
Some of verifications should be deterministic:
- Unit and integration tests
- Static code analysis
- Vulnerability scanning
- Architecture rules
- Build and deployment checks
Some can potentially come from a separate reviewing agent, and some decisions should still belong to a human. Making environments, rules and feedback loops clear and enforceable became a core engineering activity. Their write-up from OpenAI shows agents generating code, tests, CI etc. The lesson I take from that isn’t that humans should stop reviewing code. It’s that when generation becomes cheap, verification becomes more valuable.
But establishing that whether the change is correct is only part of the problem. We also need to know how it got there. Today, we can usually establish who committed a change, who reviewed it, which pipeline ran and who approved the deployment.
Agentic development adds another layer.
I want to be able to answer:
- Which human initiated the task?
- Which agent performed it, and what scope was it given?
- Which files, tools and systems did it access?
- Where did it encounter uncertainty or require human intervention?
- Who or what verified the result, and when was its access revoked?
Not every organisation needs to preserve every model interaction forever. But we do need enough information to reconstruct consequential actions. Verification tells us whether we should trust the result. Auditability tells us whether we can explain how we got there.
Both become more important as agents take on more of the execution. Accountability shouldn’t disappear just because execution became automated.
Maybe the SDLC becomes the agent’s harness
The more I’ve worked on this experiment, the less I think the answer is simply adding an “AI agent” box to our existing development lifecycle. The surrounding system matters too much.
The ticket defines intent. The repository provides context. Permissions define reach. The workspace limits blast radius. Tests provide feedback. Policies enforce boundaries. Humans resolve ambiguity. Review establishes confidence. Auditability preserves accountability. Together, those things form a harness around the agent. These ideas are not new or exotic security concepts. What’s changing is where we need to apply them.
We spent years designing an SDLC where humans operate tools and automation supports them. Now we are beginning to build systems where humans express intent and agents operate more of the tools. That doesn’t make the SDLC obsolete. It makes its boundaries more important. The question I started with was whether an AI agent could pick up a Jira card, make the change, test it and raise a pull request. It can, but the question I find more interesting now is:
What engineering system do we need around it before we’re comfortable letting it do that at scale?