ask-session-protocol

The canonical wire protocol for ask agent sessions.

The contract between the ask session host (ask-app-server) and every client: terminal TUI, web console, bots, IDE integrations, CI scripts. One runtime, many thin clients — all speaking the same versioned vocabulary.

This gem is a definition, not an implementation. It contains no runtime, no transports, and no session logic. It is the single authoritative, versioned artifact for what a session event looks like on the wire and which methods a host exposes — so the host and every client evolve independently against the same contract.

Why this exists

The ask ecosystem previously translated Ask::Agent runtime events into three incompatible wire shapes (app-server events, harness SSE events, adapter events). ask-session-protocol replaces that drift with one canonical, versioned vocabulary, generated JSON Schema artifacts, and strict validation at the boundaries.

                    

The event envelope

Every event on the wire has the same shape:

{ "type": "model.streaming", "seq": 12, "payload": { "delta": "Hello" } }
  • type — one of the canonical dot-names below (additive growth only; clients must tolerate unknown types within a major protocol version)
  • seq — monotonically increasing per session; clients use it for ordering, dedup, and replay
  • payload — the event body, string-keyed, validated against the registry

Event vocabulary (19 types)

Category Events
Session lifecycle session.created, session.ended
Turn lifecycle turn.started, turn.completed, turn.failed, turn.aborted
Model output model.streaming, model.thinking
Tool execution tool.use, tool.delta, tool.result
Interactions approval.required ✱, approval.updated, plan.proposed ✱, plan.approved, plan.rejected
Session state todos.updated, file.changed
Errors error

✱ — interaction events are resolvable by id from any client through the interaction/* and plan/* methods. A terminal, web console, or bot resolves the same pending interaction; the host tombstones delivery so each subscriber sees it exactly once.

Method surface (21 methods)

Group Methods
Handshake ping, initialize
Session lifecycle session/create, session/list, session/resume, session/subscribe, session/events, session/send, session/abort, session/close
Artifacts session/artifacts, session/artifact/get
Interactions interaction/list, interaction/approve, interaction/reject, interaction/approve-all, interaction/reject-all, interaction/respond
Plan mode plan/approve, plan/reject
Workspace workspace/readState

Host → client: the session/event notification carries canonical event envelopes. Reverse requests interaction/requestPermission and interaction/requestUserInput are defined for interop with the external app-server protocol standard.

Versioning

  • Ask::SessionProtocol::PROTOCOL_VERSION — the wire version, negotiated in the initialize handshake. Bump the minor on additive changes (new events, new methods); bump the major on breaking changes.
  • Ask::SessionProtocol::VERSION — the gem version.

Validation

The registries validate everything at the boundary:

event = Ask::SessionProtocol::Events.event(
  type: "approval.required",
  seq: 3,
  payload: { "id" => "act_1", "toolName" => "bash", "args" => { "command" => "ls" } }
)
event.to_h # => { "type" => "approval.required", "seq" => 3, "payload" => { ... } }

Ask::SessionProtocol::Methods.validate_params!("session/send", { "sessionId" => "s1", "content" => "hi" })
# => true

Unknown payload fields are allowed (forward compatibility); missing required fields, wrong types, and out-of-enum values raise ArgumentError.

JSON Schema

docs/ask-session-protocol.schema.json (draft 2020-12) is generated from the registries with rake schema and committed, so non-Ruby clients (bots, IDEs, ) can validate against the contract without loading this gem.

Installation

gem "ask-session-protocol"

Development

bundle install
rake test        # minitest suite
rake schema      # regenerate docs/ask-session-protocol.schema.json

License

MIT