Bounded the Wrong Thing: What TCP's 1988 Fix Teaches Your Agent Loop
TCP already had a bound before congestion collapse. The receive window was real, correctly enforced, and nobody had a bug in it. The internet fell over anyway, because that window protected the receiver and the thing failing was the network in between. Jacobson did not delete it. He added a second window measuring the thing that was actually going wrong and took the minimum of the two.
Bounded the Wrong Thing: What TCP's 1988 Fix Teaches Your Agent Loop
The short answer. Before congestion collapse, TCP already had a bound. The receive window was real, correctly enforced, and free of bugs. The network collapsed anyway, because that window protected the receiver from being overwhelmed, and the thing being overwhelmed was the network in the middle. The fix in 1988 was not to remove it. It was to add a second window driven by observed loss, measuring the quantity that was actually failing, and to send at the minimum of the two. Your agent's iteration cap is the receive window: real, correctly enforced, and bounding the wrong thing.
There is a failure pattern that is much harder to spot than a missing bound, because from the inside it looks like diligence: you have a limit, the limit is enforced, and the system fails anyway.
The canonical example is running underneath this page.
What TCP had, and what happened
The receive window is a sender's promise not to send more unacknowledged data than the receiver has said it can buffer. It is a genuine bound with a genuine purpose: it stops a fast sender from overwhelming a slow receiver.
It worked. It was correctly implemented. Nobody found a bug in it.
And in 1986 the network collapsed. A link between two buildings a few hundred yards apart dropped from 32 kbit/s to 40 bit/s, roughly a thousandfold, on infrastructure that had been working.
The reason is a mismatch that only becomes obvious once stated. The receive window protects the receiver. The thing that was failing was the network between sender and receiver: routers dropping packets, senders interpreting loss as a reason to retransmit, retransmissions adding load, more loss, more retransmission. A stable feedback loop running the wrong way.
No amount of correctly enforcing the receive window addresses that, because the receive window has no opinion about the network. It was never measuring the quantity that was in trouble.
The fix, and the part everyone gets wrong
Jacobson's 1988 congestion control added the congestion window: a second bound, inferred from observed loss, that estimates what the network can currently absorb. It grows while things are fine and backs off sharply when loss appears.
The detail that matters for anyone borrowing this lesson: the receive window was not removed. It is still there, still enforced, still doing its job. The sender transmits at the minimum of the two windows.
- Keep the existing boundit was correct for what it measured. Deleting it re-opens the failure it did prevent
- Add a bound on the quantity that is failinga different measurement of a different thing, not a bigger version of the first
- Send at the minimum of the twowhichever binds first, binds
That is the pattern worth carrying:
Keep the existing bound. It was correct for what it measured. Deleting it re-opens the failure it did prevent.
Add a bound on the quantity that is actually failing. Not a bigger version of the first one. A different measurement, of a different thing.
Take the minimum. Whichever binds first, binds.
Teams reliably get this wrong in one of two directions: they tune the existing bound harder, which does nothing because it was never the constraint, or they replace it, which trades one uncovered failure for another.
Your agent loop is the receive window
Now the mapping, which is exact rather than loose.
The iteration cap is real. It is a valid termination proof: the count decreases each pass and bottoms out at zero. It is correctly enforced. There is no bug in it.
And it bounds effort spent on one run. The thing that fails is whether the task is progressing, and separately what a tree of runs costs in aggregate. Neither of those is what the counter measures.
So the fix has the same shape as 1988. Keep the cap; it bounds a single run's blast radius and that is worth having. Then add the bound on the thing that is actually going wrong:
A measure of remaining work, computed in code without asking the model, that shrinks as the task nears done. Rows unmigrated, tests failing, records unmatched. This is the congestion window: it measures the quantity in trouble.
A task-level budget carried through every nested call: wall clock, spend, and depth, enforced outside any individual agent. This is what bounds the system rather than the loop.
And then take the minimum. Whichever trips first, trips.
The same mistake, in three other places
Once you have the shape, it turns up constantly.
Rate limiters and slow runaways. A rate limiter bounds requests per minute. A runaway agent tree is not fast; it is endless. Slow and endless never trips a limit designed for fast and abusive, so the bound is real, enforced, and blind to the failure. The bound you need is spend per task, not requests per minute.
Memory limits and bandwidth. A container memory cap bounds resident bytes. If your latency problem is bytes crossing the bus per inference, the memory cap is correctly enforcing a quantity that is not the one failing. Adding memory headroom changes nothing, for the same structural reason.
Per-call timeouts and total task time. A thirty-second timeout on each call bounds a call. A task making four hundred calls has no bound at all, and every individual call was inside its limit. This is the call-tree problem wearing a timeout's clothes.
In all three the diagnostic question is identical, and it is worth asking out loud in a design review: what quantity is this bound measuring, and is it the quantity that fails?
How to check your own
Three steps, and the first is the one people skip.
Name the failure precisely. Not "it ran too long" but "the task made no progress for two hours while every individual call succeeded". The precision is what reveals the quantity.
Name what each existing bound measures. Write it down next to the bound. Requests per minute. Iterations per run. Bytes resident. Seconds per call. Most bounds have never had this written down, and writing it is often enough to see the mismatch.
Check whether any of them measures the failing quantity. If none does, tuning the existing ones is wasted effort, however carefully you do it. Add the missing measurement and take the minimum.
The 1988 fix was four characters of code in the sender: take the minimum of two windows. The engineering was not in the change. It was in working out that a bound could be entirely correct and aimed at the wrong thing.
FAQ
What was TCP congestion collapse? In 1986, a feedback loop of packet loss and retransmission collapsed throughput on working infrastructure, in one documented case from 32 kbit/s to 40 bit/s between two nearby buildings. TCP already had a receive window bounding what the receiver could absorb, but the thing failing was the network in between, which that window never measured.
Why didn't Jacobson remove the receive window? Because it was correct for what it measured, and deleting it would have re-opened the failure it did prevent. He added a second window driven by observed loss, estimating what the network could absorb, and made the sender transmit at the minimum of the two.
Is my agent's iteration cap useless? No, it bounds a single run's blast radius, which is worth having. It is just measuring effort spent rather than work remaining, and it does not bound a tree of runs at all. Keep it, add a measure of remaining work and a task-level budget, and take the minimum.
Why doesn't a rate limiter stop a runaway agent? Because a rate limiter bounds requests per minute and a runaway call tree is usually not fast, it is endless. Slow and endless is a different failure from fast and abusive, and only the second trips that bound.
How do I tell whether a bound is aimed at the right thing? Name the failure precisely, then write down what each existing bound actually measures. If none of them measures the failing quantity, tuning them is wasted effort regardless of how carefully you do it.
Tell us the system, the stakes, and the date that matters. You get a straight technical reply from the person who would lead the work, within 24 hours.
Bring us the program