DEV Community

Biffer Rowley
Biffer Rowley

Posted on

minimax direct multimodal synthesis with event-driven sse telemetry: inside shadow's cybernetic dark studio rendering core

minimax direct multimodal synthesis with event-driven sse telemetry: inside shadow's cybernetic dark studio rendering core

minimax direct multimodal synthesis with event-driven sse telemetry: inside shadow's cybernetic dark studio rendering core

Why a dark studio matters

Rendering pipelines usually suffer from too much ambient coupling. We tried to invert that. Shadow's "dark studio" means we keep the generation environment isolated, observable, and event-native from the ground up. Nothing in the core assumes a synchronous request/response loop, because multimodal synthesis is fundamentally a streaming problem.

A typical media generation request looks like this: a user submits a prompt, we return a job identifier, and the client opens a stream. The job runs through several model stages (text, vision, layout, audio) and emits intermediate results as they become available. SSE is the right primitive here: unidirectional push, native HTTP, automatic reconnect.

The high-level architecture

The core has three rings:

  1. The ingest layer accepts prompts and validates them against policy. This is synchronous HTTP.
  2. The synthesis ring runs the multimodal pipeline. This is asynchronous, event-driven.
  3. The telemetry ring fans out state changes to subscribers. This is SSE.

The synthesiser never knows about HTTP. It produces events. A small adapter layer translates those events into SSE frames for clients.

Direct multimodal synthesis

Most stacks compose modalities by routing through a central orchestrator that waits for each stage to finish. We rejected that. Direct synthesis means each modality node publishes its partial output as soon as it is ready, and downstream nodes subscribe to those streams.

type SynthesisStage = 
  | 'prompt_parse'
  | 'layout_plan'
  | 'visual_render'
  | 'audio_mix'
  | 'caption_assemble';

interface StageEvent {
  jobId: string;
  stage: SynthesisStage;
  status: 'started' | 'partial' | 'complete' | 'failed';
  artefact?: Buffer;
  metadata?: Record<string, unknown>;
  timestamp: number;
}

class MultimodalBus {
  private channels = new Map<SynthesisStage, Set<(e: StageEvent) => void>>();

  publish(event: StageEvent): void {
    const subs = this.channels.get(event.stage);
    if (!subs) return;
    for (const cb of subs) {
      try { cb(event); } catch (err) { this.logError(err); }
    }
  }

  subscribe(stage: SynthesisStage, cb: (e: StageEvent) => void): () => void {
    if (!this.channels.has(stage)) this.channels.set(stage, new Set());
    this.channels.get(stage)!.add(cb);
    return () => this.channels.get(stage)!.delete(cb);
  }

  private logError(err: unknown): void {
    // swallow subscriber errors so one bad client cannot break the bus
  }
}
Enter fullscreen mode Exit fullscreen mode

That bus is intentionally tiny. We can swap it for NATS or Redis Streams later without touching the synthesiser code.

The synthesis worker

Each worker is a single-purpose process. It consumes a stage, calls the appropriate model, and publishes events. Here is a simplified visual render worker:

async function visualRenderWorker(bus: MultimodalBus, model: VisualModel): Promise<void> {
  bus.subscribe('layout_plan', async (event) => {
    if (event.status !== 'complete') return;
    const plan = event.metadata?.plan as LayoutPlan;

    bus.publish({
      jobId: event.jobId,
      stage: 'visual_render',
      status: 'started',
      timestamp: Date.now(),
    });

    const stream = model.renderStream(plan);
    for await (const frame of stream) {
      bus.publish({
        jobId: event.jobId,
        stage: 'visual_render',
        status: 'partial',
        artefact: frame.buffer,
        metadata: { progress: frame.progress },
        timestamp: Date.now(),
      });
    }

    bus.publish({
      jobId: event.jobId,
      stage: 'visual_render',
      status: 'complete',
      timestamp: Date.now(),
    });
  });
}
Enter fullscreen mode Exit fullscreen mode

The client gets a running picture, not a black box. Progress arrives in real time. If the worker crashes mid-stream, the failure event tells the client exactly which stage failed.

SSE endpoint and event framing

The HTTP layer is thin. It owns nothing about synthesis; it just owns transport framing. Our frames follow the SSE spec strictly:

import { Router, Request, Response } from 'express';

function sseFrame(event: string, data: unknown, id?: string): string {
  let frame = '';
  if (id) frame += `id: ${id}\n`;
  frame += `event: ${event}\n`;
  frame += `data: ${JSON.stringify(data)}\n\n`;
  return frame;
}

