durability and recovery
the single-writer guarantees behind spawn, the reconciliation that repairs drift after a crash, and how orcr behaves across sleep, reboot, herdr restart, and version skew.
the server is the single writer, and every state transition is a store transaction. that is what lets orcr survive crashes, sleep, reboots, and a herdr restart without losing agents or double-spawning them. this page is how those guarantees are built.
durable state before side effects
the spawn pipeline records durable state before it takes any external action, so a crash at any point is recoverable.
- the server resolves the integration and the effective path, then in one
BEGIN IMMEDIATEtransaction validates grammar and limits, allocates the uuid, allocates or validates the name against the partial unique index, and inserts the agent row with statusqueued. the data dir and itslaunch.jsonaudit payload are written before this insert, because the queue worker can promote and begin the pipeline (which readslaunch.json) within one tick. a failed insert (for examplepath_in_use) removes the just-created data dir. once the row is inserted the identity is durable and the verb returns<path> <uuid>. - queue promotion picks it up (
queuedtostarting, stuck-start guard armed): ensure the owned session's herdr server, ensure the level-1 workspace, start the agent in a new tab over herdr's socket. the row is updated withworkspace_id,tab_id, andpane_idimmediately after each herdr call, andcancel_requestedis checked before and after each one. - capture
agent_session_*as soon as herdr reports it: the gate forlogs. progress markers reset the stuck-start guard. - deliver the first prompt (turn 1, two-call rule). status
startingtoworking.
the launch token is the recovery anchor
herdr's socket exposes no pane env, so ORCR_ID and the launch token cannot be read back off a live pane. recovery instead matches a pane to its row by herdr's label (the agent's full path), which active-path uniqueness makes unambiguous. this is re-matching by identity, not location guessing.
the launch token (unique per attempt) stays injected in pane env and persisted (row plus launch.json) as the correctness anchor, ready for a future herdr that exposes pane env. on restart, a row with a recorded pane_id is confirmed against the live snapshot: present means repaired (starting to working); gone means failed if it was starting, else lost. a starting row that never recorded a pane_id becomes failed, and a matching orphan pane found by label is closed. no duplicate pane survives either way.
reconciliation
reconciliation is the drift repair between the store and herdr reality, run on server start and periodically:
- lost panes. a managed agent whose pane vanished becomes
lost(path reserved). it resolves toended (lost)once herdr is reachable and one following poll still does not show the terminal, or on an explicit kill. a herdr outage alone never frees names, but there is no indefinite quarantine either. - orphan agent panes, reported not touched. an owned-session pane herdr reports as running an agent, with no matching store row (the store was moved or reset under a live session, or a crashed duplicate attempt), is counted and reported in
server statusas an orphan agent pane, never touched. clean it up via herdr. - unmarked panes, reported not touched. a plain shell in the owned session (no agent) is counted and reported, never touched.
- half-done moves. a park or un-park with
move_stateset is completed or rolled back (see agent lifecycle and GC). - unmanaged discovery. herdr-detected agents in your other sessions are discovered as unmanaged rows keyed by
(session, terminal_id)and kept current; rows whose terminal disappears are markedended. see managed vs unmanaged agents.
server status surfaces the drift counts (lost, repaired, orphan agent panes, unmarked panes) so you can see what reconciliation is doing.
events, cursors, and intent/applied pairs
event rows are written in the same transaction as the store change they describe. events.seq is the monotonic cursor, and the durable cursor is the events table itself; an in-memory bus only coordinates subscriber wakeups.
transitions that depend on an external side effect (pane move or close, process signal, spawn) use an intent/applied pair: a *_requested event persisted before the herdr or OS call, a *_applied event after the observed fact. reconciliation emits repair events for anything it derives after a crash, so subscribers never see a state the recovery path skipped.
subscriptions accept since_seq; snapshots carry snapshot_seq. watch.open creates the snapshot and subscription under one server-side cursor pin, so high churn cannot expire snapshot_seq before the subscribe lands, with no re-snapshot livelock. replay retention is bounded; a too-old cursor on an unpinned subscribe gets a server_error with cause: cursor_expired and re-snapshots. this snapshot-then-subscribe protocol is what wait, orcr top, and the SDK's watch() ride, so none of them miss or double-apply an update. the full event catalogue is in the socket API reference.
behavior across disruptions
- sleep and reboot. missed loop fires are skipped and logged, never replayed. GC clocks are recomputed from persisted timestamps. the reconciler resolves
lostpanes and half-done moves on server start. see loops. - herdr restart or crash. the driver reconnects with backoff; agents keep running (panes are herdr-server-side). a herdr that comes back with different pane ids is re-matched by label (the full path), never by location.
- transcript drift. provider transcript formats are unstable private APIs; adapters are version-pinned and smoke-tested per provider release.
version skew
both sockets are version-negotiated:
- orcr client to orcr server: protocol negotiation on the first request; a mismatch returns
unsupported_version. - orcr server to herdr: the herdr protocol number is handshake-checked, with a clear error naming the required herdr version.
- two orcr versions sharing one store: a schema version check with refusal-and-message (
store_version_mismatch), so an older binary cannot corrupt a newer store.
unknown protocol fields are ignored (additive evolution), and a max frame size is enforced. see architecture for the protocol versions in play.
data and file conventions
the ~/.orcr/data tree that mirrors the path tree, the file convention for getting structured output from an agent, and where workflow code lives.
security and permissions model
the trust boundary. Unix-socket auth, home-directory safety, prompt injection through child output, and what runs with bypassed permissions today.