RSS Amplifier

Minvo · Jun 8, 2026

Minvo is 5x Faster | Clips in 60 Seconds or Less 🏎️

0
Sign in to vote or save

Minvo · Minvo

In our previous post, Rebuilding Minvo’s Infrastructure for Velocity, we shared the story behind why we rebuilt the platform that powers Minvo.

This post is the geek technical deep dive for all the geeks out there.

We’ll cover:

  • What was slow

  • How we rebuilt it

  • What changed for developers

  • What changed for users

  • What this means in the age of AI Agentic Engineering

Minvo’s old infrastructure was a federation of Python services, with a couple of Node services on the side.

It worked. It got us far. It helped us serve customers, process videos, generate clips, and build a serious product.

But over time, the pain became predictable and cumulative.

  • Latency. A single ingestion passed through three Python services in sequence, compounding server hops, database round trips, queue delays, and serialization overhead.

  • Debugging. Five services meant five log destinations, inconsistent context, and no shared trace ID, forcing us to manually stitch failures together.

  • Lack of types and structure. Loosely shaped Python dictionaries moved between services, allowing fields to be renamed, omitted, or changed without failing until runtime.

  • Service coordination. Adding one transcription field required updating four services and two queues in lockstep, with no compile-time guarantee that every producer and consumer stayed aligned.

The architecture had the classic symptoms of a distributed monolith.

The services were separate enough to create operational overhead, but not independent enough to give us the real benefits of microservices. They shared a database. They shared assumptions. They shared failure modes.

In late-2025, Minvo started architecting a path forward. The result is a TypeScript monorepo orchestrated by Turborepo, with three first-class runtimes:

  1. Backend

  2. API layer

  3. Async jobs

Observability sits across all of them.

The backend is built with NestJS and organized strictly by feature modules:

  • Videos

  • Ingestion

  • Transcription

  • AI generation

  • Async jobs

  • Credits

  • Feeds

Anything that talks to the outside world — the database, object storage, external media providers, or third-party APIs — lives behind an infrastructure provider and is injected through dependency injection.

Modules consume those providers.

They do not instantiate clients themselves.

That structure made the backend easier to test, easier to reason about, and easier to extend without creating hidden coupling.

Each module owns its own tRPC router.

Inputs are defined with Zod schemas. Outputs are inferred. The studio imports the backend router as a type-only import and gets a fully typed client without code generation.

This gave us a much tighter frontend-backend contract.

A backend change that breaks the studio is no longer something we discover after deploy. It shows up immediately in the development loop.

Every long-running unit of work is a versioned Trigger.dev task.

Triggering is type-safe from the backend. Retry behavior, machine sizing, and idempotency are configured per task.

This gave us a cleaner boundary between request-response work and long-running media processing.

We use a single OpenTelemetry SDK initialized once and shared across the backend and workers.

Traces are exported to Axiom.

The same tracing package is used by the Trigger.dev workers, which lets trace context propagate across process boundaries.

For AI-specific telemetry — prompts, responses, model costs, and structured outputs — we use Langfuse, linked back to the OpenTelemetry trace.

That gave us something we had never really had before:

One single connected view from user action to backend request to async job to AI call to object-storage write.

A full freeze-and-rewrite was never realistic.

Product work had to continue. Customers still needed improvements. Bugs still needed fixing. The old system still had to run.

So we chose a piece-by-piece migration instead of a big-bang rewrite.

We set up the new monorepo next to the old services and ported one endpoint, workflow, and job at a time.

That gave us three advantages:

  1. Ability to continue shipping features: The migration did not require the company to pause.

  2. Thorough testing of each migrated path against real production behavior. We were not guessing whether the new system worked; we were gradually proving it.

  3. Opportunity to discover better abstractions. Moving one workflow at a time made the repeated patterns obvious.

By the time the core ingestion and clipping paths were migrated, the new architecture had been shaped by the product’s actual behavior, not by an abstract architecture diagram.

The old system looked like microservices, but behaved like a distributed monolith.

The services shared a database, shared assumptions, and often had to be deployed in coordinated sequences. We were paying the complexity cost of microservices without getting the independent scaling and ownership benefits.

So we consolidated into a single backend image.

At our current scale, a unified monolith is the right tradeoff. It gives us a better developer experience, simpler deployment, easier testing, and fewer invisible boundaries.

We can split things apart later if the product demands it.

But we no longer pay that tax prematurely.

tRPC commits us to a tightly coupled TypeScript client.

We accepted that tradeoff because the alternative was the system we already had: hand-written clients, duplicated assumptions, and contracts that could drift silently.

After the migration, changes that used to require coordinating several files across multiple repos became single-edit pull requests that the compiler could verify.

That is not just a productivity improvement.

It changes the emotional texture of building.

You move faster because the system tells you when you are wrong.

The biggest unlock was not adding another logging tool.

