For AI agents: the complete documentation index is available at https://a3s-lab.github.io/Office/docs/0.310.0/en/llms.txt, the full documentation bundle is available at https://a3s-lab.github.io/Office/docs/0.310.0/en/llms-full.txt, and this page is available as Markdown at https://a3s-lab.github.io/Office/docs/0.310.0/en/components/collaboration-server.md.

A3S Boot collaboration server

The repository includes a complete runnable backend under examples/collaboration-server. It is not pseudocode: its Rust integration test opens two authenticated WebSocket clients, persists browser-generated Yjs updates through Yrs, checks room broadcast, creates a durable Document selection comment and an attributed text suggestion, and proves that review-only, suggestion-only, or view-only clients cannot forge canonical content. The same durable path accepts bounded character- and paragraph-formatting revisions from edit clients, preserves them across Yrs restart, and protects them from suggest clients.

The example combines a3s-boot 0.2 and the native a3s-office collaboration store:

ResponsibilityImplementation
HTTP and WebSocket runtimeA3S Boot with the Axum adapter
ConfigurationTyped A3S ACL loaded by ConfigModule
Room authenticationFive-minute HMAC-signed ticket scoped to artifact, actor ID/name/kind, mode, namespace, and kind
Document syncStandard Yjs v1 sync-step-1, sync-step-2, and update messages
PersistenceCrash-safe Yrs update log, operation receipts, and checkpoints
PresenceBounded Yjs Awareness relay; never written to document history
Native agentsAgent-scoped tickets use the same room protocol; the host bridges the transport-neutral CLI/MCP JSONL session
Authorizationedit publishes content/review updates including validated character- and paragraph-formatting revisions and decisions; Document comment publishes semantically validated review records; authenticated Document suggest publishes semantically validated text proposals while preserving formatting revisions; other non-edit pairs receive only

Run the service

From the Office repository root:

export A3S_OFFICE_TICKET_SECRET="$(openssl rand -hex 32)"
export A3S_OFFICE_ADMIN_TOKEN="$(openssl rand -hex 24)"
cargo run -p a3s-office-collaboration-server

The checked-in collaboration-server.acl listens on 127.0.0.1:8787, stores replicas under ./data/collaboration, and permits the local Playground origins. Secrets are resolved with ACL env(...) calls and are never stored in the file.

Pass a deployment ACL path as the first argument:

cargo run -p a3s-office-collaboration-server -- /etc/a3s/office-collaboration.acl

Issue a room ticket

The host backend calls the protected ticket endpoint only after its normal user authentication and document authorization have succeeded:

curl --fail http://127.0.0.1:8787/api/collaboration/tickets \
  --header "Authorization: Bearer ${A3S_OFFICE_ADMIN_TOKEN}" \
  --header 'Content-Type: application/json' \
  --data '{
    "artifactId": "quarterly-plan",
    "artifactKind": "document",
    "actorId": "user-42",
    "actorName": "Ada Reviewer",
    "actorKind": "human",
    "mode": "comment"
  }'

The standard API response contains data.webSocketUrl. That URL includes a short-lived, room-scoped ticket and is the only credential passed to the browser. Never expose A3S_OFFICE_ADMIN_TOKEN in frontend code.

Attach the browser client

The complete client.ts implements OfficeCollaborationTransport, bounded base64 wire encoding, Awareness relay, stale-peer cleanup, exponential reconnect, and a fresh two-way state-vector handshake after reconnect.

import * as Y from 'yjs';
import { Awareness } from 'y-protocols/awareness';
import {
  createOfficeCollaborationPresence,
  createOfficeCollaborationSession,
} from '@a3s-lab/office/core';
import { connectA3sBootCollaborationRoom } from './client';

const document = new Y.Doc();
const awareness = new Awareness(document);
const session = createOfficeCollaborationSession({
  artifactId: 'quarterly-plan',
  kind: 'document',
  document,
  awareness,
  actor: { id: 'user-42', name: 'Ada Reviewer', kind: 'human' },
  mode: 'comment',
});

