# Architecture

> The system's main components, boundaries, and data flow as the repository itself documents them.

- Repository: egoist/lorca
- GitHub: https://github.com/egoist/lorca
- Human docs: https://grok-wiki.com/public/docs/egoist-lorca-2cf67495e5e6
- Complete Markdown: https://grok-wiki.com/public/docs/egoist-lorca-2cf67495e5e6/llms-full.txt

## Source Files

- `ARCHITECTURE.md`

---

---
title: "Architecture"
description: "The system's main components, boundaries, and data flow as the repository itself documents them."
---

Lorca is three cooperating processes: a local UI (AppKit or Expo), a Device core (`lorca` / `lorca-mobile`), and a zero-knowledge relay (`lorca-relay`). The UI never holds keys or talks to models; the core owns identity, encryption, sync, and the agent loop on Runners; the relay stores public keys and ciphertext only.

## System boundaries

```mermaid
flowchart TB
  subgraph clients [Clients you run]
    Mac["macos/ AppKit<br/>Lorca.app"]
    Phone["mobile/ Expo<br/>iOS · Android"]
  end

  subgraph device [Device core]
    CLI["crates/cli · lorca<br/>keys · local WS · sync"]
    Agent["crates/agent · lorca-agent<br/>loop · tools · providers"]
    Mobile["crates/mobile · lorca-mobile<br/>UniFFI Core"]
  end

  subgraph network [Network]
    Relay["crates/relay · lorca-relay<br/>axum · SQLite or Postgres"]
  end

  subgraph external [External]
    Models["Model providers<br/>API keys · OAuth"]
    Push["APNs · FCM"]
  end

  Mac -->|"ws://127.0.0.1:4862/ws"| CLI
  CLI --> Agent
  Phone --> Mobile
  Mobile -->|"same JSON API"| CLI
  CLI -->|"HTTPS · signed · ciphertext"| Relay
  Mobile -->|"HTTPS · signed · ciphertext"| Relay
  Agent --> Models
  Relay --> Push
```

| Process | Owns | Does not own |
| --- | --- | --- |
| **App** (`macos/`, `mobile/`) | Chat UI, onboarding chrome, notifications presentation | Keys, plaintext store of account secrets, model calls |
| **CLI / core** (`crates/cli`, `crates/mobile`) | Identity, pairing, DEK, credentials merge, sync outbox, jobs; on Runners: agent loop | Relay plaintext, other Devices' machine secrets |
| **Relay** (`crates/relay`) | Public keys, opaque blobs, envelopes, presence, push fan-out | Nicknames, chat text, credentials, Runner role |

<Note>
Production installs use `app.lorca`, `~/.lorca`, and port `4862`. Dev installs use `app.lorca.dev`, `LORCA_HOME=~/.lorca-dev`, and port `4863`. They are separate apps and data directories.
</Note>

## Hard constraints

1. The desktop app speaks only to the local CLI over localhost WebSocket (`ws://127.0.0.1:<port>/ws`). It launches bundled `lorca serve` unless something already answers on the port.
2. The CLI owns the agent loop: inference, tools, streaming, cancellation, orchestration.
3. The relay is zero-knowledge: opaque blobs and public keys; auth is a signature challenge plus HMAC bearer.
4. Desktop `os` values (`macos`, `linux`, `windows`) are **Runners**. Phones and tablets (`ios`, `ipados`, `android`) are Devices only — never Runners.
5. Provider credentials belong to the account: one encrypted `credentials` blob, merged by `changed_at` on every Device.
6. A bot runs on exactly one Runner.

## Identity and crypto layers

| Layer | What | Where |
| --- | --- | --- |
| Master secret (32 bytes) | Root; backup as base32 phrase | `~/.lorca/identity.json` (identity device only, mode `0600`) |
| Content keypair | X25519 via HKDF from master | Secret local; public key on relay |
| Identity signing key | Ed25519 via HKDF from master | Identity id = `hash(pubkey)` |
| Account DEK | XChaCha20-Poly1305 | Sealed `key` blob on relay; in pairing reply |
| Machine keypair | Ed25519 sign + X25519 box | `~/.lorca/machine.json` |
| Job envelopes | Sealed to Runner box key | Relay ciphertext; deleted after run |
| Push key | HKDF from account DEK | Derived on every Device |

