`pty-ws/v1` — A2A Custom Protocol Binding

`pty-ws/v1` — A2A Custom Protocol Binding

URI: `https://agentic-sandbox.aiwg.io/bindings/pty-ws/v1` Spec version: `1.0.0` Stability tier: `beta` (per ADR-020; graduates to `stable` after v2.0 conformance harness validates the binding in production) Status: Authored 2026-05-09 Owner: roctinam/agentic-sandbox Related ADRs: ADR-018, ADR-019, ADR-020, ADR-022

The key words MUST, MUST NOT, REQUIRED, SHALL, SHALL NOT, SHOULD, SHOULD NOT, RECOMMENDED, MAY, and OPTIONAL in this document are to be interpreted as described in RFC 2119 and RFC 8174.


1. Overview

`pty-ws/v1` is a custom A2A protocol binding that carries the full A2A v1.0.0 core operation surface over a single bidirectional WebSocket connection scoped to one `(instance_id, session_id)` pair. The binding exists because A2A's three standard transports (HTTP+JSON/REST, JSON-RPC, gRPC) cannot model interactive terminal attach efficiently: PTY I/O is full-duplex, low-latency, and produces frames at keystroke cadence.

This binding is the transport layer. PTY-specific verbs (role assignment, replay, MembershipChanged, Keyframe) live in the companion extension `pty-extensions/v1` which MUST be activated alongside this binding for any meaningful PTY workload.

1.1 Conformance to A2A custom-binding rules

Per the A2A custom-binding specification §5, this binding satisfies:

1. Functional equivalence — Every A2A core operation (`SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`) is implemented as a request/response frame pair on the WebSocket. See §4. 2. Data model preservation — Wire payloads are JSON encodings of the A2A canonical proto messages with no semantic loss. Binary PTY data is base64-encoded for transport inside JSON envelopes. 3. Behavioral consistency — The error vocabulary, state-transition semantics, and authentication contracts of A2A core are preserved. See §6 (errors), §7 (auth).

1.2 Non-conformance / non-goals

  • The binding does NOT support `PushNotificationConfig` CRUD efficiently. Implementations MAY accept those operations but SHOULD advertise REST/JSON-RPC as the preferred transport for control-plane operations. See §10.
  • The binding is scoped to one session per connection. Cross-session multiplexing on a single WebSocket is NOT supported in v1; clients open one WS per `(instance_id, session_id)`.

2. Transport

2.1 URL pattern

wss://<host>/agents/{instance_id}/sessions/{session_id}/attach
  • Schemes: `wss://` (REQUIRED in production); `ws://` (development only).
  • `{instance_id}` — A2A AgentCard `instance_id` for the per-instance surface (per ADR-022).
  • `{session_id}` — UUIDv7; MUST identify a session that exists in the executor's session registry. Servers MUST return HTTP `404` during the WS upgrade if the session is unknown.

2.2 WebSocket subprotocol

Clients SHOULD negotiate either `pty-ws.v1` or `pty-ws.v1.binary` via the `Sec-WebSocket-Protocol` header. Servers that accept a JSON/base64 connection MUST echo only `pty-ws.v1`. Servers that accept a binary hot-path connection MUST echo only `pty-ws.v1.binary`. Servers MUST refuse the upgrade (HTTP `400`) if the requested subprotocol header is present and does not include a supported PTY token.

During the v1 compatibility window, servers MAY accept an absent subprotocol header only when PTY attach auth is disabled for local development or an explicitly trusted harness. Production deployments MUST require either `pty-ws.v1` or `pty-ws.v1.binary`.

2.3 Frame discipline

  • Under `pty-ws.v1`, all application data is exchanged as WebSocket TEXT frames containing UTF-8 JSON (see §3).
  • Under `pty-ws.v1.binary`, JSON control frames still use WebSocket TEXT frames; hot PTY input and output use WebSocket BINARY frames (see §3.1).
  • A JSON frame MUST be a single JSON object — no JSON Lines, no concatenated objects.

2.4 Keepalive and stale-client reaping

Clients and servers MUST support standard WebSocket Ping/Pong control frames. Servers SHOULD send a Ping at least every `30s` and MUST reap a connection that does not return a Pong within the implementation's timeout window. The reference executor sends Pings every `30s`, marks the connection stale after `90s` without a Pong, then exits the connection loop and runs the normal detach cleanup.