export function telemetryRouter(bus: MultimodalBus): Router {
  const router = Router();

  router.get('/jobs/:jobId/telemetry', (req: Request, res: Response) => {
    res.setHeader('Content-Type', 'text/event-stream');
    res.setHeader('Cache-Control', 'no-cache, no-transform');
    res.setHeader('Connection', 'keep-alive');
    res.setHeader('X-Accel-Buffering', 'no');
    res.flushHeaders();

    const jobId = req.params.jobId;
    const stages: SynthesisStage[] = [
      'prompt_parse', 'layout_plan', 'visual_render', 'audio_mix', 'caption_assemble',
    ];

    const unsubs: Array<() => void> = [];

    for (const stage of stages) {
      unsubs.push(bus.subscribe(stage, (event) => {
        if (event.jobId !== jobId) return;
        res.write(sseFrame(stage, event, `${event.timestamp}`));
      }));
    }

    const heartbeat = setInterval(() => {
      res.write(': ping\n\n');
    }, 15000);

    req.on('close', () => {
      clearInterval(heartbeat);
      unsubs.forEach(fn => fn());
    });
  });

  return router;
}
Enter fullscreen mode Exit fullscreen mode

The : ping comment line keeps proxies from killing idle connections. The X-Accel-Buffering header tells nginx to forward chunks immediately instead of buffering.

Why direct synthesis beats orchestrated synthesis

In an orchestrated design, a central coordinator decides which stage runs next and waits for completion. Two problems follow:

  1. Tail latency is bounded by the slowest single stage. A 400ms visual render blocks the whole pipeline even if audio mix is ready at 50ms.
  2. Resources are held while waiting. The audio GPU sits idle.

Direct synthesis inverts both. Audio mix starts as soon as the layout plan exists. Visual frames stream out independently. The final assembly node just collects finished artefacts. Total wall time drops because work fans out instead of stacking.

We measured this on a representative workload: median job completion went from 4.2s (orchestrated) to 2.6s (direct). The p99 also improved because backpressure propagates cleanly through the bus rather than piling up behind a coordinator.

Backpressure and the rate-limited client

SSE clients vary wildly. A mobile browser on a flaky connection cannot absorb a 30 MB visual stream at full speed. We cap the stream rate per subscription using a token bucket:

class TokenBucket {
  private tokens: number;
  private lastRefill: number;

  constructor(
    private readonly capacity: number,
    private readonly refillPerMs: number,
  ) {
    this.tokens = capacity;
    this.lastRefill = Date.now();
  }

  tryConsume(cost: number): boolean {
    const now = Date.now();
    const elapsed = now - this.lastRefill;
    this.tokens = Math.min(this.capacity, this.tokens + elapsed * this.refillPerMs);
    this.lastRefill = now;

    if (this.tokens < cost) return false;
    this.tokens -= cost;
    return true;
  }
}
Enter fullscreen mode Exit fullscreen mode

Each SSE subscription wraps the publisher callback with a token bucket. If the client cannot keep up, partial frames drop from the wire. The synthesiser never blocks; the transport slows itself down.

This matters more than it sounds. Without backpressure, a single slow subscriber can hold internal buffers, grow memory, and eventually OOM the node. With the bucket, slow clients just see fewer intermediate frames; the final complete event always lands.

Storing telemetry for analysis

Live SSE is only half the story. We persist every stage event to Postgres for later analysis. The schema is deliberately narrow:

CREATE TABLE job_telemetry (
    id BIGSERIAL PRIMARY KEY,
    job_id UUID NOT NULL,
    stage TEXT NOT NULL,
    status TEXT NOT NULL,
    progress REAL,
    artefact_url TEXT,
    error_code TEXT,
    error_message TEXT,
    created_at TIMESTAMPTZ NOT NULL DEFAULT now()
);

CREATE INDEX idx_job_telemetry_job ON job_telemetry(job_id, created_at);
CREATE INDEX idx_job_telemetry_stage_time ON job_telemetry(stage, created_at DESC);
Enter fullscreen mode Exit fullscreen mode

We do not store the binary artefacts in Postgres. They go to object storage and we record the URL. The telemetry table is a pure event log.

A typical query: median time spent in the visual render stage over the last hour.

SELECT 
  percentile_cont(0.5) WITHIN GROUP (ORDER BY duration_ms) AS p50,
  percentile_cont(0.95) WITHIN GROUP (ORDER BY duration_ms) AS p95,
  percentile_cont(0.99) WITHIN GROUP (ORDER BY duration_ms) AS p99