AEAD envelopes are `nonce(24) || ciphertext` with blob kind as associated data. Sealed boxes use `crypto_box_seal`. Recovery = restore master phrase → re-derive content keys → unwrap DEKs from the relay.

## Domain model

Plaintext lives on Devices. The relay never sees entity fields as columns.

```text
Identity 1──* Device
Device   1──* Bot        (Runner only)
Identity 1──* Chat
Chat     *──* Bot        (dm: 1 bot · group: 1–6 bots)
Chat     1──* Message
Bot      1──* Routine
Device   1──* Plugin     (MCP on that Runner)
Bot      1──* Job        (turn on the bot's Runner)
```

| Entity | On Device | On relay |
| --- | --- | --- |
| Identity | Master + content + signing keys | Public key |
| Device | Machine keypair, `os` | Machine pubkey + encrypted `machine` blob |
| Bot / Routine | Decrypted roster | Inside encrypted `roster` blobs |
| Credentials | `credentials.json` | Encrypted `credentials` blob |
| Chat / Message | Local SQLite + DEK | Encrypted `chat` / `file` blobs |
| Job | Created anywhere; run on assigned Runner | Sealed envelope to Runner box key |

## Cross-device turn flow

```mermaid
sequenceDiagram
  participant AppB as App on Device B
  participant CLIB as CLI / core B
  participant Relay as lorca-relay
  participant CLIA as CLI Runner A
  participant Model as Provider

  AppB->>CLIB: chats.send
  CLIB->>Relay: PUT encrypted chat + job envelope
  Relay-->>CLIA: sync nudge blobs
  CLIA->>Relay: GET envelopes for machine
  CLIA->>Model: run_agent_loop_continue
  Model-->>CLIA: stream events
  CLIA->>Relay: PUT encrypted reply chunks
  Relay-->>CLIB: sync nudge
  CLIB->>AppB: message.* / job.*
```

1. Any paired Device may create a Job; the assigned Runner decrypts and runs it.
2. Streaming replies re-send growing text at paragraph ends (or sentence ends after 1.5 s silence), encrypted before upload.
3. Hard Stop seals `job_cancel` to the Device listing the turn; the Runner seals `job_result` back to the requester.
4. If the Runner is offline, the envelope waits on the relay until fetched.

## Component map

:::files
lorca/
  ARCHITECTURE.md          # source-of-truth design doc
  Cargo.toml               # workspace members
  crates/agent/            # lorca-agent: loop, tools, providers
  crates/provider-auth/    # ChatGPT + Grok OAuth / PKCE
  crates/cli/              # lorca library + binary (runner + server features)
  crates/mobile/           # UniFFI Core over cli without runner/server
  crates/relay/            # lorca-relay + Dockerfile
  crates/markdown/         # shared Markdown FFI
  macos/                   # AppKit SPM; bundles CLI
  mobile/                  # Expo; modules/lorca-core
  web/                     # TanStack Start site + Fumadocs
  scripts/                 # bun:dev, relay, release, l10n
:::

### `crates/cli` feature split

| Feature | Ships | Used by |
| --- | --- | --- |
| `cli` (default) | `runner` + `server` + binary | Desktop `lorca` |
| `runner` | Agent loop, tools, MCP, providers | Desktop Runners |
| `server` | Local axum WebSocket | Desktop app launcher |
| `provider-auth` | Credential connect/disconnect | Desktop + phone |

`crates/mobile` depends on `lorca` with `default-features = false, features = ["provider-auth"]`: same JSON API and sync, no agent loop.

### Local data directory (`LORCA_HOME`, default `~/.lorca`)