// The server or a separately authorized edit-mode bootstrap owner must have
// initialized the room before this comment-mode reviewer mounts.

const presence = createOfficeCollaborationPresence(session);
const room = connectA3sBootCollaborationRoom({
  webSocketUrl: ticketResponse.data.webSocketUrl,
  session,
  awareness,
  onStatus: reportConnectionStatus,
});

Pass session and presence to any React, Vue, or Web Component editor. On unmount, destroy room, presence, session, awareness, and the host-owned Y.Doc in that order.

The signed ticket uses schema version 2. Its actorName must equal the local session actor name and the author of every new comment, reply, or Document suggestion. The adapter verifies artifact ID/kind, namespace, actor ID/name/kind, mode, and server Yjs client identity in collaboration.ready; a mismatch closes the connection instead of attaching the wrong room identity.

Update authorization

TicketServer behavior
editAccept canonical content and review updates, including bounded character- and paragraph-formatting revisions and their decisions, after protocol validation.
Document commentAccept only validated comment/reply creation, resolution/reopen, own-record deletion, and their exact selection marks.
Document suggestAccept only authenticated attributed insertion, deletion, and replacement proposals that preserve the canonical Document projection and every non-suggestion root.
viewAnswer synchronization and relay presence, but reject document publishing.
Non-Document comment / suggestReceive-only until that format has a durable review model.

For a Document comment update, the service holds the durable store lock, clones the committed Yrs state, applies the submitted update to the candidate, and compares their semantics. Only document.comments, retained/append-only comment order and immutable claims, commentsPresent, and documentComment marks may change. Canonical text or structure, other roots or options, forged author/actor values, reordered existing records, invalid anchors, changed foreign records, and deletion of another actor's record fail with FORBIDDEN. The candidate is persisted and broadcast only after this check succeeds, so authorization never depends on trusting caller-authored Yjs bytes or origin metadata.

For a Document suggest update, the service uses the same lock and candidate state boundary. It removes suggestion effects from the committed and candidate ProseMirror trees and requires the resulting canonical text, structure, and formatting to match. Every non-content root must also remain byte-semantically equivalent. A new insertion or deletion mark must carry a stable ID, canonical UTC date, and the exact actor ID/display name authenticated by the ticket. The actor may safely extend or withdraw its own insertion proposals, but cannot rewrite another actor's proposal or the canonical text targeted by a deletion. A replacement is authorized as one deletion proposal plus one insertion proposal. Changes to options, comments, bibliography, decision audit, roots, structure, non-suggestion formatting, foreign suggestions, or unresolved Yjs dependencies return FORBIDDEN before the durable state changes.

Character-formatting revisions use the authenticated edit path. Before the candidate is committed, the Office Yrs validator requires a formatting mark with only the stable ID, optional actor ID, author, date, kind, and bounded before snapshot. That snapshot may contain only supported bold, italic, underline, strike, subscript, superscript, text-style, and highlight marks with known scalar attributes. Accept/reject removes the live mark and appends the immutable changeKind: "formatting" record in the same update. The server then persists that standard Yjs update before acknowledgement and room broadcast. The browser can consequently import a DOCX w:rPrChange, collaborate on it, and export native w:rPrChange again without the backend parsing OOXML.

Paragraph-formatting revisions use node attributes on canonical paragraph or heading elements instead of an inline mark. The Office browser validator requires one stable identity and a canonical bounded before snapshot covering only supported paragraph properties. Accept/reject clears those live attributes, optionally restores the full snapshot, and appends an immutable changeKind: "paragraph-formatting" record atomically. Standard Yjs/Yrs persistence keeps that state restart-safe, and Rust projection recognizes the distinct decision kind. A DOCX w:pPrChange therefore follows the same room path and round-trips without the service parsing OOXML.

