Runlane
Recipes

Media generation

Generate structured content and an accompanying image.

Use a task to generate an article, social post, or campaign asset with text and an image. Runlane coordinates attempts and durable steps. Your application supplies the model and image provider, prompts, credentials, moderation, and storage.

Generate content and its image

The Runlane calls below are real. The marked writeStructuredContent() and generateImage() calls are pseudocode for your providers:

src/tasks/generate-content.ts
import { task } from '@runlane/core'
import * as z from 'zod'

const contentSchema = z.object({
  body: z.string(),
  imagePrompt: z.string(),
  tags: z.array(z.string()),
  title: z.string(),
})
const generatedContentSchema = contentSchema.omit({ imagePrompt: true }).extend({ imageUrl: z.url() })

export const generateContent = task({
  id: 'media.generate-content',
  schema: z.object({ description: z.string().min(1), theme: z.string().min(1) }),
  output: generatedContentSchema,
  async run({ description, theme }, context) {
    const content = await context.step.run('write-content', { output: contentSchema }, async ({ token }) => {
      //////////////////////////////////
      // MAKE YOUR API CALLS HERE
      //////////////////////////////////
      const result = await writeStructuredContent({
        description,
        idempotencyKey: token,
        signal: context.signal,
        theme,
      })

      return contentSchema.parse(result)
    })

    const imageUrl = await context.step.run('generate-image', { output: z.url() }, async ({ token }) => {
      //////////////////////////////////
      // MAKE YOUR API CALLS HERE
      //////////////////////////////////
      return generateImage({ idempotencyKey: token, prompt: content.imagePrompt, signal: context.signal })
    })

    return { body: content.body, imageUrl, tags: content.tags, title: content.title }
  },
})

Each expensive call has its own stable step. A retry can reuse completed text generation without requesting it again before continuing to the image.

Pass each step token to the corresponding provider when it supports idempotency. A remote call can still succeed before Runlane stores the step result, so the durable step alone does not guarantee exactly-once generation.

Handle asynchronous providers

If image or video generation returns a job id, save that submission as a durable step. Poll its status on later attempts and use context.release() while it remains pending. The task handler restarts from the beginning after every release.

Keep generated bytes in object storage or at the provider. Return stable URLs, object keys, content type, size, and other small JSON metadata.

Before production

  • Moderate input and output at the application boundary.
  • Put cost and rate limits on a bounded queue.
  • Set an overall deadline for jobs that never finish.
  • Pass context.signal to provider requests.
  • Test retries and duplicate-submission behavior.

Read wait without failing and save step results.

On this page