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.
┌───────────────────────────────────────────┐
│ ask-agent (session runtime) │
└──────────────┬────────────────────────────┘
│
┌────────────────────▼────────────────────┐
│ ask-session-protocol ← this gem │
│ events · interactions · methods │
│ versions · JSON Schema │
└────────────────────┬────────────────────┘
│ the only crossing artifact
┌────────────────────▼────────────────────┐
│ ask-app-server (THE HOST) │
│ transports: in-process | stdio | │
│ unix socket | SSE │
└───┬───────────┬───────────┬─────────────┘
terminal TUI web console bots/IDE
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" })
# => trueUnknown payload fields are allowed (forward compatibility); missing required
fields, wrong types, and out-of-enum values raise ArgumentError.
Payloads are string-keyed on the wire. Ruby callers may pass Symbol keys —
they are normalized to Strings at the boundary, and a Symbol/String
collision on the same name raises. Events.filter_snapshot consumes a
replayed snapshot (session/subscribe, session/events): canonical
entries become Events, host-internal or unknown entries are filtered out.
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