Protocol Versioning and Forward Compat
The JSON-stream protocol is designed so that an older host running against a newer engine receives no surprises. New event variants and new fields on existing events are introduced through a capability-advertisement system rather than unconditional emission. An older host that does not know about a new variant simply drops the line; the core turn stream (stream_start, text_delta, tool_request, tool_result, stream_end) is unaffected.
Protocol version
Section titled “Protocol version”The current protocol version is 0.2.0, carried in the ready event’s version field. Minor-version bumps are additive: new optional events or fields. Major-version bumps are breaking.
A host can read ready.version to detect which protocol generation it is talking to. For most hosts the practical compatibility guarantee is the W0 contract described below, not the version string.
Baseline wire shape (v0.1.21)
Section titled “Baseline wire shape (v0.1.21)”The baseline event set is what every engine emits, regardless of configuration. These events have been present since v0.1.21 and their shapes are frozen:
Event "type" | Key fields |
|---|---|
ready | version, session_id?, capabilities |
stream_start | msg_id |
text_delta | text, msg_id |
thinking | text, msg_id |
tool_request | msg_id, call_id, tool (name, category, args, description) |
tool_running | msg_id, call_id, tool_name |
tool_result | msg_id, call_id, tool_name, status, output, output_type, metadata? |
tool_cancelled | msg_id, call_id, reason |
stream_end | msg_id, finish_reason, usage? |
error | msg_id?, error (code, message, retryable) |
info | msg_id, message |
config_changed | capabilities |
mcp_ready | name, tools |
pong | (none) |
These are always emitted when triggered; no capability flag gates them.
The following diagnostic events are also always emitted, regardless of W0 flags. They carry no capability flag because a host that does not recognize them drops them silently per the decoder contract, and suppressing them would mean a buggy host misses an incident:
provider_circuit_event: circuit-breaker state transitions (closed/open/half_open), always-on per audit F4budget_exceeded: fires once when the first budget cap tripstool_panicked: emitted alongside the syntheticToolResultwhen a tool’s execute handler panickedplugin_registration_failed: emitted when a plugin partially fails to register
W0 capability flags
Section titled “W0 capability flags”ready.capabilities is a Capabilities struct (defined in crates/wcore-protocol/src/events.rs:388). The v0.1.21 baseline fields are always present. All W0 forward-additive flags default to false and are omitted from the serialized JSON when off, using #[serde(skip_serializing_if = "is_false")]:
fn is_false(b: &bool) -> bool { !*b }When all W0 flags are off, the serialized capabilities object is byte-identical to the v0.1.21 shape. This is enforced by a golden test (events.rs:835):
capabilities_default_off_serializes_without_new_flag_keysThat test asserts that every W0 key (streaming_tools, sub_agent_traces, cost_attribution, hitl_suspend, non_destructive_compact, structured_traces, rpc_tool_script, browser_suite, computer_use, plugins, gepa_enabled) is absent from the JSON object when default-off.
Flag semantics
Section titled “Flag semantics”A flag being true in ready.capabilities means the engine is advertising that it will emit the corresponding event variants during this session. It is not an opt-in that the host must acknowledge. The host’s obligation is described in the decoder contract below.
Flag-to-event mapping
Section titled “Flag-to-event mapping”| Capability flag | Wave | Event type(s) gated |
|---|---|---|
streaming_tools | W7 | tool_chunk |
sub_agent_traces | W7 | sub_agent_event |
cost_attribution | W6 | session_cost |
hitl_suspend | W7 | approval_required, suspend, approval_resume (echo) |
non_destructive_compact | W5 | compact_offload |
structured_traces | W1 | trace_event |
rpc_tool_script | W4 | expands tool_result.metadata shape |
browser_suite | W8 | browser_event, browser_policy_denied |
computer_use | W8 | cua_event, cua_policy_denied |
plugins | W2.5/W8 | plugin_event |
gepa_enabled | W10B | evolution_event |
online_evolution | W7-N | evolution_event (live session, not just offline runs) |
gepa_enabled and structured_traces are separate flags by design (F6 audit fix, W10B rev-2): a host that wants W1 turn traces should not be forced to also accept thousands of evolution_event entries per evolve run.
Additional forward-additive fields on Capabilities
Section titled “Additional forward-additive fields on Capabilities”Two string fields on Capabilities use skip_serializing_if = "String::is_empty" instead of is_false, following the same omit-when-default pattern:
user_model_backend: the active user-model backend tag ("local"or"honcho"); empty string when memory is disabled (F-093)- (baseline
modesandcurrent_modeare always present asVec<String>andString)
The Host Decoder Contract
Section titled “The Host Decoder Contract”The contract is documented in docs/json-stream-protocol.md in the engine repository and enforced by crates/wcore-protocol/tests/host_decoder_contract.rs. Every host implementation must satisfy four rules:
Rule 1: parse to generic JSON first
Section titled “Rule 1: parse to generic JSON first”Do not attempt to deserialize each line directly into a closed event enum. Parse to a generic JSON value first, then dispatch on "type".
Rule 2: three outcomes per line
Section titled “Rule 2: three outcomes per line”Each line produces exactly one of three outcomes:
- Known type, valid shape: render the event normally.
- Unknown
"type"value: drop the line silently; do not log, alert, or error. - Malformed JSON: log with rate-limiting; do not crash the session.
Rule 3: tolerate unknown fields on known variants
Section titled “Rule 3: tolerate unknown fields on known variants”New fields may be added to existing event variants in minor releases. A host must not fail when it encounters a field it does not recognize on a known variant. Use a permissive deserialization strategy (serde(deny_unknown_fields) is wrong for host-side parsers; serde(default) on additive fields is correct).
Rule 4: capabilities are advisory, not gates
Section titled “Rule 4: capabilities are advisory, not gates”The capabilities object in ready is advisory: it tells the host which event families will be emitted so the host can allocate handlers. A host must not refuse to render a known event type merely because the corresponding capability flag was false or absent. The flag may have been added in a later engine version that the host has not yet seen. If the host knows how to render an event, it should render it.
Handling version skew
Section titled “Handling version skew”When a host connects to an engine whose protocol version is newer:
- New events on known variants: handled by Rule 3 (tolerate unknown fields).
- Entirely new event types: handled by Rule 2 (drop unknown types silently).
- New W0 flag present in
ready.capabilities: the host receives an advisory it has not seen before; it should ignore it rather than treating it as an error.
When a host connects to an engine whose protocol version is older:
- A W0 flag the host expects may be absent. The host should treat absence as
false(the engine will not emit the corresponding events) and degrade gracefully. CapabilitiesderivesDeserializewith#[serde(default)]on all W0 fields, so deserializing a v0.1.21 capabilities object into the currentCapabilitiesstruct producesfalsefor every missing W0 flag cleanly.
The spec-doc stale example note
Section titled “The spec-doc stale example note”The docs/json-stream-protocol.md spec document contains an example capabilities.modes array listing "yolo". The engine serializes "force" (the canonical variant name). The "yolo" alias exists only on the deserialization path for SessionMode (for foreign-agent compat). A host that reads modes[] to build a UI will see "force", not "yolo". The spec-doc example is stale on this point.
See also
Section titled “See also”- JSON Stream Protocol - baseline event and command catalog
- Host Integration - spawning the engine and startup sequence
- Foreign-Agent Compatibility - SessionMode aliases for Claude Code, Codex, and Gemini hosts