Start

Moor your first model

Pick a model that fits your hardware, write it down as a mooring, and swap or cast it off from inside a session.

A mooring is a model tied up and written down

A dock is an inference engine or endpoint. A berth is one spot at it, holding exactly one moored model. A mooring is both halves: the model tied up in that berth, and the named record saying which model, which dock, and what it is for.

With no records saved, the launch flags become one anonymous mooring called default. With nothing saved anywhere, the engine starts unmoored, on a provider that refuses every call by name, while the fleet listing and the mooring form keep working so the model gets chosen from inside the session.

Four docks take a mooring

DockWhat it isBerths
llamacppShoal starts llama-server on this machineas many as fit
mlxShoal starts mlx_lm.server on this machine, Apple siliconone
remoteAn OpenAI-compatible server Shoal does not manage: vLLM, llama-server on another box, anything that speaks the shapeone
anthropicA frontier model over the network, billed per requestunlimited

Only the two local docks are detected at startup: mlx first on Apple silicon, then llamacpp on any platform. The remote dock is never detected. It is reached by passing an endpoint, naming the dock, or writing a record for it. The offshore anthropic dock activates only when a record names it, because a request that leaves your machine should be a decision somebody wrote down. A remote mooring needs an endpoint or a port, and an offshore one needs a key.

The models command names what fits here

Run shoal models. It prints the hardware it found, then every model in your models directory with its size, and marks the one it recommends: the largest that fits the memory budget and can carry a tool call. The tool-call column comes from each GGUF header, so no weights load. A model whose chat template renders none is named as such, beside the note that a mooring setting tools: text reaches it by describing the tools in the prompt instead.

The recommendation line carries the command that moors it, and --json gives machine-readable output. Any GGUF path or MLX identifier works, listed or not.

The berth ledger refuses what will not fit

Before a local mooring is admitted, the ledger estimates what it would take: weights at their on-disk quantized size, KV cache summed per layer, and overhead. It compares that against live device memory, read from nvidia-smi or rocm-smi under a timeout, or a deterministic budget on Apple silicon.

Then it admits or refuses. A refusal carries the estimate, the budget, and at least one alternative, structured rather than prose: a mooring to cast off and the bytes that frees, or a shorter context that does fit. The failure direction is fixed at refusal rather than an out-of-memory crash. The ledger does not guess: a device it cannot read is admitted with the reason said out loud.

A mooring record is a small JSON object

Records live under moorings in .shoal/config.json for one repository, or in the user-wide config for the ones you keep across projects. A project record wins over a user record of the same name. Names are kebab-case and unique, and one mooring holds the main purpose.

{
  "moorings": [
    { "name": "main", "dock": "llamacpp", "model": "gpt-oss-20b-MXFP4.gguf",
      "purpose": "main", "context": 32768 },
    { "name": "gate", "dock": "llamacpp", "model": "small-3b.gguf",
      "purpose": "gate", "context": 8192 },
    { "name": "shop", "dock": "remote", "model": "qwen2.5-coder-14b",
      "purpose": "reader", "endpoint": "http://host:8000/v1", "key": "shop-token" },
    { "name": "deep", "dock": "anthropic", "model": "claude-opus-5",
      "purpose": "reader", "key": "anthropic", "price_in": 5, "price_out": 25 }
  ]
}

The engine routes on five purposes: main, task, janitor, gate, and reader. Any other is a label nothing routes to. key names a secret and never holds one: store the value with shoal secret set <name>, which reads it from a prompt or stdin. chat_template names a llama.cpp built-in template or a jinja file, for a model whose own header carries none. A form writes the same record and can tie it up in the same move.

Swapping and casting off are two different acts

/moor <name> swaps the active mooring mid-session. The conversation carries over, the prefix cache does not, so the next turn re-reads the whole history at prefill speed and Shoal says so rather than letting a slow turn read as a fault. /moor with no argument lists the fleet, and /moorings lists it with what is tied up. /moor given a model path, or a name from your models directory, builds a mooring on the spot and writes it to no file.

/unmoor <name> casts off. The server stops if this process started it, the berth goes back to the ledger, and the record stays, ready for /moor again. Forgetting a record is the opposite move: it removes the record and leaves the process running. Both are labelled plainly wherever they appear. The active mooring cannot be cast off, since stopping the server that answers the next request would leave the session on a closed port. Moor somewhere else first.