Soren

Machine-facing data, priced per call, paid over x402.

Nine agents, one API quota

Distributed locks, semaphores, shared rate-limit buckets and monotonic counters for multi-agent fleets. No signup, no Redis, no infrastructure — pay once for a session and coordinate for as long as it lasts.

The problem this solves. Ten workers must not process the same job twice. Nine agents share one upstream API key with a rate limit that belongs to all of them and none of them. Two agents need the next order number and must not both get 1042. Every one of these needs an authority outside the agents, which is the one thing a fleet cannot build out of more agents.

Why a session, and not a price per call

Settlement on Base takes roughly 1.9 seconds. A lock that takes 1.9 seconds to acquire is not a lock — fine-grained coordination is the entire point. So the payment happens once, up front, and every primitive call after that runs free of the chain.

Measured on production: 57ms median for an uncontended call, 64ms at p95.

Pricing

TierOperationsValidPricePer 1k
Trial1001 hour$0.05$0.50
Standard1,00024 hours$0.25$0.25
Fleet5,0007 days$1.00$0.20

Renew and release are free. Not a promotion — a design requirement. If releasing a lock cost an operation, a fleet that exhausted its budget mid-run would be stranded holding locks it could not let go of, for want of two hundredths of a cent. You can always let go of what you hold.

The whole flow

# 1. Buy a session (this is the only call that touches the chain)
POST /v1/coord/session?ops=1000
  → { "session_token": "<64 hex>", "ops_total": 1000, "expires_at": "..." }

# 2. Coordinate. One operation each.
POST   /v1/coord/lock/build-cache?limit=1&ttl=60&holder=agent-7
  → { "acquired": true, "fence": 41, "expires_at": "..." }
  → { "acquired": false, "retry_after": 12, "next_expiry": "..." }

POST   /v1/coord/lock/build-cache/renew?holder=agent-7&ttl=60    # free
DELETE /v1/coord/lock/build-cache?holder=agent-7                # free

# A semaphore is the same primitive with limit > 1
POST   /v1/coord/lock/gpu-pool?limit=4&ttl=300&holder=agent-7

# A rate limit shared across the whole fleet
POST   /v1/coord/quota/openai?rate=60&per=60&burst=60
  → { "granted": true, "remaining": 41, "full_at": "..." }
  → { "granted": false, "retry_after_ms": 850 }

# The next number, with no collisions across ten agents
POST   /v1/coord/sequence/order-id
  → { "value": 1042 }

Every call carries Authorization: Bearer <session_token>.

A lease is not mutual exclusion, and we will not pretend it is

This is the part most lock services leave out, so read it before you build on this.

A holder that stalls past its TTL — a long GC pause, a network partition, a suspended VM — wakes up still believing it holds the lock, while another agent has legitimately taken it. Both then act. No TTL fixes this, because the stalled process has no way to know time passed.

The only fix is that the thing you are protecting refuses stale writers. So every acquire returns a fence, strictly increasing per lock. Carry it into whatever you are guarding and reject anything with a lower one:

UPDATE cache SET value = ?, fence = ?
 WHERE key = ? AND fence < ?     -- a stalled holder's write does nothing

Without a downstream fence check, what you have is an advisory lock. That is often fine — plenty of work is idempotent, or the cost of doing it twice is low. But it is your call to make, and you cannot make it if we let you believe the TTL was doing more than it was.

What else is worth knowing

What we hold

Control state and nothing else — a permit, an expiry, a counter, a number of tokens. There is no payload here to leak, and names are not persisted: a primitive is addressed by a hash of your wallet, namespace and name. That is also why there is no “list my locks” endpoint; it would mean holding the names.

Also here: attestation and secret handoff, signed oracles over public-domain sources, a free x402 conformance suite, and cross-store game pricing.