Detach cleanup MUST release any roles held by that connection, including the controller role, and MUST detach the connection from the underlying PTY bridge when one is active. This is required for idle/backgrounded browser tabs, half-open network paths, and dropped sockets: a stale controller MUST NOT leave the session permanently read-only for later controller attaches.

Application-level `ping`/`pong` frames are also defined in §5.6.


3. Frame envelope

Every frame sent on this binding MUST match the following envelope:

{
  "op": "<operation-name>",
  "id": "<request-id-uuidv7>",
  "ts": "2026-05-09T14:30:00.000Z",
  "sequence": 42,
  "replay_from": null,
  "service_parameters": { "trace_id": "..." },
  "extensions": ["https://agentic-sandbox.aiwg.io/extensions/pty-extensions/v1"],
  "payload": { ... }
}
FieldTypeRequiredNotes
`op`stringyesOperation name. See §4 for the registered set. Servers MUST reject unknown `op` with `UNSUPPORTED_OPERATION`.
`id`stringyesUUIDv7 identifying the request/response pair. Server responses MUST echo the request's `id`. Server-initiated frames generate fresh `id`s.
`ts`stringyesRFC 3339 timestamp at sender. Servers MUST NOT rely on client `ts` for ordering.
`sequence`integerserver-onlyMonotonic per-session frame counter assigned by the server on every server→client frame. Clients MUST NOT set `sequence` on outbound frames. See §8 (replay).
`replay_from`integeroptionalClient-only. Set on the first `SubscribeToTask` or extension `join_session` after a reconnect to request frames since `sequence`. See §8.
`service_parameters`objectoptionalA2A service parameters (trace context, auth hints). Present on the first client frame; cached by the server for the lifetime of the connection.
`extensions`string[]optionalA2A-Extensions activation list. Equivalent to the `A2A-Extensions` HTTP header on REST/JSON-RPC bindings. Sent on the first client frame.
`payload`objectyesOperation-specific body. Schema varies by `op`; see §4 and `frames.schema.json`.

The full envelope schema is published as `frames.schema.json` (JSON Schema 2020-12).

3.1 Binary PTY I/O frames

The `pty-ws.v1.binary` subprotocol keeps all role, resize, keyframe, error, task, replay, and membership traffic as JSON text frames. Only hot PTY bytes move to binary WebSocket frames.

Server-to-client PTY output frame:

offset  size  field
0       4     ASCII magic "PW1O"
4       8     unsigned big-endian per-session sequence
12      1     stream id: 1 = stdout/pty stream
13      N     raw PTY output bytes

Client-to-server PTY input frame:

offset  size  field
0       4     ASCII magic "PW1I"
4       N     raw PTY input bytes

Binary frames are scoped by the WebSocket URL's `(instance_id, session_id)` and the authenticated connection principal; they do not repeat `session_id` or `client_id` in every hot-path frame. The server assigns output sequence numbers from the same per-session counter used by JSON frames. Replay and keyframes remain JSON: the implementation stores a JSON/base64 replay representation so legacy clients and transcript/archive readers can consume the same retained history.


4. A2A core operation mapping

This section enumerates the mapping for every A2A core operation onto `pty-ws/v1` frames. Implementations MUST support all six operations.

4.1 `SendMessage`

Request frame:

{ "op": "SendMessage", "id": "...", "ts": "...", "payload": { "message": <a2a.Message> } }

Response frame (server → client, same `id`):

{ "op": "SendMessage.Response", "id": "...", "ts": "...", "payload": { "task": <a2a.Task> } }

Semantics: identical to A2A core `SendMessage`. The server MUST persist the message to the session's task and return the resulting `Task` snapshot. The response arrives on the same WebSocket connection.

4.2 `SendStreamingMessage`

Request frame:

{ "op": "SendStreamingMessage", "id": "...", "ts": "...", "payload": { "message": <a2a.Message> } }

Initial response (acknowledgement):

{ "op": "SendStreamingMessage.Accepted", "id": "...", "payload": { "task_id": "..." } }

Subsequent task updates flow as zero or more frames with `op = "TaskStatusUpdate"`:

