Concepts

Repositories

How a local repository and its hosted copy relate, and which commands move work through staging, commits, publication, sync, and maintenance.

A repository is one user's Flywheel project workspace. It has a local copy on disk and, after publication, a hosted copy on the Flywheel service. Most repo: commands work only with the local copy; the network commands are called out below.

Local and hosted repositories

The local repository is a .flywheel/ directory holding the database (.flywheel/db.sqlite), a staging area for pending edits, and a commit log of local history. repo: commands find the nearest parent directory containing .flywheel unless the caller selects a repository explicitly; see Quickstart and Install for details. Local edits, commits, and reads need neither credentials nor network.

The offline command set is repo:init, repo:status, repo:diff, repo:commit, repo:log, repo:revert, repo:backup, repo:restore, repo:doctor, repo:gc, repo:staging:discard, repo:conflicts:list, repo:conflicts:show, repo:conflicts:resolve, repo:rebase:continue, repo:rebase:abort, every repo:node:*, repo:node-tag:*, repo:tag:*, repo:lineage:*, repo:containment:*, repo:edge:*, repo:node-artifact:list, and repo:edge-artifact:list, plus local artifact metadata and read commands. repo:artifact:download reads locally when its bytes are present but fetches them when they are not. repo:clone, repo:pull, repo:push, repo:purge, and repo:recover-departed contact the service.

The hosted repository is the copy on the service that collaborators read through the web app, API, CLI, and MCP. It is created by the first repo:push with --create-remote, and afterwards receives new hosted commits from repo:push or from hosted: commands.

Create or clone

repo:init creates or adopts only the selected local repository; it never chooses a hosted default workspace or fork. repo:node:create places the node in that selected local repository. hosted:node:create with --repository-id commits the node in that hosted repository. Without --repository-id, the service chooses: a node under --parent goes into the parent's repository when you hold a write role there and otherwise into your fork of that repository, where it starts private unless you have added members to that fork; a node with no parent becomes a root in your default workspace. hosted:node:branch follows the same rule for the source node. Your fork is created the first time and reused afterwards.

See Add to a graph you don't own for CLI, MCP, web app and local workflows, including how to find your fork and share only your addition.

repo:clone creates a local repository from a projection v4 hosted snapshot. repo:restore recreates a local repository from a backup archive (covered below). Install and setup never create a repository.

Stage, review, commit

Edits — repo:node:create, repo:node:set, repo:edge:add, repo:artifact:attach, and the other repo: edit commands — save into the staging area. repo:status shows whether staged work exists. repo:diff with --staged shows the complete net before/after result of the staged batch. repo:commit records that batch as one local commit, attributes it to the configured/authenticated principal, and empties the staging area. The immutable author_principal_id stays with the commit if a different authorized principal later pushes it. A local commit does not publish anything. repo:staging:discard throws away all staged work in the selected repository; discarded work is not recoverable. See Quickstart and Tutorial for the worked example.

History

repo:log lists local commits from the head, newest first. repo:revert creates and records a new local inverse commit for supported changes; it does not stage work or rewrite history. Latest-value field writes are skipped. A commit containing an edge removal is rejected because the prior edge content bytes needed to recreate it are not retained. Committed reads — repo:node:get, repo:edge:list, and the other readers — show committed local state by default and pending state with --staged.

Repository boundaries

A lineage parent or tag definition can live in another repository without being copied into the local one. Projection v4 records the foreign repository explicitly as parent_repository_id or scope_repository_id. repo:lineage:list marks a crossing parent with boundary: true; local closure and cycle checks stop at that reference rather than inventing a local node. Hosted lineage writes accept --parent-repository-id when the parent is outside --repository-id. The reference grants no access to the other repository.

Pull, rebase, conflicts

repo:pull brings hosted projection v4 history into the local repository while preserving boundary references. Older recorded projection v1, v2, and v3 snapshots remain readable. A client below the server floor receives HTTP 426 with client_version_unsupported; a clone or pull with no advertised snapshot-format overlap receives HTTP 426 with repository_snapshot_format_unsupported. Both print http_error as the CLI's error.code; the hosted identifier is in server_response.body.detail (or its type field). Run flywheel update --yes and retry either refusal (update the Flywheel app instead for an app-managed command). An ordinary clone or pull refused this way changes no local state. During repo:recover-departed, the source may already have been pulled before the destination clone is refused; no destination checkout was created.

