# Cloud sync

How Borago Board cloud sync works between the desktop and the cloud, what syncs, what stays on one device, and how edits on two devices merge.

Cloud sync is a two-way exchange between the board on one desktop and a copy of that board on the cloud. It is off by default and turned on per device. The desktop does the work. The cloud stores what it receives, serves it to the browser and the phone, and merges edits from any device with one set of rules. When two devices disagree, no history entry is ever lost, a finished task never reverts, a closed task never hides on a tie, two edits to different fields both survive, and a delete is not undone by an edit. If something is missing on another device, start at [What syncs and what stays](#what-syncs-and-what-stays-on-one-device) and then [Reading a problem](#reading-a-problem).

## Where it runs

| Surface | Role in sync |
|---|---|
| Desktop app | The active side. It fetches the cloud's changes, sends its own, and applies the merge rules to what it receives. Nothing syncs while the desktop is closed |
| Cloud service | A passive copy. It stores records, serves them, and applies the same merge rules to what the desktop and the browser write |
| Cloud browser and phone | Read and edit the cloud's copy directly. An edit made there reaches the desktop within seconds while the desktop is online |
| Browser on the local network | Not a sync participant. It reads the desktop's own board over the local network, so it sees exactly what the desktop sees |

## Turning it on

| Label | Where | What it does | Default | Surfaces |
|---|---|---|---|---|
| Cloud sync | Settings, then Account | Keeps this board in sync with the cloud. Reveals the sign-in fields when on. The cloud address is fixed in the build and is not shown or editable | Off | Desktop only |
| Sign in | Settings, then Account, under Cloud sync | Signs this desktop in to a cloud account. Sync starts the moment the sign-in succeeds | Signed out | Desktop only |

The switch is a setting of the one machine. It never syncs, so turning sync on for one desktop says nothing about another. Sync needs a signed-in account with an active subscription, which is a paid plan, Basic, Pro or Max, with a free 30-day trial of Max (see [pricing](/pricing/)). Signed out, the desktop exchanges nothing and Settings says it needs a sign-in. With the subscription lapsed, both directions stop and Settings says an active subscription is required.

## What to expect

| When | What happens |
|---|---|
| You edit something on the desktop | It is on the cloud within a second or two, and on your phone at its next refresh |
| You edit something on the phone or in the cloud browser | The desktop has it within a few seconds while it is online |
| The desktop signs in, starts, reconnects after the cloud was unreachable, or its window regains focus | The two sides compare what they hold and transfer only the difference, in both directions, so nothing is re-sent that both already have |
| The desktop was closed for a while | Everything made on either side meanwhile is exchanged on the next start. Order does not matter, because every record matches by identity and the merge rules below decide the result |

Every project, task, comment, run, conversation, note, flow, notification, self-hosted service and vault key carries a stable identity that is the same on every device. Records match on it, never on the number a database happened to give a row, and the identity is never shown. The number a person sees on a card is a separate per-project number, described under [Task numbers across devices](#task-numbers-across-devices).

## How conflicts resolve

Two devices can edit the same task while apart. Edits merge field by field, so an edit on one device is discarded only when the other device changed the same field later.

| What | What happens |
|---|---|
| A task's history | No history line is ever lost. Both devices' entries are kept, in time order |
| Task status | The later change wins. A task finished on one device is not put back in progress by the other |
| Closed or open | The later change wins. A task is never hidden on one device because the two disagreed at the same moment |
| Title, description, owner, service and model, and every user-edited project field | One device renames a task while the other rewrites its description. Both edits survive. The same field edited on both keeps the later one, with no conflict marker |
| Deletion | An edit made on a device that has not heard about the delete cannot bring the deleted item back. The edit merges into the deleted record instead, so a later restore still has it |
| Project secrets | A device that never held a key, or cannot open it, cannot erase it on the device that has it. See [Secrets](#secrets) |
| Run-produced fields such as the previous output, the summary and token usage | The newest run wins |
| The running flag and which device is running | The phone shows the real spinner. A stale record from another device cannot double-start a task or clear the flag of a run in progress |
| Conversation messages | Two devices holding two halves of one conversation converge on the same list |
| Notes, self-hosted services and vault keys | The newest write is the record, and a delete stays deleted |
| Run records | Added when absent, never changed |

A device whose clock runs slow still keeps an edit it made after seeing the other side's state. Restoring a project from the recycle bin wins over the delete it undoes on every device. See [Recovery](/docs/reference/recovery/).

## What syncs and what stays on one device

Only projects owned by the signed-in account are exported, with everything under them. A project created before the first sign-in is unclaimed and invisible to sync until it is claimed (see [Signing in and switching accounts](#signing-in-and-switching-accounts)).

| Kind | Syncs | Stays on the one device | Notes |
|---|---|---|---|
| Projects | Most fields. The table below lists them | Cost budget and spend, the last authentication error, a rate-limit pause, whether this machine moved the containers onto the project's own network, the per-device override of the local path | An authentication failure or a rate limit pauses the queue only on the machine that hit it |
| Tasks | Every task, with status, position, history, previous output, summary, token usage, the running flag, the run timer start and the per-project number | A container error flag | The archived output of runs is a separate kind, below |
| Comments | All, including questions and their answers | Nothing | A question answered on the phone resumes the task on the desktop |
| Attachments | On Pro and Max, and during the trial, the files themselves, up to the plan's storage allowance | On Basic, the files stay on the desktop that holds them. Past the storage allowance, newer files stay on the desktop too | The plans and their storage are stated on [pricing](/pricing/) |
| Run records | Every run's row, with outcome, service, model, duration and error, on every plan. The newest run's output on every plan | Older runs' output, on Basic, which has no transcript history. The desktop keeps the output of its last 100 runs per task regardless | The task's Run History says when a listed run's output is not on the cloud |
| Conversations | Every conversation and its messages | Nothing | A phone reads a conversation with the desktop asleep. Sending a turn needs the desktop, see [The relay](/docs/reference/relay/) |
| Notes | All | Nothing | |
| Notifications | All, including whether each has been read | Nothing | Reading one on the desktop clears the badge on the phone. Capped at 300, the oldest dropped. A fresh device does not receive notifications raised before it signed in |
| Flows | Every flow's definition, schedule and enabled state | Every flow run record | A run record never enters sync at all |
| Self-hosted services | Name, models, tool mode, time limit, context window, API key as ciphertext, and the default address | The address this machine uses, when it differs from the default | An incoming default never repoints a machine that set its own |
| Vault keys | All, as ciphertext. No opt-out, because a key saved once is meant to be on every device | Nothing | A project's link to a vault key is an ordinary field and works from a phone |
| Account settings | Theme, time format and date format | Nothing | |
| App settings | Nothing | Everything. The Docker path, auto-start, the local port and who may connect, close-to-tray, the cloud sync switch, notification preferences | These describe one machine |
| Environment variables flagged device-local | Nothing. Not even as ciphertext | The value. Another device learns the variable exists and that it is device-local | |
| Containers, images, the working tree in the container, a cloned repository, anything a task installed | Nothing. None of this is in any database | All of it | Each machine rebuilds the container from the synced Dockerfile and clones again from the synced repository URL on first start |
| Script output | Nothing | Nothing. Output is streamed live and never stored | |
| The Docker files a task changed inside the container | Nothing | | Only the stored compose and Dockerfile text sync |

### Project fields

| Field | Syncs | Notes |
|---|---|---|
| Name and AI instructions | Yes | A rename moves the project's folder on each machine |
| Review instructions | Yes | |
| The AI's keyed memory | Yes | Written by runs, last run wins |
| Container name and sibling container names | Yes | The names travel. Each machine runs its own containers under them |
| Manage containers | Yes | Each machine sets up its own proxy |
| Container memory cap | A value set by hand, yes. Automatic means derived on whichever machine runs it, and that derived number never syncs | |
| Container architecture | Yes | A pinned architecture runs emulated on a machine of the other kind |
| Compose and Dockerfile text | Yes | Ports, sandboxing, memory, platform and the project network are applied when the files are written on each machine. A change arriving by sync raises a rebuild notice rather than recreating the container |
| Ports | Yes | A host port may already be taken on the other machine |
| Startup script and named scripts | The definitions, yes | Output never |
| Connected projects | Yes | Each machine builds its own pair networks |
| Repository URL and branch | Yes | The clone itself never |
| Commit name and email | Yes | |
| Git key, API keys for Claude, OpenAI, Gemini and Grok, environment variables | As ciphertext, unless the project keeps that secret on this device only | See [Secrets](#secrets) |
| Environment variable flags and vault links | Yes, in plain text. They name a credential without being one | |
| Service and model for the runner, the two reviewers, chat and flows | Yes, each independently | |
| The self-hosted service a role uses | The name, yes. The address is per device | A name the account does not have blocks the run with a message, never a silent fallback |
| Pre-run and post-run review toggles, chat toggle | Yes | |
| Local path | Yes, as the account's path | Each machine may override it, and the override never syncs. On arrival the machine tries to create the folder and raises a notification pointing at the field when it cannot |

## Secrets

A git key, a provider API key, a set of environment variables and a vault key travel as ciphertext under the account data key. The cloud stores a blob it cannot open. It stores neither the password nor the unwrapped key. It sees them only in memory, at sign-in, when the password is set, changed or reset, and when a new recovery code is issued, and it hands the key only to the linked desktop. A secret is written only through the desktop. A project secret or environment variable typed on the browser or the phone is relayed through the cloud, which holds it in memory only, to the linked desktop, encrypted there, and synced from there. A vault key or a self-hosted service key can be entered only on the desktop. What the cloud can and cannot read is stated on the [security page](/security/).

Each project can keep any of its secrets off the cloud: the git key, each provider API key and its environment variables. The control is a checkbox under each of those fields in the project's settings, labelled **Keep this key on this device only**, **Keep this API token on this device only** or **Keep these variables on this device only**. Ticking it removes that secret from the cloud on save, so it stops reaching the other devices. It does not freeze what was already there. Untick it and the value syncs again. Vault keys and self-hosted service keys have no such checkbox, because their purpose is to be on every device.

A removal is an explicit act, so a device that never held the secret, or holds a copy it cannot open, cannot delete it on the device that has it. When a sync does remove a credential this desktop held, the desktop deactivates it but keeps the value and offers to put it back, in the notification bell and in Settings, then Keys & credentials. See [Recovery](/docs/reference/recovery/).

## Task numbers across devices

The number on a card, typed as `@#42`, is a per-project number given out by whichever device creates the task. Notes (`@~3`) and conversations (`@=5`) are numbered the same way, counted separately. Two devices apart from each other can both hand out the same number.

| Situation | What happens |
|---|---|
| A new task arrives on the cloud with a number the cloud already uses | The arriving task takes the next free number, and the sending desktop adopts it on its next fetch |
| A task arrives on the desktop with a number one of the desktop's own unsynced tasks holds | The desktop's own task moves to the next free number first, then the arriving task keeps its number |
| A task is renumbered | Every `@#old` in that project's titles, descriptions, comments, conversations and notes on that device is rewritten to the new number in the same step, and the rewrites sync |
| The two sides hold different numbers for one task and neither is edited | The mismatch is detected at the next full comparison and aligned |

The cloud's number wins in every case, and a number only ever changes on the side that had not published it yet, so no other device can hold a reference to it. A number is never reused, deleted tasks included. History lines and run transcripts are never rewritten.

## One linked desktop per account

An account links exactly one desktop app. At sign-in the desktop sends a random installation identifier, generated once on first use, not derived from hardware or the operating system account, and never used for telemetry.

| Event | What each side sees |
|---|---|
| A second desktop signs in | A dialog titled **Link this desktop app?** explains that another desktop is already linked, and that continuing signs the other one out of cloud sync and remote terminals. Its confirm button is **Log out other desktop & link this one** |
| The takeover is confirmed | The new desktop is linked and syncing. The replaced desktop learns of it within seconds |
| The replaced desktop learns of it | One notification, titled **Cloud sync stopped: another desktop is now linked**, and a Settings error reading that another desktop app is now linked to this account. Its local board keeps working. It stops syncing and its relay stops serving until someone signs in there again, which takes the link back |
| The replaced desktop signs in again | Everything it changed while unlinked is sent. The other desktop becomes the unlinked one in turn |

Sign-ins from the browser and the phone never touch the link. An account with no desktop linked yet accepts the first desktop that signs in.

## Signing in and switching accounts

Each account has its own board on the desktop, in its own database file with its own project folders. An install that has never signed in works in a default board. There is no prompt and no network needed to create work while signed out.

| Event | What happens |
|---|---|
| First sign-in ever on this install | The default board becomes this account's board. The whole folder is renamed, so restore points, the secret journal and attachment files come with it. Then a full comparison sends the local work and fetches the account's cloud work |
| Sign-in to a different account | Runs are stopped after a warning, since their output belongs to the account being left. Sync, the relay and every terminal stop. The current database is closed and the other account's is opened. Nothing is deleted and the first account's board is left untouched. Signing back in reopens it as it was |
| Creating a project while signed out, with a known last account | The project form asks who owns it, with two choices: **Add to** the last account, which syncs when you sign in, and **Unclaimed**, which stays local until an account claims it |
| Unclaimed work when an account signs in | Unclaimed projects follow the device, not the account. They are moved into the incoming board and claimed by the signed-in account after the cloud fetch has completed, at which point they sync |
| An unclaimed project has the same name as one of the account's projects | The account's project keeps its name and folder. The local one stays unclaimed until it is renamed in the dialog titled **One project needs a new name**, which cannot be dismissed. Nothing is lost by waiting |
| Sign-out | Sync stops. The board stays where it is. A new project asks the ownership question above. New tasks inherit their project's owner without asking |

Several accounts can be signed in on one desktop. **Switch account** in the profile menu and in Settings, then Account, opens the chosen account's board without a password, and works offline. Only the account on screen runs tasks and serves the relay. The other signed-in accounts keep syncing in the background at a slower pace, and their unread notification counts show on the switcher. A phone signed in to an account that is not on screen sees the desktop as not connected until that account is switched to.

## Sessions

A cloud session lasts 30 days and renews itself while you use it, so a desktop, browser or phone in regular use never sees an expiry. A desktop off for more than half of that may need to sign in again, and one off for the whole 30 days always does. When that happens the desktop raises one notification, titled **Cloud sync signed out**, and Settings says the session has expired, which is distinct from never having signed in. A password change ends existing sessions the same way.

## Reading a problem

Settings, then Account, under Cloud sync shows one of these states.

| State | Meaning | What to do |
|---|---|---|
| Connected, with the time of the last sync | The last request the cloud answered succeeded | Nothing |
| Needs a sign-in | Sync is on and no account is signed in. Nothing is exchanged | Sign in |
| An active subscription is required | The account's subscription is not active. Both directions stop | Renew on the cloud |
| Another desktop app is now linked | A takeover, see above | Sign in here to take the link back |
| The session has expired | The session lived out its 30 days or the password changed | Sign in again |
| Update Borago Board to keep syncing | This build is below the cloud's minimum version. Sync halts so an old record format cannot reach the cloud | Update the app and restart it |
| Any other error | The cloud's answer, with a short excerpt of its reason | Read the reason |

When a record is refused on arrival, for instance a comment whose task is out of scope, the desktop re-offers it at its next full comparison rather than dropping it. A record owned by another account is refused and never merged, because it can never become this account's.

Why is X not on my other device, in the order to check:

1. Is cloud sync on, signed in, and connected on the desktop? Nothing leaves a desktop that is closed, signed out or unlinked.
2. Is it the same account on both devices? The cloud shows each account only its own projects.
3. Is X in the [device-only column](#what-syncs-and-what-stays-on-one-device)? A container, a clone, a script's output, a device-local variable, a cost total, a self-hosted address override or a queue pause never travels by design.
4. Was X created before the first sign-in and never claimed? Look for the rename dialog, or for a project the board still lists as unclaimed.
5. Was X a notification raised while the desktop was offline? A fresh device does not receive notifications raised before it signed in.
6. Was X a secret? It reaches another desktop of the same account, as ciphertext it can open. The browser and the phone can only see that it exists.

Related: [Security](/security/), [Recovery](/docs/reference/recovery/), [Accounts](/docs/reference/accounts/), [Cloud sync, browser and phone](/docs/guides/cloud-sync-browser-and-phone/), [Switching accounts on one machine](/docs/guides/switching-accounts/).