{ "op": "TaskStatusUpdate", "id": "<fresh-uuid>", "sequence": 17, "payload": { "task": <a2a.Task>, "delta": <a2a.TaskStatusUpdateEvent> } }

A terminal frame (`task.status.state ∈ {COMPLETED, FAILED, CANCELED, INPUT_REQUIRED}`) MUST be the last update for that task.

4.3 `GetTask`

Request:

{ "op": "GetTask", "id": "...", "payload": { "task_id": "...", "history_length": 50 } }

Response:

{ "op": "GetTask.Response", "id": "...", "payload": { "task": <a2a.Task> } }

4.4 `ListTasks`

Request:

{ "op": "ListTasks", "id": "...", "payload": { "page_size": 50, "page_token": null, "filter": null } }

Response:

{ "op": "ListTasks.Response", "id": "...", "payload": { "tasks": [<a2a.Task>], "next_page_token": "..." } }

Implementations MAY scope `ListTasks` results to the current session only; this is a documented binding-level deviation from the A2A core (which scopes to the agent). Clients that need full agent-scoped listing SHOULD use the REST/JSON-RPC binding. The deviation MUST be advertised in the AgentCard `bindings[].notes` field.

4.5 `CancelTask`

Request:

{ "op": "CancelTask", "id": "...", "payload": { "task_id": "..." } }

Response:

{ "op": "CancelTask.Response", "id": "...", "payload": { "task": <a2a.Task> } }

The server MUST transition the task to `CANCELED` and return the updated snapshot. Active streaming subscriptions on that task MUST receive a final `TaskStatusUpdate` with the `CANCELED` state.

4.6 `SubscribeToTask`

Request:

{ "op": "SubscribeToTask", "id": "...", "payload": { "task_id": "..." }, "replay_from": 100 }

Initial acknowledgement:

{ "op": "SubscribeToTask.Accepted", "id": "...", "payload": { "current_sequence": 142 } }

Subsequent frames: zero or more `TaskStatusUpdate` frames as in §4.2. The subscription MUST persist for the life of the WebSocket connection unless explicitly canceled with `Unsubscribe`.

`Unsubscribe`:

{ "op": "Unsubscribe", "id": "...", "payload": { "task_id": "..." } }

Server response: `{ "op": "Unsubscribe.Response", "id": "..." }`.

4.7 `PushNotificationConfig` (degraded)

Implementations MAY support `SetTaskPushNotificationConfig`, `GetTaskPushNotificationConfig`, `ListTaskPushNotificationConfig`, and `DeleteTaskPushNotificationConfig` as request/response frames mirroring the A2A core shapes. If unsupported, servers MUST respond with the `UNSUPPORTED_OPERATION` error (§6) and clients MUST fall back to REST/JSON-RPC for these operations.


5. Server-initiated frames

5.1 `binding_hello`

The server MUST send a single `binding_hello` frame as the first frame on every accepted connection, before any client frames are processed:

{
  "op": "binding_hello",
  "id": "...",
  "ts": "...",
  "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": ["https://agentic-sandbox.aiwg.io/extensions/pty-extensions/v1"],
    "session": { "session_id": "...", "current_sequence": 0 },
    "session_host": {
      "supported_backends": ["native"],
      "default_backend": "native",
      "supported_classes": ["direct"],
      "default_class": "direct",
      "observe_supported": true,
      "drive_supported": true,
      "reattach_supported": true
    },
    "payload_mode": {
      "binary": false,
      "binary_subprotocol": "pty-ws.v1.binary",
      "json_subprotocol": "pty-ws.v1"
    }
  }
}

Clients MUST read `binding_hello` before issuing any operation and SHOULD feature-gate based on `supported_operations`. `session_host` advertises the #461 terminal-host capability set for this binding. `native` means the PTY bridge starts a process directly without a terminal multiplexer. The default no-op bridge advertises only `native`/`direct`; the real agent PTY bridge advertises `native`/`direct` plus `screen`/`managed`, `zellij`/`managed`, and `tmux`/`managed`.

5.1.1 Session Host Selection

`pty.join_session` MAY include session-host selection and process-start metadata. Servers MUST validate the requested backend/class against `binding_hello.payload.session_host` before assigning a role or starting a bridge session.

