# Lakeshore

Lakeshore runs Python functions somewhere other than the machine in
front of you. You decorate a function with `@udf`, call it, and the
result comes back — Lakeshore packs the arguments, hands the work to a
queue, and a worker somewhere else runs it and returns the value.

You'd reach for it when the work doesn't fit locally: a GPU job, a long
sweep, a fan-out of identical calls. You write Python; Lakeshore owns
the queueing, the transport, and the worker side.

> **Note:** Material that describes Lakeshore *as part of DreamLake* has moved to the
>   **Lakeshore tab** at
>   [docs.dreamlake.ai/lakeshore](https://docs.dreamlake.ai/lakeshore) — the
>   provider pages, the Python architecture overview, and the newer pages on
>   declaring access, mounting storage, host setup and queues.
> 
>   This site remains the reference for the SDK, the CLI, the daemon, and the
>   control-plane API. Old URLs redirect, so nothing you have bookmarked breaks.

## Install

Pick the surface you are starting from — each page below covers the
authentication and first-run check for that one.

| You are… | Install | Then read |
| --- | --- | --- |
| Writing Python | `pip install dreamlake-lakeshore` (0.3.7) | [Python SDK](/python-sdk.md) |
| Operating a fleet | `npm i -g @dreamlake/lakeshore` (0.2.0) | [CLI installation](/cli/installation.md) |
| Bringing up a compute host | normally installed *through* the CLI — `lakeshore daemon install <ssh-alias>` | [Daemon installation](/nymph/installation.md) |

The CLI is optional for the local tier and required once you manage
providers, daemons, or a hosted control plane. See
[Release notes](/cli/release-notes.md) for the CLI changelog.

The CLI and the SDK each need a **server URL**, a **namespace**, and a
**token**. Get them from whoever runs your control plane, or stand one
up yourself with [Admin setup](/admin/setup).

## Two tiers, one decorator

A bare `@udf` is a **local** call — an ordinary in-process function call
with a run context wrapped around it. Nothing touches the network, and
the HTTP/msgpack stack is never even imported.

```python file="hello.py"
import dreamlake.lakeshore as dls

@dls.udf
def stats(xs: list[float]) -> dict:
    return {"n": len(xs), "mean": sum(xs) / len(xs)}

print(stats([1.0, 2.0, 3.0]))     # {'n': 3, 'mean': 2.0} — runs here
```

Adding `queue=` makes the same function **remote**. A plain call now
submits to that queue and blocks for the result; `.submit()` returns the
invocation id instead, so you can walk away and reconnect later.

```python file="remote.py"
import dreamlake.lakeshore as dls

@dls.udf(queue="cpu")
def double(x: int) -> int:
    return x * 2

print(double(21))                 # 42 — submit, wait, unpack
inv_id = double.submit(21)        # a durable string id; no Future type
```

Point the SDK at a control plane with `LAKESHORE_URL` (plus
`LAKESHORE_CLIENT_TOKEN` and `LAKESHORE_NAMESPACE` when the plane
requires auth). With `LAKESHORE_URL` unset, submits go to a local
SQLite plane under `LAKESHORE_HOME` (default `~/.lakeshore`) — handy for
trying the remote tier without any server at all.

Something has to drain the queue. The simplest drainer is a native
Python worker on your own machine:

```bash
lakeshore worker start --url http://localhost:8080 --queue cpu
```

> **Note:** `lakeshore worker` deliberately does not read the credentials written by
> `lakeshore auth login`. The plane a worker drains must come from `--url`
> or `LAKESHORE_URL`, so a stray login can never redirect a worker.

## The three pieces

```text
your process                 control plane                  worker host
────────────                 ─────────────                  ───────────

 @udf call    ──submit──▶   queue: "cpu"     ◀──poll──   nymph daemon
 result       ◀──result──   invocation done   ──ack───▶   nymph daemon
```

| Piece | What it is | Where it runs |
| --- | --- | --- |
| `lakeshore` CLI | Node binary on npm. Providers, secrets, queues, daemons. | Your laptop |
| Control plane | Fastify 5 + Prisma + MongoDB. Owns the registry and the dispatch loop. | A server (Heroku today) |
| `nymph` daemon | Rust binary. Long-polls the control plane, runs invocations. | The compute host |

Every wire call is daemon-initiated and outbound. The control plane
never opens a connection to a worker, so a worker needs no inbound port,
no port forward, and no firewall change.

## Where to go next

| You want to… | Start here |
| --- | --- |
| Run your first function | [Quick start](/get-started/quick-start.md) |
| Install the CLI and connect a provider | [CLI installation](/cli/installation.md) |
| Understand what's running where | [Architecture](/get-started/architecture.md) |
| Follow a longer tutorial | [Tutorials](/get-started/tutorials.md) |
| Understand queues and scheduling | [Queues](/get-started/queues.md) |
| Run a fleet daemon on a remote host | [Install a remote daemon](/nymph/install-remote-daemon.md) |