It was making trace context cross every boundary.

A studio click generates a W3C trace parent. The frontend sends it. NestJS extracts it and starts the request span as a child. tRPC procedures nest under that span, tagged with the resolved user.

When the handler triggers a Trigger.dev task, we inject the trace context into the payload. The worker continues the trace. AI calls are recorded in Langfuse and linked back to the same request path.

The result is a single flame graph from browser click to final asset write.

Instead of asking, “Where did this fail?” we can now ask, “Which span failed, what inputs did it receive, what did it call, and how long did each step take?”

That changes debugging from archaeology into inspection.

While we expected Minvo’s new infrastructure to bring performance improvements, nobody on the team expected to be able to create clips 5x faster!

Today, Minvo creates clips from videos up to 1 hour long, in just 60 seconds or less!
That is roughly a 5x improvement in video-to-clips processing time.

The performance gain came from several places:

  • Fewer service hops

  • Less serialization overhead

  • Cleaner async boundaries

  • Better control over retry behavior

  • Better visibility into bottlenecks

  • More precise AI interactions

The largest unlock was visibility.

With Langfuse and Zod in the loop, we could inspect every prompt input, model response, structured output, and failure mode. That let us tighten prompts, reduce waste, and improve AI interactions in ways that were extremely difficult on the old stack.

The old system made AI behavior feel opaque.

The new system made it observable.

Once it was observable, it became optimizable.

Our biggest mistake was not treating observability as a day-one concern.

Even inside the new backend, we initially carried over an old instinct:

Add logging where it hurts.

It meant:

  • We did not put tracing at every level of the stack from the start

  • We did not connect all frontend and backend traces early enough

  • We wired OpenTelemetry metrics for system health later than we should have

The cost showed up exactly where expected:

  • Debugging sessions took longer than necessary

  • Slow regressions were harder to spot

  • Some flame graphs had gaps that made them harder to interpret

Most of these issues could have been avoided by spending a day or two on instrumentation up front.

The silver lining is that the new infrastructure made retrofitting observability surprisingly painless.

Because everything lives in one monorepo, with one tracing package shared across NestJS and Trigger.dev, adding traces at a new level was usually a one-line wrapper. Propagating context across a boundary was a few lines of middleware. Wiring OpenTelemetry metrics was a single SDK configuration.

On the old stack, this would have been a month-long, multi-repo retrofit.

On the new stack, it took days.

The lesson is not that we got lucky.

The lesson is that a coherent foundation makes the right thing cheap, even when you do it later than you should have.

Most of the engineering investments in this refactor would have been worth making in any era:

  • Full-stack type safety

  • Queryable distributed traces

  • Structured backend modules

  • Shared schemas

  • Unit and integration tests

  • Clear async job boundaries

But they matter even more now that AI coding agents are becoming a serious part of how software gets written.

When an AI agent is generating, refactoring, or debugging code, the constraint on velocity changes.

It is no longer just:

How fast can a human understand and type the change?

It becomes:

How quickly can the system verify whether the change is correct?

That verification loop is where the new foundation shines.

Detailed, queryable traces show what production is actually doing.

An AI agent can be pointed at a specific trace and asked:

Why did this span take four seconds?

Without traces, the agent is guessing from code.

Full-stack type safety turns entire categories of mistakes into compile errors.

The agent sees the same failure a human would see, but it can react to it almost instantly.

A field rename that might have silently broken a loosely typed Python dictionary now becomes a compiler error. A backend output change that would have drifted from the frontend now fails in the same loop where the change is being written.

That matters enormously.

The faster the agent moves, the more valuable the guardrails become.

Type checking, linting, unit tests, and integration tests turn correctness into something the agent can verify automatically.

The loop becomes:

  1. Change code

  2. Run checks

  3. Read failures

  4. Fix

  5. Repeat

Without that loop, every AI-generated change requires a human to manually validate it.

With that loop, humans can focus on product judgment, architecture, taste, and business context — the things humans are still best positioned to decide.

Minvo’s 2.0 infrastructure was architected in late 2025, just as Claude Code and other AI coding agents began changing the daily practice of software engineering.

We rebuilt the codebase so humans could ship features and fix bugs faster.

That worked.

But the pleasant surprise was:

On a typed, traced, and well-structured foundation, AI agents could move faster than the humans we built it for.

AI agents are fast, but they are not inherently careful. They need a system that is careful around them. They need types that catch contract drift, traces that reveal runtime behavior, tests that verify changes, and structure that makes the codebase legible.

The migration made Minvo faster for users, engineers… and agents 🎉

Unexpectedly, it made Minvo ready for a world where the fastest contributor to the codebase might not be human.

The compounding benefit is hard to overstate.

Until next time,
Minvo Engineering Team

Read the original on minvo.substack.com

Comments

Nothing yet. Say the first thing.

    Sign in to join the conversation.