{
  "op": "pty.join_session",
  "payload": {
    "session_backend": "native",
    "session_class": "direct",
    "argv": ["/bin/bash", "-l"],
    "cwd": "/workspace",
    "env": { "TERM": "xterm-256color" },
    "terminal_size": { "cols": 132, "rows": 43 }
  }
}

`backend` is accepted as an alias for `session_backend`; `working_dir` is accepted as an alias for `cwd`. `argv` is preferred for exact process launch. For simple command strings, `command` MAY be supplied and is launched via `/bin/sh -lc <command>`. If neither `argv` nor `command` is supplied, the server uses its default login shell command. Unsupported selections fail closed with `session_backend.not_implemented` or `session_class.not_implemented`. Backends/classes are also validated as pairs: `native` is currently direct only, and multiplexer backends are managed only.

When a client joins an existing managed session and omits `cwd` (or supplies an empty value), the server reuses the effective working directory recorded when that session was created. An explicit non-empty join `cwd` overrides the stored value. This fallback keeps REST-created sessions and later PTY WebSocket joins on the same working-directory contract.

5.2 `TaskStatusUpdate`

See §4.2.

5.3 `Error`

See §6.

5.4 `binding_goodbye`

Sent by the server immediately before initiating an orderly close (close code `1000`):

{ "op": "binding_goodbye", "id": "...", "payload": { "reason": "server_shutdown" } }

5.5 Extension-defined frames

Frames whose `op` is namespaced by an activated extension (e.g. `pty.session_frame`, `pty.session_input`) are governed by that extension's spec. The binding only requires the envelope shape (§3); payload semantics are deferred.

5.6 `ping` / `pong`

Application-level keepalive in addition to WS control frames:

{ "op": "ping", "id": "...", "payload": { "client_ts": "..." } }
{ "op": "pong", "id": "...", "payload": { "client_ts": "...", "server_ts": "..." } }

6. Error mapping

A2A errors are mapped to `Error` frames:

{
  "op": "Error",
  "id": "<echoes-the-failed-request-id>",
  "ts": "...",
  "payload": {
    "code": "TASK_NOT_FOUND",
    "message": "Task abc-123 not found in session xyz",
    "a2a_error": <a2a.Error>,
    "retryable": false
  }
}
Binding codeA2A core codeWhen
`TASK_NOT_FOUND``TaskNotFound``GetTask`/`CancelTask`/`SubscribeToTask` against unknown task
`TASK_NOT_CANCELABLE``TaskNotCancelable``CancelTask` against terminal-state task
`INVALID_REQUEST``InvalidRequest`Malformed envelope, missing required fields
`UNSUPPORTED_OPERATION``UnsupportedOperation`Unknown `op`, or `op` not in `supported_operations`
`UNAUTHENTICATED``Unauthenticated`Missing/invalid auth (also expressible as WS close `4401`)
`PERMISSION_DENIED``PermissionDenied`Caller lacks role for the requested action
`RATE_LIMITED``RateLimited`Server-side throttling
`INTERNAL``Internal`Catch-all server fault
`REPLAY_OUT_OF_RANGE`(binding-specific)`replay_from` precedes the oldest retained frame

Servers MUST populate `a2a_error` with the canonical A2A error object so clients can route on it identically to other bindings.

Fatal binding errors MAY be delivered as a final `Error` frame followed by an immediate WebSocket close. Recommended close codes:

Close codeMeaning
`1000`Normal closure (client `leave_session`, server orderly shutdown)
`1011`Server error or heartbeat timeout when the implementation can send an orderly close
`4400`Protocol violation (bad envelope, unknown subprotocol)
`4401`Authentication failure
`4403`Authorization failure
`4404`Session or task not found
`4429`Rate limited

Codes in the `4400`–`4499` range are application-defined per RFC 6455 §7.4.2.


7. Authentication and authorization

7.1 Auth schemes

This binding inherits the agent's A2A `securitySchemes`. Supported schemes:

1. Bearer token — `Authorization: Bearer <token>` HTTP header on the WebSocket upgrade request. REQUIRED for production. Tokens MUST be validated against the same identity provider as the agent's REST/JSON-RPC bindings. 2. mTLS — client certificate validated during the TLS handshake of `wss://`. Agents that publish mTLS in `securitySchemes` MUST enforce certificate identity claims as the caller principal. 3. Subprotocol-embedded token — for browser clients that cannot set `Authorization` headers, the bearer token MAY be passed as `Sec-WebSocket-Protocol: pty-ws.v1, bearer.<base64url-token>`. Servers MUST strip the bearer half before echoing the subprotocol.