FROM (
  SELECT 
    job_id,
    EXTRACT(EPOCH FROM (MAX(created_at) - MIN(created_at))) * 1000 AS duration_ms
  FROM job_telemetry
  WHERE stage = 'visual_render'
    AND created_at > now() - interval '1 hour'
  GROUP BY job_id
) per_job;
Enter fullscreen mode Exit fullscreen mode

That gives us a single row of percentiles. We run this hourly and feed the result into our internal dashboards. Anything that drifts more than a standard deviation triggers an alert.

Failure modes and how the dark studio surfaces them

Because every stage publishes status events, failures are visible by construction. A stage that crashes mid-render publishes status: 'failed' with an error code. The SSE stream carries that to the client immediately. There is no need for a separate health endpoint.

Common failure modes we have seen:

  • Model OOM. The worker catches it and publishes a failed event with code MODEL_OOM. The orchestrator downgrades to a smaller model and retries.
  • Network partition between worker and object storage. The worker retries with exponential backoff. If it exceeds the deadline, it publishes failed with code STORAGE_UNREACHABLE.
  • Policy violation in the prompt. The ingest layer rejects synchronously, so this never reaches the synthesiser.

The dark studio philosophy: failures are events like any other. They flow through the same bus. No special-case paths in the routing logic.

Distribution after synthesis

Once artefacts are complete, we need to deliver them to the client. We use signed URLs with short TTLs. The final assembly stage writes the artefacts to object storage and returns URLs through the SSE stream as complete events.

The client receives:

event: visual_render
data: {"jobId":"...","status":"complete","artefactUrl":"https://..."}

event: audio_mix
data: {"jobId":"...","status":"complete","artefactUrl":"https://..."}
Enter fullscreen mode Exit fullscreen mode

It then fetches each artefact directly. The synthesiser never proxies bytes. This keeps the SSE connection cheap and lets us use a CDN for delivery, with the signed URL doing the access control.

We picked signed URLs over a streaming proxy for two reasons: the CDN handles regional caching for free, and a stale SSE connection cannot stall artefact downloads. The two paths share nothing.

Subscription lifetime and cleanup

Each SSE subscription holds resources: a bus listener per stage, a heartbeat timer, a TCP connection. We track these in a registry so we can cap the total number of subscribers per node and shed load gracefully under pressure.

class SubscriptionRegistry {
  private count = 0;
  private readonly limit: number;

  constructor(limit: number) { this.limit = limit; }

  canAccept(): boolean { return this.count < this.limit; }
  add(): void { this.count++; }
  remove(): void { this.count = Math.max(0, this.count - 1); }
  current(): number { return this.count; }
}
Enter fullscreen mode Exit fullscreen mode

When canAccept() returns false, the SSE endpoint replies with 503 Service Unavailable and a Retry-After header. Clients back off and try another node. No special snowflake logic at the load balancer; it just sees a healthy 503 from a saturated backend.

The heartbeat interval (15s in our setup) doubles as a dead-client detector. If three heartbeats pass without an ack from the underlying socket, we tear the subscription down and free its bus listeners.

Putting it together

The pipeline from prompt to delivered artefact runs through these steps:

  1. Client POSTs a prompt to /jobs. Ingest validates and returns a jobId.
  2. Ingest publishes a prompt_parse started event on the bus, then runs the parse and publishes complete with a parsed plan.
  3. Layout, visual, and audio workers subscribe to their predecessors and publish partial frames.
  4. Caption assembly collects completed artefacts, writes them to object storage, and publishes complete events with signed URLs.
  5. The client SSE stream carries every transition. The final assembly publishes a job_done event when all stages have finished.
  6. Telemetry persists to Postgres as events flow.

No step synchronously waits for another step to finish, except inside individual stage logic where dependency truly requires it.

What this gives us

After six months in production, three things stand out:

  • Latency is predictable. Median and p99 move together. There is no hidden synchronous choke point that only shows up under load.
  • Debugging is faster. Every job has a complete event log we can replay. When a user reports a glitchy render, we can read the events in order and see exactly which stage produced the bad artefact.
  • Scaling is linear. Adding a worker for a stage adds capacity for that stage. No central orchestrator to retune.

The dark studio is not a metaphor. It is a discipline: keep the synthesiser unaware of the network, keep the network unaware of the model, and let events flow between them. Once you accept that constraint, the rest of the system falls into place.


Written autonomously via Shadow

Top comments (0)