Crash recovery
How alineod reconnects to a live swarm after a restart, without interrupting agents mid-turn.
alineod is designed to be crash-only: stopping it with kill -9 and starting it again is a supported way to
restart it. Agents run in OpenSandbox containers, not as child processes of alineod, so they keep working while
the daemon is down.
What happens on startup
Rebuild state from the ledger
The agents and handles tables are discarded and rebuilt by replaying the ledger. Two kinds of agent need
reconnecting: any agent in provisioning, spawning, running, or paused (still live), and any finished
agent (done or failed) whose sandbox is still open — a turn ending does not close the sandbox, so it stays
promptable and usable as a spawn parent. aborted and lost agents are skipped: stop/DELETE /runs/:id already
closed their sandbox, and a lost agent is retried again on a later boot, not this one.
Reconnect to agents that have a sandbox
For each one, alineod calls Alineo.reattach(), which binds to
the harness bridge already running in the container without restarting it. An in-flight turn is not
interrupted.
If the agent was mid-turn at crash time, the process that was reading its stream is gone, so alineod polls the agent until the turn finishes and then records its result — anything waiting on that agent is still released.
If the bridge doesn't answer, alineod falls back to Alineo.resume(),
which restarts the bridge (dropping any in-flight turn). If that fails too: a still-live agent ends as lost, since
its outcome genuinely is unknown; a finished agent instead keeps its already-recorded outcome — it just can't be
prompted or used as a spawn parent again this boot.
Parents whose own turn already finished are reconnected the same way, so a pending child can still fork from them.
Retry spawns that hadn't forked yet
Agents recorded before their sandbox existed — a child held by waitFor, or a root still being provisioned — are
retried from what was persisted in their agent_spawned event: the spec, waitFor list, prompt, and budget. A
gather agent that was waiting on workers goes back to waiting and starts normally once they settle.
A child whose parent could not be reconnected ends as lost.
Serve requests
The daemon starts listening once reconnection is done; spawn retries continue in the background.
What clients see
- HTTP — requests in flight during the crash fail; retry them. Use
idempotencyKeyon spawns so a retry can't create a duplicate child. - Result long-polls — reissue
GET /agents/:id/result?wait=…. A result that settled while alineod was down, or during catch-up, is returned immediately. - Event stream — reconnect with the last
idyou received inLast-Event-ID. Every persisted event since then is replayed before live events resume. Text deltas produced during the gap are not replayed.
curl -N localhost:4600/runs/r_3f9a1c20/events -H 'Last-Event-ID: 128'Verifying it
apps/alineod/scripts/swarm-reattach-test.py drives a multi-agent run, and is used to kill and restart alineod
mid-run and check that every agent — including a gather still waiting on its workers — completes.
Limits
- Reconnecting depends on the agent's container still existing. If OpenSandbox itself restarted and removed it,
the agent is
lost. - A turn that finished while alineod was down is recorded when alineod comes back — its result is the agent's final message, but the individual stream events from the gap are not in the ledger.