The reference executor uses an optional hash-only PTY attach token map. When the map is configured, missing or unknown bearer tokens fail before WebSocket upgrade with HTTP `401`. When the map is absent, unauthenticated attach remains available only for local/back-compat deployments.

7.2 Authorization

The connection principal is established at upgrade time and MUST NOT change for the life of the WebSocket. Per-frame authorization is performed by the binding implementation against the principal. Role-based access (controller vs observer) is governed by the `pty-extensions/v1` extension.

The minimum PTY scopes are:

ScopeGrants
`pty:observe`Attach, live output, replay, keyframe requests, and task/control-plane reads for the scoped session.
`pty:control`All observe permissions plus controller role assignment, `pty.session_input`, and `pty.session_resize`.
`pty:admin`All control permissions plus future forced transfer/close operations.

Observer-scoped principals MUST NOT be promoted to controller and MUST receive `PERMISSION_DENIED`/HTTP-equivalent `403` errors for input, resize, and controller role requests.

7.3 Token rotation

Clients whose bearer tokens approach expiry SHOULD open a fresh WebSocket with the new token, then close the old one with code `1000` after the new connection is established.


8. Streaming, ordering, reconnection, replay

8.1 Ordering

Server→client frames within a single connection are strictly ordered by `sequence`. The server MUST assign `sequence` monotonically (no gaps) per session — the counter is shared across all clients attached to the session, not per-connection.

8.2 Replay buffer

The server MUST retain at least the larger of:

  • The last 1000 frames per session, OR
  • All frames produced in the last 24 hours.

Implementations MAY retain more. Eviction beyond the minimum is implementation-defined.

8.3 Reconnection with `replay_from`

After a transport failure, the client MAY reconnect to the same `(instance_id, session_id)` URL and on its first qualifying request frame include `replay_from: <last-received-sequence>`:

{ "op": "SubscribeToTask", "id": "...", "payload": { "task_id": "..." }, "replay_from": 142 }

Server behavior:

  • If `replay_from` is within the retained window, the server MUST emit all frames with `sequence > replay_from` in order, then resume live streaming. For PTY sessions, the server SHOULD prepend a `Keyframe` (extension-defined) to give the client a coherent restart point.
  • If `replay_from` precedes the oldest retained frame, the server MUST respond with a single `Error` frame (`code: REPLAY_OUT_OF_RANGE`) and SHOULD follow with a fresh `Keyframe`. The client MUST treat its prior state as lost.
  • If `replay_from` is omitted on reconnect, the server treats it as a fresh subscription and emits the current `Keyframe` (if applicable) plus live frames.

8.4 Cross-connection replay scope

The `replay_from` cursor is bound to `session_id`, not to the connection or principal. Two separate clients reconnecting with the same `replay_from` MUST receive identical frame sequences (modulo any per-principal authorization filtering).


9. Service parameters and metadata

A2A service parameters (trace context, idempotency keys, tenant scoping) are carried on the first client frame in `service_parameters`. The server MUST cache the values for the connection lifetime and MUST propagate them into emitted task events and logs.

To update service parameters mid-connection, the client sends:

{ "op": "UpdateServiceParameters", "id": "...", "payload": { "service_parameters": { ... } } }

The server replies `{ "op": "UpdateServiceParameters.Response", "id": "..." }`.


10. AgentCard advertisement

Agents that support this binding MUST declare it in their AgentCard:

{
  "bindings": [
    {
      "uri": "https://agentic-sandbox.aiwg.io/bindings/pty-ws/v1",
      "endpoint": "wss://host.example/agents/inst-42/sessions/{session_id}/attach",
      "preference": "secondary",
      "operations": ["SendMessage", "SendStreamingMessage", "GetTask", "ListTasks", "CancelTask", "SubscribeToTask"],
      "notes": "Optimal for interactive PTY attach; control-plane operations preferred via REST."
    }
  ],
  "capabilities": {
    "extensions": [
      {
        "uri": "https://agentic-sandbox.aiwg.io/extensions/pty-extensions/v1",
        "required": false
      }
    ]
  }
}

