Back up a mem-repo to a remote
A mem-repo workspace keeps every mem as a branch in one local git
repository that the engine owns. That repository can be pushed to any
git remote — a bare repo on a NAS, a private GitHub repository, a USB
stick — and recovered from it, using only memstead commands. This
guide walks a full backup and recovery cycle.
Transport is deliberately a CLI-only surface: agents connected over MCP have no push/fetch primitive. Moving a mem-repo off-machine is an owner decision, made at the terminal.
The transport commands need the full build (the default memstead binary)
and a git-branch-backed workspace (memstead mem-repo init).
1. Configure the remote
Section titled “1. Configure the remote”Point a named remote at wherever the backup should live. Any URL git accepts works — SSH, HTTPS, or a local path:
# a private GitHub repo…memstead mem-repo remote-add origin git@github.com:you/mem-backup.git# …or a bare repository on any diskgit init --bare /backups/mems.git # one-time, outside the workspacememstead mem-repo remote-add origin /backups/mems.gitremote-add is an upsert: re-running it with a new URL re-points the
remote. Transport commands default to origin; pass --remote <name>
to use another.
2. Push
Section titled “2. Push”Each mem is one branch; push the ones you want backed up:
memstead push knowledgeThe argument is the mem’s name; the pushed ref is the branch the mem’s
mount declares. The two usually coincide (knowledge lives on
refs/heads/knowledge), but a mount may declare any branch it likes —
a mem named engine on refs/heads/team/engine pushes that declared
branch, and pull --all restores that declared branch from
refs/remotes/origin/team/engine.
The push refuses rather than surprises:
UNKNOWN_REMOTE— the remote name isn’t configured (step 1).NON_FAST_FORWARD— the remote has commits this workspace lacks (another machine pushed). Runmemstead pull <mem>first, or--forceto overwrite deliberately (force-with-lease under the hood).LOCAL_INVALID_STATE— the local branch fails schema validation; nothing is sent. Fix the named entities first.
3. Recover on another machine
Section titled “3. Recover on another machine”Recovery is memstead pull --all, the inverse of push --all. It
brings the workspace’s schema-and-config ref first, so every schema and
mem config the backup carries is in place before any content is
checked against it; then every mounted mem’s branch, validated against
the schema it now resolves to; then the mounted mems’ check records.
It only fast-forwards: a branch missing locally is created, a branch
with local commits the remote lacks is refused by name and left alone.
A mem that could not load because its schema existed only in the
backup serves again afterwards.
The workspace’s engine state came with a clone
Section titled “The workspace’s engine state came with a clone”The usual case: the workspace lives inside a repository that tracks
.memstead/ (the mount roster, the workspace config) and ignores
mem-repo/, so a clone on the second machine has the roster but no
mems. Two commands restore it:
memstead mem-repo init --remote git@github.com:you/mem-backup.gitmemstead pull --all--remote configures origin as part of the bootstrap. The output
lists every ref that moved; memstead status --remote then reports
everything in sync.
Nothing but the backup
Section titled “Nothing but the backup”When only the backup exists, the roster has to be rebuilt, one mount
per mem. pull --all names each mem the backup carries together with
the exact command that mounts it:
mkdir restored && cd restoredmemstead mem-repo init --remote git@github.com:you/mem-backup.gitmemstead pull --all # schemas and mem configs arrive; each unmounted mem is namedmemstead workspace allow-create --schema '*' knowledgememstead mem init knowledge --schema widgets@0.1.0 --reattachmemstead pull --all # the mem's branch and its check recordsmemstead status # your entities are back--reattach mounts the mem against the config the backup already
holds instead of writing a fresh one, so the schema-and-config ref
keeps the backup’s history and the next push --all from this
machine fast-forwards. The pin to pass is the one pull --all
printed for the mem.
If the mem was published as an archive instead, memstead install is
the simpler recovery; this flow is for live workspaces with history.
4. Keep machines in sync
Section titled “4. Keep machines in sync”Once the histories are shared, the ongoing cycle is plain fetch-inspect-pull:
memstead fetch knowledge # see what moved (updated refs print per ref)memstead pull knowledge # fast-forward + reload, refuses on divergencememstead pull --all # the schema ref, every mem branch and the check recordsmemstead push knowledge # send your own commitsmemstead push --all # every mem branch plus __MEMSTEAD and __MEMSTEAD_CHECKS, fast-forward onlyA mem’s check records travel with it. The check ledger lives in the
workspace, not on the mem’s branch (a check is never a commit, which is
what keeps staleness derivable by hash), so the mem-repo carries one
more engine-owned ref, __MEMSTEAD_CHECKS, with the mem’s ledger rows
as last published. push publishes the mem’s rows there and pushes the
ref beside the branch; fetch and pull fetch it and add the mem’s
rows to the local ledger. A row is a fact whose identity is its bytes:
two machines checking the same mem merge by union, nothing is rewritten,
a round trip adds nothing, and an export made on the other machine
seals the checks recorded here. Only the rows of a mem this workspace
mounts are ever imported, and only through its own fetch, pull and
push; a fork’s ref is never read, so a contributor cannot ship check
rows that close your gates.
push --all is the whole-workspace form: it lists the remote once, skips
every ref already in sync (a run with nothing to send prints nothing),
prints one line per ref it moved, and names a ref it could not
fast-forward (NON_FAST_FORWARD) while still pushing the others, exiting
non-zero at the end. It also carries the __MEMSTEAD ref, where schemas
and mem configs live, which the single-mem form has no route for. There
is no --force on --all. pull --all is its inverse and the one
route that moves the local __MEMSTEAD ref from a remote: a fetch
given a refspec that writes into a local branch is refused, since a
fetch only updates remote-tracking refs.
pull validates the incoming tree against the mem’s pinned schema
before moving anything (SCHEMA_VIOLATION_IN_FETCH refuses the whole
pull), fast-forwards the branch, and reloads the in-memory store — the
graph reflects the pulled state immediately.
Divergence (both sides committed) never merges silently: pull refuses
with LOCAL_DIVERGENCE, and you choose — branch-reset to adopt the
remote, or push from the other machine first.
5. Fork a mem at a recorded ancestor
Section titled “5. Fork a mem at a recorded ancestor”A proposal against a mem is a second lineage, not an unrelated mem: fork it, and the diff between the two later has a common ancestor to stand on.
memstead mem fork knowledge knowledge-proposal # at the source's tipmemstead mem fork knowledge@<sha> knowledge-proposal # at a commit the source reachesmemstead mem fork knowledge knowledge-proposal --remote origin # source fetched from the remotememstead mem fork knowledge knowledge-proposal --remote origin --adopt # the fork branch already on the remote, mounted as a fork of the local sourceThe fork’s branch starts at the source’s commit and makes one commit of
its own right above it, the fork commit: the anchors and derivations
sidecar ids and every body link the source qualified with its own name
([[knowledge--slug]], [[knowledge:slug]]) now carry the fork’s name,
with hashes, spans and observations unchanged and links naming any other
mem untouched. Its config is the source’s (same schema pin, same version
and description) with forkedFrom written in: source mem, sha (the
ancestor on the source), remote when one was used, and base (the fork
commit), the commit a later comparison against the source stands on. The
entities read as the source’s at that commit, under the fork’s own name.
A local fork carries the source’s outgoing cross-link grants under its
own name, so copied cross-mem edges read conformant from the first read;
it starts with no check records (a fork’s entity is not the same entity
as its source’s). mem list shows the origin (forked from ..., with the
base). The name obeys the same create rules as mem init, and every
refusal (a sha not on the source branch, a name a branch sits above or
below, an existing name, a folder or archive source) lands nothing.
With --remote, the source branch and the remote’s __MEMSTEAD config
are fetched into remote-tracking refs (the local __MEMSTEAD never
moves), the fetched tree is validated as pull validates before the
mount exists, no grant is inherited (the remote’s policy is not this
workspace’s), and a schema pin this workspace cannot resolve refuses
naming the remedy, memstead schema install.