Skip to main content
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 import pioneer-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 from pioneer_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.
Protocol DTOs should not include raw secrets, database connection details, local absolute paths from clients unless explicitly user-provided, provider-private HTTP response bodies, or implementation-only cache keys.

Schema generation

Protocol schemas are generated from Rust types and written under schemas/. 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 or has_* booleans on read operations. Examples:
  • provider keys are written through provider key methods and read back as configured/unconfigured state;
  • MCP env and headers secrets 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.
If a protocol response would echo a raw secret, the design is wrong.

Change guidelines

When changing protocol types, ask three questions:
  1. Is this field truly client-visible, or is it an implementation detail?
  2. Does the gateway have enough information to populate it consistently?
  3. Does the schema output need to change together with the Rust type?
If the answer to the first question is no, keep the type in the owning crate. If the answer is yes, add it here and update the gateway, docs, and schemas together. Use this checklist before merging a protocol change:
  • 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-client reducers/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.