Every automated job names the model it runs on
Two subscriptions kept running dry while a third sat at one percent. That was the whole finding, and it took an inventory to see it. My fleet runs about sixteen scheduled jobs: a nightly drain of the work queue, a noon pass, a disclosure gate that votes on every commit, a memory router at every session end, sweeps of the open web, a weekly digest draft, four triage jobs each morning. Every one of them calls a model. Nobody had written down which one, and the answer in most cases was: whichever the launcher inherited.
The inventory was done by a read-only agent from primary sources, launcher paths, environment variable names, scheduler plists and the meter log, and its most useful column was “bills to today”. The drain children and the memory router ran through the cheap provider’s launcher with no model flag, so they resolved my interactive sessions’ model pin, an explicit id from the premium provider. The launcher’s slot remap does not catch an explicit id, so the request went to the cheap provider carrying a name it does not serve, and the provider served whatever it maps that name to. One probe confirmed it: the usage report named the wrong model, the wrong provider, and a cost of 29 cents that nobody was paying. Every nightly child and every memory-router run for days had run on an undeclared model with a lying cost column.
job flag resolved to billed to
drain children (none) interactive pin cheap, mislabelled
memory router (none) interactive pin cheap, mislabelled
disclosure gate small slot remapped small model cheap, correct
radar digest explicit premium + web search premium, correct
morning triage (none) premium default premium, unmetered
That is the failure class a table kills, so I wrote the table. One deliberate rail per automated job, decided once, recorded in a document with a machine-readable twin, and asserted by the machine doctor every morning.
The rails are the accounts I actually pay for. The premium subscription my interactive sessions use. The same account reached by the worker’s containers through a vault, so the containers spend what my sessions spend and follow an account switch. The cheap provider’s coding plan, reached through a launcher that now carries a model id on every call. And a third provider’s plan, which had zero automated calls before this. The rule that followed the probe is one line: every caller of the cheap launcher names its model. The rule that followed the inventory is one column: why this rail.
The choices are not about saving money. Quality over cost; nothing moved to a weaker model to save quota. What moved is work no human reads. Nightly code work in worktrees, the memory router’s per-session summaries and the bulk web sweeps went to the cheap rail with an explicit large model. The gate stayed on the cheap rail’s small slot: it makes over a hundred calls a day, each a one-word verdict. Whatever is owner-facing and needs web search stayed premium. Morning triage, four reports a day, went to the third provider so that plan carries something. The premium account was left to the sessions where I am sitting, plus the voice assistant that only runs when I am present.
The meter came first, and it is why I trust the table. Every rail call is wrapped by a script that re-emits the exact output and appends one row per call: model, input, cached and output tokens, cost, duration, exit code. It never changes a rail’s outcome; a parse failure emits the raw bytes and a note row, and the child’s exit code survives. Its first week said things I would not have guessed. A tool-less one-word judge call sent 13,723 input tokens, because every call carries the operating files, and the follow-up read 13,504 of them from the provider’s cache. The gate was 654 of the 694 rows ever logged. And the lint that checks every call site is metered caught an unmetered probe on its first run over the tree.
- A convention nobody checks decays. A table the doctor reads every morning does not.
The doctor closes the loop. For each rail it asserts three things: the launcher still carries its model line, the last meter row names the declared model, and, for the containers, the vault entry belongs to the account currently active and is granted to the worker’s agent. A drifted launcher, a mismatched row or a wrong-account vault becomes a finding in the morning brief, filed as a queue entry with the fix named. The grant leg exists because of one night: a fresh vault entry had been registered and granted to nobody, and every container task until morning failed with “this agent does not have access”.
The models were never the problem. Not knowing which one was talking was.