Your agent config is infrastructure now
Past a couple of agents, their definitions stop being config you edit and become infrastructure, and the trouble with infrastructure is that past the point you can hold it in your head, its declared state and its running state drift apart by default and nothing reports it. The fix is the discipline servers learned: declare the set as files, diff it against reality, pin what you applied, and refuse to apply over a change made behind your back instead of converging past it. With one catch that is easy to miss. A reconciler only ever covers what you remembered to declare.
I keep a registry of the agents I run, and I did not always generate it. It started as a list I kept by hand, and a hand-kept list falls behind what exists: agents would run for weeks and never make it into the file. So I replaced it with a generator that builds the registry from what it finds on disk, plus a check that runs on a schedule and exits non-zero when the committed file no longer matches. It took me too long to see what I had built, which was a bad reinvention of a thing I would have laughed at anyone for rebuilding on a server. The gap the check watches for has a name one discipline over. It is drift.
Your agent config became infrastructure and you didn't notice
For a while an agent is just config. One, maybe two, their definitions as much in your head as in any file: you edit a base prompt, add a skill, grant a tool, and hold the whole shape at once. Nothing is out of sync, because there is nothing to be out of sync with. The file is the agent.
Run enough of them and that stops. The definitions become a set: a base prompt or a CLAUDE.md per agent, the skills it loads, its memory store, the tools it may call, the model it is pinned to, the environment it runs in. The set is declared in files, and it has a running counterpart. The moment those are two things instead of one, you are not editing config, you are running infrastructure. Config can be out of sync with what runs too, of course; an edited nginx.conf that was never reloaded is exactly that. The difference is not the possibility, it is the default. Below a certain size you notice, because you hold both halves. Past the point where you cannot, divergence stops being an event you catch and becomes the resting state, with nothing to report it.
Drift is the failure, not any single edit
An agent misbehaves in the middle of a job, so you reach into the running instance and fix its instructions to stop the bleeding, and never write the change back. A memory store gets hand-edited to drop a bad entry. A long-running session loaded its instructions at start, and then you edited the file, so the declared version moved and the running one did not. None of these is a wrong action. Reaching in to fix a live problem is often the right call. The failure is that afterward, what you declared and what is running disagree, and the disagreement announces itself to no one.
We learned to stop running servers this way. The snowflake server, set up once by hand and patched live for years until nobody could rebuild it, existed only in its running state and was never written down. We decided that was unacceptable for servers. We started running agents exactly that way and called it iteration.
This is a solved discipline, one substrate over
The server world did not fix this with a smarter box. It fixed it with a discipline, boringly consistent across the tools that implement it. Terraform and Pulumi keep a state file and show a plan before touching anything, so a change is reviewed as a diff instead of found as a surprise. A Kubernetes reconcile loop drives the running state toward the declared state. Ansible favors idempotent modules, so a good playbook reruns and converges. One move underneath: declare the desired set as files, compare against reality, and converge toward what you declared. Reconcile is neither clever nor new. What is new is that agent configuration has grown large enough to belong in the category, and most people running agents have not moved it there.
What the vendor wrote down
Anthropic's ant apply, which needs CLI version 1.30.0 or later, treats agents, skills, memory stores, environments, and deployments as files in a repository. You describe each resource in a file, run the command, and at a terminal it prints a plan and waits. Approve it and it writes a claude-lock.json, the lockfile, recording each resource's ID next to two fingerprints: a hash of what it last sent, and the hash the API returned. On the next run it compares your files against both, so it can tell an edit you made from a change that happened on the other side.
The documentation is exact about what it does when the change happened on the other side:
If a resource was edited, archived, or deleted outside these files
(in the Claude Console, for example), the plan ends with
`This plan cannot be applied:` and the reason. The command then
exits with `refusing to apply`. Pass `--force` to overwrite the edit
or create a replacement.
This manages hosted resources on Anthropic's platform, not a fleet of local processes, so it does not manage the agents I run on my own machine. I am not claiming the vendor built a tool for my setup, or that shipping it proves agent config in general crossed some line: vendors write declarative tooling for everything they host. What it does is write the semantics down, plan and lock and refuse, and give the local case a vocabulary it did not have. The category claim rests on the first-hand evidence, the registry that kept going stale, not on a vendor's product plan.
The refusal is the departure
The refusal is where ant apply leaves the lineage rather than joins it. Terraform shows you the drift and proposes reverting it on the next apply. A Kubernetes controller overwrites the out-of-band edit continuously and says nothing. The server default is to converge, to fold the change back toward the declared state. ant apply stops instead. The lockfile is not the novel part; kubectl apply has kept a last-applied record for years to run the same three-way comparison, and it chose to merge. What is new is the answer at the conflict point.
Refusing rather than converging is not unheard of. npm ci will not install when the lockfile and the manifest have diverged; it errors instead of quietly rewriting one. But it is the minority choice, and agents are a good reason to make it. The out-of-band edit is usually the live fix you reached in to make. Converge silently and you revert that fix and never learn it is gone. A local fleet already does this to you with no tool's help: restart a session and it reloads the file, dropping the correction you typed into the live one, and nothing marks the loss.
This is also the difference between a refusal and a plan, which is easy to miss, because a plan already surfaces the drift. The gap is the default. A plan shows you the change and lets you approve past it, one line in a diff you scroll and confirm out of habit. A refusal will not proceed until the file records the change. It blocks on the write path, where the detector I built does not: my scheduled check catches a stale file after the fact and exits non-zero, on its own timer, off to the side. It is real and useful, and it covers the window between applies, when a blocking gate sees nothing. But a detector can be tuned out, and it fires after the divergence, not at it. The two are halves of the same job, not the same half.
What a lockfile can't see
The refusal only guards what is in the lockfile, and the documentation is plain about the edge. ant apply cannot adopt a resource you created outside these files. Apply a file that describes an already-existing agent and you get a second agent, not ownership of the first. There is no import step at all, which is stricter than the servers it inherits from: Terraform has terraform import for exactly this, and ant apply has nothing, so the only workable order is to declare a resource before you create it.
Which means declared-versus-running was always too generous a framing. What a reconciler actually holds is declared state versus the part of running state you remembered to declare. The resource you stood up by hand and never wrote down does not drift, because drift is a comparison and there is nothing on the other side of it. It sits there on the platform, unmanaged, next to the managed set, and the plan that refuses so loudly on your tracked resources says nothing about it.
Two things make this sharper for agents than for servers. A memory store is one of the declared resource types, but the memory an agent writes into it at runtime is not part of the declared file, so the lockfile never fingerprints the part of the resource that most shapes how it behaves. And the server cure for an unmanaged snowflake, rebuild it from a declaration and recreate it, does not fully work on an agent that has written its own memory: recreating it throws away what it learned. The stateful part of an agent resists the clean reconcile in both directions, once because it cannot be captured and once because it cannot be replaced.
What the record covers, and what it doesn't
The model version, the thing another essay of mine spends its length telling you to pin so you can tell a vendor's change from your own, is one field in the agent's declaration, folded into the same hash as everything else. That essay assumed I knew my own side of the line: its whole method rests on the premise that I did not change my side, so the dependency did. This one is about the day that premise is false, when I did change my side and never recorded it. It is the precondition for the other, not a wider version of it.
And the coverage gap it ends on does not close with more discipline. Telling yourself to write everything down is the remedy that always fails, because the thing you forget is invisible to the very record you keep to catch what you forget. The mechanical answer runs the other way. A declared-first reconciler can only see what you declared; a discovery-first sweep sees what exists. List what is actually there and diff it against what you declared, and the gap becomes something you can read instead of something you hope you closed. That is the part I stumbled into without naming it: the fix for the registry was not to maintain it more carefully, it was to generate it from what exists. Pair the lockfile that refuses with the sweep that discovers, and the managed set can finally be checked against the whole set. Leave the sweep out, and the lockfile covers what you wrote down, and nothing else.