~/potatohd.orgRU
← All posts
Aug 10, 2026·7 min read

Nagovori and Saylo: a transcription service on your own GPUs

GoTemporalGPUбиллинг

A user uploads a recording of a call and gets back a transcript and a structured summary a few minutes later. Everything below is what sits behind those two sentences.

The Nagovori home page: a headline promising an hour of audio turned into a finished document in five minutes, next to a preview of a meeting summary with decisions, tasks and quotes
Nagovori — the Russian brand

#The file never passes through the backend

The first decision is the cheapest and the most useful. Audio runs to hours and gigabytes; pushing it through the application means holding a connection open for the whole upload, spending memory on buffers, and running into proxy limits.

The browser gets a presigned link from the Go API, uploads the file straight to S3, then creates the job; the API reserves minutes and starts a Temporal workflow
The backend issues a pass and gets out of the way

The Go backend hands out a presigned URL and removes itself: the file travels from the browser directly into the bucket. The application only learns about the recording when the frontend comes back to create a job. A pleasant side effect — the backend can be redeployed in the middle of someone's hour-long upload without breaking it.

#One card, several workers

A GPU host doesn't run a single process: ASR, TTS, whatever else. There's one card. If two workers each take a job at the same time, neither gets enough memory and both die — not immediately, but halfway through someone's hour-long recording.

A Temporal queue hands an activity to a worker on the GPU host; workers on that host serialize through a Redis lock, and the running one calls LiteLLM for ASR
A host-slot lock: exactly one job on the card at any moment

So before taking a job, a worker acquires a host-slot lock in Redis and holds it until the work is done. Every worker on that host serializes through that lock, however many there are. The scheduler knows nothing about GPUs: Temporal just hands out activities, and the physical constraint lives where it physically exists — on the host.

The model itself is called through LiteLLM's OpenAI-compatible API rather than directly. That looks like a pointless layer until the model needs to change: the job carries a model id, and which checkpoint stands behind it is a gateway configuration question, not a worker deploy.

#Billing: reserve first, charge on facts

Per-minute pricing raises an awkward question: how much do you deduct when the duration is only known at the end?

The charge lifecycle: reserve an estimate at start, count the real minutes, replace the estimate with the actual, return the difference
Reserve at the start, reconcile at the finish — the balance is never negative in between

Starting a job reserves an estimate, so the balance can't go negative while the workflow runs. At the finish the estimate is replaced by the real duration and the difference is returned. If the workflow failed, all of it comes back: the service pays for the service's failures.

That order is the only one that survives a crash in the middle. Charging purely at the end hands free work to anyone who starts ten jobs at once on an empty balance.

#Two brands, one codebase

The Saylo home page: the headline 'Your searchable memory for every recording' next to a transcript preview with timecodes and speaker names
Saylo — the same backend, a different market

Nagovori and Saylo are literally the same code. The differences are configuration keyed to the user's organisation: its own organisation in the identity provider, its own billing account, its own currency, its own allowance of free minutes, its own interface language. A request arriving from saylo.pro lands on its own billing configuration, and that is where the difference ends.

The temptation to fork for a second market is strong and expensive six months later: every change becomes two changes, and the drift is discovered by a customer. Splitting by configuration takes one careful pass to draw the boundary — after that it's free.

DecisionWhat it buys
Presigned upload around the backenda deploy never breaks someone's upload
Host-slot lock in Redisone job per card, with no GPU-aware scheduler
Model behind an OpenAI-compatible gatewayswitching models is config, not a deploy
Reserve, then reconcilethe balance stays non-negative even through a crash
Brand as config, not a forkone change instead of two

#Where the line is today

The Russian side is fully in production: payments, per-minute accounting, invoices for companies. On the international side card payments are still being finished — everything else, from sign-in to transcript, works.

// available for hire

Need a website or setup, done right?