Onboarding — for the next person
Read this first if you've just been handed the keys. It assumes Python + TypeScript + a little Rust, and that you know roughly what Lakeshore does. It does not assume you have seen the repos before.
By the end you should be able to boot a local stack, run the smoke test, ship a docs change, and know where to look when something breaks.
The shape
Lakeshore is not a monorepo. Every component is an independent
repository with its own tests, lockfile, CI, and release cadence. There
are no workspace:* dependencies across them and no cross-repo source
imports; anything shared has to become a published package.
lakeshore-docs — the repo you are reading — owns the docs site, the
local-dev tooling (mprocs.yaml, docker-compose.yml,
scripts/dev-up.sh), and the version-coordination scripts. It does not
contain the sibling checkouts.
To get the whole stack side by side, clone the container repo — it carries each sibling as a submodule:
Repo map
| Directory | Language | Source repo | Distribution |
|---|---|---|---|
lakeshore/ | TypeScript | dreamlake-ai/lakeshore | npm: @dreamlake/lakeshore |
lakeshore-controlplane/ | TypeScript (Fastify 5 + Prisma 5) | dreamlake-ai/lakeshore-controlplane | Heroku — live at api.lakeshore.dreamlake.ai |
lakeshore-py/ | Python (httpx + msgpack) | dreamlake-ai/dreamlake-lakeshore | PyPI: dreamlake-lakeshore |
nymph/ | Rust (tokio + reqwest) | dreamlake-ai/nymph | Cloudflare R2 (publish = false — never on crates.io) |
lakeshore-web/ | TypeScript | dreamlake-ai/lakeshore-web | Netlify — demo.lakeshore.dreamlake.ai |
lakeshore-examples/ | Python + YAML | dreamlake-ai/lakeshore-examples | not published; clone and run |
lakeshore-docs/ | MDX | dreamlake-ai/lakeshore-docs | Netlify — lakeshore.dreamlake.ai |
Spending your first hour
mprocs drives the local stack. Only the db pane autostarts;
everything else is opt-in with s on the pane.
Then, from a shell:
scripts/dev-up.sh is the non-interactive equivalent of the db +
lakeshore + nymph panes — background processes, logs under
/tmp/lakeshore-dev/*.log, idempotent, torn back down with
scripts/dev-down.sh.
For the docs site itself:
Every CLI command except the auth group resolves the control plane
from LAKESHORE_URL (with LAKESHORE_NAMESPACE, default default).
LAKESHORE_SERVER is read only by lakeshore auth login/status.
Setting the wrong one is the most common "why is it not talking to my
local stack" bug. Note also that when LAKESHORE_URL is set the CLI
sends no bearer token at all — it deliberately bypasses the saved auth
file.
How each component releases
Each repo ships on its own. You can release one without touching any other.
CLI (lakeshore/ → npm)
The npm OTP expires fast — generate it immediately before publishing.
Python SDK (lakeshore-py/ → PyPI)
Daemon (nymph/ → R2)
CI builds on a v* tag push, for three targets:
x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu, and
aarch64-apple-darwin. Intel macOS was dropped. Each target ships a
.tar.gz (for scripts/install.sh) and a raw binary (for the OTA
path), each with a .sha256 sidecar, into the R2 bucket
lakeshore-releases under dreamlake/nymph/<version>/.
Control plane (lakeshore-controlplane/ → Heroku)
No version bump — the deploy is git-sha based. See Deployment for the env-var contract and the schema-push step.
Docs site (this repo → Netlify)
A push to main gets you a branch-deploy preview, not production.
Production is a command:
pnpm prod refuses to run when the two package.json versions
disagree.
Where to look when something breaks
| Symptom | First place |
|---|---|
lakeshore exec hangs locally | /tmp/lakeshore-dev/nymph.log and controlplane.log, or the mprocs panes |
| CLI says "server not configured" | LAKESHORE_URL unset and no saved auth login |
| CP build fails on the Heroku push | The push output — usually Prisma schema drift or a stale pnpm-lock.yaml |
| Daemon registers but gets no work | Check Worker.queues — default-queue membership is the empty array, not a literal "default" entry |
Nymph won't build with gcc-11 not found | A stale CC export. unset CC, or use the mprocs nymph pane, which forces CC=clang |
| Docs site 404s a new page | docs/pages/<slug>/+Page.mdx needs all three required frontmatter fields (title, section, order) |
pnpm install says the lockfile is out of sync | Someone bumped a dep without committing pnpm-lock.yaml. Re-run and commit it — Netlify uses --frozen-lockfile |
Reading the design notes
Everything under /dev/* is internal design rationale — hidden from the
public sidebar, reachable by URL. Each is the source of truth for its
concept; the public pages are user-facing distillations. Start with:
- /dev/queues — the scheduling primitive
- /dev/payload-store — the inline/S3 two-tier payload path
- /dev/python-sdk — SDK ergonomics and open threads
- /dev/concurrency — daemon work classes
- /dev/controlplane-internals — the server's moving parts
Rough edges that bite everyone
-
Two config-file families, easily conflated.
.dreamrcis the YAML with!providers.<Kind>tags (providers + modes, seven-step resolution chain including a server pull)..lakeshore/.lakeshore.localhold project defaults fordaemon launchand are walked up from cwd, stopping at the git root.lakeshore.yamlis the compose file forup/down/ps/status/logs, and is looked up in cwd only. -
Compose verbs are top level.
lakeshore up,down,ps,status,logs— there is nolakeshore composecommand, even though the source lives insrc/cli/compose/. -
The Python SDK reads
LAKESHORE_URL+LAKESHORE_CLIENT_TOKEN. The legacy nameLAKESHORE_TOKENstill works with a one-time stderr deprecation warning. WithLAKESHORE_URLunset the SDK falls back to a local SQLite plane underLAKESHORE_HOME(default~/.lakeshore) — tests that "pass locally but not against staging" are usually this. -
Heroku does not run Prisma migrations. This project uses schema-push: after changing
prisma/schema.prisma, runpnpm prisma db pushagainst the target database yourself. -
Nymph's
keep_alive_sdefaults to 300. A daemon you started for testing exits after five idle minutes.-1is pool mode (never idle-exit) and0exits the moment it goes idle. The local dev config atdev/local-udf-daemon.tomlalso drops the poll wait so smoke steps don't sit in a long-poll. -
Sibling checkouts are independent repos. The container repo pins submodule commits, but each checkout has its own branches and remotes.
git statusinside a checkout before assuming it matchesorigin/main. -
Parallel git operations on one checkout clobber each other. Two terminals in
lakeshore/, one switches branches, the other's working tree silently changes. Usegit worktree addfor parallel work.
When in doubt
- Read
CLAUDE.mdat the root of whichever repo you are in — those are the "what is load-bearing here" briefs. RUNBOOK.mdcarries copy-paste recipes for smoke, release, and deploy.mprocs.yamlis heavily commented and is the fastest description of the local topology.- Git history is short and well-commented;
git log --onelinein a sibling repo gives you the whole story.
Welcome aboard.