pi-mono/packages/protocol
2026-08-17 14:12:57 +02:00
..
src feat: stream remote session events to clients 2026-08-17 14:12:57 +02:00
test feat: stream remote session events to clients 2026-08-17 14:12:57 +02:00
CHANGELOG.md Add [Unreleased] section for next cycle 2026-08-14 12:00:04 +02:00
package.json feat(protocol): add Harness wire DTOs 2026-08-16 12:48:59 +03:00
README.md feat: stream remote session events to clients 2026-08-17 14:12:57 +02:00
tsconfig.build.json feat(protocol): add remote session wire protocol 2026-07-30 21:45:58 +03:00
tsconfig.test.json feat(protocol): add Harness wire DTOs 2026-08-16 12:48:59 +03:00
vitest.config.ts feat(protocol): add remote session wire protocol 2026-07-30 21:45:58 +03:00

@earendil-works/pi-protocol

Runtime-neutral schemas, types, CBOR encoding, and byte-stream framing for the experimental Pi protocol.

Protocol version 1 currently contains the first Session-operation slice:

  • a version handshake that identifies the logical serverId;
  • a service RPC manifest with Session discovery, attachment, prompting, and lane-watch operations;
  • correlated responses, attachment-scoped lane events, and bounded protocol errors.

The manifest generates typed client methods and validated server dispatch. The wire uses generic { serverId, method, args } calls rather than a hand-written command union. list() returns durable SessionMetadata. create() creates durable metadata for a working directory without opening a Harness; its ID is optional. attach() exclusively binds that Session to the client connection and returns only its sessionId. prompt() targets an explicitly identified Session attached to the requesting connection. The real Session and AgentHarness remain hosted by the server. Disconnecting releases the attachment after accepted work settles.

PromptArguments contains one serializable AgentLane.prompt() overload. PromptMessage is the protocol's closed set of built-in message DTOs; application-defined AgentMessage extensions are not accepted implicitly. RunResult is the wire-safe structural equivalent of the Harness result and contains no JavaScript Error instances.

A lane watch uses three RPCs so snapshot ordering does not depend on response/event scheduling: watch() creates a buffering watch and returns its authoritative snapshot, startWatch() flushes buffered events and begins live delivery, and stopWatch() releases it. Events carry their watch ID. Streaming message updates carry compact assistant-message frames rather than cumulative partial messages. Reconnection creates a new watch and snapshot rather than replaying old events.

Server and worker lifecycle is intentionally outside this public protocol. The experimental local coordinator is only an opaque message router; each replaceable server process owns the private lifecycle protocol.

Each wire frame consists of a four-byte unsigned big-endian payload length followed by one definite-length CBOR item. encodeClientMessage() and encodeServerMessage() validate and encode complete frames. ClientMessageDecoder and ServerMessageDecoder accept arbitrary stream fragmentation and coalescing.

import {
  PROTOCOL_VERSION,
  encodeClientMessage,
  ServerMessageDecoder,
  type ClientHello,
} from "@earendil-works/pi-protocol";

const hello: ClientHello = { type: "hello", version: PROTOCOL_VERSION };
transport.send(encodeClientMessage(hello));

const decoder = new ServerMessageDecoder({ maxFrameLength: 1024 * 1024 });
for (const message of decoder.push(incomingChunk)) handleServerMessage(message);
decoder.end();

All schemas reject unknown object properties. Schema violations, malformed CBOR, and invalid framing throw ProtocolValidationError. Transports authenticate peers before passing protocol bytes and must preserve byte order.

Default limits are 16 MiB per CBOR payload/frame, 1,000,000 array elements or map entries, and 64 nested item levels. The protocol is experimental and has no compatibility guarantees.