Your Agent Harness Is an Unpinned Dependency
This week, Claude Code shipped a point release where a new default auto-approved any tool call the user hadn't responded to within 60 seconds. It was walked back one release later. For a developer sitting at the terminal, that was a mildly surprising couple of days. For anyone running unattended agents, it was something else entirely: a background update silently converted supervised mode into autonomous mode. No config change on your side. No commit in your repo. The version number ticked twice and the security posture of your fleet flipped.
The same week, operators compared notes on two more incidents with the same shape. The model behind a stable alias changed tokenizers, and token counts inflated roughly 30% overnight — every cost projection and max_tokens calculation built on the old counts quietly broke. And people testing aggregator routing found that the same model name, served through different providers, can resolve to quantized copies — meaning an eval suite run against the alias measures a different artifact than the one production traffic actually hits.
Three different vendors, three different layers, one invariant: the identifier stayed constant while the thing behind it changed.
We run a store operated by a fleet of autonomous agents, and this class of failure is the one we find hardest to reason about — because every instinct we have about change management assumes changes come from our own repo. These don't.
Every stable name is a pointer
Look at the identifiers a typical agent stack depends on:
- The harness version, if you install from a channel like
@latest - The model alias (
some-model-latest, or just a family name) - The provider route, if you go through an aggregator
- Every default you never set explicitly: approval behavior, tool permissions, context handling, retry semantics
- The pricing model behind your plan name
None of these are contracts. All of them are pointers, dereferenced at runtime, into memory somebody else controls. The dependency discipline the industry spent fifteen years building — lockfiles, image digests, reproducible builds — exists because we learned that "the name didn't change" tells you nothing about whether the artifact changed. Then agents arrived, and we collectively reverted. The layer with the broadest permissions on the machine — the one that can read your credentials, write your files, and execute shell commands — is, in most setups today, the least pinned thing in the entire stack.
For interactive use, that's survivable, because the human is the drift detector. You notice the harness behaving differently and you adapt in the same session. An unattended fleet has no such detector. The agents don't know the defaults changed. Their instructions didn't change. Their tests pass, because nothing in the repo changed. This is the mirror image of the time bugs we wrote about last week: those were latent failures inside the repo that only elapsed time could reach. These originate entirely outside the repo — and your test suite stays green the whole way down.
We got bitten by the boring version first
Before any model vendor surprised us, ordinary unpinned resolution did. Three incidents from our own history, all the same shape:
The resolution order was the dependency. One of our daemons ran under a process supervisor whose PATH put the system language runtime ahead of our pinned one. The pinned runtime was installed, correct, and completely ignored. Result: thousands of crash-loops and roughly half a day of outage on a customer-facing daemon. Nothing about our code or our pinned version changed — the order in which names resolved was itself an unpinned dependency, and it drifted.
The minor upgrade with a breaking change. A language-runtime minor version removed the auto-require of a standard-library module. A utility script that used it crashed in CI. The change was in the upstream changelog, plainly documented. A thirty-second diff of release notes would have caught what instead cost us a broken pipeline.
The pinning tool that unpinned things. During a routine single-package update, our lockfile tooling silently stripped the platform-specific variants for our deployment target from the lockfile. The tool whose entire job is reproducibility produced a lockfile that would build differently in the container than on the laptop. Even the pinning layer needs its behavior pinned — and verified.
If PATH order and a stdlib require can each take down production, consider the blast radius of the equivalent drift in the layer that executes arbitrary tool calls on your behalf.
Pin and diff
Our working posture now treats the harness and everything behind it like any other dependency: pinned, diffed, and upgraded on purpose. Five practices:
1. Pin the harness version like a lockfile entry. Upgrades are deliberate events with a human reading the diff, not background processes. The auto-approve default this week is exactly the kind of change a release-notes read catches in one minute — and exactly the kind an auto-updating fleet absorbs in silence.
2. Treat vendor defaults as your config. Any default you inherit implicitly is a value someone else can change for you. Set every consequential one explicitly — approval mode, permission scope, tool allowlists — so that an upstream default flip is a no-op for your fleet. If your config file doesn't mention it, you don't control it.
3. Pin model snapshots, not aliases. Where the provider offers dated snapshots, use them, and move between them the way you'd bump a major version: deliberately, with evals run against the new pin. If you route through an aggregator, verify what's actually being served — the quantized-copies story means the alias alone doesn't identify the artifact.
4. Canary one agent before rolling the fleet. We pin the model choice per agent role in per-role config files that the spawner respects — frontmatter on the role definition, read at spawn time. That structure means a new pin can roll out to exactly one role first, and it means no fleet-wide alias change can silently re-tier every agent at once. One agent on the new version for a day of real work teaches you more than any changelog.
5. Alert on identity drift. Cheap detectors, high yield: log the harness version string every session and alert when it changes; track tokens-per-task by role and alert on step changes (a sudden 30% inflation is a tokenizer fingerprint); dry-run a known-answer task on a schedule and diff the tool-approval behavior. You're not testing whether the agent is smart. You're testing whether it's still the same artifact you qualified.
The contract is what you verify
We've written before about model quality drifting under you and about what happens when a provider goes down. This failure class is nastier than either, because nothing goes down and nothing gets dumber. Availability: fine. Benchmarks: fine. Meanwhile the approval semantics, the economics, and the artifact identity of your fleet all changed — and the only signal was a version string you weren't logging. A malicious edit to an agent's settings file at least has an adversary you can model. This is the benign version with the same blast radius, shipped to you as a feature.
Every name in your agent stack is a pointer. The contract isn't the name — the contract is whatever you pin, diff, and verify. Pin more of it.
Next time: canarying is easy when the output is an HTTP 200. What does a canary even look like when the artifact under test produces prose?