A repository repair may move a node back to its creator's repository. Pull applies the resulting departure atomically, removes that node from the old local repository, and reports its target_repository_id. When local commits and hosted commits both changed the same thing, pull instead leaves a rebase in progress with structured conflicts: repo:conflicts:list, repo:conflicts:show, and repo:conflicts:resolve inspect and choose a resolution for each conflict. List and show name both sides' changes with the values they set, the base value when the rebase kept it, and the choices that work for that conflict: local or remote keeps one side, and manual, for node field conflicts only, sets the field to the text of a --value-file. Human output shortens long values; --format=json shows them whole. repo:rebase:continue replays every local commit, including commits made while the rebase was open, with the chosen values and advances the head. If those later commits add or change a conflict, continue saves it for resolution instead and keeps earlier choices for unchanged conflicts. Pending staged edits are kept when they still apply to the rebased history. repo:rebase:abort restores the pre-pull remote-tracking head and keeps every local commit and pending staged edit.

A graph_membership conflict records that one side removed a node from a graph, directly or through a container above it, while the other side added or edited a semantic edge that needs that node inside the graph. Resolving it keeps either the removal, dropping only the dependent edge work, or the edge, restoring only the containment the edge needs. Keeping the edge is refused when a node it needs was purged, tombstoned, or moved to another repository.

A tag_holder conflict records that both sides gave the same one-only tag to different nodes. Resolving it keeps one side: local removes the tag from the pulled holder and replays this checkout's changes to who holds it, and remote keeps the pulled holder and drops those local changes.

Two rules come from the sync contract. A push rejected because the hosted head moved requires an explicit pull and rebase: there is no automatic merge, hidden pull, or last-writer-wins. Replacement commits produced by a rebase keep a reference to the commit they replaced.

Publish

Only repo:push sends local commits to the hosted repository. The first publication uses repo:push with --create-remote, which creates the hosted repository; later pushes reuse the stored association. Publishing requires authentication; everything before this section works offline.

Add --dry-run to preview a push before publishing. It prints the commits, nodes, and artifacts the push would upload, the total size, the target server, and who will see the result, and it sends nothing. A new hosted repository is visible only to the person who pushed until they share it, and every push states who can see the repository.

At first publication, --name "My repository" can set an optional hosted label. The label preserves whitespace and capitalization, can match another repository's label, and appears in hosted:repository:list; the UUID remains the repository identity. Omit --name to leave it unnamed. Repeat the same name with the same idempotency key when retrying an uncertain creation; changing the name with that key is refused. Names are not copied into the local SQLite repository, and existing default workspaces and forks remain unnamed.

If a later push still touches a departed node, it fails as repository_node_departed and names current_repository_id. The server never redirects a repository-bound push. Clone or pull the current repository and replay the local commits there; the refused push leaves the old local repository unchanged.

Publishing is an explicit action a user chooses: a commit never publishes on its own, and agents acting on a user's behalf first show the user the dry-run summary, then run repo:push only after the user approves publication.

Backup and restore

repo:backup writes and verifies an archive of the local repository, including its staging area. repo:restore recreates a local repository from that archive into an empty directory. Restoring does not repair an invalid staged batch.

Maintenance

repo:doctor reports local object availability disagreements and interrupted push, pull or purge operations. repo:gc deletes local artifact objects that no commit, staging entry, or backup references; its page documents a report-only option. repo:purge permanently removes one node from both the hosted repository and local history; it is irreversible and requires confirmation. Unlike a tombstone, which repo:node:restore can undo, a purge cannot be undone. It acts only on a node whose tombstone has been pushed and that nothing in the repository still references, and tombstoning does not remove those references. Remove the node's relationships first, then tombstone, commit, push, and purge; a refused purge changes nothing and lists what still blocks it. If another user's node continues from the node you purge, purging removes that link instead of failing, and records the removal so they can see what happened. The node is never copied to them: purging exists to erase it, so handing it to someone else would defeat the command.

Hosted repository administration

These commands act on the hosted repository as a whole and need authentication: hosted:repository:list lists repository UUIDs, optional labels and roles visible to you, with each repository's owner and whether it is your default workspace, a fork you own (and the repository it continues) or another repository, hosted:repository:head shows the published head, and hosted:repository:members, hosted:repository:member:set, and hosted:repository:member:remove inspect and change collaborators and roles.

Two of these commands are consequential. hosted:repository:transfer hands the hosted repository to another member; you keep an admin role. hosted:repository:retire permanently retires it: the ID is never reused, existing local clones keep working offline but can no longer sync, and publishing that work again needs a new repository. If another user's node continues from one of yours, retiring does not cut their link: when copying that parent node into their repository shows it to nobody new — they could already read it, and so can everyone their repository would show it to — it is copied and their link re-pointed at the copy, so their graph keeps a parent they can read. Otherwise the link is removed and the removal is recorded for them with its reason. Either way retiring never waits on another user and never asks their permission. Because these change which user the repository belongs to or whether collaborators can keep reading it, run them only on the user's explicit instruction, never as a side effect of another task.