ITAugust 18, 2026

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

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

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

Every text prompt in the Green Yoga Inc content pipeline now targets GPT-5.6. 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 19 of them today, plus a JSON manifest for one-off image batches. Fifteen carry MODEL: gpt-5.6.

Two do not, on purpose:

  • 21-content-translation.txt stays on gpt-5.4-mini. It translates the site into eight languages, and it is the one workload where volume matters more than the last increment of quality. A full re-translation of everything costs about ten cents.
  • The three gptimage-* prompt files carry no MODEL: header at all, because nothing in the Lambda sends them to OpenAI any more. More on that below.

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

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

Any legacy id — gpt-5 through gpt-5.5, and the old gpt-image-1.5 — collapses to gpt-5.6. A prompt file nobody has touched in six months cannot call a deprecated endpoint; it silently gets the current flagship 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 thing left calling POST /v1/responses is scripts/generate-pose-images.mjs, a developer script run by hand from a workstation. It uses the image_generation tool on gpt-5.6 at 1024x1024. That is a local batch job, not browser traffic, and it calls 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-5.6. 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.