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 theinitializehandshake. 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