Bidirectional live sync between Overleaf and an AI coding agent — without going through Git.
Overleaf's Git integration is painful when two beings are editing simultaneously (human-human, human-agent): every push risks a conflict, and pushing while someone is typing in the browser fails outright. The Git bridge was not designed for high-frequency, automated writes.
This project uses the Overleaf real-time collaboration protocol and speaks it directly. Changes flow both ways in real time — the same way a second browser tab would — so there are no commits, no pushes, and no conflicts.
Early MVP. The core sync loop is fully working:
- reads the current document state from Overleaf
- applies agent edits via
applyOtUpdate(insert / delete ops) - polls for remote changes and merges them locally
- triggers LaTeX compile on demand
Not yet covered: folder operations.
pip install requests websocket-client
python3 overleaf_sync.py <project_id>Get overleaf_session2 from your browser cookies while logged in to overleaf.com. Get project_id from the URL (/project/<id>).
These invariants are enforced by the test suite in test_sync_reconnect.py (103 tests covering every combination of edits during a network outage).
Both sides unchanged. No-op. Local file stays as-is.
Local file is identical to the last-synced state (the user did not edit locally during the outage).
✅ Invariant: the local file is overwritten with the latest server content. The poll loop does NOT push anything to Overleaf.
💣 This was the critical data-loss bug: the old code treated a stale file as a pending local edit and pushed old content back to the server, reverting all changes made during the outage.
Local file has new content relative to the pre-disconnect state; server is unchanged.
✅ Invariant: local edits are preserved as-is. The poll loop computes OT ops from server content → local content and pushes them to Overleaf.
Both local and server have new content in distinct parts of the document.
✅ Invariant: a 3-way merge is performed: local changes are computed as OT ops against the old state, then applied on top of the server state. Both sides' changes survive. The poll loop then pushes the merged result to Overleaf.
Both local and server modified the same span of text.
✅ Invariant: OT merge is applied deterministically. No crash, no corrupted output, no data loss. Insertions from both sides survive where OT semantics allow.
For every reconnect, the in-memory state is set to the server content. The poll loop then diffs (in-memory state → local file) and pushes the result.
✅ Invariant: apply_sharejs_ops(new_memory, ops_to_push) == new_local — the poll loop always faithfully reproduces the merged local content.