crates/protocol is the public contract for Pioneer. It defines the JSON-RPC envelopes, method names, request payloads, response payloads, notification payloads, domain DTOs, and generated JSON Schema documents.
Clients should depend on the protocol, not on gateway internals. The gateway may reorganize storage, scheduling, or runtime code without changing how clients call turn/start, listen for item/completed, install MCP servers, update provider keys, or manage CLI runtime approvals.
This crate separates implementation from the product API. Once a field appears here, desktop, mobile, custom clients, tests, and generated schemas can depend on it.
Why this layer exists
Pioneer is designed to support more than one client. The desktop app, mobile app, CLIs, automation scripts, and third-party protocol clients all need the same public API. That only works if the public API is explicit and generated from one source of truth. The protocol crate also prevents accidental coupling. A client should not importpioneer-crud to understand a turn. It should receive Turn, TurnItem, and notification payloads from pioneer-protocol.
Transport contract
Pioneer uses JSON-RPC 2.0 over WebSocket. Requests have a method name frompioneer_protocol::constants::methods, an id, and typed params. Responses return either a typed result or a JSON-RPC error. Notifications use method names from pioneer_protocol::constants::events and have no response id.
RequestId is a typed protocol value with fixed length validation. This avoids treating client correlation ids as arbitrary unbounded strings.
The gateway dispatch path lives in crates/gateway/src/message/dispatch.rs. It parses the JSON-RPC envelope, deserializes method-specific params into pioneer-protocol types, calls gateway/domain services, and serializes method-specific response DTOs back through the same protocol crate.
Do not make clients depend on handler-local structs. If a request, response, notification, or error detail crosses the WebSocket boundary, it belongs in crates/protocol even when only one current client consumes it.
Envelope rules
Pioneer uses domain-specific unavailable/rejection DTOs heavily. Prefer returning a typed “cannot do this because X” result for normal product state. Reserve JSON-RPC errors for invalid requests, failed IO, corrupted state, auth failures, and unexpected execution failures.
Method groups
crates/protocol also exports TaskUpdateParams, TaskUpdateResponse, and the task/update method constant for task update contracts used by the task service and model-facing task_update tool. The gateway JSON-RPC dispatcher does not currently route task/update as a client method.
Notification groups
The protocol includes notifications for thread changes, timeline block changes, turn lifecycle, turn work state/items, item lifecycle, turn permission approval/audit, voice chunk/session results, tool retry and recovery, context compression, CLI runtime status/account/request/app changes, remote-access status changes, memory changes, skills changes, MCP status/catalog changes, task events, delivery state, and write locks. The gateway treats durable agent events as the source for many notifications. A client can render live progress without knowing whether an event originated from the provider stream, a built-in tool, an MCP tool, a skill runtime tool, or the task scheduler.Events versus read models
Protocol notifications are not a substitute for durable storage. They are live delivery hints and UI synchronization messages. A reconnecting client should be able to call read methods such as thread history, turn items, task events, settings get, skills list, or MCP list to reconstruct state. This is why many gateway paths both persist events and publish notifications. The persisted event/read model is the source of truth; notification fanout is how connected clients stay current. When adding a notification, define:- which durable state or runtime state it reflects;
- whether clients can recover by refetching a read model;
- whether the event is ordered relative to other events in the same turn/task/session;
- whether duplicate delivery should be harmless.
Method surface discipline
New methods should usually match a product action or a stable query, not an internal helper. Good method names are verbs over product concepts:turn/start, mcp/install, skills/update, settings/get, cli_runtime/turn/steer.
Avoid exposing a method just because a gateway function exists. If the function is an implementation step in a larger flow, keep it internal and expose the higher-level operation.
Domain dtos
Protocol DTOs should be boring, serializable product facts:- ids and foreign ids;
- status enums;
- user-facing labels and messages;
- capability flags;
- timestamps;
- redacted settings;
- typed rejection/unavailable reasons;
- stable references to artifacts, skills, MCP tools, tasks, turns, runtime requests, or settings.
Schema generation
Protocol schemas are generated from Rust types and written underschemas/. The schema export helpers live in the protocol crate. When a protocol type changes, the Rust type and generated schema should be updated together.
Review API changes at the protocol boundary. A client-visible type belongs in crates/protocol; a type used by one implementation layer belongs in that layer.
There is a second schema layer for shared client/FFI DTOs. Protocol schemas describe gateway contract. Client schemas describe shell-facing projections produced by pioneer-client and pioneer-client-ffi. Do not confuse the two:
Changing protocol may require changing client schemas, but many client schema changes are presentation-only projections and should not become gateway protocol fields.
Versioning discipline
Developers should treat field names, enum variants, method constants, and notification payloads as user-facing architecture. A storage refactor should not force a protocol change unless the product behavior changes too. Compatibility is especially important for mobile because generated TypeScript contracts can lag behind Rust changes during development. If a field is optional in practice, model it explicitly as optional. If a state is impossible for one client but possible for another, keep it in the shared enum and let presentation decide how to render it.Auth and secrets boundary
Protocol requests carry bearer auth at connection level, not by embedding raw gateway secrets in each method. Methods that manage secrets should accept new secret values only on write operations and should return redacted snapshots orhas_* booleans on read operations.
Examples:
- provider keys are written through provider key methods and read back as configured/unconfigured state;
- MCP
envandheaderssecrets are redacted after install; - remote-access key updates return
has_key, not the key; - desktop and mobile store gateway bearer tokens in their own platform stores and refer to token refs in registries.
Change guidelines
When changing protocol types, ask three questions:- Is this field truly client-visible, or is it an implementation detail?
- Does the gateway have enough information to populate it consistently?
- Does the schema output need to change together with the Rust type?
- method/event constant added or renamed in one place;
- request/response/notification structs added to
crates/protocol; - gateway dispatch handles the method and returns typed errors;
- notification fanout is recoverable through a read method where needed;
- schema export output updated;
pioneer-clientreducers/selectors updated if clients consume it;- FFI and mobile generated contracts updated when the mobile shell needs the projection;
- permission profiles, approval requests, and audit events updated when the method can trigger tool side effects;
- turn security snapshot, sandbox/resource policy, and client security projections updated when execution semantics change;
- protocol reference docs updated.
Related pages
- Gateway explains where protocol methods are dispatched.
- Persistence Layer explains how protocol-visible state maps to durable records.
- Client Architecture explains how desktop and mobile shells consume protocol contracts through shared client code.
- Permission System explains
turn/permission/request/respond, security snapshots, sandbox/resource policy, approval notifications, and permission audit events. - CLI Runtime Architecture, Memory Architecture, Tasks And Subagents, MCP Architecture, Skills Architecture, and Remote Access Architecture explain large protocol surfaces.