The suggestion authorizer strips only insertion and deletion proposal effects when comparing canonical state. Existing character- and paragraph-formatting revisions remain in that comparison and pass the relevant bounded validators, so a suggest connection cannot add, remove, or rewrite them. Native projection recognizes both formatting decision kinds, while the closed document-suggestion-create and document-suggestion-decide mutation payloads remain insertion/deletion-only.

Attach a native agent

Issue an actorKind: "agent" ticket after authorizing the agent for the file. The WebSocket endpoint accepts the same Office envelope from browser and native participants. a3s-office collab session intentionally exposes a JSONL host-channel instead of opening its own network provider: relay its outbound records as collaboration.document events, and pass room document events back as receive records on stdin. Start the session with --actor-name set to the ticket's exact actorName to enable native Presence. Relay outbound-awareness as collaboration.awareness, pass room Awareness back as receive-awareness, and translate collaboration.peer-left into peer-left:

a3s-office collab session .a3s/report-agent.replica \
  --poll-ms 100 --actor-name "A3S Agent" --json

The native Presence controller emits browser-compatible actor, mode, activity, and format-location state from a separate in-memory Yrs Awareness peer. The service validates its actor ID, kind, display name, mode, artifact, namespace, and sender client ID against the signed ticket before fan-out. Presence and Awareness clocks never enter the agent replica or the service's durable room store. The process bridge must use ready.clientId in collaboration.hello and every room envelope. Presence-enabled sessions generate this connection ID afresh; ready.replicaClientId is only the stable Yrs author ID inside the durable replica.

Keep each agent's actor-scoped replica separate from the service's internal data_dir. The host owns the small JSONL/WebSocket process bridge and its reconnect lifecycle; the example owns the complete server-side room, authorization, persistence, fan-out, and protocol validation boundary.

Native agents use the same closed review contract as browser editors. document-suggestion-create requires an actor-scoped suggest replica and derives actorId from its manifest. document-suggestion-decide requires an edit replica, exact proposal identity and text from projection v3, and may accept or reject a replacement's insertion/deletion pair atomically. The resulting standard Yjs update travels through the same authenticated WebSocket room, semantic authorization, durable store, acknowledgement, and fan-out path as a browser update. See Suggest changes to a Document for complete CLI and MCP payloads; agents must not construct private ProseMirror/Yjs marks.

Reconnect and persistence semantics

Local offline changes stay in the browser Y.Doc. The adapter deliberately does not queue every dropped WebSocket frame. After reconnect, the browser and durable server replica each send a state vector, so both sides transfer exactly the updates the other side is missing. Repeated delivery is safe because the server derives a stable operation ID from room identity, sender client ID, message kind, and update bytes.

The server persists a received sync-step-2 or update before broadcasting it and then emits collaboration.ack. Awareness is kept only in memory. When a socket disconnects, the server publishes collaboration.peer-left, allowing every remaining browser to remove the stale Awareness client immediately.

Security and deployment boundary

The sample fails closed across ticket, URL path, protocol version, artifact kind, namespace, Yjs client ID, Office presence actor/name, session mode, Origin, payload size, and ticket expiration. Comment and suggestion mutations also pass semantic candidate-state authorization under the same lock used for persistence. Client-supplied origin metadata is replaced with attribution derived from the signed ticket. Durable receipts retain that trusted host authorization metadata for audit and reconnect.

The checked-in topology is complete for one service process. A multi-replica deployment must use sticky room routing or add A3S Boot Redis/NATS fan-out and a shared store with one writer/lock policy. Production deployments must also use wss://, rate-limit ticket issuance and room fan-out, and keep the ticket issuer behind the host application's authenticated backend.

Verify

cargo test -p a3s-office-collaboration-server
bun run collaboration-server:typecheck

Continue with Real-time collaboration for each editor's shared data model, bootstrap rules, native mutations, and framework bindings.