Putting AI agents into a small company's software process without losing control of it
AI agents make the typing fast. What keeps the software trustworthy is the process around them: identity, isolation, review, tests and a few boring safeguards. This is the process we run every day, including the parts that went wrong first.
Why this is a process problem, not a model problem
LanceTek is one developer. Since the middle of 2026 most of the code we write is typed by AI coding agents, which run as separate sessions, each with a job to do. The speed-up is real. So is the new failure mode: when five sessions are working at once, it becomes very easy to lose track of who changed what, why, and whether anyone checked it.
The first instinct is to look for a better model. In our experience that is the wrong lever. The mistakes that hurt us were not "the model wrote bad code". They were process mistakes: work pushed under the wrong identity, two sessions editing the same files, a finding reported in a terminal window that nobody read again, a clean-up command that hit every container on a machine instead of one. A better model makes those mistakes faster.
So we treat agents the way a careful small company treats new staff. Each has a role, an identity and limited access. Work arrives as tickets, happens on a branch, and is reviewed before it merges. Deploys are backed up. And the rules are written down where the agents read them at the start of every session.
1. An identity per role, not one shared login
We run six agent roles. Each one has its own account on our git server, its own e-mail address and its own access token. The roles are deliberately narrow:
| Role | What it does | What it may touch |
|---|---|---|
| Dispatcher | Reads every backlog and assigns work. Never writes code. | Read-only |
| Goal decomposer | Turns a goal into a plan of small tasks, and stops at the plan. | Read and write |
| Feature implementer | Does one task in its own working copy and opens the pull request. | Read and write |
| Test author | Writes tests only. It does not edit production code. | Tests only |
| Security reviewer | Reviews a pull request's changes for security problems. | Read-only |
| Architecture reviewer | Reviews boundaries, naming and evidence of test-first work. | Read-only |
The reviewers end their review with a single verdict line, such as SECURITY: PASS or ARCH: CONCERNS, so the result is machine-readable as well as human-readable.
We did not start here. For the first few months every project had its own account instead of every role. It looked tidy, and it failed in two ways. In one week in September 2026, sessions pushed work under the wrong identity three times, because credentials for several projects sat in one shared file and the wrong one answered first. And the per-project identity answered the wrong question. The git log already says which repository a commit landed in. What we actually wanted to know was who did the work. Moving identity from the project to the agent fixed both.
Three settings on every agent account matter more than the account itself:
- The account is restricted. It cannot see a repository it was not explicitly granted, whatever that repository's visibility.
- It has write access, never admin. An agent cannot delete a repository or change who else has access to it.
- Its token is narrow. It can push code and work on tickets. It cannot read user data or administer the organisation. It also gets no access to the CI runners, because a token that can start a CI job can run arbitrary code on the machine that runs the job.
Each of these on its own looks optional. Leave any one out and the identity still works. It just also sees, or can do, a great deal more than you meant, and nothing tells you so.
2. No credentials in the project folder
The project folders hold no identity and no password. When we start an agent session, a small launcher gives it that agent's identity through the environment: the name and e-mail that go on its commits, and a credential file that only that agent's sessions read. Any setting left over inside the project folder is overridden, so a stale credential cannot answer first.
A shell that was not started this way has no credential at all, and git is told not to prompt for one. It fails closed, on purpose. The old arrangement, where one shared file held every project's token, failed open: it quietly used whichever token matched. Failing closed is annoying exactly once per machine, and then it is the reason you trust the git log.
3. One task, one working copy, one branch, one pull request
Every agent session gets its own working copy of the repository (a git worktree), on its own branch, named after the agent and the task. When the work is done it becomes one pull request. The launcher refuses to put two sessions in the same working copy.
That last rule came from a mistake. We once ran two implementer agents in the same working tree because their tasks touched "different files". They did, until one of them ran the formatter, the test suite and a commit across the whole tree. "Disjoint files" is not isolation. A separate working copy is.
Branches also make review cheap. A pull request is a small, named, reviewable unit, and if it is wrong it is simply not merged. Agents never merge their own work.
4. Everything goes in a ticket
A finding in a terminal window is gone as soon as the window scrolls. So every finding, plan, question and result goes into a ticket on our own git server, in the same turn it is found. The terminal gets a short summary and a link.
Three rules made tickets work for us rather than becoming a second inbox:
- If a person has to decide, read or do something, the ticket is assigned to that person. An unassigned question is invisible: it sits in a list that nobody is looking at.
- A ticket that is being worked on says so. A short "Now / Next update" comment when work starts, at each milestone and when it is blocked. An hourly job lists tickets that a person commented on and that nothing has answered. It exists because one ticket sat for five hours with a question on it and no reply.
- All work runs against a ticket. Every agent's description starts with the ticket it serves, every scheduled job names its standing ticket, and a status page shows a red "no ticket" for anything that breaks the rule.
A ticket also has two identities: the project whose queue it sits in, and the agent that does the work. Keeping those separate means work can move between agents without moving between queues.
5. Tests first, and "done" means it ran
Every feature or fix starts as a failing test. The test is the definition of done. Our standing rule for agents is blunt: never report something as done, working, fixed or tested without running it. That means building it, running the tests, loading the page in a real browser or calling the endpoint. If an agent cannot verify something, because it lacks a credential or an environment, it has to say so explicitly.
This matters more with agents than with people, because an agent's mistakes come back in the same confident tone as its successes. We have watched a cheap model report, four times out of four, that it had created a record it never created, once with an invented ID. The reply looked perfect and the request was logged as successful. Only checking the system of record caught it. Since then, an agent's claim about the outside world is only accepted when something has checked the outside world.
6. Two reviewers, on a different model, then a person
Before anything merges, two review agents read the change: one for security, one for architecture. They run on a different AI provider from the implementer. A reviewer that shares the implementer's model tends to share its blind spots, and the point of a second opinion is that it is a different opinion.
We also tried a much cheaper model as an extra reviewer. Its findings were real three times out of twenty on its first run and none out of eight on its second. It is still useful as a prompt for a closer look, but every finding it raises is checked against the code before anyone acts on it. An unverified review finding is noise with a confident voice.
After the reviewers, a person signs off. That is the one step we do not automate. It is quick, because by then the change is small, tested and already read twice.
7. Backups before every deploy, and checks from outside
The deploy rules are the most boring part of the process and the most important:
- A backup before every deploy or upgrade. It is taken and checked first, and it is never skipped. If anyone ever seems to allow skipping it, the agent has to ask again in the same conversation. An older answer never counts.
- Deploy from a fresh copy of the main repository, never from a working copy that might hold someone's half-finished change.
- Pin the version before building, check that the built artefact reports it, and check that the running service reports it.
- "Done" means health-checked from outside: the version, a real request to the service and, for anything with a page, a look at the page. "The container is up" is not a health check.
We hold ourselves to this because the cost is asymmetric. A backup costs a minute. Discovering that an upgrade ran a one-way database migration and there is no copy from before it costs a day, or the data.
8. Guard rails for the commands an agent should never run
Agents run shell commands, and some shell commands do a great deal of damage in very few characters. Ours run with a guard hook: a small program that reads every command a session is about to run and refuses the dangerous shapes before they execute.
It exists because of an incident. A session cleaned up "its" containers with a blanket one-liner that stopped every container on the machine, sixteen of them, most belonging to other projects. The guard now refuses commands that hit everything at once (stop everything, prune everything, more than a handful of names at once, stopping the container engine itself). Outside the control-plane session, it also lets a project create and remove only the containers, images and networks it owns. The guard has its own test suite, more than seven hundred cases, most of them commands that tried to get past an earlier version.
We are clear about what it is not. It reads the text of a command. It does not control what the command can do, and a determined program can get round it through a script file or an API client. It is an accident-stopper, not a security boundary. The things that actually contain damage are separate environments for tests, keeping client systems off shared machines, and backups.
A second guard came from a different incident. Unthrottled video renders and model runs ran one of our machines out of memory three times in one day. Heavy work now goes through a small gate that sizes how many jobs run at once by the memory they need, rather than by a fixed number.
9. Write the rules where the agents will read them
Every session, human-started or scheduled, reads the same instruction files at the start: the company-wide rules, then the project's own rules. Each rule carries its reason and, often, the date and incident that produced it. "Never run a blanket container kill" means much more to a model, and to a person, when it is followed by "this stopped sixteen containers on one machine".
When a rule is broken or a person corrects an agent, the correction is written down at once, in the instruction files or in a shared memory service that every session can search. The aim is that no mistake has to be made twice. We do not fully achieve that, and we wrote about why in our review of the memory service. The instruction files are what we rely on most.
What this does not solve
Being straight about the limits:
- Our agents share one operating-system user on the machine where they run. Their private credential folders are a convention, not an operating-system boundary. The design is meant to stop accidents, not a deliberate attacker.
- The git tokens are long-lived. Short-lived tokens are planned work, not done work.
- Development secrets are not isolated per project yet. Git access is separated by project and role. The store of development secrets is not separated the same way, and we say so in each project's documentation rather than implying otherwise.
- People still have to read things. The process makes review small and cheap. It does not make it optional, and the day a person signs off without reading is the day the process stops protecting anyone.
What we would tell another small company
If you are bringing AI coding agents into a small team, you do not need our exact tooling. You need these habits:
- Give each agent a narrow role and its own identity, with the least access that does the job.
- Keep credentials out of the project folders, and make anything unconfigured fail closed.
- One task, one working copy, one branch, one pull request. Agents never merge their own work.
- Put every finding and question in a ticket, and assign the ticket to whoever has to act.
- Write the failing test first, and accept "done" only when something has run it.
- Review with a different model, verify every finding, and keep a person's sign-off.
- Back up before every deploy, and check the result from outside.
- Put guard rails in front of the commands that can do the most damage, and know they are not a security boundary.
The agents make a small company faster. The process is what lets it go faster without losing control of its own software.