coop: stop a pair dying with one of its agents - #83
Merged
Conversation
A pair is graded only when both sides return, so one agent's death loses the pair. Three things had to line up wrong for that, and all three did. Sandbox lifetimes were 3600s while a heavy reasoner needs ~4x the wall-clock per step, so agents were still working when Modal reclaimed them. Four sites pinned the value and the explicit one in adapter.py quietly beat the dataclass default, so raising that default alone did nothing. The git daemon expired on the same clock, and it hosts the bare repo both agents push and fetch through -- one expiry breaks every git operation at once and leaves agents chasing commits the remote no longer has (`fatal: invalid object name`). All now 10800. Departure was self-reported and only on a clean submit, so a crash or step-limit exit left the peer's has_exited() False forever. Now announced from a finally, on every path. That still cannot cover the case that actually happens: the sandbox is reclaimed, the process is killed outright, and no finally runs. Liveness is therefore proven rather than death announced -- agents refresh an `:alive` key each step and a lapsed heartbeat reads as gone. An agent that had not started yet is never mistaken for one that died, and the survivor is told which of the two occurred instead of being informed that a killed peer "completed their work". Measured on the run that prompted this: one survivor issued 42 sleeps totalling 91 minutes, received zero exit notices, and outlived its own sandbox waiting.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A pair is graded only when both sides return, so one agent's death loses the pair. Last night four of five pairs were lost that way. Three things had to line up wrong, and all three did.
1. Sandbox lifetimes were shorter than the agents
3600s, while a heavy reasoner generates ~4x the tokens per step and needs correspondingly more wall-clock. Agents were still working when Modal reclaimed them.
Four sites pinned it, and the explicit one wins, which is the part that bit:
adapter.pyenv_kwargs["timeout"](explicit — this is the binding one)environments/modal.pydefaultgit_servers/modal.pygit_servers/__init__.pyfactorygit_servers/{docker,gcp}.pyRaising only the dataclass default did nothing, because
adapter.pypassestimeout=3600explicitly intoenv_kwargs. Verified end-to-end through the live call chain (coop.py→create_git_server→ModalGitServer.create→Sandbox.create), not by reading defaults.The git daemon matters more than it looks: it hosts the bare repo both agents push and fetch through, so one expiry breaks every git operation in the pair simultaneously. Observed as
fatal: invalid object name '0ef55fd'x12 andunknown revision '18e1d15'x6 — agents chasing commits the remote no longer had.2. Departure was announced on only one exit path
mark_exited()sat after a clean submit, so a crash or step-limit exit left the peer'shas_exited()False forever. Now called from afinally, covering every in-process exit.3. …which still cannot cover a hard kill
When the sandbox is reclaimed the process is killed outright and no
finallyruns. Self-reported death cannot work for the case that actually happens.Liveness is now proven rather than death announced. Agents refresh an
:alivekey each step;has_exited()treats a lapsed heartbeat as gone. Silence is the signal, so detection needs no cooperation from the dead.ALIVE_TTL = 600comfortably exceeds a slow step (long prompt + test run), so a working agent is never declared dead._seen_alivegate).is_unreachable()separates killed from finished, and the message injected into the survivor's history says which occurred — a killed peer is no longer described as having "completed their work". Telling an agent something untrue about the remote is the failure this whole area exists to avoid.Why it matters
The survivor is not misbehaving — it is running the disciplined protocol we want, gating submission on peer confirmation. With no exit signal that becomes a deadlock: one agent issued 42
sleepcommands totalling 91 minutes, received zero exit notices, and outlived its own sandbox waiting.Verification
447 passed, 63 skipped; ruff clean.