Skip to content

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 — confirmedState with pendingActions replayed on top (computed, not stored).
confirmedState ──► apply pending[0] ──► apply pending[1] ──► ... ──► optimisticState

The UI always renders optimisticState. Users see their actions reflected immediately.

Reconciliation Algorithm ​

When the client receives an ActionEnvelope from the server:

  1. Own action echoed (origin.clientId === myId and matches head of pendingActions):

    • Pop from pending, apply to confirmedState.
  2. Foreign action (different origin or server-originated):

    • Apply to confirmedState, rebase remaining pendingActions.
  3. Rejected action (server echoed with rejectionReason present):

    • Remove from pending (optimistic effect reverted). The rejectionReason MAY be surfaced to the user.
  4. Recompute optimisticState from confirmedState + remaining pendingActions.

Example ​

The homepage walks through this flow in a short, scripted demo that runs entirely in your browser: the "host" and every client mirror run through the canonical reducers, the agent and terminal output are scripted, and no network requests are made. An optimistic message renders faded until the host acknowledges it. The phone then approves a tool call and loses its connection while the host keeps going. When it reconnects, it sends one reconnect request with lastSeenServerSeq and its subscriptions, and the missed envelopes arrive in order inside the response. The client drops its pending queue and applies them to its confirmed state to converge. An opt-in detail shows the snapshot fallback for a gap too old to replay. The demo then shows automations, terminals and capability negotiation as examples of extending AHP with more channels. With reduced motion enabled, the steps are identical, but devices swap in place without sliding or zooming. The navigation's theme switch applies to the demo, device screens and the rest of the documentation; your light or dark preference persists between pages.

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 confirmed

Why Rebasing Is Simple ​

Most chat 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:

jsonc
// Client → Server (request)
{
  "jsonrpc": "2.0",
  "id": 2,
  "method": "reconnect",
  "params": {
    "channel": "ahp-root://",
    "clientId": "client-1",
    "lastSeenServerSeq": 42,
    "subscriptions": ["ahp-root://", "ahp-session:/<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 ​

Released under the MIT License.