This file provides guidance to ai agents when working with code in this repository.
Loro Protocol is a transport‑agnostic synchronization protocol for collaborative CRDTs. This monorepo contains a TypeScript implementation (protocol, WebSocket client/server, adaptors) and Rust counterparts. It supports Loro, Yjs, and other CRDT systems over WebSocket and P2P connections. An optional end‑to‑end encrypted flow for Loro documents ("%ELO") is included.
# Install dependencies
pnpm install
# Run development watch mode
pnpm dev
# Run tests
pnpm test
# Build all packages
pnpm build
# Type checking
pnpm typecheck
# Linting
pnpm lint
# Clean build artifacts
pnpm clean- packages/loro-protocol: Protocol types and binary encoders/decoders, bytes utilities, and
%ELOcontainer/crypto helpers (TypeScript). Key files:src/{protocol,encoding,bytes,e2ee}.tswith tests undersrc/. - packages/loro-websocket: WebSocket client and a
SimpleServer(TypeScript).- Features: message fragmentation/reassembly (≤256 KiB), connection‑scoped keepalive frames (
"ping"/"pong"text), permission hooks, optional persistence hooks.
- Features: message fragmentation/reassembly (≤256 KiB), connection‑scoped keepalive frames (
- packages/loro-adaptors: Adaptors that connect the WebSocket client to
loro-crdt(LoroAdaptor,LoroEphemeralAdaptor) and%ELO(EloAdaptor). - examples/excalidraw-example: React demo using
SimpleServer; syncs a Loro doc and ephemeral presence. - rust/: Rust workspace mirroring the TS packages:
rust/loro-protocol: Encoder/decoder parity with JS (snapshot tests included).rust/loro-websocket-client: Minimal client.rust/loro-websocket-server: Async server with workspace isolation, auth hooks, and persistence example (seeexamples/simple-server.rs).
- CRDT magic bytes:
%LOR(Loro),%EPH(Ephemeral),%YJS,%YAW,%ELO(E2EE Loro). - Messages: JoinRequest/JoinResponseOk/JoinError, DocUpdate (with batchId), DocUpdateFragmentHeader/Fragment, Ack, RoomError, Leave.
- Limits: 256 KiB max per message; large payloads are fragmented and reassembled.
- Keepalive: Text frames
"ping"/"pong"are connection‑scoped and bypass the envelope. - %ELO: DocUpdate payload is a container of encrypted records (DeltaSpan/Snapshot). Each record has a plaintext header (peer/version metadata,
keyId, 12‑byte IV) and AES‑GCM ciphertext (ct||tag). Servers route/broadcast without decrypting.
- TypeScript:
vitestacross packages viavitest.workspace.ts(unit + e2e inpackages/loro-websocket). - Rust:
cargo testin each crate; server/client e2e and auth tests underrust/loro-websocket-server/tests.
When implementation behavior doesn't match expectations, these documents are the source of truth:
/protocol.md: Wire protocol specification - defines message formats and syncing process/protocol-e2ee.md: End-to-end encryption protocol
These documents represent the ground truth design. If there are inconsistencies between code and these specs, follow the specifications.
- Incremental progress over big bangs - Small changes that compile and pass tests
- Learning from existing code - Study and plan before implementing
- Pragmatic over dogmatic - Adapt to project reality
- Clear intent over clever code - Be boring and obvious
- Single responsibility per function/class
- Avoid premature abstractions
- No clever tricks - choose the boring solution
- If you need to explain it, it's too complex
- Avoid over-engineering, don't write low-value docs/comments/tests. They'll increase the maintenance cost and make code review harder.
- Your changes should be easy to review. Please address the part that you want human to focus on by adding
TODO: REVIEW [reason]. - Don't test obvious things.
Break complex work into 3-5 stages. Document in IMPLEMENTATION_PLAN.md:
## Stage N: [Name]
**Goal**: [Specific deliverable]
**Success Criteria**: [Testable outcomes]
**Tests**: [Specific test cases]
**Status**: [Not Started|In Progress|Complete]- Update status as you progress
- Remove file when all stages are done
- Understand - Study existing patterns in codebase
- Test - Write test first (red)
- Implement - Minimal code to pass (green)
- Refactor - Clean up with tests passing
- Commit - With clear message linking to plan
CRITICAL: Maximum 3 attempts per issue, then STOP.
-
Document what failed:
- What you tried
- Specific error messages
- Why you think it failed
-
Research alternatives:
- Find 2-3 similar implementations
- Note different approaches used
-
Question fundamentals:
- Is this the right abstraction level?
- Can this be split into smaller problems?
- Is there a simpler approach entirely?
-
Try different angle:
- Different library/framework feature?
- Different architectural pattern?
- Remove abstraction instead of adding?
- Composition over inheritance - Use dependency injection
- Interfaces over singletons - Enable testing and flexibility
- Explicit over implicit - Clear data flow and dependencies
- Test-driven when possible - Never disable tests, fix them
-
Every commit must:
- Compile successfully
- Pass all existing tests
- Include tests for new functionality
- Follow project formatting/linting
-
Before committing:
- Run formatters/linters
- Self-review changes
- Ensure commit message explains "why"
- Fail fast with descriptive messages
- Include context for debugging
- Handle errors at appropriate level
- Never silently swallow exceptions
When multiple valid approaches exist, choose based on:
- Testability - Can I easily test this?
- Readability - Will someone understand this in 6 months?
- Consistency - Does this match project patterns?
- Simplicity - Is this the simplest solution that works?
- Reversibility - How hard to change later?
- Find 3 similar features/components
- Identify common patterns and conventions
- Use same libraries/utilities when possible
- Follow existing test patterns
- Use project's existing build system
- Use project's test framework
- Use project's formatter/linter settings
- Don't introduce new tools without strong justification
- Tests written and passing
- Code follows project conventions
- No linter/formatter warnings
- Commit messages are clear
- Implementation matches plan
- No TODOs without issue numbers
- Test behavior, not implementation
- One assertion per test when possible
- Clear test names describing scenario
- Use existing test utilities/helpers
- Tests should be deterministic
NEVER:
- Use
--no-verifyto bypass commit hooks - Disable tests instead of fixing them
- Commit code that doesn't compile
- Make assumptions - verify with existing code
ALWAYS:
- Commit working code incrementally
- Update plan documentation as you go
- Learn from existing implementations
- Stop after 3 failed attempts and reassess