| Path | Role |
| --- | --- |
| `identity.json` | Master secret (identity device) |
| `machine.json` | Machine secret, public keys, account DEK, `name`, `os` |
| `credentials.json` | Merged account providers |
| `settings.json` | Relay URL |
| `lorca.sqlite3` | Devices, bots, chats, messages, outbox, jobs |
| `files/<id>` | Attachment bytes |
| `plugins/` | Per-Runner MCP installs (Runners) |
| `workspaces/<bot id>/` | Bot memory and workdir |

## Protocols

### App ↔ CLI (localhost)

- Endpoint: `ws://127.0.0.1:4862/ws` (or `4863` for Lorca Dev / `LORCA_PORT`).
- Requests: `{ id, method, params }` → `{ id, result }` or `{ id, error: { message } }`.
- Events: `{ event, data }` — `snapshot`, `roster.changed`, `message.*`, `job.*`, `relay.status`, `identity.changed`, …
- First load: `bootstrap` snapshot; clients buffer events until it applies.

Phone path: `Core.start` / `request` / `wake` in `crates/mobile` expose the same methods and event frames without a WebSocket.

### CLI ↔ relay

| Concern | Mechanism |
| --- | --- |
| Protocol gate | Header `Lorca-Protocol: <n>` (currently `1`); below `--min-protocol` → `426` |
| Machine auth | Challenge → sign → 1-hour HMAC bearer |
| Identity writes | Signed `{ payload, signature }` (register / attest) |
| Blobs | `PUT/GET /v1/blobs`, kinds `roster` \| `chat` \| `job` \| `machine` \| `credentials` \| `key` \| … |
| Files | Binary ciphertext on `/v1/files/{id}` (up to ~100 MiB) |
| Sync | `GET /v1/sync` WebSocket nudges `{type:"blobs"|"machines"}` |
| Cross-Runner verbs | Sealed `request` / `response` (`memory.*`, `plugins.*`, `bash.*`, `permission.answer`) |

Without a relay URL, the CLI runs single-Device. `LORCA_RELAY_URL` wins over saved settings; release macOS may set `LORCA_DEFAULT_RELAY_URL=https://relay.lorca.app`.

## Relay storage surface

The relay keeps: identity and machine public keys; blob ids, kinds, seq, size, slot, group; recipient on envelopes; presence; revoked machine keys; APNs/FCM tokens; pairing mailboxes (10-minute TTL).

Default quota: **5 GiB** ciphertext per identity (`--quota-bytes`, `0` = unlimited). File ciphertext lives in `--files-dir` or S3 (`--s3-bucket`); rows keep metadata with empty ciphertext.

Backends: SQLite (one process, WAL) or Postgres (`postgres://…`, multi-process via `NOTIFY` + advisory locks). Pick with `--db` / `LORCA_RELAY_DB`.

## Dev entry points

| Command | What starts |
| --- | --- |
| `bun run dev` | Builds CLI + Lorca Dev, launches app, local relay on `0.0.0.0:8787` |
| `bun run relay` | `lorca-relay` on `0.0.0.0:8787` |
| `bun run mobile` / `mobile:dev` | Expo phone Device |
| `cargo test` | Agent loop, crypto, SSE |
| `lorca serve` | Local Device core (default bind `127.0.0.1:4862`) |

## Related pages

<CardGroup>
  <Card title="Overview" href="/overview">
    What the project exposes and the highest-value entry points.
  </Card>
  <Card title="crates/relay reference" href="/ref-crates-relay">
    Relay routes, storage, quotas, and deploy knobs.
  </Card>
  <Card title="crates/cli reference" href="/ref-crates-cli">
    Binary commands, local API surface, and Runner features.
  </Card>
  <Card title="crates/agent reference" href="/ref-crates-agent">
    Agent loop, tools, providers, and harness behavior.
  </Card>
  <Card title="HTTP API reference" href="/http-api-reference">
    Relay `/v1` routes, auth, blobs, sync, and push.
  </Card>
</CardGroup>

Next: open `ARCHITECTURE.md` at the repo root and skim **Three processes** plus **Protocols** (about two minutes).
