Spindle is a music distribution agentic OS. It maps the industry into a vector index — 43,191 radio stations and curators, from public registers — then works out who should hear a record and writes to them individually. Not a blast. Every page below is live against the cluster.
read-only · no sign-in · every number queried on page load
One query over the whole index, on page load. The countries light in the order we first reached them. Click one to search what is in it — the radio is indexed now; the creator platforms are the next job, and they are listed dark rather than left off.
An autonomous system that acts on the world has to be answerable for
each act, and the answers all have the same shape: what was true when we
decided. So every consequential decision — a shortlist, a send, a raised cap,
an opt-out, even a decision not to act — carries the hybrid logical clock
it happened at. That coordinate is not a copy of the answer; it is a place to
stand in the database and read the answer back, with
AS OF SYSTEM TIME. No
audit table, no snapshot, nothing that can drift from what actually happened.
reading the ledger…
Now one of them all the way down. Replaying this shortlist re-runs it against 34,332 embedded counterparties at the exact instant it was stamped — and checks itself against the rank written down at the time.
Four predicates — tenant, embedding model, party class, contact state — sit in the index prefix, so the search happens inside the filtered subspace rather than over everything followed by a discard. A test asserts this plan on every run, because a query that quietly degrades to a full scan still returns rows that look correct.
Sending is the one act this system cannot undo, so the guarantees are constraints rather than conventions — things the database will not let us get wrong, even when we are in a hurry.
FOR UPDATE SKIP LOCKED, and a fence that
fails closed.
A partial unique index on (tenant_id, counterparty_id) where the
thread is not closed. Two campaigns cannot work the same person at once, and
the lock releases the moment the conversation ends.
UNIQUE (message_id). A double approval is a failed insert, not a
second copy in flight. The gate is safe to double-click.
Every agent action is fenced on a lease_token stamped at claim
time. Two workers with the same name, one lead: the name cannot tell them
apart, the token can. The stale claim is refused.
Ask us to stop and opted_out is a state no discovery stage can
overwrite. Guessed addresses are refused outright. Not spam by construction,
not by policy.
A submission that claims everything is unique is one a judge stops believing. So here is the audit we ran on ourselves, and the single row that survived it.
| What we use | Postgres equivalent | Verdict |
|---|---|---|
| Vector index, cosine, prefix-filtered | pgvector + composite indexes | replaceable |
| pgvector does this, and has for years. Our prefix trick is a composite index with the vector column last — an idea Postgres can express directly. If the shortlist were all this system did, there would be no argument here. | ||
| SERIALIZABLE by default | one SET statement | replaceable |
SET default_transaction_isolation = 'serializable'. The honest
difference is that we did not have to remember, and a default nobody has to
remember is worth something — but it is a default, not a capability.
| ||
| FOR UPDATE SKIP LOCKED | Postgres shipped it first | replaceable |
| Postgres 9.5, in 2016. The lease fence you just played with would work unchanged on it. We are not going to claim a 2016 feature as a 2026 reason. | ||
| Scale to zero | Neon, Aurora Serverless | replaceable |
| Several managed Postgres products scale to zero, some of them more aggressively than we do. This is a cost property of a deployment, not a property of a database, and it belongs in the concessions column. | ||
| AS OF SYSTEM TIME over a vector index | nothing | irreplaceable |
| This is the row the section exists for. Our agents write to real people, so the question why did you contact me has to be answerable — and answering it means re-running the ranking against the index as it stood at that second, not as it stands now. Four extra words of SQL, no snapshot table, no audit copy of the embeddings. The tuner above is that query: run it against a past window and watch the shortlist differ from today's. | ||
Our agents contact real people. When someone asks why did you write to me, we re-run the ranking that caused it against the memory as it stood at that second — the same index, the same embeddings, the same lessons. Four extra words of SQL. That is the reason an autonomous system that acts on the world can be held to account, and it is the reason this is built on CockroachDB.
Most pricing pages are a promise. This one is a predicate. Every
plan below is a row in budget, and the spend gate that reads it
refuses the work rather than invoicing for it afterwards. The unit is an
open conversation, because one open thread per counterparty — label-wide
— is already a partial unique index. We are not inventing a meter to bill you
with; we are charging for the one thing the schema was already counting.
Everything the console does, on one artist, with sending off. Enough to judge the product without a card.
An independent label running three acts. Fifty conversations a month is roughly one campaign per act per quarter, worked properly.
A full roster, no cap on how many acts. The meter is conversations, so an artist you are not pitching costs nothing.
Back catalogue, multiple labels, or a shape none of the above fits. Priced against the work; talk to us.
What this page does not claim.
Sending is off in this deployment on every tier — the
console reports sender_wired: false and will keep reporting it until
a human wires a sender. A free account is genuinely bounded rather than
trust-based: the cap is enforced by the same gate that protects our own spend,
and that gate fails closed.