Agentic once, deterministic forever

In one session, Claude Fable 5 renamed our production analytics domain, diagnosed the outage the rename exposed, fixed the cause upstream, and moved the whole deployment from DigitalOcean to Vultr with about ten minutes of downtime and zero lost history. The safety story is not that the model was careful. It is a ratio: how much of the work was deterministic before the session started, and how fast the agentic remainder got converted.

Infographic of the article: the Rybbit conversion incident timeline from the 403 error to the successful migration, the safety model as a conversion ratio from agentic to deterministic tasks, and the agent perimeter of limited scope, human controls, and safe verbs.

Our Rybbit analytics instance used to live at a hostname on a side domain. On the day it moved torybbit.getcolors.ai, the converge came back green: new proxied DNS record, new certificate, every container healthy, and the acceptance step's synthetic pageview landed. Then sign-in returned 403 Forbidden, and the only evidence was a browser screenshot of the DevTools console.

The agent driving the session was Claude Fable 5, running the Rybbit Package Skill. It traced the 403 from the edge inward: Caddy had the new hostname, the reverse proxy was fine, and the backend container was six days old with BASE_URL still pointing at the retired domain. The cause was a design decision, not a typo. The stack writes stack.env once, on first install, so that generated database passwords and auth secrets survive every later converge. But the file froze the desired-state lines along with the secrets. The playbook even documented this exact trap for one variable, the signup policy, and patched that one line back into step on every converge. It never generalized the lesson, so renaming the host silently left the auth layer rejecting the new origin.

The conversion, same day

The immediate remediation was agentic: edit two lines on the host over SSH, recreate the two containers that read them, watch the 403 turn into an honest 401 for bad credentials. That fix worked and proved the diagnosis. It is also exactly the kind of work you do not want to depend on twice.

So the same session moved it upstream. The playbook's one-variable patch became a loop over every desired-state line in stack.env, recreating the services that read them when any of them drift. The package's unit tests passed, the golden renders showed exactly the intended diff and nothing else, and the change shipped as a pinned SHA that every deployment picks up through its normal update flow. From that commit on, a host rename is not an incident. It is a converge.

The payoff did not wait long. Hours later we decided to move the same deployment off DigitalOcean entirely. New provider, same hostname, same data. The plan leaned on the morning's fix: flippingrybbit-host in the new deployment's desired state was now guaranteed to propagate everywhere it needed to. The final backup ran at 09:30:00Z, and the agent verified it in object storage before destroying anything. The droplet and its DNS record went away, one converge built the Vultr instance under the production hostname, and the restore brought back 35 PostgreSQL tables and the full ClickHouse event history. Total dark time: about ten minutes. The tracking snippet embedded in forty-nine repositories never changed, because the site ID and hostname both survived the move.

One detail from the verification is worth keeping. The agent sent a test pageview to prove ingestion end to end, got HTTP 200, and found the event count unchanged. Rybbit's bot filter had discarded a request with a synthetic user agent while still returning success, which is the right behavior. The agent noticed the number had not moved, investigated instead of declaring victory, and repeated the test with a realistic browser fingerprint. Event 557 landed next to the 556 historical ones. A status code is a claim; a row in ClickHouse is evidence.

The ratio

Here is the frame I now use for whether agentic operations are safe: count the tasks. A deterministic task produces the same result from the same input, lets you review it before it runs, and fails loudly: a converge to desired state, a golden test, a restore-checked backup, a dry-run that walks the whole DAG without credentials. An agentic task is judgment: reading a screenshot, forming a hypothesis about a frozen file, choosing the order in which a migration destroys and rebuilds things. The KPI is the ratio between them, and everything about Colors pushes it in one direction: agentic work converts into deterministic work, and never converts back.

In this session the ratio was already high before anything interesting happened. Everything routine ran as converges and test suites that behave the same for an agent as for a human: the renames, the rebuilds, the certificate issuance, the backup and its restore-check, the acceptance pageview. The agentic remainder was small: one diagnosis, one remediation, one migration plan. And the remediation did not stay agentic for even a day; it became a package commit, protected by golden renders, that turns the next occurrence into a non-event. That is the compounding mechanism from the compounding-advantage article, read as a safety property: every conversion removes a place where an agent has to improvise, and with it a place where an agent can be wrong.

The perimeter the agent worked inside

A rising ratio is the trend; the perimeter is what makes the residual agentic work acceptable on a production system. None of it is exotic, and all of it was load-bearing that day:

  • Scoped credentials, rotated after the session. Each deployment holds its own gitignored credential file. The Cloudflare token could edit exactly the DNS zones it needed. The migration's first attempt failed because the token could not see the destination zone; that is the control working, and widening the token was a deliberate human act. The object-storage keys reach one bucket. After the session, we rotated every credential the agent held, so anything it might have leaked into a log or a transcript is dead.
  • Keys that die with the machine. The agent reached the Vultr instance with a disposable SSH keypair generated into the deployment directory, registered through the API, and destroyed with the instance. No long-lived personal key ever touched a machine built to be deleted.
  • Destruction is a different permission. Desired state commitscompute-prevent-destroy: true, and a real delete needs a one-run environment override plus an explicit human confirmation. The session's single irreversible step, destroying the droplet, happened after the agent verified the final backup in the bucket, and after a yes.
  • The cheap verbs need no credentials at all. build renders everything from desired state; --dry-run walks the full DAG with every side effect skipped. The agent used both before every real converge, so every risky command got a rehearsal by construction, not by discipline.

The model is load-bearing too

The deterministic half does not make the model optional. The agentic remainder in this session was exactly the part you cannot turn into a procedure: connecting a 403 to a write-once file created six days earlier, recognizing that a green converge and a working product are different claims, distrusting its own successful-looking verification. A model at the level of Fable 5 handles that remainder reliably enough to be left alone with scoped credentials; the converges and tests keep the remainder small enough that "reliably enough" is a reasonable bet. Neither half is safe on its own. Capability without determinism improvises everywhere, including where it should not; determinism without capability stalls at the first screenshot.

The ratio is also why this gets safer over time rather than merely staying safe. Every session like this one ends with the deterministic side larger than it started: a playbook that now holds three lines in step instead of one, a golden render that would catch the regression, a migration sequence written down as desired state and a backup contract rather than as heroics. The agentic column does not grow back.

Run the same stack

The deployment in this story is the analytics instance that counted your visit to this page, and it runs the same Package Skill you can install. The skill provisions the full Rybbit stack as desired state, with the verbs used throughout this article: PostgreSQL, ClickHouse, Redis, Caddy behind Cloudflare, and nightly restore-checked backups to R2. Try the Rybbit Package Skill, hand it to your agent, and start converting.