CockroachDB Built for the CockroachDB × AWS Hackathon — Agentic Memory

Most labels start every release from zero. Spindle remembers who said yes.

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

43,191counterparties
34,332embedded
29,667with genre
1lessons learned
2sent, ever
Counterparty index

Every country we can already take a record to.

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.

counterparties indexed
countries reached
placed on the map
added today

Decision provenance

Every decision is rooted to an instant you can read the database at.

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.

the decision ledger — every row is a question somebody can ask apps/spindle/schema/035_decision_ledger.sql

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.

loading a recorded decision…
The filters are inside the index

It is a vector search, not a scan with a filter bolted on.

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.

party@party_shortlist — the index, as declared apps/spindle/schema/009_counterparty_index.sql
cosine profile_embedding target count 20
Unbind a column to see what the planner can still use.

What we cannot take back

A missed pitch costs nothing. A second pitch costs us the curator.

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.

two workers · one lead · b844314c… · both named the same thing
ingest-cliworker A
no lease
ingest-cliworker B
no lease
“lease expires” is worker A stalling past the TTL — the stall is what makes the race reachable at all.
    messages actually sent 0 duplicates 0 try to make it two
    The name cannot tell them apart. The token can — it is stamped at claim time, only the database mints it, and every write is fenced on it being current. Serializable isolation, FOR UPDATE SKIP LOCKED, and a fence that fails closed.
    what an agent does when it finishes a piece of work
    BEGIN
    1. the writewhatever it learned — a fact, a metric, a lesson
    2. agent_runone row: state, duration, tokens, and what it cost
    3. complete the leadfenced on the lease token, which releases it
    COMMIT
    Three statements, one transaction — so a worker that dies halfway leaves nothing behind rather than a fact with no run, or a run with no completion. The network call that produced the write happens outside the boundary, on purpose: a transaction held open across an HTTP request is a different bug.

    One open thread per counterparty

    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.

    One outbox row per message

    UNIQUE (message_id). A double approval is a failed insert, not a second copy in flight. The gate is safe to double-click.

    A token only the database mints

    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.

    Opting out is terminal

    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.

    Where Postgres would do

    Most of this is replaceable. One thing is not.

    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 usePostgres equivalentVerdict
    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.
    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.
    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.
    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.
    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.

    What it costs

    A tier is a budget cap, and the cap is a constraint.

    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.

    18

    Free / Judge $0
    • 5 open conversations per calendar month
    • 1 artist

    Everything the console does, on one artist, with sending off. Enough to judge the product without a card.

    Label $49 / month
    • 50 open conversations per calendar month
    • 3 artists

    An independent label running three acts. Fifty conversations a month is roughly one campaign per act per quarter, worked properly.

    Roster $199 / month
    • 250 open conversations per calendar month
    • Unlimited artists

    A full roster, no cap on how many acts. The meter is conversations, so an artist you are not pitching costs nothing.

    Catalogue Talk to us
    • Conversation volume by agreement
    • Unlimited artists

    Back catalogue, multiple labels, or a shape none of the above fits. Priced against the work; talk to us.

    Stripe live mode — real charges

    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.