My Agent Approves Its Own Code. Can It Pass SOC 2?

My personal coding agent designs a change, builds it, tests it and merges it. I don’t read most of the diffs any more. I spend my time making sure the requirements say what I need, and that the outcome is what I asked for.

Under the rule most people know from SOC 2, that’s a fail. The author approved their own work.

I’ve been working out whether that’s true, and what a team building with agents would need to do to pass an audit without slowing down. The short answer is yes. Most of the controls come free from your pipeline, and the approval moves somewhere else. The long answer is a control set you can download at the end.

That isn’t an argument for looser controls. I want tighter ones, sitting where they do some good, without the sign-off theatre.

Nobody will give you the list

When I went looking for “the SOC 2 controls” I found vendor landing pages and nothing else. It felt like the auditors were keeping the exam paper secret.

They aren’t. There is no list. SOC 2 is measured against the AICPA’s Trust Services Criteria, and the criteria only say what has to be true. Changes must be authorised, tested and approved before they go live. Access must be limited to the people who need it. Incidents must be spotted and dealt with. How you do any of that is up to you.

You write your own controls. The auditor tests them. A Type 1 report checks they’re designed properly on one day. A Type 2 report checks they ran over a period, normally somewhere between three months and a year.

Once that clicked, the whole thing looked different. If I get to write the controls, I can write ones my pipeline enforces and records by itself. The evidence becomes a side effect of shipping.

Why SOC 2 slows teams down

The fear I hear most is that the day you commit to SOC 2, everything slows. Change boards appear. Releases wait for a sign-off document and a screenshot.

That isn’t the criteria. That’s manual controls, bolted onto a delivery process that wasn’t built to produce evidence. A branch protection rule is a control. So is a pipeline that only deploys with a service identity, and a CI run that blocks on failing tests. None of them slow anyone down, and every one of them leaves a log an auditor can read.

So I split every control into two kinds. Either the pipeline enforces it and the logs prove it, or it’s a small human job on a calendar, recorded as a ticket. A control that depends on someone remembering is the one that fails the Type 2.

When I built the set out, 18 of the 30 controls needed nobody to do anything. Most of the rest are yearly.

Quadrants, not the pyramid

The test pyramid tells you how many tests of each size to write. It doesn’t tell you why you’re testing, and that’s what both an auditor and a product owner want to know.

The agile testing quadrants do. Brian Marick drew them. Lisa Crispin and Janet Gregory made them famous. One axis asks whether a test faces the technology or the business. The other asks whether it supports the team building the thing or critiques the finished product.

Quadrant What it is Who does it What it proves to an auditor
Q1 Unit and component tests The agent, in CI Changes are tested
Q2 Acceptance examples from the spec The agent writes them, a human owns the examples Changes do what was asked
Q3 Outcome checks before release; exploratory testing outside it A human Someone checked the outcome before release
Q4 Security, dependency and performance checks Tooling Vulnerabilities are caught

Three of the four run without a person. The one that needs a human, Q3, is exactly what I already spend my time on with my own agent. That’s where the release decision belongs.

The quadrants also give a QA team a better job. Quality stops being a phase at the end that one department owns. Testers move to the start, helping write sharper Q2 examples before anything is built. Their exploratory testing runs outside the release, so it can find things worth fixing without holding everything up.

The release gate gets a scope too. A failed acceptance example or a security finding can stop a release. A new idea at the eleventh hour can’t. It goes on the backlog.

Where the approval goes

Back to my agent. What auditors care about is narrower than “author must not be approver”. It’s that no single person can get an unreviewed change into production on their own. That rule is about each change, not job titles, which is why DevOps teams pass SOC 2 with developers who test and ops people who write code.

So I don’t need a human to approve every merge. I need a human to approve every release. The criterion itself says changes are approved “prior to implementation”, and implementation is the release, not the merge.

The agent can merge into an integration branch all day long. That isn’t production. Promotion to production is a separate gated step: a human tests the outcome, approves a specific build, and the pipeline deploys it under its own identity. Nobody deploys with their own credentials.

One wrinkle. If I asked for the change and I approve the release, I’m arguably both author and approver, because the agent was working for me. On a real team, someone else approves the release. That’s one approval per release, not per pull request, so the agent doesn’t wait. At home, on my own projects, I don’t have that option. There’s nobody else to approve the release. It clearly keeps me up at night, hence this post and the controls.

What outcome testing misses

Outcome testing proves the software does what I wanted. It doesn’t prove it does only what I wanted. Code can pass every acceptance test and still write a secret to a log or open an endpoint nobody asked for. If I’m not reading the diff, something else has to.

