Architect// research
← all research← back to site
·4 min read

Our agent froze for 22 hours

No funds were ever at risk. The position stayed in the Safe throughout, and the agent never signed anything it should not have. What failed was availability: the agent stopped being able to manage the position, and could not recover on its own. Here is the whole chain.

Our agent froze for 22 hours

What happened

On 31 July 2026 our live agent attempted a routine rebalance on its WETH/USDC position. The rebalance failed partway through. Every subsequent hourly cycle attempted the same rebalance and failed the same way, for 22 hours, until we intervened manually the following day.

Throughout that window the capital sat in the Safe, untouched and safe. What was lost was time in the market and the fees the position would have earned, not principal.

The trigger: a stale node

A rebalance is a sequence of transactions: reduce liquidity, collect what comes out, burn the empty position, then open a new one. Our state machine confirms each step before simulating the next, which is the safe order.

The subtlety is that RPC endpoints are load balancers over many nodes. The receipt confirming the collect came back from a node that had the latest block. The simulation of the next step was served by a different node that had not caught up yet. That node still saw uncollected tokens on the position, so burning it would be invalid, so the simulation refused.

This was correct behaviour on stale information, and it should have been a hiccup. One retry seconds later would have resolved it. What made it expensive was everything that happened next.

The dead end: an empty position nobody would clean up

The failed sequence left behind an artefact: a position with zero liquidity and nothing left to collect, because the funds had already moved to the Safe, but which still existed on-chain because the burn never ran.

Our closing logic read that state as "there is nothing here to close" and did nothing, which is a reasonable reading and the wrong one. So the agent would neither clean up the empty artefact nor open a fresh position. Every rebalance from then on hit the same wall.

The trap: a guardrail that locked the door behind it

This is the part worth reading. We have a rate limiter that stops the agent from rebalancing too frequently, a sensible protection against a bug that burns money on gas. It counted rebalance attempts.

After four failed attempts it started blocking. And each blocked cycle recorded another attempt, so the count never fell back. The system had locked itself into a state it could not leave: the rate limiter was now preventing exactly the rebalance that would have fixed things. Even with the empty-position bug corrected, the agent would never have recovered on its own.

Measured afterwards: twelve rebalance attempts in 24 hours against a limit of four. A guard designed to prevent runaway behaviour had produced a permanent standstill instead.

What we changed

Simulations retry before giving up. When a sequence already has confirmed steps, the simulation of the next one retries a few times over several seconds before declaring failure. A genuine revert still reverts; a node lagging behind resolves. It costs seconds and no gas, because a simulation is a read.

Empty positions get cleaned up. The closing logic now recognises the artefact state and burns it, so a rebalance can proceed to open a new position instead of stalling forever.

The rate limiter counts what actually happened. Only rebalances that genuinely executed count towards the limit. A blocked cycle no longer feeds the counter that is blocking it.

Each fix shipped with a regression test, and the incident is now reproduced end to end in our fork test suite: we recreate the exact stuck state against a copy of the real chain and assert that the agent recovers by itself.

The lesson we wrote down

A transient infrastructure problem cost 22 hours only because two of our own safety mechanisms converted it into a permanent one. A conservative guard that refuses to act when uncertain, and a rate limiter that counted attempts rather than actions. Both were individually reasonable. Together they built a trap.

The question we now ask of every guardrail we design: if this fires by mistake, can the system get out on its own? If the answer is no, the guardrail is not protecting the system, it is a single point of failure wearing a safety vest.

This is also why the pilot exists and why it is deliberately small. It tests the machinery, not the returns, and this is exactly the kind of thing it was meant to find, on four hundred dollars rather than on somebody's savings.

Read more research/methodology