ITAugust 18, 2026

Our AI Content Pipeline Runs on GPT-6 Sol — and Generates Fewer Images Than It Used To

By Andrea Borghi
Our AI Content Pipeline Runs on GPT-6 Sol — and Generates Fewer Images Than It Used To

Our AI Content Pipeline Runs on GPT-6 Sol — and Generates Fewer Images Than It Used To

Updated September 24, 2026: this post originally described the pipeline on GPT-5.6. It now runs on GPT-6 Sol, with translation on GPT-6 Luna, and the model names and code below reflect that.

Every content-generation prompt in the Green Yoga Inc pipeline now targets GPT-6 Sol. That part is a one-line change per file. The part worth writing about is what happened to image generation, which we removed from the pipeline entirely.

Where the model actually gets chosen

Prompts live in ai-prompts/ as plain text files with a header, never as string literals in Lambda source. There are 23 of them today, plus a JSON manifest for one-off image batches. Sixteen carry MODEL: gpt-6-sol.

The rest do not, on purpose:

  • 21-content-translation.txt runs on gpt-6-luna, the smaller GPT-6 model. It translates the site into eight languages, and it is the one workload where volume matters more than the last increment of quality.
  • The three gptimage-* prompt files the Lambda still reads carry no MODEL: header at all, because nothing in the Lambda sends them to OpenAI any more. More on that below.
  • The remaining three are two context fragments spliced into other prompts and one image-to-video prompt that targets a different provider.

Both Lambdas — ai-content-engine and yoga-script — resolve whatever the header says through normalizeModel():

function normalizeModel(raw?: string): string {
  if (!raw) return "gpt-6-sol";
  // Legacy GPT-5.x ids -> current model
  if (m === "gpt-5" || m === "gpt-5.1" || ... || m === "gpt-5.6" || m === "gpt-image-1.5") {
    return "gpt-6-sol";
  }
  if (m === "gpt-4o" || m === "gpt4o") return "gpt-4o";
  return m;
}

Any legacy id — gpt-5 through gpt-5.6, and the old gpt-image-1.5 — collapses to gpt-6-sol. A prompt file nobody has touched in six months cannot call a retired model; it silently gets the current one instead. gpt-4o and gpt-4o-mini pass through untouched, because one code path still needs gpt-4o deliberately.

Both Lambdas talk to Chat Completions

Text generation goes to POST /v1/chat/completions from both Lambdas. If you read our April post, that is a reversal: we had consolidated onto the Responses API. We moved back. Chat Completions is the boring, well-understood surface for what these two functions actually do, which is take a system prompt and a user prompt and return prose.

The only things left calling POST /v1/responses are developer scripts run by hand from a workstation. scripts/generate-pose-images.mjs uses the image_generation tool on gpt-6-sol at 1024x1024, and two one-off image scripts do the same at 1536x1024. Those are local batch jobs, not browser traffic, and they call OpenAI directly on purpose.

The Yoga Sequence Builder budgets its own time

The Yoga Sequence Builder generates teaching scripts through the yoga-script Lambda, driven by 05-yoga-script-generator.txt on GPT-6 Sol. Script generation is slow and variable, so the Lambda spends most of its logic on arithmetic rather than prompting:

  • a single primary attempt is capped at seven minutes, so one slow call cannot eat the whole invocation
  • 25 seconds are reserved at the end to serialize and return a response
  • a gpt-4o fallback exists, but it only runs if at least two minutes of headroom remain

That last rule is the one that matters. If the primary attempt fails late and there is not enough time left, the Lambda refuses to start the fallback and returns an explicit error instead — Skipping gpt-4o fallback: insufficient Lambda time remaining. Starting a call you cannot finish just converts a clear error into a timeout, which is strictly worse to debug.

We stopped generating images automatically

This is the real change since April.

generateImage() in ai-content-engine is now a no-op stub. It takes its arguments, ignores them, and returns the fallback cover:

/** Kept as a no-op stub for callers that still reference it directly. */
async function generateImage(imagePrompt: string, s3Key: string): Promise<string> {
  void imagePrompt;
  void s3Key;
  return FALLBACK_COVER_IMAGE;
}

What replaced it is not another model. It is a person. generateOrReuseImage() writes the image prompt into DynamoDB with an empty S3 key and returns the fallback, and the approval email carries that prompt to a human. When the reply comes back with an image attached, a separate media-receiver Lambda fills in the real asset.

The prompt is stored as three separate fields — clipL, t5xxl, and negative — rather than one blob, because whoever renders it is generally not using OpenAI. Those are diffusion-model prompt slots, and keeping them apart means the operator can paste each into the right box instead of unpicking a concatenated string.

Why a slower pipeline is the right pipeline

Automatic cover generation was the feature most likely to put something embarrassing on the site without anyone seeing it first. Text goes through an approval email already. Images did not — they were generated, uploaded, and published in one unattended pass.

Removing that did make publishing slower. It also means every image on this site was looked at by a human before it went up, which is the property we actually wanted. The model got better and we gave it less to do unsupervised. Those are not in tension.


Prompts are in ai-prompts/; the Lambdas are in lambda/ai-content-engine and lambda/yoga-script. If you want help designing an approval flow for your own AI content pipeline, reach out.