Getting started
Set up the machine once
Three optional keys in ~/.shipd-config.json tune materialization, wiki
fallback, and where job workspaces live:
{
"clone_sources": ["~/projects"],
"wiki_base": "~/projects/.shipd/wiki",
"workspaces_root": "~/workspaces"
}
clone_sources— directories whose immediate children the engine probes for a clone with a matching origin URL. Undeclared means no probing, and every member full-clones.wiki_base— the optional base wiki the oracle falls back to after the workspace chain, and the promote-to-base target for/s:teach. It holds a durable base outside the chain; for a base reached by nesting instead, see Nesting job workspaces.workspaces_root— the mandated parent directory, covered below.
A workspace read needs none of them: a machine with no ~/.shipd-config.json
still resolves every read verb — see Headless consumers.
The mandated parent directory
Declaring workspaces_root turns the one-folder-per-job convention into a rule:
- A bare name given to
shipd workspace initresolves to<workspaces_root>/<name>, and the engine creates the leaf directory. - The engine refuses an init target — or a
/s:workspace clonedestination — outside the root, naming the target, the declared root, andworkspaces_root. - The root and everything under it counts as inside, so
--nestedjob workspaces stay legal (nesting). - Undeclared means no mandated root, and every surface behaves as it always has.
shipd configreports the value as declared, with~left unexpanded, and the installed sample config documents the key.- Doctor's
configcheck validates it. A malformed value fails — not a non-empty string, or not absolute once~expands. A missing root warns.
Create a job workspace
mkdir -p ~/workspaces/myapp
shipd workspace init ~/workspaces/myapp --git
--git turns the root into a git repo and seeds the managed .gitignore
block. The explicit path works with or without workspaces_root. With the key
declared, shipd workspace init myapp --git needs no mkdir.
Then declare the job in ~/workspaces/myapp/.shipd-config.json:
{
"workspace": {
"focus": "api",
"projects": {
"api": {"repos": [{"path": "api", "url": "[email protected]:acme/api.git", "branch": "main"}]},
"web": {"repos": [{"path": "web", "url": "[email protected]:acme/web.git"}]},
"mobile": {"repos": [{"path": "mobile", "url": "[email protected]:acme/mobile.git"}]}
}
}
}
focusnames the job's primary project — the oracle and/s:teachweight its surfaces first.- Every materializable repo entry needs a
url. A bare string ("repos": ["tools"]) stays valid, but the engine cannot clone it elsewhere.
Finish the bootstrap from inside the workspace:
shipd wiki init # job wiki store
shipd workspace sync --write-gitignore
shipd workspace sync prints the materialization plan and never touches the
network; --write-gitignore fills the managed members block, so the member
directories stay untracked. Then run /s:workspace sync in a Claude session to
execute the plan's git commands, or run the printed command: lines yourself.
Check it into git
The engine keeps member repos out of the workspace repo, so you commit only the manifest and the knowledge:
cd ~/workspaces/myapp
git add .shipd-config.json .gitignore .shipd/
git commit -m "myapp workspace: manifest + wiki"
git remote add origin [email protected]:acme/myapp.git
git push -u origin main
- Never remove the managed
.gitignoreblock (# >>> shipd-workspace members…# <<< shipd-workspace members). It keepsapi/and its siblings out of the workspace repo — no submodules, ever. - Wiki writes (
/s:teach, queued oracle questions) auto-commit locally and never push. End a session withgit push, and start one withgit pull. The wiki then travels between your machines, and between everyone sharing it. - Sharing the repo across several engineers works the same way, with a few conflict surfaces — see Sharing a workspace with a team.
Load it on another machine
Run one command in a Claude session:
/s:workspace clone [email protected]:acme/myapp.git ~/workspaces/myapp
It clones the workspace repo, then runs the sync flow, which executes the engine's plan member by member — cheapest rung first:
- worktree of an existing local clone with the same origin (near-instant),
git clone --referenceborrowing a local object store (seconds),- full clone from the manifest
url(only when the machine has nothing).
Nothing materializes before you consent. The flow opens one round over the whole plan. It offers reuse of the checkouts the scan found (the default, mapped not cloned), fresh materialization, a member-by-member review, or a stop.
The manifest never records how a member landed: materialization is per-machine, so one workspace repo works on every machine and in every teammate's clone.
Day to day
shipd workspace— roster, focus, absent members,[url]markers.shipd workspace sync— re-plan any time. It reports drift (an on-disk origin differing from the manifest) and never repairs it;--jsonemits machine-readable records./s:workspace sync— execute the plan again after you edit the manifest, for example when the job gains a member repo./s:ask— the oracle answers from the job wiki, then enclosing workspace wikis (nearest first), thenwiki_base, then the repo's spec surfaces. Unanswerable questions queue in the job's own wiki, for whoever answers first./s:teach— distill decisions into the job wiki, and promote job-independent answers to the base wiki so every future job inherits them.- Per-change work inside a member repo does not change: each member keeps its
own
.worktrees/<change>flow. You pay the workspace worktree or clone cost once per job, not per task.