An Spinloop is a small, declarative file that captures one provider
selection — which provider, and which model — so you can
apply it with a single command instead of remembering flags. Think of it like a
Dockerfile, but for pointing your coding agent at a model.
# Spinloop — point your coding agent at one provider
PROVIDER openrouter
MODEL deepseek/deepseek-v4-pro # the provider-native model ref
ALIAS deepseek # optional; friendly name for the model
CONTEXT 128k # optional; context window (per request)
OUTPUT 32k # optional; max output tokens
PARALLEL 2 # optional; concurrent request slots for `serve`
BASEURL https://gateway/v1 # optional; API base URL override
PRESET ./preset.ini # optional; engine preset for `spinloop serve`Applying it is the same as running the equivalent
spinloop harness add, so everything you already have in your coding
agent's config is preserved.
The harness (opencode or Pi) is deliberately not part of a Spinloop — so
the same file applies to either. Choose the harness when you apply it, with
--harness/-H, the SPINLOOP_HARNESS env var, or a stored default
(spinloop harness config --set).
One file, several commands:
spinloop harness apply— apply the selection to your agentspinloop harness unapply— take it back outspinloop harness open -O— apply it, then launch the agentspinloop serve— runllama-serverfor the model it namesspinloop up— the one-word start: its server, or the fleet's nodes where afleet.yamlis beside itspinloop alias— register it under a short namespinloop harness export— write one from your current setup
Every command that takes a Spinloop path accepts a directory that holds one and
takes a registered alias in place of a path. Given no path
at all, it uses the alias SPINLOOP_ALIAS names, and failing that ./Spinloop in
the current directory.
A Spinloop path can also be an http:// or https:// URL, fetched instead of
read from local disk:
spinloop harness apply https://example.com/team/SpinloopA URL ending in / is treated like a directory — Spinloop is appended, so
spinloop harness apply https://example.com/team/ fetches
https://example.com/team/Spinloop. spinloop alias can
register a URL too, so a team can hand out a short name for a published Spinloop
instead of a link:
spinloop alias -n team-default https://example.com/team/Spinloop
spinloop harness apply team-defaultA relative PRESET in a URL-sourced Spinloop resolves against that
URL rather than a local directory — see Syntax below — and is
fetched only when the command that actually needs it runs, never merely
because the Spinloop itself was read.
For a model too big for your machine, spinloop remote runs it on a
scale-to-zero GPU endpoint — one that runs only while you're using it. The
Spinloop says what the endpoint serves:
# Spinloop — what the environment serves
PROVIDER llamacpp # the engine to run there, as it would run here
ALIAS qwen3.6-27b
CONTEXT 131072
PRESET ./preset.iniThe Spinloop says what; it no longer says where. Where is an
environment — a deployed, registered endpoint — and the environment is named
with a --env <name> flag on the commands that act on it:
spinloop remote deploy --env qwen3.6-27b-prod # from the directory holding the Spinloop
spinloop harness apply --env qwen3.6-27b-prod # point opencode at it
spinloop harness open --env qwen3.6-27b-prod # work
spinloop remote stop --env qwen3.6-27b-prod # doneEach --env <name> reads the environment's registered config at
${XDG_CONFIG_HOME:-~/.config}/spinloop/remotes/<name>/remote.json — the file
spinloop remote deploy writes when it creates the
environment — so deployment state stays per-user and per-machine while the
Spinloop itself stays clean enough to commit. A command given no --env uses
the default environment, and an unregistered name fails, naming the deploy --env that would create it. A name is a plain identifier: --env ./x.json
fails, saying so. See spinloop remote for the full
lifecycle.
Note the missing BASEURL: the endpoint's address belongs to the deployment,
which records it in the environment's remote.json as base_url, and
spinloop harness apply reads it from there. Write a BASEURL only
to override that.
Applying with --env also names the harness provider after the environment
rather than the engine: the example above is configured under qwen3.6-27b-prod,
with the model reading as qwen3.6-27b-prod/qwen3.6-27b. PROVIDER still
supplies the engine's settings; only the name changes, so several environments
built from the same engine each keep their own entry instead of overwriting one.
The provider's display name is qualified by the environment too — llama.cpp (qwen3.6-27b-prod) rather than a bare llama.cpp — so a remote environment reads
distinctly from a local engine of the same kind in a harness model picker.
A launch may not state both --env and a fleet (the --fleet flag, or the
./fleet.yaml in force when the Spinloop is not named): each names where the
model is served from, so spinloop fails naming both.
spinloop harness open --env qwen3.6-27b-prod also works with no Spinloop at
all: the environment already knows what it is serving, so the harness
configures itself from that — the same result as the Spinloop above, without
needing a copy of it on the machine doing the launching. See
spinloop harness open.
Because PROVIDER names the engine, this is the same file that would run the
model locally with spinloop serve — pointed at a bigger
machine.
A fleet file names the machines on your network
running spinloop daemon, and spinloop harness open can pick one for you.
Which fleet file a launch routes through is a launch concern, not a Spinloop
field:
spinloop harness open --fleet <path>names it explicitly.- Without the flag, a Spinloop you did not name — the default
./Spinloop, worn by a valueless--spinloop— takes thefleet.yamlin the working directory. A Spinloop you did name — a path, a--spinloopvalue, or the aliasSPINLOOP_ALIASnames — routes only by flag.
# Spinloop
PROVIDER llamacpp
MODEL qwen3-27bLaunching against a fleet queries it, picks a node already serving that model,
and points the agent at that node's engine. When nothing is serving it, spinloop
starts one and waits for it to load — so the machine you sat down at needs
nothing but the fleet file. --node <name> pins one machine, and --no-wake
refuses to start anything.
A fleet file may name a gateway instead of nodes: a
single endpoint that has already done the choosing. A launch through such a file
is pointed at the gateway's address — with the OpenAI-compatible /v1 prefix
added when it carries no path — and the agent it launches authenticates with the
gateway's token, resolved the way a key is resolved elsewhere: an ENV
instruction, then the process environment, then the .env beside the Spinloop.
A variable already set wins. Set nowhere, the launch fails before it writes
anything, naming the variable the section names — OPENAI_API_KEY where the
section names none.
A gateway resolves the model per request, so spinloop fleet harness needs no Spinloop at all
when the fleet file names one: with none given, it configures a generic
OpenAI-compatible provider at the gateway's address itself, its model list
populated from the gateway's own listing, and no MODEL/ALIAS to pick.
That provider reads the way a remote environment's does — labelled by the
gateway's own name, or its address when the section names none — the same
"llama.cpp (dev-2)" pattern described above.
Note the missing BASEURL — the address is whichever node gets chosen. Writing
one pins the address and turns routing off, and spinloop says so rather than
choosing a node and discarding it.
See spinloop fleet route to check
which node you would get before launching anything.
One instruction per line: a keyword followed by a single value.
| Keyword | Required? | Maps to | Example |
|---|---|---|---|
PROVIDER |
yes | --provider |
PROVIDER openrouter |
MODEL |
one of MODEL/ALIAS |
--model |
MODEL deepseek/deepseek-v4-pro |
ALIAS |
one of MODEL/ALIAS |
--alias |
ALIAS deepseek |
CONTEXT |
no | --context |
CONTEXT 128k |
OUTPUT |
no | --output |
OUTPUT 32k |
PARALLEL |
no | spinloop serve, spinloop remote deploy |
PARALLEL 2 |
BASEURL |
no | --base-url |
BASEURL https://gateway/v1 |
PRESET |
no | spinloop serve |
PRESET ./preset.ini |
ENV |
no (repeatable) | spinloop remote, spinloop harness |
ENV AWS_PROFILE=prod |
Rules:
- A Spinloop describes exactly one provider.
PROVIDERis required and may appear only once; so may every other keyword, exceptENV. - You need at least one of
MODELorALIAS. Give aMODELto add a specific model; give anALIASto name it. MODELis the reference the provider itself understands: an OpenRouter/Bedrock model id, an Ollama name, or — for llama.cpp — a Hugging Face repo (org/model:quant) or a path to a.gguf.ALIASis the friendly name the harness shows for the model (and, underserve, the namellama-serverreports and the preset section to run). It defaults toMODEL. For a llama.cpp server the model key is only a label, so anALIASkeeps it readable; anALIASon its own is enough to select one.CONTEXTsets the context window for the model(s) — always the context a single request gets, whatever serves it. It accepts human suffixes (128k,1m) or an absolute count (200000).OUTPUTcaps the max output tokens, in the same format asCONTEXT. Left out,spinlooprecords a quarter of the context. It cannot exceed the context window.PARALLELsets the number of concurrent request slots forspinloop serveandspinloop remote deploy— a plain integer, not a size. It has no meaning for a hosted provider selection, only for a served engine, so unlikeCONTEXT/OUTPUTit has noadd/removeCLI flag. SinceCONTEXTalways means "context per request", and llama.cpp's own--ctx-sizeis a total budget it divides across its--parallelslots, allamacppSpinloop with bothCONTEXTandPARALLELset gets a--ctx-sizescaled by the slot count so each slot still gets whatCONTEXTpromised (CONTEXT 128k+PARALLEL 2→--ctx-size 256000 --parallel 2).vllm,mtplx, andomlxhave no such coupling —PARALLELbecomes--max-num-seqs/--max-active-requests/--max-concurrent-requestsrespectively, andCONTEXTis never scaled by it. Seespinloop servefor the full per-engine mapping.BASEURLoverrides the provider's API base URL — handy for a gateway or a llama.cpp server on a non-default port.URL,BASE-URL, andBASE_URLare accepted as aliases.PRESETpoints at a preset.ini, used only byspinloop serve;applyignores it. A relative path resolves against the Spinloop's own directory, or against its URL when the Spinloop itself was fetched from one;PRESETmay also be an absolute URL of its own, fetched only whenserve(orspinloop remote deploy) builds the launch command — never merely because the Spinloop was read. The file is read in the flag vocabulary of the enginePROVIDERnames, so a preset written for llama.cpp is not portable to oMLX and vice versa.ENVsets an environment variable on the machine runningspinloopand is the one keyword that may repeat. Its value is a singleKEY=VALUEtoken (no spaces). Thespinloop remotecommands read it — along with a.envbeside the Spinloop — before they sign their AWS calls, so credentials, region andSPINLOOP_REMOTE_*overrides can travel with the Spinloop.spinloop harness openreads it too, passing the whole.envand theENVlines to the agent it launches. Precedence, highest to lowest: anENVline, then a variable already set in your shell, then the.env— the same rule everywhere spinloop resolves local variables.ENVapplies only on the machine runningspinloop; it is never sent to a deployed instance, and on the harness path it shapes only the launched agent, never spinloop's own environment.- The
REMOTEkeyword was removed: aREMOTEline fails, naming the line and the replacement —spinloop remote deploy --env <name>at deploy time and--env <name>onapply,unapply,harness, and theremotesubcommands. Where aREMOTEline pointed at a path or a URL, register the environment's config withspinloop remote deploy --env <name>instead, and name it from the flags. - Keywords are case-insensitive —
provider,Provider, andPROVIDERare all accepted — but UPPERCASE is canonical and is whatspinloop harness exportwrites. - Comments start with
#, either on their own line or at the end of a line. Blank lines are ignored.
To see the available providers, run spinloop provider list. To find a MODEL id for one,
run spinloop provider list --models <provider>, which asks the provider's own endpoint
what it currently serves.
A local model served by llama.cpp (no API key needed). ALIAS is the name
opencode shows; add a MODEL (an HF repo or .gguf path) or a PRESET if you
also want spinloop serve to launch it:
PROVIDER llamacpp
ALIAS qwen3.6-35b-a3bA single model from OpenRouter (its key comes from your .env or
environment, exactly as with spinloop harness add):
PROVIDER openrouter
MODEL deepseek/deepseek-v4-proAny OpenAI-compatible endpoint, with a single pinned model:
PROVIDER openai-compatible
MODEL my-modelReady-to-use Spinloops live under examples/, including
examples/remote-spinloop/ for fetching one from
a URL.