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.
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.
The normative wire contract (RFC-2119).
Withdrawn in 0.6.0 — it never worked end to end. Kept for the wire contract, and for what replaced it.
Pair a phone — by QR, by link, or by a spoken code.
Broker pairing, turns, tools, confirms.
One tool catalogue, for a paired phone and a browser agent alike.
The golden vectors, and a CLI that runs them against your implementation.
Every message, with field tables + schemas.
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-knowndiscovery.SPEC/— normative prose (RFC-2119):core.md,companion.md,companion-provider.md,embed.md,provider.md,mcp.md.retired/sealing.mdis 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).