Implementing 'You Can't' for AI Coding Agents
AI coding agents are getting better at writing software. I use them in my own open-source projects, too. But there's a distinction I keep coming back to: Teaching an AI what it shouldn't do is not the same as enforcing
AI coding agents are getting better at writing software. I use them in my own open-source projects, too.
But there's a distinction I keep coming back to:
Teaching an AI what it shouldn't do is not the same as enforcing what its code is allowed to do.
We ask agents not to break architectural boundaries, introduce side effects, or add unapproved dependencies. Those instructions are useful, but instructions are not guarantees.
Prompts aren't enforcement
Imagine an agent generates this code inside a Domain layer:
namespace MyApp.Domain;
public class OrderService
{
private readonly Infrastructure.OrderRepository _repository;
}
If the referenced type exists, the C# compiler will not normally know that the dependency violates your architecture.
The code may work, compile, and still be unacceptable.
A README or AGENTS.md communicates a rule. It does not independently enforce it.
Move the decision outside the agent
The workflow I want looks like this:
AI agent or human writes code
|
v
Compiler + Analyzers
|
Policy satisfied?
/ \
Yes No
| |
Candidate Diagnostic
for merge |
v
Revise code
The agent can propose a solution. An independent tool decides whether the proposal satisfies the project's checkable constraints.
For C# projects, Roslyn Analyzers let us report diagnostics during compilation. Configured as errors, those diagnostics can make a violating build fail.
This gives AI agents immediate, deterministic feedback rather than another natural-language reminder.
Three open-source projects exploring the approach
ArchitectureAnalyzer: Compile the architecture contract
ArchitectureAnalyzer uses a JSON architecture contract and Roslyn diagnostics to check rules such as layer dependencies, forbidden APIs, and dependency graph constraints.
An architecture document records intent. A machine-checkable contract helps detect drift between that intent and the codebase.
PureSharp: Enforce specific code-level contracts
PureSharp introduces functional-programming-oriented checks for C#.
For example:
[PureMethod]
public static int Add(int a, int b) => a + b;
It detects selected side effects within its documented purity contract. It also checks conventions for immutable local variables and FluentIf termination.
var _value = Calculate();
// _value = 100; // Diagnosed reassignment
This is not a whole-program mathematical proof of purity. It's enforcement of explicitly defined, mechanically checkable properties.
PolicySharp: Allow by policy, deny by default
PolicySharp takes an allowlist-oriented approach.
A denylist catches violations someone remembered to enumerate. A new API or dependency may not appear on that list.
A default-deny policy instead follows this model:
Explicitly allowed -> ALLOW
Explicitly denied -> DENY
Unknown or ambiguous -> DENY
For example, a policy can describe which namespaces a Domain scope is allowed to depend on:
{
"version": 1,
"mode": "default-deny",
"scopes": [
{
"id": "domain",
"match": { "namespace": "MyApp.Domain.**" },
"allow": {
"namespaces": [
"System",
"MyApp.Domain.**"
]
}
}
]
}
This illustrates the policy model; precise enforcement coverage depends on the implemented version.
What if the agent changes the rules?
There's an obvious bypass:
- The agent introduces a prohibited dependency.
- An analyzer reports an error.
- The agent edits the policy or project configuration.
- The build passes.
That's why an analyzer alone isn't enough.
The authority to modify application code should be separate from the authority to weaken the constraints. Policy files, project files, and analyzer references may need independent approvals and CI checks.
Static analysis also has limits. It cannot enforce properties it does not analyze, and a build gate only matters when the project actually runs it.
Freedom to propose is not permission to merge
I don't want to constrain every implementation choice an AI makes.
I want to distinguish freedom to propose a solution from permission to accept the resulting change.
Within the approved boundaries, the agent can explore different approaches. If a boundary needs to change, that should become an explicit design decision instead of a workaround silently introduced during implementation.
Conclusion
Prompts communicate intent.
Analyzers enforce checkable rules.
CI validates changes.
Approval processes protect the rules themselves.
Instead of expecting AI to remember everything it must never do, we can create development environments that reliably detect prohibited changes before accepting them.
That is the direction I'm exploring with ArchitectureAnalyzer, PureSharp, and PolicySharp.
The goal isn't less capable AI. It's more predictable, maintainable AI-assisted software development.
Originally published by Dev.to AI. Aggregated on AIWithGhost for educational purposes — full credit and traffic to the original publisher.