---
url: /sibyl/cli/migrate.md
---
# migrate

Move a project from your personal Sibyl into a shared team server, as yourself.

## Commands

* `sibyl migrate to-team` - Migrate one project's memories and graph into a team server

***

## migrate to-team

Reads one project from your local Sibyl and writes it into a team server through the ordinary
authenticated API, so everything lands owned by your account there. No cluster or server access is
involved: if you can sign in to the team server, you can migrate.

The command runs two passes:

1. **Raw memories.** The project's verbatim captures are replayed, with provenance recording each
   original id and timestamp.
2. **Graph.** The project's authored entities are created with the links between them: tasks with
   their status, priority, epic, parent, dependencies, and learnings; epics and milestones;
   decisions, error patterns, procedures, notes, episodes, plans, patterns, rules, and the rest.

What the target derives on its own is left behind and rebuilt there: topics and their mention links
from the memory projection, passages from long memories, and projected facts.

### Synopsis

```bash
sibyl migrate to-team --target-context <context> --project <project_id> [options]
```

### Options

| Option                  | Default                   | Description                                                     |
| ----------------------- | ------------------------- | --------------------------------------------------------------- |
| `--target-context`      | (required)                | Named context for the team server                               |
| `--project`             | (required)                | Source project id (`project_...`) to migrate                    |
| `--target-project`      | same id, then same name   | Target project id or name                                       |
| `--source-org`          | found automatically       | Source organization UUID, when several orgs hold the project    |
| `--dry-run`             | off                       | Print what would move, write nothing                            |
| `--graph / --no-graph`  | `--graph`                 | Include the graph pass                                          |
| `--share-private`       | off                       | Make your private memories visible to the project on the target |
| `--allow-personal-org`  | off                       | Migrate into your personal org instead of a team org            |
| `--source-surreal-url`  | `ws://localhost:8000/rpc` | Local SurrealDB endpoint                                        |
| `--source-surreal-user` | URL userinfo, else `root` | Local SurrealDB username                                        |
| `--source-surreal-pass` | URL userinfo, else `root` | Local SurrealDB password (prefer `SIBYL_SOURCE_SURREAL_PASS`)   |
| `--limit`               | none                      | Migrate at most N raw memories and N graph entities             |
| `--undo`                | off                       | Remove what this migration wrote that nobody has changed since  |

### Reading your local Sibyl

The command reads your project straight from the local SurrealDB at `ws://localhost:8000/rpc`. An
install set up with `sibyl local` (or `sibyl up`) generates its own database password; the command
finds it in `~/.sibyl/local/.env` and uses it without being asked. A development server that takes
`root`/`root` works as before. For anything else, pass `--source-surreal-user` and set
`SIBYL_SOURCE_SURREAL_PASS`.

### Before you start

```bash
# 1. Sign in to the team server; your login joins its team org
sibyl auth login https://sibyl.example.com --context team

# 2. Create the project on the team server if it is not there yet
sibyl -C team project create --name "Backend API"
```

The command refuses a personal org by default because the team could not see the result. Switch with
`sibyl -C team org switch <team-slug>`, or use `--allow-personal-org` when that personal org is your
intended destination.

### Example

```bash
# See what would move
sibyl migrate to-team --target-context team --project project_abc123 --dry-run

# Migrate
sibyl migrate to-team --target-context team --project project_abc123
```

A dry run prints the plan: entity counts by type and scope, the links it will declare, and what the
target will re-derive instead.

### Privacy

Memories that are private to you locally stay private to you on the team server: teammates cannot
read them, but your own recall and context packs there include them. Pass `--share-private` to make
them visible to everyone in the project instead. Rows flagged as holding a credential or token stay
private either way, and rows taken out of recall on your instance (contested or retired) are not
migrated.

### Re-running

A ledger under `~/.sibyl/migrations` binds progress to the source server and project, target server,
organization, and signed-in account. Running the command again skips completed rows and finishes the
rest. The ledger stores pending request bodies with private file permissions. Keep the ledger until
migration is complete.

Task status changes use the revision returned by the original creation transaction, or the revision
the migration's own last write left. If someone edits the task before its status is set, the status
change stops instead of replacing that edit.

Stable operation keys let the server replay a completed receipt when a response was lost. If the
server cannot confirm whether a create or link write completed, migration stops with
`idempotency_reconciliation_required`. Preserve the ledger and ask your server administrator to
check the operation before continuing; repeated runs do not overwrite the row to resolve the doubt.
A task status in that state is settled by reading the task instead: the status it holds stays.
Unless the server reported that write as applied, the migration no longer counts the task as
unchanged, so an undo keeps it.

When a linked row failed to migrate, the next run can add the missing link after that row lands.
Link repair changes only relationships and task topology. The server checks the saved revision in
the same transaction, so a concurrent edit stops a new link write. If the link write completed but
its response was lost, the server can acknowledge the existing links without changing the row.

Older raw ledgers are adopted only after checking each receipt against the destination account,
organization, and project. An older graph ledger without server and account identity stops the run
for target-row verification; deleting that ledger can cause existing rows to be written again.

