Skip to content

System overview

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 — an optional read-through in front of core-database, keyed by the cache_key annotation on read RPCs.
  • Queue signal — a tiny 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).
  • Live event bus — per-task Redis streams. 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.