Collaboration & sync (architecture)
Copy page
Plan Desk stays local-first while letting you share a project — read-only and live — with an external client or another team, and take their issues back into your plan. It does this with two cleanly separated planes.
Two planes
Section titled “Two planes”1. Local-first core — the source of truth. Plan Desk runs on your machine (plandesk serve, a SQLite workspace, the canvas/board/docs UI, your agent over MCP). All authoring and agent execution happen here, offline-capable. Sharing changes nothing about it.
2. Hosted sync tier — a rendezvous, not a source of truth. A small Hono server (@plandesk/sync-server) that holds only what’s shared: the curated projections you push, participant identities/sessions, a moderated submission inbox, and an activity log. It runs anywhere — your laptop, a Node box, or Cloudflare Workers + D1 at the edge (see Portable store).
LOCAL (source of truth, offline-capable) HOSTED sync tier (rendezvous; NOT source of truth) ┌────────────────────────────────────────┐ push ┌──────────────────────────────────────────────┐ │ plandesk serve + SQLite workspace │ ──────▶ │ @plandesk/sync-server (Hono) │ │ services (only write path) + SSE │ │ /api/sync/v1/* (sync token — owner) │ │ ShareService → ClientView (allow-list) │ pull │ /api/portal/v1/* (participant token — guest) │ │ syncService: publish / push / pull / │ ◀────── │ store: libSQL (Node) | D1 (Cloudflare edge) │ │ triage / sync --watch │ │ shares · participants · submissions · log │ └────────────────────────────────────────┘ └──────────────────────────────────────────────┘ ▲ MCP token (agent) ▲ participant token (named guest) coding agent the same web app, in read-only "portal" modeThe flow
Section titled “The flow”- Outbound (owner → hosted).
syncService.pushbuilds an allow-listClientViewfor each share — only the tasks, edges, progress, and documents you’ve shared; internal documents, comments, agent runs, and assignees are structurally absent, never serialized — and PUTs it to the sync server.plandesk sync --watchdoes this debounced on every local change, so the guest sees status move in ~2s. - Participant (guest → hosted). A guest opens
/p/:shareToken, joins with their name (invite-scoped or public) to get a scoped session, and views the projection (session-gated, isolated to that one share). They can file issues into a moderated inbox. An SSE ping triggers a live refetch when the projection updates. - Inbound (hosted → owner).
plandesk pullbrings submissions into a local triage inbox. Accepting one creates a real task through the normal write path — so it appears on your canvas/board and your agent canget_next_taskit — and the status is acked back so the guest seesaccepted. - Agent-operable. The whole loop is available over MCP —
publish_project,sync_push,sync_pull,list_submissions,triage_submission— so an agent can pull a client’s bug, triage it, scaffold it, and work it.
Security by construction
Section titled “Security by construction”The two load-bearing invariants are enforced by the shape of the system, not by careful filtering:
- Allow-list egress. The only bytes that leave your machine are the projected
ClientView. Internal entities are never written into a projection — so even a breach of the hosted server or its database exposes only what was already shared with that client. - Proposals, never writes. A participant can only append a
pendingsubmission. They cannot create, edit, or delete a task, edge, document, or status. Real work is created solely by the owner’s (or agent’s)accepton the local source of truth.
Supporting these: three capability-token types — agent↔local (MCP), local↔hosted (sync), guest↔share (participant) — each hashed at rest, scoped, expiring, and revocable; session-gated views that reject a session presented for a different share; and invite-email scoping.
Portable store
Section titled “Portable store”The sync server is async over a portable SQLite type, with adapters chosen at deploy time — the same approach PayloadCMS uses for its SQLite/Postgres adapters:
- libSQL for local development, the test suite, and Node/Docker self-hosting.
- Cloudflare D1 for an edge Workers deployment (
wrangler deploywith a D1 binding +nodejs_compat).
The handler and query code are identical across both; only the client factory differs. This keeps the hosted tier honest about Plan Desk’s local-first, self-hostable ethos — you can run it on your own box, or push it to the edge.
What’s not here yet
Section titled “What’s not here yet”- Multi-tenancy — today a deployment is single-tenant (one team / one self-hosted instance). Org isolation with fail-closed tenant scoping and intra-org project ACLs is the gated final phase.
See The Skill for how an agent already drives the planning loop, and the REST + MCP API for the current 27 tools.