Extension ID: codex.collaboration
Version: 0.2
Status: Draft
The Collaboration Extension enables multi-user editing and feedback:
- CRDT-based real-time collaboration
- Comments and annotations
- Change tracking and history
- Presence awareness
{
"extensions": [
{
"id": "codex.collaboration",
"version": "0.2",
"required": false
}
]
}Migration note (0.1 → 0.2): Version 0.2 replaces the
blockRef+rangeaddressing pattern with the unifiedanchorfield using ContentAnchor objects from the core Anchors and References specification. Implementations SHOULD migrate existingblockRef/rangepairs to the equivalentanchorobject:{ "blockId": "<blockRef>", "start": <range.start>, "end": <range.end> }. Block-level references (no range) become{ "blockId": "<blockRef>" }.
Conflict-free Replicated Data Types (CRDTs) enable:
- Real-time collaborative editing
- Offline editing with automatic merge
- No central coordination required
The Codex format doesn't mandate a specific CRDT implementation. Instead, it defines:
- How CRDT state maps to content blocks
- Synchronization protocol hooks
- Conflict resolution semantics
Each content block can carry CRDT metadata:
{
"type": "paragraph",
"id": "block-123",
"crdt": {
"clock": { "site1": 5, "site2": 3 },
"origin": "site1",
"seq": 42
},
"children": [...]
}Hashing: CRDT metadata (crdt fields on content blocks) MUST be stripped before computing the document content hash (see Core Specification, Section 6 — Document Hashing). CRDT metadata represents transient synchronization state, not document content. Implementations MUST materialize CRDT operations to plain content before hashing.
For rich text editing within blocks, integrate with text CRDTs:
{
"type": "paragraph",
"id": "block-123",
"children": [
{
"type": "text",
"value": "Hello, world",
"crdt": {
"positions": [
{ "id": "s1:1", "char": "H" },
{ "id": "s1:2", "char": "e" },
...
]
}
}
]
}Compatible with:
- Yjs - Popular JavaScript CRDT library
- Automerge - JSON-based CRDT
- Diamond Types - High-performance text CRDT
Documents using CRDT-based collaboration MUST declare the CRDT format in their collaboration metadata. This enables implementations to correctly interpret CRDT state and determines compatibility for synchronization.
{
"crdtFormat": "yjs",
"crdtVersion": "13.6"
}| Field | Type | Required | Description |
|---|---|---|---|
crdtFormat |
string | Yes | CRDT implementation identifier |
crdtVersion |
string | No | Version of the CRDT library used |
Recognized crdtFormat values:
| Value | Description |
|---|---|
yjs |
Yjs CRDT library |
automerge |
Automerge JSON CRDT |
diamond-types |
Diamond Types text CRDT |
Implementations MAY define additional crdtFormat values for other CRDT libraries. Unrecognized values SHOULD be treated as opaque—implementations that do not support the format can still read static content but cannot participate in CRDT synchronization.
Documents can include sync metadata for tracking collaboration state:
{
"collaboration": {
"crdtFormat": "yjs",
"crdtVersion": "13.6",
"syncVersion": 1234,
"lastSync": "2025-01-15T10:00:00Z",
"peers": [
{ "id": "peer1", "lastSeen": "2025-01-15T09:55:00Z" }
]
}
}| Field | Type | Required | Description |
|---|---|---|---|
syncVersion |
integer | No | Logical clock or sequence number for sync state |
lastSync |
string | No | ISO 8601 timestamp of last synchronization |
peers |
array | No | Known collaboration peers |
When exchanging documents between implementations using different CRDT formats, or when exporting for tools that do not support CRDTs, documents MUST be materialized to static content.
Materialization process:
- Resolve CRDT state — Flatten all CRDT operations to produce final content values
- Preserve content — All text, blocks, and structure MUST be preserved exactly
- Preserve collaboration data — Comments, suggestions, change history, and revision history MUST be preserved
- Strip or reset CRDT metadata — The
crdtfield on blocks MAY be removed; alternatively, importing implementations MAY initialize fresh CRDT state from the static content - Update provenance — The document's provenance chain SHOULD record the materialization event
Interoperability rules:
- When a document with
crdtFormat: "yjs"is opened by a tool using Automerge, the tool SHOULD materialize the content and MAY initialize new Automerge CRDT state - The original
crdtFormatSHOULD be replaced with the new format - Real-time synchronization between different CRDT formats is NOT supported—implementations MUST materialize before cross-format exchange
Example materialization provenance entry:
{
"action": "materialized",
"timestamp": "2025-01-15T12:00:00Z",
"agent": "codex-tool/2.0",
"details": {
"fromCrdtFormat": "yjs",
"toCrdtFormat": "automerge",
"reason": "cross-tool-exchange"
}
}| Type | Description |
|---|---|
comment |
General comment on a block |
highlight |
Highlighted text with optional note |
suggestion |
Proposed text change |
reaction |
Emoji reaction |
Location: collaboration/comments.json
{
"version": "0.2",
"crdtFormat": "yjs",
"comments": [
{
"id": "comment-1",
"type": "comment",
"anchor": { "blockId": "block-456", "start": 10, "end": 25 },
"author": {
"name": "Jane Doe",
"email": "jane@example.com"
},
"created": "2025-01-15T10:00:00Z",
"modified": "2025-01-15T10:05:00Z",
"content": "This section needs a citation.",
"resolved": false,
"replies": [...]
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Unique comment identifier |
type |
string | Yes | Comment type |
anchor |
ContentAnchor | Yes | Anchor to content (see Anchors and References spec) |
author |
object | Yes | Comment author |
created |
string | Yes | Creation timestamp |
modified |
string | No | Last modification timestamp |
content |
string | Yes | Comment text |
resolved |
boolean | No | Whether comment is resolved |
replies |
array | No | Reply comments |
The replies array contains reply objects. Replies are flat — they do not nest (a reply cannot contain further replies). Replies do not have their own anchors; they inherit the anchor context of the parent comment.
{
"id": "c1-r1",
"author": { "name": "Author", "email": "author@example.com" },
"created": "2025-01-15T10:30:00Z",
"content": "Good point, I'll expand this section."
}| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Unique reply identifier |
author |
object | Yes | Reply author (see Author Object below) |
created |
string | Yes | ISO 8601 creation timestamp |
content |
string | Yes | Reply text content |
The author field uses a consistent structure throughout the collaboration extension:
{
"name": "Jane Doe",
"email": "jane@example.com",
"userId": "user-12345",
"avatar": "https://example.com/avatars/jane.png",
"color": "#ff6b6b"
}| Field | Type | Required | Description |
|---|---|---|---|
name |
string | Yes | Display name |
email |
string | No | Email address |
userId |
string | No | User identifier in an external system |
avatar |
string | No | URL to avatar image |
color |
string | No | Color for real-time cursor/highlight display (CSS color value, e.g., "#ff6b6b" or "blue") |
The color field enables consistent visual identification across real-time collaboration sessions. When a user's cursor or selection is shown, implementations SHOULD use this color. If not specified, implementations SHOULD assign a consistent color based on userId or email.
The anchor field uses a ContentAnchor object from the core Anchors and References specification. Block-level, point, and range anchors are supported:
{ "blockId": "block-456" }
{ "blockId": "block-456", "start": 10, "end": 25 }
{ "blockId": "block-456", "offset": 15 }Character offsets follow the computation rules defined in the Anchors and References specification.
{
"id": "suggestion-1",
"type": "suggestion",
"anchor": { "blockId": "block-789", "start": 0, "end": 5 },
"author": { "name": "Editor" },
"created": "2025-01-15T11:00:00Z",
"originalText": "Hello",
"suggestedText": "Greetings",
"status": "pending"
}Suggestion statuses: pending, accepted, rejected
{
"id": "reaction-1",
"type": "reaction",
"anchor": { "blockId": "block-123" },
"author": { "name": "Reader" },
"created": "2025-01-15T12:00:00Z",
"emoji": "thumbsup"
}Standard emoji identifiers using Unicode CLDR short names (without colons). Examples: thumbsup, heart, thinking, rocket. See the Unicode CLDR annotations for the canonical list.
{
"id": "highlight-1",
"type": "highlight",
"anchor": { "blockId": "block-456", "start": 20, "end": 45 },
"author": { "name": "Reviewer" },
"created": "2025-01-15T14:00:00Z",
"color": "#ffeb3b",
"note": "Important passage to revisit"
}| Field | Type | Required | Description |
|---|---|---|---|
color |
string | No | Highlight color (CSS color value, defaults to yellow) |
note |
string | No | Optional note attached to the highlight |
Track changes between document versions:
- Insertions
- Deletions
- Modifications
- Moves
Location: collaboration/changes.json
{
"version": "0.2",
"baseVersion": "sha256:abc123...",
"changes": [
{
"id": "change-1",
"type": "insert",
"anchor": { "blockId": "block-new" },
"position": { "after": "block-456" },
"author": { "name": "Jane Doe" },
"timestamp": "2025-01-15T10:00:00Z"
},
{
"id": "change-2",
"type": "modify",
"anchor": { "blockId": "block-789" },
"before": { "type": "text", "value": "old text" },
"after": { "type": "text", "value": "new text" },
"author": { "name": "Jane Doe" },
"timestamp": "2025-01-15T10:01:00Z"
}
]
}| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Unique change identifier |
type |
string | Yes | Change type (see Change Types below) |
anchor |
ContentAnchor | Yes | Anchor to the affected content (see Anchors and References spec) |
position |
object | No | Position for insert/move operations (e.g., { "after": "block-id" }) |
before |
object | No | Content state before the change (for modify and delete types) |
after |
object | No | Content state after the change (for modify and insert types) |
author |
object | Yes | Change author (see Author Object in section 4.3a) |
timestamp |
string | Yes | ISO 8601 change timestamp |
status |
string | No | Change status: "pending", "accepted", or "rejected" (default: "pending") |
| Type | Description |
|---|---|
insert |
New block inserted |
delete |
Block deleted |
modify |
Block content changed |
move |
Block moved to new position |
format |
Formatting changed |
Changes can be:
pending- Not yet reviewedaccepted- Incorporated into contentrejected- Reverted
For real-time collaboration, track user presence:
- Who is viewing
- Who is editing
- Cursor positions
- Selection ranges
This is typically ephemeral (not stored in document), but can be synchronized:
{
"presence": [
{
"userId": "jane@example.com",
"name": "Jane Doe",
"color": "#ff6b6b",
"cursor": { "blockId": "block-123", "offset": 42 },
"selection": { "blockId": "block-123", "start": 40, "end": 50 },
"lastActive": "2025-01-15T10:00:00Z"
}
]
}Track document evolution over time:
{
"revisions": [
{
"version": 1,
"documentId": "sha256:v1hash...",
"created": "2025-01-10T08:00:00Z",
"author": { "name": "Jane Doe" },
"note": "Initial draft"
},
{
"version": 2,
"documentId": "sha256:v2hash...",
"created": "2025-01-12T14:00:00Z",
"author": { "name": "John Smith" },
"note": "Added executive summary"
}
]
}To show differences between versions:
- Load both versions
- Compare block trees
- Generate change list
- Display inline or side-by-side
Note: This section is informative, not normative. Real-time synchronization protocols are implementation-defined. Implementations SHOULD use established CRDT sync protocols (e.g., yjs-websocket, y-webrtc, automerge-repo) rather than defining custom wire protocols.
For real-time collaboration, common transport mechanisms include:
- WebSocket connections — Persistent bidirectional communication
- WebRTC data channels — Peer-to-peer with NAT traversal
- Server-sent events — Server-push for read-heavy scenarios
The choice of transport is orthogonal to the CRDT format. Implementations SHOULD leverage the sync infrastructure provided by their chosen CRDT library.
The following message types represent common patterns in CRDT sync protocols. These are illustrative—actual wire formats depend on the CRDT library used.
| Pattern | Direction | Description |
|---|---|---|
sync-request |
Client → Server | Request current state or missing updates |
sync-response |
Server → Client | State snapshot or update batch |
update |
Bidirectional | Incremental CRDT operation(s) |
presence |
Bidirectional | User presence update |
awareness |
Bidirectional | Cursor position, selection, user info |
CRDT-based collaboration handles conflicts automatically:
- CRDT operations — Merge deterministically without coordination
- Non-CRDT metadata — Last-write-wins or implementation-defined merge
- Unresolvable conflicts — User notification (rare with CRDTs)
{
"version": "0.2",
"comments": [
{
"id": "c1",
"type": "comment",
"anchor": { "blockId": "intro-para" },
"author": { "name": "Reviewer", "email": "reviewer@example.com" },
"created": "2025-01-15T10:00:00Z",
"content": "Consider adding more context here.",
"resolved": false,
"replies": [
{
"id": "c1-r1",
"author": { "name": "Author" },
"created": "2025-01-15T10:30:00Z",
"content": "Good point, I'll expand this section."
}
]
}
]
}{
"version": "0.2",
"baseVersion": "sha256:original...",
"changes": [
{
"id": "ch1",
"type": "delete",
"anchor": { "blockId": "old-section" },
"author": { "name": "Editor" },
"timestamp": "2025-01-15T11:00:00Z",
"status": "pending"
},
{
"id": "ch2",
"type": "insert",
"anchor": { "blockId": "new-section" },
"position": { "after": "intro" },
"author": { "name": "Editor" },
"timestamp": "2025-01-15T11:05:00Z",
"status": "accepted"
}
]
}