Write-Ahead Reconciliation
Clients optimistically apply their own actions locally, then reconcile when the server echoes them back alongside any concurrent actions from other clients or the server itself. This provides instant UI feedback while maintaining server-authoritative consistency.
Client-Side State
Each client maintains per-subscription:
confirmedState— Last fully server-acknowledged state.pendingActions[]— Optimistically applied but not yet echoed by server.optimisticState—confirmedStatewithpendingActionsreplayed on top (computed, not stored).
confirmedState ──► apply pending[0] ──► apply pending[1] ──► ... ──► optimisticStateThe UI always renders optimisticState. Users see their actions reflected immediately.
Reconciliation Algorithm
When the client receives an ActionEnvelope from the server:
Own action echoed (
origin.clientId === myIdand matches head ofpendingActions):- Pop from pending, apply to
confirmedState.
- Pop from pending, apply to
Foreign action (different origin or server-originated):
- Apply to
confirmedState, rebase remainingpendingActions.
- Apply to
Rejected action (server echoed with
rejectionReasonpresent):- Remove from pending (optimistic effect reverted). The
rejectionReasonMAY be surfaced to the user.
- Remove from pending (optimistic effect reverted). The
Recompute
optimisticStatefromconfirmedState+ remainingpendingActions.
Example
Time Server Client A Client B
─────────────────────────────────────────────────────────────────────────────────
t1 dispatch(turnStarted)
pending: [turnStarted]
optimistic: has activeTurn
t2 receives turnStarted from A
applies, broadcast seq=10
t3 receives seq=10 (own echo)
pop from pending
confirmed = optimistic
t4 agent produces delta
broadcasts seq=11
t5 receives seq=11 (foreign) receives seq=11
apply to confirmed apply to confirmedWhy Rebasing Is Simple
Most session actions are append-only:
- Add turn, append delta, add tool call, append response part.
Pending actions still apply cleanly to an updated confirmed state because they operate on independent data — the turn the client created still exists; the content it appended is additive.
The rare true conflict (two clients abort the same turn) is resolved by server-wins semantics — the server's echo is the source of truth.
Reconnection
If the transport connection drops:
// Client → Server (request)
{
"jsonrpc": "2.0",
"id": 2,
"method": "reconnect",
"params": {
"clientId": "client-1",
"lastSeenServerSeq": 42,
"subscriptions": ["agenthost:/root", "copilot:/<uuid>"]
}
}The server MUST include all replayed data in the response. If the gap is within the replay buffer, the response contains missed action envelopes. If the gap exceeds the buffer, the response contains fresh snapshots instead. In both cases, the client resets confirmedState accordingly and clears pendingActions.
Protocol notifications (like sessionAdded/sessionRemoved) are not replayed — the client should re-fetch the session list.
Next Steps
- Actions — The action types that flow through reconciliation.
- Subscriptions — How URI-based subscriptions work.
- Lifecycle — Session creation, handshake, and shutdown.