Open Companion Protocol

An open protocol for answering a question about a page from a device that is not the one showing the page — spoken into the visitor’s own phone, answered by a model the visitor chose and can audit, while the site supplies the context and the tools and pays for no inference.

The screen and the microphone are different devices: a kiosk in a station hall, a TV, a shared terminal, a laptop nobody wants to speak into in an open-plan office. Where the page and the microphone are the same machine, a browser-native agent is simpler and needs none of this — OCP does not try to win that case.

rendering diagram…

Surfaces and Companions never talk directly — the Hub brokers every frame and re-checks authorization on every tool call. It is not blind: answerMode says where inference happens, not what the relay can read. By default the question and every token of the answer cross it in plaintext. A Companion set to phone-only withholds them — the Hub is left with turn boundaries — and answering & disclosure §5 states what that still does not cover, the tool path above all. Content sealing was the earlier answer to this paragraph and is retired.

Core spec

The normative wire contract (RFC-2119).

Content sealing (retired)

Withdrawn in 0.6.0 — it never worked end to end. Kept for the wire contract, and for what replaced it.

Build a Companion

Pair a phone — by QR, by link, or by a spoken code.

Implement a Hub

Broker pairing, turns, tools, confirms.

Embed the widget

One tool catalogue, for a paired phone and a browser agent alike.

Conformance

The golden vectors, and a CLI that runs them against your implementation.

Message reference

Every message, with field tables + schemas.

Changelog

Wire-visible changes, additively, since 0.1.0.

@schwaizer/ocp-protocol

OCP — Open Companion Protocol. An open standard for answering a question about a page from a device that is not the one showing the page: a QR-paired phone carrying the microphone and, optionally, the model — for a kiosk, a TV, a shared terminal, or any screen whose own microphone is not the one to use.

For the same-device case a browser-native agent needs none of this; the root README says so and says why this protocol exists anyway.

This package is the standard:

  • src/ — the authoritative message contracts (Zod): envelope, errors, sessions, context, turns, tools, confirms, pairing, .well-known discovery.
  • SPEC/ — normative prose (RFC-2119): core.md, companion.md, companion-provider.md, embed.md, provider.md, mcp.md. retired/sealing.md is the sealing profile — retired in 0.6.0, never functional — under a banner stating what happened and what replaced it.
  • vectors/ — golden conformance vectors. An implementation that rejects a "valid" vector (or accepts a "reject" one) is non-conformant; changing that boundary is a major-version protocol change.

Design pillars: four strictly-separated roles (Surface / Hub / Companion / Provider), Hub-brokered trust, delegated QR pairing with proof-of-possession tokens and mandatory human approval, additive-only evolution with ignore-unknown semantics.

Companion contracts include silent token refresh (session.refresh / session.refreshed) and negotiated answer generation — capabilities.answer, answerMode, answeredBy — for the Companion that brings its own model (SPEC/companion-provider.md); the Embed profile adds the host↔iframe postMessage contract (embed.ts, consumed by @schwaizer/ocp-embed).

answeredBy is disclosure, not routing (SPEC/core.md §5.2). A session whose Hub relays to a phone that answers and a session whose Hub answers itself are indistinguishable on the wire without it, and they have opposite privacy properties: in hub mode the question reaches the Hub operator and whatever provider it runs. Both are conformant deployments of this standard.

The sealing profile is retired — it never worked end to end, in any implementation — and its wire contract stays registered in 1.0 for parsing only, because withdrawing a registered message type is a major-version change: sealed, sealMode, pair.reveal, SEAL_REQUIRED (sealing.ts), and the byte-level seal-contract.ts — HKDF labels, AAD layout, nonce rule, SAS reduction — with known-answer vectors in vectors/sealing.json. Nothing in this repository negotiates any of it. The design was a relay that routes without reading: keys agreed half by camera and half through the Hub, compared as six digits on the approval screen pairing already requires. SPEC/retired/sealing.md has the reason it never worked on its first page, and its §1 states what sealing did not protect even as designed, including the Hub serving the widget's own JavaScript; that limit is part of the specification, not a footnote to it.

Status: v1.0-draft (Core + Companion shipped; Embed backbone shipped). License: Apache-2.0 (code) / CC-BY (spec).