Download URLs are short-lived
Clients never get a permanent blob URL. To fetch an asset they call MintDownloadUrl
and receive a SAS URL — a signed, time-boxed link minted by
core-storage. A leaked link rots harmlessly.
Soundverse 2.0 is a contract-first polyrepo: around twenty small, single-purpose
repositories under backend/core/ (enumerated in the
repository catalog), stitched together by one Protobuf
contract. A user types a prompt in the browser; a minute later a finished master lands in
their library. In between, a handful of services authenticate the request, price and hold
tokens, queue a job, run an LLM or a model provider, stream live progress back, store the
result, and reconcile the bill.
This page is the map of that whole system. It is the deeper landing that the 60-second mental model links into — the same hero diagram, but with the “why” behind each box. If you read one architecture page first, read this one.
flowchart TB
U([Browser / user]):::frontend
subgraph EXT[External services]
LOGTO[Logto OIDC IdP]:::external
BLOB[(Azure Blob Storage)]:::external
PROV["LLM + model providers<br/>Anthropic Claude · Kimi · Sansaarm"]:::external
end
FE["soundverse-saas-2.0<br/>Next.js SPA + BFF"]:::frontend
GW["core-gateway-consumer<br/>trust boundary · authz · billing"]:::gateway
ID["core-identity<br/>OIDC validate · JIT provision"]:::gateway
MCP["core-mcp<br/>MCP registry · 2nd billing pipeline"]:::gateway
DB[("core-database<br/>sole Postgres data plane")]:::data
PG[(PostgreSQL)]:::data
RDS[("Redis<br/>cache · queue signal · event bus")]:::data
ST["core-storage<br/>blobs · SAS · HLS"]:::data
subgraph WK[Tool workers · soundverse-py WorkerFleet]
TA[core-tool-agent]:::worker
TS[core-tool-sansaarm]:::worker
TST[core-tool-stitch]:::worker
end
U --> FE
FE -->|OIDC login| LOGTO
FE -->|gRPC consumer/v1| GW
GW <-->|SSE stream| FE
GW -->|validate token| ID
ID --> LOGTO
GW -->|reserve · QueueTask · settle| DB
GW -->|publish task:wake| RDS
ID -->|gRPC| DB
DB --> PG
DB --> RDS
RDS -.->|task:wake| WK
WK -->|ClaimNextTask · CompleteTask| DB
WK -->|XADD progress| RDS
RDS -->|read stream| GW
TA -->|MCP sub-tools| MCP
MCP -->|2nd bill · QueueTask · settle| DB
MCP --> RDS
WK -->|UploadFile| ST
ST --> BLOB
TA --> PROV
TS --> PROV
classDef frontend fill:#6366f1,color:#fff,stroke:#4338ca
classDef gateway fill:#0ea5e9,color:#fff,stroke:#0369a1
classDef data fill:#8b5cf6,color:#fff,stroke:#6d28d9
classDef worker fill:#10b981,color:#fff,stroke:#047857
classDef external fill:#f59e0b,color:#111,stroke:#b45309
Colour marks the tier: frontend (indigo), gateway (blue), data plane (purple), worker (green), external (amber) — the shared 5-tier legend every diagram in these docs uses. Five ideas hold the whole picture together; the rest of this page is those five.
core-gateway-consumer is the trust boundary. It is the only
backend service the outside world is allowed to reach. Everything past it is the internal
mesh, and internal calls authenticate to each other with a shared secret (canonical name
INTERNAL_RPC_SECRET).
The browser never talks to the gateway directly, either. It talks to the BFF — the
server-side /api/* route handlers inside soundverse-saas-2.0 —
which holds the session, attaches the OIDC access token, and speaks gRPC on the browser’s
behalf. A raw gateway credential never reaches the client.
Notice that core-database is the only box touching PostgreSQL. It is the sole Postgres-credentialed service in the entire system — no gateway, worker, or tool ever holds a database password. They all reach data through core-database’s gRPC contract.
That single door is what makes the contract-first data plane work: the SQL lives in the
proto as an annotation, and there is exactly one place that runs it. A representative
example is the queue-claim RPC, ClaimNextTask, whose entire body is a
SELECT ... FOR UPDATE SKIP LOCKED declared in
task.proto.
Codegen turns that annotation into the Go handler that binds request fields to the SQL
placeholders. See The contract and
the DB codegen plugin.
Redis is not the task broker — it plays three smaller roles:
cache_key
annotation on read RPCs.task:wake publish nudges idle workers awake instead of making
them poll. The channel is env-namespaced: {env}:common:task:wake (suffix constant
task:wake; the common: prefix is the cross-language contract shared by the Python
gateway and the Go/Python workers).XADD progress events; the
gateway tails the stream and relays each one to the browser as Server-Sent Events.The actual queue is a Postgres table, generation.tasks, not a separate broker — see
Task queue on Postgres.
Every generation — a song, stems, album art, a stitch — runs on a tool worker, not a
web service. A worker has no ingress: it never receives a request. It polls the queue,
claims a task with a time-boxed lease (claim_expires_at = NOW() + INTERVAL '2 minutes',
renewed by HeartbeatTask), runs the model, uploads the result to
core-storage, reports its real usage, and drains on
SIGTERM.
All of that plumbing — registration, task-claiming, heartbeats, deduped uploads, usage
reporting, graceful drain — lives in the soundverse-py WorkerFleet,
so a tool author writes only three things and a process() method (see
Add a tool worker). Registering a tool is also what makes it
visible to agents and renders its form in the AI Tools panel — the panel is schema-driven
from the tool’s input model, with no frontend code.
Everything above is the consumer path: a human in the browser drives one generation
through the gateway, which runs authenticate → rate-limit → price → reserve → QueueTask,
then settles the bill against the worker’s reported usage once the terminal event
arrives. The full nineteen-step walk-through is
Life of a generation.
There is a second path you must know about. The agent path starts when
core-tool-agent runs an LLM turn-loop (Anthropic Claude for the
agent_standard / agent_pro tiers, Kimi for agent_flash, routed by litellm model-string
prefix). When the model decides to call a sub-tool, it reaches it through
core-mcp — the MCP tool registry — and core-mcp runs
the same rate-limit → price → reserve → QueueTask → settle pipeline, re-implemented in
Go. The full sequence is The agent + MCP path.
Download URLs are short-lived
Clients never get a permanent blob URL. To fetch an asset they call MintDownloadUrl
and receive a SAS URL — a signed, time-boxed link minted by
core-storage. A leaked link rots harmlessly.
Settlement is at-least-once, effectively exactly-once
If the browser closes before the terminal event, background reconcilers in the gateway and core-mcp sweep unsettled tasks. The token ledger’s idempotency index makes running settle twice a no-op.
task:wake, SAS, and more