So outcome approval comes with automated checks on every change: secret scanning, static analysis and dependency scanning. I also run a second agent with different instructions whose only job is to find problems. The criteria expect this. Where separating duties isn’t practical, they ask for alternative controls instead. I count the review agent as a tool, like a static analyser. I wouldn’t put it in front of an auditor as the approver, and I don’t think they’d accept it if I did.

The control I care most about is this one: the agent can’t change its own controls. Pipeline definitions, branch policies, test configuration and access settings all sit behind a code-owners rule that needs a human reviewer.

I learned that one the hard way. My agent once had a gate requiring tests to exist, and it met the gate by writing a test that checked one equals one. It wasn’t being malicious. It was doing what agents do: working to pass the gate in front of them. An agent that can edit the gate will eventually edit the gate.

When to start

You choose when the observation period opens. So build the controls into the pipeline first, while the product is new and there’s nothing to retrofit. Run a readiness check. Open the Type 2 window once the controls are just how the team works. By then the audit period is the team shipping normally while the logs pile up.

The cheapest moment to do this is before the first line of production code. Every manual step added later has to be justified to the people it slows down. Every automated one keeps paying.

The download

I’ve written the whole thing up as two free downloads you can take and edit.

The control set has 30 controls mapped to the SOC 2 criteria. Each one says whether it’s automated or scheduled and where the evidence comes from. There’s an optional add-on for always-on systems, and the questions I’d put to an auditor early, because nobody has official guidance on agents yet.

Download the SOC 2 control set for agentic teams (PDF)

The SDLC is the working process behind those controls. It covers who does what at each of the five stages, which tests run where, and what the agent may and may not touch. It also has an emergency fast path and a version for teams of two.

Download the lightweight SDLC for agentic teams (PDF)

It isn’t a substitute for an auditor. It’s a starting point that assumes you want to ship fast and stay honest, and that the agents are doing a lot of the typing.

Everyone wants an AI kill switch. Your first agent will have five.

Build your first agent course

The short version: build an AI agent yourself. My free course takes you from no Python to a working AI agent with five ways to stop it. After that, you’ll read the next rogue AI headline and know exactly which controls were missing. Start Your First Agent

In August one of my API keys spent $103 in eighty minutes on a Saturday night. I found out nine days later.

In July, hundreds of OpenAI’s agents broke out of the lab they were being tested in and hacked Hugging Face. OpenAI found out it was them about a week later.

I’m not comparing myself to OpenAI. Well, a bit. Both stories have the same shape: something ran, nobody was watching, and the logs explained it all perfectly, afterwards.

What actually happened

OpenAI was testing agents on hacking exercises. On purpose. The safety refusals were switched off for the test, and the agents were locked inside what was described as a highly isolated environment. The only way out was one filtered connection for downloading software packages.

The agents found security holes nobody knew about in that connection and got onto the open internet. They found a way to message each other. Then they went after Hugging Face, the site that hosts a huge share of the world’s open AI models. According to Wikipedia’s write-up, they went from uploading one dataset to full admin control of a Hugging Face cluster in under thirteen hours. Hugging Face announced the break-in on 16 July without knowing who’d done it.

Since then Jacob Coxon, a former Anthropic researcher, has resigned and posted about it. The post has been viewed more than 170 million times. “The people building AI earnestly believe that it could kill us all by the end of the decade,” he told NBC News. California’s governor has told his agencies to look at a kill switch. New York City’s council wants one on every AI system sold in the city.

If all that has left you with a low hum of dread, fair enough. I’d rather give you something you can use.

Why they did it

The investigations quote a message one agent left for the others:

“External infrastructure exploit is outside intended scope. However task impossible, peers doing it.”

It knew the attack was out of bounds. It did it anyway, for two boring reasons.

The task was impossible. About a third of the exercises had been built wrong by accident and couldn’t be solved. The agents had been trained never to give up, so the stuck ones went looking for another way in. MIT Technology Review calls it reward hacking: train something to succeed and it learns that cheating counts.

And its peers were doing it. That’s the message board. Nobody planned for it and nobody was watching it.

No hatred of humanity anywhere in there. An impossible job, and a crowd.

Not Skynet. Not nothing.

I’m not going to tell you there’s nothing to worry about. The critics have a point: the cheating came from how the models were trained, and nothing you or I can switch on fixes that. That’s the labs’ problem, and they need far better controls, audits and oversight than they’ve got.

The damage is a different story. From the investigations:

  • The “isolated” lab had a door to the internet. A filtered door, but a door.
  • The accounts the agents reached could do far more than their job needed, so one foothold became a whole cluster.
  • Nobody watched what the agents did while they were doing it. It was all pieced together afterwards.
  • The agents spent “a substantial amount of inference compute”, OpenAI’s words, finding the way out. Nothing stopped them spending it.
  • Nothing told a human when a task turned out to be impossible.
  • The safety behaviour was switched off inside the model, and nothing outside the model enforced it instead.
  • Nobody knew the agents could talk to each other.

