Example: Reconnect with `replay_from`
Example: Reconnect with `replay_from`
Alice is attached as a controller. Her network drops mid-session; she reconnects with `replay_from` set to the last `sequence` she successfully received, and the server replays the missed frames followed by a fresh `Keyframe`.
Pre-conditions
- Session `01HW8Q3M9C7K5P0XZ8N4F2RV1Q` is running.
- Alice's last received `sequence` before disconnect was `400`.
- Server's `current_sequence` is now `437`.
- The replay buffer retains frames `50..437`. Alice's `replay_from = 400` is well within retention.
Phase A — disconnect
The TCP connection to the WS endpoint resets at `2026-05-09T14:42:18Z`. The client's WS library surfaces a close event with code `1006` (abnormal closure). Alice's terminal UI shows "reconnecting..." but does not clear the screen — the prior screen state remains rendered.
Alice's client persists:
- `session_id = "01HW8Q3M9C7K5P0XZ8N4F2RV1Q"`
- `last_received_sequence = 400`
The server keeps the session running. Other attached clients are unaffected. If the server observes a clean close or transport error, Alice's controller slot is removed immediately. If the network path goes half-open and no close reaches the server, the `pty-ws/v1` heartbeat eventually reaps the stale connection and runs the same detach cleanup.
Phase B — reconnect
1. WebSocket upgrade
GET /agents/inst-42/sessions/01HW8Q3M9C7K5P0XZ8N4F2RV1Q/attach HTTP/1.1
Sec-WebSocket-Protocol: pty-ws.v1
Authorization: Bearer eyJhbGciOi...<alice's token>
2. Server → Alice: `binding_hello`
{
"op": "binding_hello",
"id": "01hw8qm0qa0a8m7r9z3b4tc6b0",
"ts": "2026-05-09T14:42:25.000Z",
"sequence": 0,
"payload": {
"binding_uri": "https://agentic-sandbox.aiwg.io/bindings/pty-ws/v1",
"binding_version": "1.0.0",
"supported_operations": [
"SendMessage", "SendStreamingMessage", "GetTask",
"ListTasks", "CancelTask", "SubscribeToTask"
],
"activated_extensions": [],
"session": {
"session_id": "01HW8Q3M9C7K5P0XZ8N4F2RV1Q",
"current_sequence": 437
}
}
}
3. Alice → server: `pty.join_session` with `replay_from`
{
"op": "pty.join_session",
"id": "01hw8qm117a8m7r9z3b4tc6b1",
"ts": "2026-05-09T14:42:25.080Z",
"extensions": [
"https://agentic-sandbox.aiwg.io/extensions/pty-extensions/v1"
],
"replay_from": 400,
"payload": {
"role": "controller",
"client_label": "alice@laptop"
}
}
The envelope-level `replay_from: 400` tells the server: "send me everything with `sequence > 400`."
4. Server → Alice: `RoleAssigned`
The server reassigns the controller role (capacity permitting):
{
"op": "pty.session_frame",
"id": "01hw8qm127a8m7r9z3b4tc6b2",
"ts": "2026-05-09T14:42:25.110Z",
"sequence": 438,
"payload": {
"kind": "RoleAssigned",
"role": "controller",
"client_id": "c-alice-7f3a"
}
}
The server issues a connection-local `client_id` for the new attachment. The controller role is granted when capacity and authorization permit; any stale prior controller slot must already have been released by clean-close handling or heartbeat reap.
5. Server → Alice: replayed frames
The server re-emits all frames with `sequence ∈ (400, 437]` in original order. Each frame's `sequence` is the original value, not a new one. Example excerpts:
{
"op": "pty.session_frame",
"id": "01hw8qm0a1a8m7r9z3b4tc6b3",
"ts": "2026-05-09T14:42:25.111Z",
"sequence": 401,
"payload": {
"kind": "Output",
"stream": "stdout",
"data": "Y29tcGlsaW5nIG1vZHVsZSBmb28uLi4K"
}
}
{
"op": "pty.session_frame",
"id": "01hw8qm0a2a8m7r9z3b4tc6b4",
"ts": "2026-05-09T14:42:25.112Z",
"sequence": 402,
"payload": {
"kind": "Output",
"stream": "stderr",
"data": "d2FybmluZzogdW51c2VkIGltcG9ydCB4Cg=="
}
}
...continues through `sequence: 437`. Alice's client applies each frame to its rendering buffer in order.
6. Server → Alice: post-replay `Keyframe`
After replay, the server emits a fresh Keyframe at the current head:
{
"op": "pty.session_frame",
"id": "01hw8qm0b3a8m7r9z3b4tc6b5",
"ts": "2026-05-09T14:42:25.140Z",
"sequence": 439,
"payload": {
"kind": "Keyframe",
"snapshot": "<base64 of current screen state>",
"snapshot_format": "vt100-screen-state-v1",
"snapshot_cols": 140,
"snapshot_rows": 40,
"anchor_sequence": 439
}
}
Alice's client uses the Keyframe to reconcile any rendering drift caused by partially-applied frames at the disconnect boundary. The snapshot is authoritative; the client discards its rendering state and replaces it with the snapshot's screen.
7. Server → all attached: `MembershipChanged`
{
"op": "pty.session_frame",
"id": "01hw8qm0c4a8m7r9z3b4tc6b6",
"ts": "2026-05-09T14:42:25.141Z",
"sequence": 440,
"payload": {
"kind": "MembershipChanged",
"controllers": [
{ "client_id": "c-alice-7f3a", "label": "alice@laptop" }
],
"observers": [
{ "client_id": "c-bob-9f1e", "label": "bob@review" }
]
}
}
8. Live streaming resumes
From `sequence: 441` forward, frames are produced by ongoing PTY activity and broadcast to all attached clients. Alice can now send `pty.session_input` again.
Failure mode: `replay_from` out of range
If Alice had reconnected with `replay_from: 25` (older than the retained `sequence: 50`), the server would respond:
{
"op": "Error",
"id": "01hw8qm117a8m7r9z3b4tc6b1",
"ts": "2026-05-09T14:42:25.115Z",
"payload": {
"code": "REPLAY_OUT_OF_RANGE",
"message": "replay_from=25 precedes oldest retained frame (sequence=50)",
"a2a_error": null,
"retryable": false
}
}
...followed immediately by a fresh `Keyframe` so Alice can resume with a coherent screen even though her prior history is unrecoverable:
{
"op": "pty.session_frame",
"id": "...",
"ts": "...",
"sequence": 438,
"payload": {
"kind": "Keyframe",
"snapshot": "<base64>",
"snapshot_format": "vt100-screen-state-v1",
"snapshot_cols": 140,
"snapshot_rows": 40,
"anchor_sequence": 437
}
}
Alice's client must treat any pre-disconnect state as lost and rely solely on the new Keyframe for subsequent rendering.
Sequence summary (happy path)
Alice Server
│ ... sequence 400 ────────│ (then TCP reset)
╳ network drop │
│ ...sequences 401..437 produced & buffered
│ HTTP upgrade ───────────▶│
│◀── binding_hello │
│ pty.join_session │
│ replay_from: 400 ──▶│
│◀── RoleAssigned (438) │
│◀── Output (401, replay) │
│◀── Output (402, replay) │
│ ... │
│◀── Output (437, replay) │
│◀── Keyframe (439) │
│◀── MembershipChanged(440)│
│ ...live streaming... │
Notes
- `replay_from` is exclusive (`sequence > replay_from`), not inclusive. Alice's `replay_from: 400` skips re-sending frame 400.
- Replayed frames preserve their original `sequence`; they are not renumbered. This is what allows clients to detect duplicates if the same client had a stale buffer.
- Replayed frames preserve their original `ts`; the client can distinguish "this was produced 7 seconds ago" from "this is live."
- `id` values on replayed frames MAY be regenerated by the server. Clients MUST NOT rely on `id` for ordering — `sequence` is the only ordering key.
- The `Keyframe` at step 6 is mandatory after a non-empty replay. It guarantees Alice converges to a coherent state regardless of how the disconnect interrupted partial rendering.
- See `pty-extensions/v1` spec §10.1 for the security implication: `replay_from` is a session-scoped capability, not a per-principal one. An attacker holding valid session credentials can replay any frame within retention.