A rollback answers one question: How do I put the old binary back?
It does not answer the harder question: What happens to work, state, credentials, and queued effects that crossed the version boundary while the upgrade was running?
For an AI agent, that boundary is everywhere. A new worker may read an old state row, a queued tool call may use a changed schema, or a restarted process may replay an effect created by the previous release. A clean process rollback can still leave an incompatible state store and duplicate side effects.
This article presents a small compatibility gate you can run before and during an agent-runtime upgrade.
The contract to gate
Give every persisted object and outbound effect an explicit producer contract:
default_version = {
state_schema: 7,
tool_contracts: { browser_navigate: 3, repo_apply_patch: 2 },
effect_protocol: 2,
credential_epoch: 19
}
The exact format is not important. The important part is that the worker cannot silently assume that the current version is compatible. Persist the versions beside the run, task, queue item, and effect record.
A worker should accept work only when it can prove all of these conditions:
- It can read the state schema and preserve fields it does not own.
- Its tool-input and tool-output contracts match the queued request.
- Its effect protocol understands the existing idempotency and reconciliation states.
- Its credential epoch is still authorized for this run.
- It can write evidence in a format the recovery tooling understands.
If any check is unknown, pause the item. Do not guess that an old payload is harmless.
Use expand, gate, migrate, contract
A safe rollout is usually four phases:
1. Expand
Deploy readers that understand both the old and new representation. Add new columns or fields without deleting old ones. New writers should continue producing the old representation until the compatibility gate is live.
2. Gate
Put the new worker in a canary mode. It may claim only runs whose contract passes the compatibility test. Keep a visible counter for rejected, unknown, and accepted items.
A useful gate result is more informative than a boolean:
def check_compatibility(item, worker):
reasons = []
if item.state_schema not in worker.readable_state_schemas:
reasons.append("state-schema")
if item.effect_protocol not in worker.effect_protocols:
reasons.append("effect-protocol")
if item.credential_epoch > worker.max_authorized_epoch:
reasons.append("credential-epoch")
return {"decision": "reject" if reasons else "accept", "reasons": reasons}
In production code, compare capabilities rather than requiring every version number to be identical. A worker that can safely read schema 6 and 7 should say so explicitly.
3. Migrate
Migrate durable state in resumable, idempotent batches. Record the source version, destination version, migration attempt, and checksum. If a migration stops halfway through, rerunning it should either complete the same transformation or report a deterministic conflict.
Never treat a successful database transaction as proof that an external effect completed. Keep execution state and delivery state separate. An upgrade can commit a dispatch-requested row immediately before the worker dies.
4. Contract
Only after the queue is drained or gated, the state is migrated, and reconciliation is complete should you remove the old reader and writer path. Keep the old path long enough to recover records created before the cutover.
What rollback must preserve
Before declaring a rollback successful, verify these invariants:
- Every claimed run has one owner or an explicit expired lease.
- Every tool call has a stable idempotency key.
- Every outbound effect is CONFIRMED, NOT_SENT, or UNKNOWN, never silently absent.
- Every UNKNOWN effect has a provider lookup or a human/operator decision path.
- Credential revocation and policy changes apply to both old and new workers.
- The evidence writer can still render the run history after the version change.
This is why a rollback should restore a compatible runtime, not merely an older container image.
A failure-injection drill
Run this in a staging environment with a disposable state store:
- Queue a task using the old tool contract.
- Claim it with the new worker, then kill the worker after the claim transaction.
- Change the credential epoch before the worker restarts.
- Deploy the old worker and attempt recovery.
- Inject a timeout after an outbound provider accepts the request but before your ledger records the response.
- Run reconciliation, then inspect whether the task is resumed, paused, or marked unknown.
- Repeat with a partially migrated state row and a duplicate delivery attempt.
The expected result is not that everything finishes. The expected result is that every ambiguous transition becomes visible and no stale worker can perform an effect after losing authority.
Hosting is part of the recovery surface
If the agent must stay available during a migration or recovery window, a managed runtime can reduce the operational work of keeping the process online. For teams evaluating that option, managed OpenClaw hosting on Ampere is one hosting path to compare.
That does not provide the compatibility contract for you. You still need durable state, scoped credentials, migration tests, reconciliation, and a rollback plan that accounts for work already in flight.
The checklist
Before upgrading an agent runtime, ask:
- Which state schemas can the new worker read and write?
- Which queued tool contracts can it execute safely?
- What is the effect protocol for requests that are UNKNOWN at the cutover?
- How are credential and policy versions rechecked after restart?
- Can the migration resume without duplicating a transformation?
- Can the old runtime recover records created by the new runtime?
- Which evidence proves the cutover was safe?
If you cannot answer those questions, you have a deployment plan and a rollback button, but not a compatibility gate.
The right upgrade test is not whether the new process started. It is whether old and new workers can share the boundary without losing authority, state, or effect evidence.
Top comments (0)