Twenty minutes from an empty directory to two people exchanging changes
through one server. Everything runs on one machine over plaintext loopback,
which needs no certificates or tokens. For a shared server, add TLS and
authentication as described in auth.md; the client commands are
the same with a tls:// address and a token file.
Scribe is centralized, so two Git habits translate to one command each:
scribe submit is "commit and push" (a change exists only once the server
has made it durable), and scribe sync is "pull" (the working copy is
brought to the server's head). There is no local history to reconcile.
Build both binaries once:
cargo build --release --lockedThe examples below assume target/release is on PATH. Every output shown
is real; ids and timestamps will differ on your machine.
1. Start the server
Pick a directory for the deployment. init creates the data root and a
configuration file whose paths are relative to that file:
mkdir demo && cd demo
scribe-server init --data-dir ./server-data --config-out ./server.toml --listen tcp://127.0.0.1:7447initialized data root ./server-data
wrote configuration /home/ann/demo/server.tomlRun it. The command stays in the foreground; use a second terminal (or a service manager) for the clients:
scribe-server serve --config ./server.tomlscribe-server: logging to ./logs (filter info from configuration)The data root now holds the descriptor, the catalog, the ownership lock, and
an empty repos/ directory. The runtime directory ./run holds the pid file
and listen, which records the bound address. Logs go to ./logs; they are
diagnostics only and are never needed for recovery.
Only one process may own a data root: a second serve, or any maintenance
command such as doctor, fails on the lock while this one runs.
2. Connect a client and create a repository
The client finds the server through --server, SCRIBE_SERVER, or the
address recorded in a workspace, defaulting to tcp://127.0.0.1:7447, which
is what we chose above, so nothing needs to be set.
scribe repo create game
scribe repo listcreated: game id=5cf102e21bdd155313b1ee6ba0ace7a1 head=0 main=70d2d532f938 storage-compression=none
game head=0 id=5cf102e21bdd155313b1ee6ba0ace7a1A workspace is a directory bound to one repository, one branch (main
until you create others), and an author name. Create Ann's:
scribe workspace create game ./ann --author annworkspace fe49c4949a91
root /home/ann/demo/ann
repository game (5cf102e21bdd155313b1ee6ba0ace7a1)
branch 70d2d532f938 (main)
server tcp://127.0.0.1:7447
view (entire repository)
author ann
synced change 0
readonly false
compression noneFrom now on every scribe command run anywhere under ./ann finds this
workspace by walking up; scribe workspace info prints the block above
again. Large projects usually restrict a workspace with --view PREFIX so
that only part of the tree is synced; leave it out here.
3. First change: add files and submit
Create some content, including a versioned ignore file:
cd ann
mkdir -p Assets Source
printf 'hero mesh v1\n' > Assets/Hero.txt
printf 'fn main() {}\n' > Source/main.rs
printf 'target/\n*.log\n' > .scribeignoreNothing is tracked until it is opened. add on a directory recurses and
honours the ignore rules; status lists what is opened:
scribe add .
scribe statusadd .scribeignore
add Assets/Hero.txt
add Source/main.rs
scope: opened (synced to change 0)
add present .scribeignore
add present Assets/Hero.txt
add present Source/main.rsSubmit turns the opened files into one change. It hashes and chunks them, uploads only what the server lacks, and reports success only after the server has flushed the objects and the commit record to disk:
scribe submit -m "Initial import"submitted change 1 (3 files, 3 of 3 chunks sent, 607 object bytes sent for 40 logical bytes, 3 server flushes)That is the whole "commit and push". scribe log shows it on the server:
change 1 2026-09-06T17:58:19.188Z ann 3 entries
Initial importIf the connection drops during a submit, the command exits with code 3 and
keeps the candidate; the next scribe submit first asks the server whether
change 1 was accepted and never creates a duplicate.
4. Second client: connect and pull
Bob works in another directory (on another machine he would point at the
server with --server tcp://host:7447). A new workspace starts at change 0
with no files; sync brings it to head:
cd ..
scribe workspace create game ./bob --author bob
cd bob
scribe syncsynced change 0 -> 1: 3 updated, 0 deleted, 0 unchanged (40 bytes fetched in 1 batches, 0 chunks from cache, 0 bytes reused locally) [full reconcile]find . -type f -not -path './.scribe/*' | sort./.scribeignore
./Assets/Hero.txt
./Source/main.rsEvery file is verified against its content id before it is placed, and the workspace's watermark advances only after everything is in place, so an interrupted sync resumes from its journal rather than leaving a mix.
5. Round trip: Bob changes a file, Ann picks it up
Tracked files must be opened for edit before they change. Bob edits the hero and submits change 2; Ann syncs and receives it:
# in ./bob
scribe edit Assets/Hero.txt
printf 'hero mesh v2\n' > Assets/Hero.txt
scribe submit -m "Hero v2"edit Assets/Hero.txt
submitted change 2 (1 file, 1 of 1 chunks sent, 207 object bytes sent for 13 logical bytes, 2 server flushes)# in ./ann
scribe sync
cat Assets/Hero.txt
scribe logsynced change 0 -> 2: 1 updated, 0 deleted, 2 unchanged (13 bytes fetched in 1 batches, 0 chunks from cache, 0 bytes reused locally) [full reconcile]
preserved local Assets/Hero.txt at /home/ann/demo/ann/.scribe/recovery/1788717499825-0b42117921d692d35e1ab92bb94b74cc
hero mesh v2
change 2 2026-09-06T17:58:19.721Z bob 1 entry
Hero v2
change 1 2026-09-06T17:58:19.188Z ann 3 entries
Initial importThe "preserved" line is Scribe's rule that a sync never destroys bytes it is
about to replace: the previous copy of the file is moved into
.scribe/recovery/ first. That directory is yours to empty whenever you
like. Had Ann opened the file for edit and changed it, the sync would have
skipped it and listed it as deferred instead.
For binary assets that cannot be merged, take an exclusive lock before
editing (scribe lock Assets/Hero.uasset); a submit from anyone else
touching that path is rejected until scribe unlock.
6. Stop and inspect the server
Stop the server with Ctrl-C, SIGTERM, or the runtime file:
touch ./run/shutdown-requestIn-flight requests get a drain period; a forced kill is also safe because acknowledged changes are already durable. With the server stopped, the maintenance commands can take the lock:
scribe-server usage --data-dir ./server-data
scribe-server doctor --data-dir ./server-data --quickREPOSITORY ID HEAD PATHS BRANCHES OBJECTS LOOSE PACKED METADATA TOTAL LAST COMMIT
game 5cf102e21bdd 2 3 1 12 0 B 2 KiB 164 KiB 166 KiB 2026-09-06T17:58:19Z bob
1 repository, data root 1.2 MiB in 8 files (catalog 1.0 MiB)
filesystem: 623.63 GiB available of 1858.14 GiB (66% used) in 32 msdoctor: healthy (1 repositories, 0 problem(s), 0 warning(s)) in 35 msusage (alias df) is the inventory; doctor (alias scan) is the
read-only integrity check, and without --quick it re-hashes every object.
Run doctor before every scribe-server backup --data-dir ./server-data --to <empty dir>.
Where next
- Daily commands and exit codes: client-usage.md.
- Branches, merge, and conflict resolution: branches.md.
- Configuration, limits, and every maintenance command: server-usage.md.
- TLS, users, tokens, and roles: auth.md.
- Backups and what to do after a crash: operations.md, durability-and-recovery.md.
- Ignore rules and path restrictions: ignore-and-paths.md.