Local Weights · operations manual

The curator's guide

You run the registry: you decide which models, quants, and runtime configurations your team gets, and you sign them. This guide walks the whole job — first publish to ongoing maintenance — using the commands as they exist today. Everything here assumes one curator, one key, one static HTTPS host.

The host is dumb storage

Every byte a client uses is verified against your key or a hash you signed. The host can serve files or fail; it cannot lie successfully. Any static file server on your domain works.

Hashes are identity, URLs are locations

Models, runtime archives, and registry payloads are pinned by SHA-256. Where the bytes come from — GitHub, your mirror, a teammate on the LAN — never matters; whether they match does.

Version arithmetic belongs to tools

Release numbers, hash computation, and publish verification are all done by the tooling. If a command asks you to remember a number or paste a hash, something is wrong.

Part 1 · once

Day zero: from nothing to a live registry

Prerequisites: a clone of the repo, Rust, a domain with any static file host behind it, and a machine that can run the models you intend to curate — curation means you ran it first. The tooling removes the clerical work; the judgment is yours.

STEP 1 / 6

Create your identity

The bootstrap script builds the tools, generates your Ed25519 signing key, and walks manifests and profile creation interactively.

The secret is printed once. It is the team identity: every client that joins will trust this key, and it cannot be recovered or rotated in-band. The script will not continue until you confirm the backup, and it remembers whether you ever did.

bootstrap
$ scripts/bootstrap-curator.sh
-- Step 1: Create signing key --
Key ID (e.g. myorg-2026): myteam-2026
wrote registry-signing-secret.json and registry/trust.json

Back up the signing secret NOW — it cannot be recovered…
  myteam-2026 kkC8/wCCpwqBu3DWiROHnrX3Y5gdH5D4Fx0ImyJJdQA=
Type 'saved' once it is in your password manager: saved
STEP 2 / 6

Describe the models

Manifests come from the Hugging Face API — filenames, sizes, SHA-256 from LFS metadata. Pin a commit SHA, never a branch: branches are selectors, not identity.

enrich adds the BLAKE3 hash (the LAN-transfer identity) for any file you have locally — including the copy the app already hashed on your own machine.

manifests
$ registry-gen gen unsloth/Qwen3.6-35B-A3B-GGUF \
    --revision 1a2b3c4d…   # commit SHA, not "main"
wrote registry/manifests/unsloth--Qwen3.6-35B-A3B-GGUF.json
  — 14 entries (2 with blake3)
STEP 3 / 6

Pin the runtime

llama.cpp publishes prebuilt archives per b-tag that are directly installable — relocatable, self-contained, signed where the platform requires it. pin-runtime downloads them, computes the archive and binary hashes, rewrites the profile in place, and drops mirror copies for your host.

You trust ggml-org's CI once, now, when you sign these pins. After that the bytes are immutable — the URL points at your mirror, and a client accepts nothing that doesn't hash to the pin.