`preference: "secondary"` signals that clients SHOULD prefer REST/JSON-RPC for `ListTasks`, `PushNotificationConfig`, and other control-plane operations, reserving `pty-ws/v1` for actual session attach.


11. Security considerations

  • Transport — Production deployments MUST require `wss://`. Plain `ws://` MUST be rejected unless an explicit operator override is set.
  • Auth on upgrade — Authentication MUST be enforced at the HTTP upgrade phase (before WS frames flow). Anonymous WS attach is forbidden.
  • Token leakage in URLs — Tokens MUST NOT be carried in the WS URL query string. Use headers or subprotocol-embedded form.
  • Resource exhaustion — Servers MUST rate-limit accepted connections per principal and per `instance_id`. Per A2A, sustained abuse warrants `RATE_LIMITED` errors and connection close.
  • Replay buffer information disclosure — `replay_from` does not perform principal-based filtering of historical frames. Implementations that gate session access on per-principal ACLs MUST apply those ACLs to replayed frames as well as live frames.
  • Cross-session multiplexing — Forbidden in v1; servers MUST reject any frame whose `payload` references a `session_id` other than the connection's bound session.
  • See `pty-extensions/v1` §Security for PTY-specific risks (controller hijack, observer disclosure, cursor tampering).

12. Reference implementation

  • Rust crate: `agentic-sandbox-executor`, module `bindings::pty_ws` (per ADR-021).
  • Frame serialization: `serde_json` with explicit envelope structs.
  • Replay buffer: shared with mission outbox storage (ADR-014, SQLite).

13. Conformance

A `pty-ws/v1` implementation MUST pass the conformance harness scenarios:

1. `binding_hello` first — the first server frame is `binding_hello` and advertises all six core operations. 2. All six core ops — request/response round-trip succeeds for each of `SendMessage`, `SendStreamingMessage`, `GetTask`, `ListTasks`, `CancelTask`, `SubscribeToTask`. 3. Error mapping — every entry in §6's error table is reachable and produces a frame with the documented `code` and a populated `a2a_error`. 4. Replay — disconnect after `sequence = N`, reconnect with `replay_from: N`, receive frames `N+1..M`. 5. Replay out of range — reconnect with `replay_from` older than retention; receive `REPLAY_OUT_OF_RANGE` followed by a Keyframe. 6. Auth enforcement — when attach auth is configured, upgrade without bearer or with an invalid/expired bearer is rejected before upgrade with HTTP `401` or closed with `4401`. 7. Scoped control — `pty:observe` attaches can replay and watch but cannot input, resize, or request controller; `pty:control` attaches can control authorized sessions. 8. Subprotocol negotiation — upgrade with a conflicting subprotocol is rejected with HTTP `400`/`4400`; accepted responses echo only the selected `pty-ws.v1` or `pty-ws.v1.binary` token. 9. Binary output — under `pty-ws.v1.binary`, hot PTY output is delivered as `PW1O` binary frames containing raw PTY bytes, not JSON/base64 `output` frames. 10. Binary input — under `pty-ws.v1.binary`, client `PW1I` binary frames are accepted as raw PTY input and remain subject to `pty:control` authorization. 11. JSON fallback — legacy `pty-ws.v1` clients continue to receive JSON/base64 output and send `pty.session_input` JSON frames. 12. Ordering — `sequence` is strictly monotonic across multi-client attach. 13. Service-parameter propagation — trace IDs in the first frame appear on every emitted task event. 14. Stale controller reaping — if a controller connection stops answering WebSocket Ping frames, the server eventually detaches that connection, releases its controller role, and allows a later authorized attach to become controller and send input.


14. Versioning

This document defines `pty-ws/v1`. Per ADR-019 versioning rules, v1 admits only additive, backward-compatible changes within the `1.x` spec-version line. Any breaking change MUST be published under a new URI (`pty-ws/v2`). Spec-version updates within v1 are recorded in the Change Log below.

15. Change log

Spec versionDateNotes
1.0.02026-05-09Initial publication.

  • `pty-extensions/v1` — companion extension for PTY-specific verbs.
  • `docs/ws-protocol.md` — v1 baseline (formal session protocol on `:8121`).
  • ADR-020 — decision rationale.
  • A2A custom-bindings governance: A2A repo `docs/topics/custom-protocol-bindings.md`.