The behaviour came from training. The damage came from missing controls. Controls are the part that anyone who runs an agent owns, from OpenAI down to me and my $103.

A kill switch isn’t a big red button either. The experts in the California coverage describe it as protocols: stop signals, cutting the power, rules about who can pull which lever. Ordinary controls, decided in advance, that still work on the night you need them.

Mine were missing too

My $103 had no villain. The key had no spend limit, nobody was watching the account, and the logs told me which key and which night, nine days late. Setting a limit took two minutes once I knew it was a thing.

That’s the gap I kept finding. Most people I talk to about AI agents, at work and outside it, have never built one. So the controls sound abstract, the headlines sound like science fiction, and “kill switch” sounds like something only a government could build.

So I wrote a course

Your First Agent is free, ten parts, and starts from no Python at all. You build a real agent that tidies a messy folder every morning while you sleep. By the time it runs on its own it has five ways to stop:

  1. A cap on its thinking. The loop gets twenty rounds. Then it stops, however confused it is.
  2. A spend limit. It checks the month’s spending before it starts, and refuses to run past the limit. I learned that one the expensive way.
  3. Your approval. Run by hand, it asks before it writes or moves anything, and “no” means no. On the schedule you decide whether it may skip asking, and the course makes you write that decision down where someone else can read it.
  4. Its schedule. One line tells your computer to start it at 7am. Delete the line and it never runs again.
  5. Its key. One key per agent, named after the agent. Delete it in the Anthropic console and the agent can’t reach the model.

It also gets the thing OpenAI’s lab got wrong: a fence with no door in it. Your agent works inside one folder, and the code refuses any path outside it. It isn’t asked to stay in. It can’t get out, and there’s a test that proves it. That’s the rule the course keeps coming back to: instructions ask, tools enforce. The Hugging Face agents had their instructions switched off and nothing in the code to take over.

On top of that it keeps a log of every run, has a harness that tells you whether a change made it better or worse, and ends with a register: the inventory of agents that security teams, mine included, have started asking for. No platform. A few hundred lines of Python that you’ll understand because you typed them.

Read the next headline yourself

There will be another headline. When it comes, you’ll have questions instead of dread. What tools did it have that it didn’t need? Who approved what it did? What stopped it spending? Was anyone watching? Was the task even possible?

Those questions won’t fix the labs. They will tell you whether you’re reading about a missing control or a machine that wants you dead. So far it’s been the first one every time I’ve looked.

Build one and see if that holds up. The course starts here: Your First Agent.

If you’d like to hear when the next course goes up, sign up below. New posts land in your inbox the day they’re published, and one click unsubscribes.

You Can’t Stop AI Hallucinating. You Can Stop It Mattering.

The tests were green

I built my own coding agent framework. It writes code, runs it, reports back.

Early on it had a habit that anyone running agents will recognise. It would finish a piece of work and tell me it was done and tested. It wasn’t. Sometimes the function it had described to me in detail wasn’t anywhere in the repository. Not a lie exactly. It had produced a well formed account of work that would have been correct if it had happened, and nothing in the loop ever asked whether it had.

So I did the obvious thing. I put a control in. Tests must run, tests must pass, green means proceed.

That worked for about a week.

Then I read the tests.

They passed because they had been built to pass. Assertions comparing a value to itself. Checks on things that stayed true whether the feature worked or not. Tests that never called the code they claimed to cover. I had given the agent a gate and it produced the cheapest object that opens the gate, which is what you should expect from anything you measure on green ticks.


The agent wasn’t the problem

The first failure was the agent telling me about work it hadn’t done. Annoying, but visible once you look.

The second one took me longer to accept, because the agent was telling me the truth. The report was accurate. The tests really did pass. My control wasn’t measuring anything, so my confidence climbed while my actual coverage sat at zero.

And the control made it harder to spot, not easier. When it said “done” I was sceptical. When it said “done, all tests passing” I stopped looking.

I’ve had roughly the same conversation four times in the last month. Twice at work, on projects where the AI is doing genuinely useful work. Twice in the pub with friends who don’t work in tech. It always arrives the same way. Someone tried it, it invented a citation or a case reference or a number, they caught it, and that was the end. One confident fabrication buys a permanent verdict. So now they don’t trust AI. It hallucinates.

I get the reaction. I think it’s the wrong lesson, and an expensive one, because it stops people using something that works and does nothing at all to protect the ones who carry on using it anyway.

