# WRFI Handoff Protocol — Version 1.2.0

**Status:** stable · **Published:** August 2026 · **License:** CC-BY-4.0

This is the normative specification of the WRFI handoff protocol: the wire contract an implementation must honor to call itself WRFI 1.2 compatible. It is written for someone building an independent server or client, not for someone using wr.fi.

Companion documents: [WRFI-SPEC.md](WRFI-SPEC.md) (the instruction-block and discovery layer), [GOVERNANCE.md](GOVERNANCE.md) (stability tiers, versioning, how to claim conformance).

The key words **MUST**, **MUST NOT**, **REQUIRED**, **SHOULD**, **SHOULD NOT**, and **MAY** are to be interpreted as described in RFC 2119.

---

## 1. Terminology

**Handoff** — the protocol object: a unit of in-flight work at a stable URL, carrying content, version history, an optional task layer, an optional workspace manifest, and the instructions for continuing it. (The reference implementation's API vocabulary calls this a `creation`, and some internal endpoints say `relay`; these are frozen legacy names for the same object. See §10.)

**Version** — a monotonically increasing integer, starting at 1, incremented by every write that changes handoff state. Versions are immutable once created.

**Short ID** — the handoff's identifier and the last path segment of its URL. Implementations MUST support opaque IDs and SHOULD offer both a short speakable form and a longer unguessable form. Where both exist, unauthenticated publishing SHOULD default to the unguessable form: anonymous content is unlisted work-in-progress, and a short id space is enumerable. The speakable form SHOULD be reachable as an explicit opt-in (in this implementation, `"secure": false`).

**Capability token** — a bearer credential granting a specific power over one handoff (read, write, or append-only). Possession is authorization; there is no separate identity requirement.

**Participant** — any writer: an AI agent, a human in a browser, or a script. The protocol does not distinguish between them.

---

## 2. Protocol identification and discovery

A conforming server MUST:

1. Send `X-WRFI-Protocol-Version: <version>` on every machine-contract response.
2. Serve a bootstrap document at `/.well-known/wrfi` containing at minimum the protocol version and a map of the current machine surfaces.
3. Serve version-pinned, immutable contract documents at `/protocol/{major}.{minor}/…`. A request for a version the server does not implement MUST return **404**, and MUST NOT fall back to a different generation.

A server SHOULD additionally serve mutable aliases (`/u.txt`, `/llms.txt`, an OpenAPI document) representing the current generation. These MUST carry revalidation headers; clients that cache them MUST revalidate rather than assume freshness.

Clients discovering a server MUST prefer `/.well-known/wrfi` over cached copies of any human-facing page.

---

## 3. The handoff object

A handoff has exactly one REQUIRED field on creation: `title`.

Content is supplied by exactly one of:

- `content` — a string; the content type is inferred by the server, or
- `artifacts` — an array of `{ data (base64), mimeType, filename? }`.

Supplying both `content` and `artifacts` MUST be a validation error.

### 3.1 The task layer (continuation state)

A handoff MAY carry a `task` object. This is the protocol's representation of *what should happen next*, and is distinct from content:

```json
"task": {
  "objective": "string — what the work is trying to achieve",
  "requestedAction": "string — what should happen NEXT",
  "completed": ["string"], "openQuestions": ["string"],
  "decisions": ["string"], "risks": [{ "severity": "string", "text": "string" }],
  "acceptanceCriteria": ["string"]
}
```

`status` is a separate field with exactly three values: `"open"`, `"needs-human"`, `"done"`. It MAY be null (no declared state).

`handoffMessage` is a short note to whoever continues. It is distinct from `message`, which describes what changed in *this version*. Implementations MUST NOT conflate them: `handoffMessage` is forward-looking intent, `message` is backward-looking history.

**Inheritance:** on a replace update, `task` and `status` MUST be inherited from the previous version unless explicitly supplied. Passing `null` clears the field. This makes metadata-only updates safe: a write that sets only `status` MUST NOT discard the existing task, content, or artifacts.

### 3.2 Provenance

A handoff MAY carry `generation` (model, provider, …), `provenance` (tool, agent, pipeline, …), `promptChain`, `modelContext`, `humanEdits`, and `modelInterpretation`. These are OPTIONAL. Implementations MUST NOT require provenance to accept a write, and teaching surfaces SHOULD present continuation state before provenance.

### 3.3 Workspace manifest

A handoff MAY carry an `environment` object declaring the tooling the next participant needs:

```json
"environment": {
  "mcp": [{ "name": "...", "command": "npx", "args": ["..."], "registry": "..." }],
  "skills": [{ "name": "...", "source": "..." }],
  "plugins": [{ "name": "...", "marketplace": "owner/repo" }]
}
```

**Safety invariants — a conforming server MUST enforce all of these at write time:**

1. A manifest entry MAY name a package launched through a known runner (e.g. `npx`, `uvx`, `docker`, `node`, `python`). An arbitrary shell string (e.g. `bash -c …`) MUST be rejected.
2. `env` declares variable **names** only. An entry carrying a value MUST be rejected (secret smuggling).
3. A plugin MUST reference a marketplace (an `owner/repo` or http(s) URL), never a local path.
4. Skill paths MUST be traversal-checked; executables MUST be refused.

A conforming **client** that reconstructs a manifest MUST obtain explicit per-item human consent before installing anything, and MUST surface each item's trust provenance. Silent reconstruction is non-conforming. Note that a package launched through an allowed runner still executes code; the allowlist bounds the shape of the request, not the trustworthiness of the payload.

---

## 4. Write operations

There are exactly three ways to write, with deliberately different concurrency semantics.

### 4.1 Create

```
POST /api/p
{ "title": "...", "content": "..." }
→ 201 Created
{ "url", "shortId", "editToken", "version": 1, ... }
```

The response MUST include the handoff URL and a write capability token. Servers MUST return `422` with per-field messages on validation failure.

### 4.2 Replace update — version-safe by default

```
POST /api/p
{ "update": "<shortId>", "expectedVersion": <n>, "content": "..." }
```

This is the core safety guarantee of WRFI, and a conforming server MUST implement it exactly:

| Condition | Status | Response MUST include |
|---|---|---|
| `expectedVersion` matches current | **200** | new `version` |
| `expectedVersion` absent, `force` not set | **428** Precondition Required | `currentVersion`, and a hint describing recovery |
| `expectedVersion` present but stale | **409** Conflict | `currentVersion` |
| `force: true`, no `expectedVersion` | **200** | `forced: true` and the overwritten version |

A conforming server **MUST NOT** silently apply a replace update that lacks a precondition. Last-write-wins MUST be reachable only through an explicit, audited `force`.

Rationale: an agent that omits the precondition receives a self-describing 428 and can correct in one round trip; a stale writer receives a 409 naming the version it must catch up to. Neither failure mode is silent, and neither requires the server to guess intent.

### 4.3 Append — conflict-free by default

```
POST /api/creations/{shortId}/append
{ "text": "...", "author": "..." }
→ 200 { version, bytes, offset, len }
```

Appends are **server-serialized**: the server assigns ordering. A conforming server MUST NOT require a read-before-write, and MUST NOT return 409 for an append unless the caller explicitly opted into strict mode by supplying `expectedVersion`.

**Idempotency:** a client MAY send an `Idempotency-Key` header. A repeated key within the replay window MUST return the original result unchanged and SHOULD signal the replay (the reference implementation sends `Idempotency-Replayed: true`). The reference replay window is **10 minutes**; implementations MUST document theirs. Clients MUST NOT rely on idempotency for deduplication across longer intervals — durable queues MUST maintain their own commit state.

**Limits:** the reference implementation caps an entry at 64 KB and a handoff's total append log at 5 MB, returning **413** beyond either. Implementations MAY choose different caps but MUST signal exhaustion distinctly from other failures.

### 4.4 Review decisions — version-bound

```
POST /api/relays/{shortId}/accept
{ "action": "accept", "expectedVersion": <n> }
```

`action` MUST be one of: `accept`, `edit`, `reject`, `respond`, `reviewing`, `blocked`.

A review decision MUST carry `expectedVersion` and MUST follow the same 428/409 rules as a replace update. This prevents the subtle race where a reviewer approves version 4 while the builder has already published version 5 — the approval MUST be rejected rather than silently marking unseen work as reviewed.

Actions map to handoff status: `accept` → `done`; `edit`, `reviewing` → `open`; `reject`, `respond`, `blocked` → `needs-human`.

---

## 5. Read operations

A conforming server MUST serve, for a given handoff:

| Surface | Purpose |
|---|---|
| `GET /{shortId}` | Negotiated: a human representation to browsers, a machine representation to agents |
| `GET /{shortId}?h` | The **handoff view** — plain text: context, task, status, content, history, and instructions for writing back |
| `GET /{shortId}?format=json` | Structured representation including version, status, task |
| `GET /{shortId}?raw` | Content bytes |
| `GET /{shortId}?v=N` | A specific immutable version |
| `GET /api/history/{shortId}` | Version list with per-version message and author |

### 5.1 Catch-up

```
GET /{shortId}?since=N
```

The server MUST return only what changed after version `N`: per-version messages, a diff, and **the version the caller should write against**. Catch-up is the protocol's answer to bounded context windows; returning the full document in response to `?since=` is non-conforming.

A server MAY additionally offer `?tail=N` for append logs (with an `after=<version>` cursor) and `?diff=A` / `?diff=A..B` for explicit ranges.

### 5.2 The handoff view is self-describing

The `?h` representation MUST tell a reading agent how to write back, including the current version and the precondition requirement. This is what allows an agent with no installed integration to participate: the URL carries its own contract.

The handoff view MUST also mark retrieved content as untrusted input (§8).

---

## 6. Authorization

Capability tokens, not identity:

| Credential | Grants | Transport |
|---|---|---|
| Edit token | Read + write on one handoff | `X-Wrify-Edit-Token` header |
| API key | Account-wide write | `x-api-key` header |
| Append-only token | Append only — MUST NOT permit read or replace | `X-Wrify-Append-Token` header |
| Password | Read only | `X-Wrify-Password` header |
| View key | Read only | `?key=` query parameter |

Servers MUST accept credentials as headers. Servers MAY accept them as query parameters for header-less environments, but MUST then treat the URL as a credential: such responses MUST be marked `no-store` and `no-referrer`.

Servers MUST rate-limit token verification per handoff and per source to make short tokens infeasible to brute-force, and MUST return **429** with `Retry-After` when limited.

**Authorization precedes precondition.** A write presented with insufficient authority MUST fail with an auth status (401/403), never with 428 or 409 — a caller must not be able to probe version state with an invalid token.

---

## 7. Visibility and lifecycle

A conforming server MUST support at least: a discoverable mode, an unlisted mode, and a mode where the server cannot read plaintext (client-side encryption). It MUST NOT describe unlisted as private.

Implementations that expire or delete handoffs MUST treat deletion as a state transition rather than history erasure where an audit trail is promised, and MUST NOT allow one participant's deletion to blocklist the same content for others.

Servers MUST send `noindex` directives for non-public handoffs on **every** representation — the human page *and* the machine surfaces (raw, handoff view, artifacts, hosted files). A leaked link is crawlable; the directive is what keeps it out of an index.

---

## 8. Security model

**The service is not an actor.** A conforming server MUST NOT execute handoff content, and MUST NOT require a reading agent to take any action beyond retrieval and publication.

**Content is untrusted.** Handoff content MAY be authored by anyone. A conforming client MUST treat retrieved content as data to evaluate, never as instructions to obey. The distinction a conforming implementation MUST maintain:

| Layer | Trust |
|---|---|
| Protocol instructions (from `/.well-known/wrfi`, version-pinned contracts) | Trusted — the operator opted in |
| Handoff content | **Untrusted** — arbitrary third-party input |

A server SHOULD state this boundary inside the handoff view itself, so a reading agent receives it in-band.

**Publication is disclosure.** A push is a publish. Clients SHOULD preview scope and data-use consequences before a user's first publication, and servers SHOULD scan for credentials and warn before storing.

**Rendered content** MUST be isolated: user HTML MUST NOT execute in the origin that holds credentials for the API.

---

## 9. Conformance

An implementation claiming **WRFI 1.2 compatible** MUST pass a conformance run covering, at minimum:

1. Anonymous create returning a URL and write capability
2. Replace update with a correct precondition (200)
3. Replace update without a precondition (**428**, carrying `currentVersion`)
4. Replace update with a stale precondition (**409**, carrying `currentVersion`)
5. Explicit `force` overwrite, audited in the response
6. Append without read-before-write, server-serialized
7. Append idempotency replay within the window
8. Review decision without `expectedVersion` (**428**)
9. Catch-up (`?since=N`) returning changes and the write-against version
10. The handoff view containing write-back instructions
11. Non-public handoffs marked `noindex` on page *and* machine surfaces
12. Credential-bearing URLs marked `no-store` + `no-referrer`

The reference suite runs against any instance:

```bash
node scripts/matrix-probe.mjs https://your-instance.example.com
```

It exits non-zero on failure and publishes a machine-readable report. Self-certification is ungated — publish your run output; anyone may re-run it against you. See [GOVERNANCE.md §6](GOVERNANCE.md).

---

## 10. Compatibility notes

- The API resource is named `creation` (`/api/creations`, `creationId`) and some endpoints use `relay` (`/api/relays`). These are **frozen** legacy names for the handoff object. New implementations SHOULD support them for client compatibility and SHOULD use "handoff" in all human-facing language.
- Unknown fields MUST be ignored, not rejected. Vendor extensions use an `x-` prefix.
- `503` responses SHOULD carry `Retry-After`; some AI sandbox proxies return transient 503s on cold requests, and conforming clients SHOULD retry once.

---

## 11. What this protocol deliberately does not do

Stated so implementers do not add them expecting blessing:

- **No automatic conflict resolution.** A conflict is surfaced, never merged by the server. The participant with context resolves it.
- **No bidirectional sync or local mirroring.** Reads are live; catch-up exists so that returning is cheap.
- **No identity system.** Capability tokens are the authorization model; account identity is an implementation convenience, not a protocol requirement.
- **No orchestration.** WRFI transports work between participants; it does not schedule, route, or supervise them.

The test for any proposed addition: *does this make an active unit of work easier to continue safely across a boundary?*
