Crosscode architecture

human / coding agent
        |
        |  CLI (`crosscode …`)      MCP tools (stdio)
        v                            v
per-worktree daemon --- SQLite events + outbox
        |
        | authenticated HTTP sync
        v
coordination service --- Supabase-hosted PostgreSQL operations + audit log
        |
        v
other daemons receive reviewable proposals

Crosscode is CLI-first. Every coordination operation — status, tasks, claims, proposal review, accept/reject, checkpoints, validation, publish — happens against the local daemon through the CLI or MCP. The website (apps/docs-site) is not part of this topology: it is a landing page, the auth pages (sign-up, sign-in, password reset, and the crosscode login callback at /auth/cli.html), and the documentation generated from the root docs/*.md. Nothing else lives behind auth, and no browser page reads or writes workspace state.

Daemon (apps/daemon)

One daemon runs per Git checkout/worktree. It watches filesystem and Git activity, captures settled edits as immutable transactions (stable before/after content hashes), and appends them to a local append-only SQLite event log alongside projections, an offline outbox, and the download cursor. It creates safety checkpoints under refs/crosscode/checkpoints/<replica-id>/... before any materialization and never moves HEAD or changes the real index. It binds only to loopback and exposes a mode-0600 connection descriptor under the worktree's Git directory; the same descriptor is what apps/mcp-server and apps/cli connect to.

Coordination service (apps/service)

The service is a Supabase-hosted-PostgreSQL-backed record of workspace state: operations, tasks, claims, handoffs, intents, and an audit log (apps/service/migrations/001_initial.sql, 002_handoffs_intents.sql, 003_validations_cursor.sql, 004_supabase_auth.sql). Workspace members authenticate directly against Supabase Auth — crosscode login (loopback browser callback) or crosscode login --email/--password (headless), see Sign-in below; the service verifies the resulting Supabase-issued JWTs (fetched from SUPABASE_URL's JWKS endpoint, apps/service/src/auth.ts) rather than signing its own. A replica (an individual daemon/device identity) is self-registered by an authenticated member calling POST /v1/replicas (apps/service/src/http.ts) instead of being minted through an admin-issued enrollment token. Every authenticated request carries an x-crosscode-workspace-id header naming which workspace it targets, since a Supabase access token only carries the member's auth.users id, not a workspace/replica scope the way Crosscode's own previously-issued tokens did. Daemons upload operations idempotently and download them back in cursor order. Live updates also fan out over WebSocket (presence, task, claim, handoff, intent, operation), with a durable poll fallback when a replica is offline. The service enforces workspace membership and role on every request (including Postgres Row Level Security as defense-in-depth alongside the service's own resolveMembership checks); it does not execute anything a replica sends it beyond storing and relaying it. Workspace and member provisioning (pnpm service:provision) remains an administrator-side operation, now backed by the Supabase admin API (SUPABASE_SERVICE_ROLE_KEY) to create or invite Supabase Auth users instead of writing one-time enrollment tokens.

The service is multi-tenant and keeps the whole team surface — workspaces, memberships, roles, invites (/v1/invites), one-time pairing codes (/v1/pairing-codes), projects (/v1/projects), presence, and billing. All of it is reached from the CLI or over HTTP; none of it has a web UI.

Sign-in (crosscode login)

crosscode login has two paths to the same Supabase session, and the daemon cannot tell them apart afterwards.

Browser (default, needs a TTY). The CLI starts a short-lived HTTP server bound to 127.0.0.1 on an ephemeral port with a single /callback route, and generates a 32-character random state. It opens ${WEB_URL}/auth/cli.html?port=<port>&state=<state>WEB_URL from --web, else CROSSCODE_WEB_URL, else the deprecated CROSSCODE_DASHBOARD_URL. There is no production default: no site is deployed yet, so with none of the three set this fails with WEB_URL_REQUIRED rather than guessing a domain. That page signs the visitor in against Supabase (rendering the ordinary sign-in form if they aren't already), then POSTs the session back to http://127.0.0.1:<port>/callback as { state, access_token, refresh_token, expires_at, user: { id, email } } and tells them to return to the terminal. The loopback server answers the CORS preflight (OPTIONS /callbackAccess-Control-Allow-Origin: *, Access-Control-Allow-Methods: POST, OPTIONS, Access-Control-Allow-Headers: content-type) so that fetch succeeds. A mismatched or missing state fails with LOGIN_STATE_MISMATCH; no callback within 300 seconds fails with LOGIN_TIMEOUT. --no-browser prints the URL instead of opening it.

Headless. crosscode login --email <e> --password <p> signs in directly against Supabase with no browser, no loopback server, and no TTY. This is the path for coding agents and CI.

Either way the session is persisted by the same daemon config writer into the mode-0600 <git-dir>/crosscode/config.json (refresh token to the OS keychain where one is available). Tokens are never printed and never appear in --json output; crosscode login --json emits only {"value":{"userId":"…","email":"…"}}. See security.md for why.

Thin clients (apps/cli, apps/mcp-server)

The CLI and the MCP server hold no sync state of their own. They talk to the local daemon's HTTP API and render or forward its state: status, tasks, claims, proposal inspection, accept/reject, checkpoints, and validation runs. apps/mcp-server additionally bootstraps the daemon on first connection if one isn't already running for the worktree. These two, plus the daemon itself, are the entire supported product surface: every editor and agent, including VS Code and Cursor, integrates via MCP (mcp-clients.md). There is no editor extension. This CLI/MCP-first contract — humans and agents alike get direct access to every routine operation (status, claims, checkpoints, accept/reject, publish) with no website required — is formalized in AGENTS.md.

Safety invariants

  1. The local filesystem remains authoritative for local work.
  2. Remote operations arrive as proposals and are never automatically applied.
  3. Every materialization checks the local base again and creates a checkpoint first.
  4. Excluded paths, common secret files, symlink traversal, and payloads that are malformed or whose content does not match its recorded hash are rejected. (Binary files themselves are supported — they travel base64-encoded and are materialized byte-exactly — but any conflict involving one requires human approval, since hunk-level merge analysis is text-only.)

If Crosscode is stopped or removed, the repository remains an ordinary Git repository — Git is the durable history and publishing layer, and checkpoints live under refs/crosscode/checkpoints/... without polluting normal branch history.

See README.md for setup and current capabilities, and BUILD_INSTRUCTIONS.md for the milestone-by-milestone implementation status.

View raw markdown · generated from docs/architecture.md at build time, do not hand-edit this page.