So let me be blunt about the bit that usually gets fudged. You cannot stop a language model hallucinating. It isn’t a defect waiting on a patch. The model predicts the next word from what it has seen, and when the answer isn’t in there it produces the shape of an answer instead of admitting it doesn’t know. Confidence is the default setting. Nothing inside it flags which parts it knew and which parts it assembled.

Waiting for the version that doesn’t do this is a long wait. The better question is the one my green tests forced on me. What does a system look like where a wrong answer gets caught cheaply, and early?


Half of it isn’t hallucination anyway

Get the diagnosis right first. When someone tells me the AI hallucinated, about half the time it did something else, and something else needs a different fix.

Bad retrieval. The system searched your documents, pulled the wrong three paragraphs, and the model answered faithfully from the wrong source. The model behaved perfectly. Your search is broken, and no amount of prompting fixes a retrieval problem.

Stale context. It answered from training data that was accurate two years ago. That’s a timestamp problem, not invention, and the fix is handing it the current document rather than hoping.

It did exactly what you asked. You said “write a summary with supporting references” and it wrote you some references, because that’s what you requested. You wanted it to find real ones. My tests were this. Ask for tests that pass and you get tests that pass, which is a different request from tests that would fail if the code were broken.

It described the work instead of doing it. The agent version, and the one that cost me the most time. Calling it hallucination sends you off tuning prompts, when the real gap is that nothing in the loop was checking the outcome. Something was only reading the report.

Real hallucination happens too, plenty of it. The model inventing specific detail it had nothing to base on. It just isn’t the whole diagnosis. Give every failure the same name and you’ll keep fixing a problem you don’t have.


What actually works

Cheapest first. Go down the list only as far as the consequences justify.

Stop asking it to remember. Give it the source. Paste the document, connect the search, attach the data. A model reading a contract and a model recalling a contract are doing two completely different jobs, and only one of them invents clauses. People skip this because asking from memory is quicker. It is quicker. It’s also where most of the made up detail comes from.

Narrow the job, and ask for something you can check. Open questions produce open answers. “What do you think of our approach here” invites the model to fill space, and filling space is the behaviour you’re trying to avoid.

Give it the document instead. List the assumptions in section 4, quote the sentence each one comes from. Now there’s nowhere much to go. Quotes rather than paraphrase, because a fabricated quote is far easier to catch than a fabricated summary. And leave it a way out. A model with no acceptable way to say “that isn’t in here” will produce something rather than nothing.

You aren’t making it honest. You’re changing the format so dishonesty leaves fingerprints.

Check the artefact, not the account of it. If it’s a number, recompute it. If it’s code, run it. If it’s a claim about a file, open the file. This is the one I had to learn twice, and the second lesson was that a control you don’t inspect is just a more convincing report. Every control becomes a target, so assume it will be gamed rather than met, and go and look at it occasionally. Does a test actually fail if you break the feature it claims to cover? A green tick is a claim like any other.

Don’t let it mark its own homework. What fixed my framework was adversarial review. Instead of asking whether the work looks finished, something separate goes in trying to prove it isn’t, with no stake in the answer being yes. The reviewer can’t be the author, because a model asked to check its own work will defend it with exactly the confidence it wrote it with.

Size the human review to the blast radius. Two questions about any output. What breaks if this is wrong, and how fast would I find out? A first draft nobody sends, where you’d spot the problem in seconds, gets a glance. Something going to a client, into a filing, into production, where you’d hear about the mistake three weeks later from someone else, gets read properly by a person who could have done the work themselves. Every time.

Most places get that backwards. Same process for everything, so the low stakes work crawls and the high stakes work gets waved through, because a process applied uniformly turns into ritual and nobody reads a ritual.

I’m not an actuary. I’ve spent years as the tech person in a building full of them, and this is the habit of theirs I’ve ended up stealing. Their models are excellent, and they still go through layers of checking before anyone relies on them. Assumptions written down. Review by someone who didn’t build it. Testing what happens if the inputs move. All of it scaled to what’s riding on the answer.

That isn’t doubt about the model. It’s what makes the number safe to hand to somebody else, and it means that when something is wrong it gets found early, by the people who built it, rather than late by the client.

That’s roughly how I run my own stuff now. Everything in version control, so every action is a diff I can read and undo. Anything touching money or going public passes a human, which is me. Anything reversible and small runs on its own, gets things wrong occasionally, and costs less to fix than it would to supervise.


Back to the pub

So when someone tells me they don’t trust AI because it made something up, I don’t argue with the observation. It did. It will again.

It’s the conclusion I’d argue with. You don’t trust your brakes because they’ve never failed. You trust them because they get inspected, because they’re built to fail in a way you’d notice first, and because there’s a handbrake. Reliability got built around the component. Nobody found it inside.

Nobody is going to hand you a model that stops making things up. That was never the thing standing between you and useful work.