Re-running carries a later local status change on a task while nobody else has changed the team
copy. When the task changed on the team server first, its status there stays, and the run lists the
task if that status differs from yours. A task the migration cannot track as its own (one it linked
to rather than created, or one whose first write it could not confirm) keeps its team status after
this migration's last write to it, and the run lists it once if that differs from yours. A status
request that never got its answer is settled under its own key before the current status goes, so a
delayed copy of it cannot land later; the team copy may pass through that earlier status on the way,
and an epic can start from it. Other edits you make locally after a row was migrated stay local.

When a create's answer was lost and you then edit the row locally, the next run sends the edited
version. If the first version had already reached the team server, that version stays and the run
lists the row. A row that is now less visible locally than the version that landed (made private,
flagged as sensitive, or narrowed to a smaller scope) is listed separately, so you can hide it on
the team server or undo the migration. Once a project has moved, work on it in the team server.

`--limit N` migrates at most N new raw memories and N new graph entities. Repeat the command to
advance through another batch; completed rows remain available for unfinished status and link work.

### Undoing a migration

Run the same command with `--undo` to take a migration back out of the team server. Pass the same
`--target-context`, `--project`, and any `--target-project` you migrated with, so the command finds
the same ledger. Start with `--dry-run` to see what would go; it asks the server the same questions
the real undo does. Undoing deletes rows, so it needs project maintainer access on the team server;
if you migrated as a contributor, ask a maintainer of the project to grant it for the undo.

```bash
sibyl migrate to-team --target-context team --project project_abc123 --undo --dry-run
sibyl migrate to-team --target-context team --project project_abc123 --undo
```

An undo removes only what the migration created and still owns:

* A graph row goes only while it still sits at the revision the migration's own last write left it
  at. A row anyone edited since, including you, stays and is listed.
* A row stays while anything outside the migration depends on it: a teammate's task filed under a
  migrated epic, a decision someone linked to it, or a row you created on the team server since. The
  server checks this, and the revision, as it deletes.
* A row that a kept row links to stays too, including links added on the team server after the
  migration, so nothing kept is left pointing at a deleted row.
* Rows the migration updated rather than created stay: a teammate's epic or milestone of the same
  name that your tasks linked to, and a row of yours with the same title that was already on the
  team server.
* Raw memories the migration replayed are deleted through the memory lifecycle, the same path as
  `sibyl correct --action delete`, and only while nobody has corrected them since.

The undo reads only the ledger, so it works even after the project changed on your instance or your
local Sibyl is gone; pass `--source-org` in that case, since finding the organization automatically
needs the local database. Rows that are already gone from the team server are reported and dropped
from the ledger. Undoing needs a team server that advertises guarded deletes; the command checks
before it deletes anything.

Run the undo while nobody is working in the migrated rows. The checks above see what the team server
has written by the time the undo reaches a row; a write still queued in the background, or a
teammate linking to a row in the same moment the undo removes it, can leave that link pointing at
nothing, as with any delete.

A ledger written by a build from before migrated rows recorded their source project shows a cautious
dry run: rows linked from inside the same migration can be reported as kept because "a row outside
its migration links to it". The real undo removes the linking rows first and then their targets, so
it removes more than such a dry run predicts.

Migrating again after an undo writes fresh rows for everything the undo removed. Rows the undo kept
stay in the ledger and are not written twice. An undo that stops part way can be run again; it first
checks any writes whose answer was lost, and a migration after it checks the rows the undo had
started on before trusting the ledger.

### Migrating as a team

Several people can migrate into the same team project. Each person's rows land under their own
account. The server protects same-titled memories with a recorded owner, including legacy private
ownership, and rejects a write when its qualified ID is occupied by another owner. Older rows with
no recorded owner can still be updated within the same project. When a teammate already created an
epic or milestone with the same name in the project, your tasks link to theirs instead of creating a
second one.

If you already wrote memories with the same titles in that project on the team server, the migration
updates those rows rather than adding copies.

### What changes on the way

* Ids can differ on the target. Each migrated row keeps its original id, timestamps, and scope under
  `metadata.migration`, along with the original title when a repeated title had to be numbered.
* Creation time on the target is the migration time; the original sits in
  `metadata.migration.origin_created_at`. A task's original start and completion times sit there
  too.
* An epic's status derives from its tasks, so its original status is kept only under
  `metadata.migration.origin_status`.
* The API declares links at creation and accepts `supersedes`, `contradicts`, `requires`,
  `supports`, and `decides` as typed links. Other link types (such as `DERIVED_FROM` or
  `USES_PROCEDURE`) arrive as untyped links, and the original type is recorded under
  `metadata.migration.coerced_edges`.

### Server compatibility

Upgrade the team server before migrating. The command checks the server's authenticated migration
capabilities before writing and refuses a server without protected retry support. Graph migration
also requires the server to advertise atomic ownership checks and additive link writes. A raw-only
run (`--no-graph`) needs only protected retry support. Link repair never falls back to rewriting an
entire entity.

Older servers can replace a teammate's same-titled memory or move a row out of another project.
Migration writes record their author and atomically check existing ownership and project before
writing. A concurrent first writer cannot replace the winning author's row. A second author's
project, epic, or milestone with the same name in the same project is refused. Older rows without a
recorded author are protected across projects but can still be updated within one project.