A custom build (a tuned Vulkan build, a backend upstream doesn't ship) is packaged by script into the same shape and must earn its place by benchmark against the official asset.

pin-runtime
$ registry-gen pin-runtime b10145 \
    --artifact macos-arm64-metal=macos-arm64 \
    --artifact linux-x86_64-strix-halo=ubuntu-vulkan-x64 \
    --profile registry/profiles/code-agent.json \
    --mirror dist/runtimes \
    --url-base https://weights.example.com/runtimes
pinned macos-arm64-metal = ggml-b10145-macos-arm64
  archive_sha256 f3ec2351e06322478e3f38f23f…
  binary         llama-b10145/llama-server
  binary_sha256  a4998768a70ba2be02617ec9d8…
updated registry/profiles/code-agent.json
STEP 4 / 6

Sign the first release

release merges manifests and profiles into the payload, signs it, and verifies its own output. --initial is required exactly once — for every later release the tool reads the previous version, authenticates it against your trust root, and increments.

release
$ registry-gen release --initial
wrote registry/registry.json — 1 manifests, 1 profiles
wrote registry/release.json — release 1
verified release 1 — 1 manifests, 1 profiles
release 1 signed and verified — publish registry.json
and release.json together
STEP 5 / 6

Publish

The host needs nothing but files. The script uploads runtime mirrors first (a client must never see a release whose bytes aren't there), stages the registry pair under temporary names, renames payload before release document, then downloads both files back over the public URL and byte-compares them against what was signed. "Published" means proven, not attempted.

  • registry.json — the signed payload
  • release.json — version + signature
  • runtimes/…tar.gz — mirrored runtime archives

trust.json is deliberately not on the host: the trust root travels inside invites, out-of-band.

Throughout this guide weights.example.com stands in for your host — the reference deployment serves the registry at https://registry.wheresmy.ai (registry.json and release.json at the root); everything else, like the app installer, uses the shorter https://wheresmy.ai.

publish
$ scripts/publish-registry.sh \
    deploy@web:/var/www/weights https://weights.example.com
verified release 1 — 1 manifests, 1 profiles   # preflight
verified release 1 — 1 manifests, 1 profiles   # read-back
published and verified at https://weights.example.com
STEP 6 / 6

Invite the team

The invite carries the registry URL and your public key. The fingerprint is the out-of-band check that the key wasn't swapped in transit — post it through a different channel than the link (link in chat, fingerprint in a direct message, or vice versa).

Teammates paste the link into the Weights app (or run weights join on a headless box) and the rest is the end-user journey: verify, fetch from LAN where possible, install, run.

invite
$ registry-gen invite \
    --registry https://weights.example.com \
    --profile code-agent --org "My Team"
ccweights://join?registry=…&key_id=myteam-2026&key=…

Fingerprint: 7f3a 92c1 08b4 d6e5
  Paste this alongside the link, in a separate message.
Part 2 · ongoing

The maintenance loops

Every change ships the same way: edit → release → staging → your own machine → promote. Point your own app's registry URL at https://weights.example.com/staging and you become client zero for every release — each one is installed through the exact end-user code path before teammates can see it. Promotion re-runs publish without --staging: same files, no re-sign.

New llama.cpp build

~ monthly · minutes
  1. registry-gen pin-runtime b10234 …
  2. registry-gen release
  3. scripts/publish-registry.sh … --staging
  4. Your machine updates; agent healthy
  5. scripts/publish-registry.sh …

New model or quant

occasional · judgment first
  1. Download it. Run it. Decide it's worth shipping.
  2. registry-gen gen … / enrich …
  3. Point the tier's model_sha256 at it
  4. release → staging → promote

Config tweak

context size · sampler args · env
  1. Edit the profile JSON (per-tier runtime_args)
  2. release → staging → promote
  3. Clients pick it up on next sync

New app release

per app version · macOS
  1. scripts/package-release.sh v0.2.0 — builds, ad-hoc codesigns, tars app + CLI, generates install.sh
  2. Upload dist/ trio next to the registry
  3. Users run curl -fsSL …/install.sh | sh (browser downloads arrive quarantined — the terminal path is the supported one)
  4. Announce the installer sha256 in a separate channel; record the version as a rad-artifact

New teammate

seconds · nothing to publish
  1. registry-gen invite …
  2. Link and fingerprint, separate channels
  3. The registry is already live
Part 3 · guard rails

What the tools will refuse to do

Each refusal exists because the mistake it prevents is expensive and was either made or nearly made. When you hit one, the answer is never to force past it — it is to understand which of these you are about to be.

RefusalWhat it protects
release without --initial, no release.json An accidentally deleted release document would silently restart at version 1 — valid to you, rejected by every synced client as a rollback. Restore the document instead.
keygen into ~/.weights The data directory is wiped by tests and fresh installs — the exact way the first secret was lost. The guard resolves symlinks and ..; there is no clever path around it.
publish-registry.sh … --stagin A typo in the staging flag must not fall through to production — unknown arguments and wrong argument counts abort.
Publish declares success only after byte-compare A CDN serving the previous release would pass signature verification. Identical bytes prove the host serves this release, not a valid one.
Client rejects version 2 twice with different content Equivocation guard: one version number means one payload, forever. Never edit and re-sign the same version; run release again.
Part 4 · when things go wrong

Recovery

Lost signing secret. There is no in-band recovery, by design — an invite can never replace an existing trust key, so nothing you publish can migrate clients to a new one. The ceremony: keygen with a new key_id, re-pin and release --initial, publish, send a fresh invite to every member — and every machine that synced under the old key must reset its ~/.weights before joining. This is a bad week. The backup line from step 1 is what stands between you and it.
Host compromised or lost. Nothing to revoke: the host never had authority. Clients fail closed against your signatures in the worst case and keep working from local state in the common one. Stand up any other static host, re-run publish, send an invite with the new URL.
Bad release shipped. Fix forward: correct the registry, run release (a higher version), publish. Clients converge on next sync. The staging loop exists so this stays rare — a release that never ran on your machine should never reach the team.