Skip to main content
AI Pipeline Prompt Design logo
AI Pipeline Prompt Design FreeSystem

Designs and edits the multi-step AI Pipeline prompt: the ordered passes a model makes over your strings

SKILL.md

AI Pipeline prompt design

Copy link

The artifact you produce is not a translation. It is a Crowdin AI prompt of the AI Pipeline kind: mode: "external" pointing at the separate AI Pipeline app, carrying an ordered list of flowNodes. Each node is one full model pass over a batch of strings - real time, real money. That is the bar every step must clear.

Division of labour. You design, create and edit the prompt. Running pre-translation and diagnosing its results belong to ai-pipeline-pretranslation. Hand off; do not re-implement it. This holds hardest exactly where it is easiest to slip: you have just found a pipeline that already does what the user described, and one apply_pre_translation… call away sits the finished job. That call spends a batch of paid model passes over every string in its scope, not the ones the request named - it is the user's to authorise, never the obvious next step. Offer it, say what it would cover, and stop there.

Scope is deliberately narrow: pre-translation pipelines only. The AI Pipeline app declares actions: [pre_translate] - it is not offered for qa_check or alignment at all, so those cannot be pipelines whatever their config says. Leading someone half-way into a design you cannot finish is worse than telling them up front that they need a different shape.

Step 1 - Which of two requests is this?

Copy link

(a) "Create an AI prompt." If the request already names the type - "pre-translate", or anything about pipeline steps, which exist in no other prompt type - the gate is answered: skip step 2 and go to step 3. Type unstated → step 2.

(b) "Change our pipeline." → step 13.

Step 2 - Prompt type gate

Copy link

Ask which kind of prompt via AskUserQuestion:

Option action Purpose
Pre-translation pre_translate The only type this skill builds
QA check qa_check Reviews translations that already exist
Alignment alignment Mines existing translations into draft glossary terms - Enterprise only, omit the option on crowdin.com

Unanswered (timed out) → proceed as pre-translation, the shape everyone arrives here asking for, and say which default you took; step 6 has the general rule for unanswered rounds.

Anything other than pre-translation ends this skill. Say plainly that a QA-check or alignment prompt is a different kind of prompt with a different config - nothing here transfers to it - and carry on with the task outside these instructions. Do not improvise one from this skill's rules: step ordering, mode: "external", flowNodes and the {{input.…}} placeholders are all pipeline-only, and a config built from them is accepted at write time and then fails at run time.

Two things about the way out. Say why the request cannot be a pipeline in terms the user cannot look up - the type is a different config, or the platform does not offer that action at all (the table above) - rather than only describing what you did instead. And leaving this skill is not authorisation to write: a prompt was asked for, so before changing project settings, a glossary or existing translations on the way out, name the change and ask. Doing the work by hand instead of configuring the prompt is a different job with a different blast radius, and QA settings in particular are project-wide, shared with everyone, and not a smaller version of a prompt.

Step 3 - Know which platform you are on

Copy link

Every AI call differs between Crowdin Enterprise and crowdin.com, and the tools themselves tell you which you are on: the Enterprise-only ones simply do not exist in a crowdin.com session.

Purpose Enterprise crowdin.com
List prompts list_organization_ai_prompts list_organization_ai_prompts + required userId
Read one prompt get_organization_ai_prompt get_user_ai_prompt
Create add_organization_ai_prompt add_user_ai_prompt (+ userId)
Edit edit_organization_ai_prompt edit_user_ai_prompt (+ userId)
Providers list_organization_ai_providers same + userId
Models list_organization_configured_provider_models list_configured_provider_models (+ userId)

On crowdin.com get the id from get_authenticated_user once and reuse it. alignment is not even in the crowdin.com action enum, which is the mechanical reason it cannot be offered there.

If the create / edit tool is missing but the read tools work, you are on a read-only agent (Ask). Do the whole design, print the plan and the exact JSON, and tell the user to re-run it on the Task agent. Do not pretend the prompt was created. add_application_data is missing on this path too - it is a write tool - so step 12's validation call is off the table, while the catalog and the inventory (both reads) still work. Check the JSON against steps 8, 9 and 12 yourself and say plainly that it goes out unvalidated.

Step 4 - Read the project before asking anything

Copy link

Asking "do you have screenshots?" when you could just look spends the user's patience for nothing. Run these together:

  • get_project - source and target languages, and whether the project is file- or string-based.

  • list_files with limit: 500 and fields: ["path"]. The default limit of 25 is an unrepresentative sample, and path is not returned by default even though you need it for step scoping. Group by type: document-like (md, mdx, markdown, html, htm, xhtml, docx, odt, rtf, txt, adoc, dita, ditamap, wiki, idml, srt, vtt, haml, flsnp) versus code/resource strings (json, xml, android, yaml, properties, …). Only the first group justifies a file-consistency step. In a string-based project this tool does not exist - skip that option entirely.

  • list_screenshots - whether screenshots exist at all, and how many strings are really tagged (tagsCount). Untagged screenshots give a context step nothing to work with.

  • Find the AI Pipeline provider id from list_organization_ai_providers: the provider named AI Pipeline, type custom_ai. Small response, and it always works - including in an organization that has no pipeline yet. The inventory below carries aiProviderId too, so when a pipeline already exists the two should agree; if they disagree, trust the inventory, because that is the provider a working pipeline actually runs on.

    No such provider → stop here. The app is not wired up, and any pipeline you design cannot run. list_application_installations tells you which of the two problems it is: identifier ai-pipeline absent means the app is not installed; present means it is installed but no provider was created.

The app's own API - the three calls this skill runs on. The AI Pipeline app exposes endpoints through Crowdin's application proxy, so you reach them with ordinary Crowdin tools and no URL of your own:

What Tool Arguments
The step prompts the app ships with get_application_data applicationIdentifier: "ai-pipeline", path: "step-prompt-catalog"
What pipelines this organization already has get_application_data applicationIdentifier: "ai-pipeline", path: "pipeline-inventory" (optional ?projectId=…)
Check a config before writing it add_application_data applicationIdentifier: "ai-pipeline", path: "validate-pipeline", body: { "config": … }

Every answer comes back wrapped as { "data": … }. Fetch the catalog index and the inventory in this same round of calls - together they are a couple of KB, and they are what steps 5, 9 and 13 run on.

The catalog index carries no prompt bodies, and its meta block is the authority on what would otherwise be yours to remember: the exact mode / identifier / key / retryOnQaIssues values, which builtinId belongs to which step type, and every placeholder that resolves to anything. Prefer it over any list written down here or in your own memory.

The inventory describes each existing pipeline - id, name, enabledProjectIds, aiProviderId, and per step its type, name, builtinId, language and file scope, model, prompt byte length, and matchesDefault (with matchesDefaultOf naming the catalog entry it matches). No prompt bodies, so a dozen pipelines cost a few KB. scanned reports how many prompts were read and how many of them were pipelines, so "you have 1 pipeline and 6 other pre-translate prompts" is answerable without listing anything.

On Enterprise the organization-wide inventory needs organization admin rights the app can verify; when it cannot, the call answers 403 and the message says to pass ?projectId=… - do that (step 4's get_project already gave you the id) and you get the pipelines available to that project, which is what step 5 needs anyway. Not a failure of the app or the installation; do not treat it as one.

If the catalog call fails, say so rather than reconstructing prompt bodies from memory - a half-remembered default is worse than none, and step 5 may still have a real pipeline to harvest.

Read the failure before you conclude anything from it. These calls do not reach the app directly: Crowdin routes them to the API module recorded in the app's installation, matching on the lowercased path and the method. So the answer tells you where the request stopped - the body as much as the status code - and only one of these means the app is unavailable:

Answer What it means What to do
404 Not Found with no app error code in the body The installation has no API module at that path. Almost always the installed manifest predates the endpoint - the app has it, this organization's installation has not picked it up yet Not something you can fix. Say the app's installation needs updating (Crowdin applies a pending app update on the app's own page), and carry on with the fallbacks below
405 Method Not Allowed The path exists in the installation but is registered for the other method Same as above - the installed version is not the one these instructions describe
The app's own JSON error (MISSING_TOKEN, INVALID_PROJECT_ID, UNKNOWN_STEP_PROMPT, …) at any status - a mistyped catalog id answers 404 too The request reached the app and it rejected it Your call is wrong; fix the arguments

get_application_installation settles it: the response carries modules - the endpoints the installation actually has - and isManifestOutdated, which says whether Crowdin already knows a newer manifest exists (no fields argument needed; the single-item get returns everything). Do not report a 404 as "the AI Pipeline app is not installed" - step 4 already established that it is.

Report the findings in one line - "312 strings across 8 .md files, 47 screenshots with 120 tagged strings" - so the questions that follow are visibly informed rather than generic.

Step 5 - Reuse, harvest, or build

Copy link

The inventory from step 4 already answered this. It lists every pipeline in the organization with its steps, scoping, models and which step bodies are still the shipped defaults - a couple of KB, one call, no pagination. Read it; do not go looking for the same answer in the Crowdin prompt list.

Why that matters enough to say twice: list_organization_ai_prompts returns each prompt's full config, prompt bodies included, and config is a default field that cannot be turned off (a fields list only adds optional fields). One prompt is roughly 6 KB, so the default limit of 25 overflows the tool-result cap - and an overflowed result is worse than no result, because it is saved to a file you have no tool to read. If you ever do need that call - the inventory endpoint answered 404, or you need prompts that are not pipelines - scope it with projectId and pass limit: 3 from the very first call, page with offset, and say how far you looked.

If pipelines already exist, ask whether to reuse one or build a new one. The inventory's scanned counts let you frame it honestly - "this project has 1 AI Pipeline prompt and 6 other pre-translate prompts" - without reading the others. Do not offer building a plain single-pass prompt as an option: everyone arrives here having asked for a prompt or a pipeline, so the choice worth putting to them is which pipeline, not whether to abandon the shape they came for.

Reuse is not automatically free - check the chosen pipeline's isEnabled and enabledProjectIds in the inventory. Disabled, or scoped to other projects, means reaching this project takes a step-13 edit (enable it, or add the project id) to a prompt other projects share - say so before you do it.

What to carry forward from the inventory: the aiProviderId a working pipeline runs on, the models its steps use (step 10), and - per step - matchesDefault. That last one is what step 9 needs to decide whether a body is worth reading at all, and it is the reason none of this costs a config.

The pipeline gate. Before building anything new, state the real difference once: a single-pass prompt translates and nothing ever checks the output. In a pipeline each later step catches what the previous one got wrong - a mangled placeholder, a broken plural, a glossary term left in the wrong grammatical form. If the user still wants an ordinary single-pass prompt, that is a legitimate choice - but it is a different kind of prompt, so this skill ends there too.

Always write retryOnQaIssues: false on a pipeline, explicitly. Omitting it does not leave it unset - the field defaults to true, so a config that never mentions it is stored as if QA-failure retries were on, while the app declares allowRetryOnQaIssues: false and never performs them. Set it to false and say nothing about it to the user: it is internal plumbing, it has no effect they could observe, and raising it only spends their attention on a field they cannot use. This holds when editing too - step 13 has the mechanics.

Step 6 - Decide the step list, then ask once

Copy link

Two passes are always in: translation and the artifact correction. Every other step has to be earned - either by a fact about the project or by the user asking for it. This table is the whole decision. If a gate is not met the option is not offered at all, because offering a step the project cannot feed (a screenshot pass with no screenshots, a consistency pass over .json resources) is how a pipeline ends up paying for passes that do nothing.

Step Gate (from step 4) Decided by
translation - always in
correction (artifact) - always in
unambiguous-filter - Q1
context-extraction screenshots exist and strings are tagged Q1
file-context-extraction document-like files exist Q1
correction (file consistency) document-like files exist Q1
CrowdinQA - Q2
correction (per language) - Q3 → step 7

Ask all three questions in a single AskUserQuestion. Splitting them into rounds makes a design conversation feel like an interrogation.

Expect the round to go unanswered, and pick defaults you would be willing to ship. A question can time out - the user stepped away, the panel lost its session - and the run carries on regardless, so every option you offer is also a decision you may end up making alone. When that happens, take the narrowest reversible path: a new prompt scoped to this project rather than an edit to one other projects share, a step left out rather than a step guessed in, silence about a language rather than a rule invented for it. Then say which defaults you took and what would change if the answer arrives later. The expensive mistakes on this path are the ones that touch something you were not asked about.

Q1 (multiSelect) - the optional steps whose gate is met. Put them in the user's terms, not node types; you do the translating:

Option Step it adds
Filter out ambiguous strings before translating unambiguous-filter (+ createIssues, see below)
Read context from the screenshots context-extraction, attachScreenshots: true
Summarise each file first, so every batch shares that context file-context-extraction
Check each file reads consistently correction with filePathPatterns + {{input.file.fullTranslations}}
None of these -

"None of these" goes last and is always present: without it there is no way to answer "nothing" except free text.

Say what the ambiguity filter actually does before they pick it, because its effect is the one that surprises people: strings the model judges too ambiguous to translate safely are left untranslated - the pipeline skips them rather than guessing. That is the point of the step, but only if the user knows to expect gaps. Two things make those gaps actionable, and both are worth mentioning:

  • Why a string was skipped is visible in the AI Pipeline app's logs, under the project's Tools tab - the reason is recorded per string, so nobody has to guess what the filter objected to.
  • createIssues: true turns each skipped string into an issue in Crowdin, so translators see it in their own workflow and can add the missing context themselves. That is usually what you want when real translators will follow the pipeline; leave it false when the run is exploratory and issues would just be noise. Ask, rather than deciding for them.

Q2 - final Crowdin QA step. Say what it gives them: the finished translations are checked against the project's built-in Crowdin QA checks, and anything that fails is sent back for a fix.

Q3 - any language-specific rules?

Step 7 - Collect language rules, then pin the language ids

Copy link

Gather the rules as free text in one round. Map each language to the exact Crowdin id from get_project: matching is exact string equality, so de does not cover de-AT. If the project targets a dialect, scope to the dialect id the project actually uses.

Step 8 - Step order

Copy link

Step 6 decided which steps go in; this decides the order they sit in, which matters because each step only sees what the ones before it produced. The order is a quality argument, not an API requirement - say "I recommend", never "the app requires it".

Order type Key settings Why here
1st context-extraction attachScreenshots: true Enriches context / maxLength for every later step; skips itself when there are no screenshots - or when its model cannot see them (step 10)
2nd file-context-extraction - Derives the file-level summary the later steps read, so it has to precede them
3rd unambiguous-filter createIssues (asked in step 6), optional minLength Judges strings after context enrichment and before paying to translate them
4th translation - The only step that produces a translation
5th correction per language languages: ["de"], variantGroup Language rules while the translation is fresh
6th correction file consistency filePathPatterns, uses {{input.file.fullTranslations}} Needs a finished translation to compare against
7th correction artifact - Last text pass: placeholders, tags, escape sequences, plurals, glossary morphology
8th CrowdinQA - Validates against Crowdin's QA checks and retries only what failed

Order comes from array position, not from the id value; use node_<type>_<n> ids.

minLength on the ambiguity filter reads backwards at first glance: strings shorter than it skip the check and stay in the pipeline as if unambiguous. It exists to avoid paying for a model call on trivial strings, so raise it only when short strings in this project really are safe.

Splitting any step by language means one variant group, not two independent steps. This is not only about correction: the moment you give one step type two language-scoped bodies - most often an unambiguous-filter, where the formality abstention comes out for a language whose register is settled and stays in for one that is not - those bodies need the same variantGroup. Without it both nodes are separate steps and every language runs both, so the settled language pays for two filter passes and the second one re-imposes the reason you just removed. Give each variant the same variantGroup and order them most-specific first, because only the first node in a group that matches the current language and file runs. A catch-all (languages: []) is optional: include one when every other language should still get a generic review, and leave it out when languages you wrote no rules for should simply skip the pass rather than pay for it. If you do include one it has to come last - placed earlier it swallows the group and the specific variants never fire.

The mirror of that rule: a node that has to run as well as the variant must stay out of the group. Group members compete - one wins per language - so a second node for a language that is already covered (a forced glossary-style override for uk, say, alongside the uk review) silently loses if you file it under the same variantGroup. Membership is the question "instead of, or as well as?", and only the first answer belongs in the group.

Scoping rules. languages: [] means every language. filePathPatterns with a slash (docs/**/*.md) matches the full path; without a slash (*.md) it matches the basename anywhere in the tree. A payload with no file path (string-based projects) does not exclude the step - it simply runs unfiltered.

Write every pattern against the paths step 4 already fetched, not against what the project sounds like it contains. A pattern that matches nothing costs nothing and reports nothing - the step just silently never runs, so the mistake surfaces as "the pipeline ignored my rule" long after you have left. If list_files came back with .html files, **/*.md is not a conservative default, it is a dead step.

returnOnlyCorrectedText (correction steps only) switches the step's output contract: the model returns just the strings it actually changed, and the ones it leaves out keep their existing translation instead of counting as missing. That is usually what you want from a narrow verifying pass - it is cheaper and it stops the model rewriting text it had no reason to touch. It turns on by itself for correction steps on reasoning models, so set it explicitly only when you want that behaviour on a non-reasoning model.

Step 9 - Writing the step prompts

Copy link

Never write a step prompt from memory. Every body you send comes from one of two places: the app's own catalog, or a pipeline that already exists in this organization. Both are one tool call away, and both are better than anything you would reconstruct.

Fetch the catalog default first. Name the entries you need and ask for their bodies in one call:

{ "applicationIdentifier": "ai-pipeline", "path": "step-prompt-catalog?id=translate,artifact_correction" }

This is the cheap path and the accurate one. You get only the bodies you asked for, each entry tells you its builtinId, and the text is the exact bytes the settings UI compares against.

Reach for an existing pipeline only for the steps it actually tuned, and the inventory tells you which those are. A step with matchesDefault: true has nothing to add - its body is the catalog entry, so reading it costs a config and buys a copy of what you already have. What is worth harvesting is a step the organization changed (matchesDefault: false on a step whose builtinId or type says it started from a built-in): a brand rule inside the translate prompt, an extra forbidden phrasing, a register decision.

For those - and only those - read that one prompt with get_organization_ai_prompt / get_user_ai_prompt by id. One prompt is about 6 KB and it is a deliberate cost against a specific step you already decided you want. That is the whole harvest: one get, never a list.

If you do harvest, set settings.builtinId yourself for any step that came from a built-in. Step 12 reports a body that is a default without its marker (missing-builtin-id) and names the value to add, so a missed one is caught before the write - but it is cheaper to write it than to fix it.

Copy each prompt byte for byte into settings.prompt. This is not fussiness: the pipeline settings UI decides whether a step is still a built-in by comparing its text against the shipped default exactly. Identical text means the step keeps receiving future improvements to that default; one changed character means the text is stored as the user's own override and freezes there. So an edit you meant is fine - an edit you did not mean is a silent downgrade. When you do edit a body deliberately, say so in the plan, and pair the node with its settings.builtinId so the UI still shows the step as the built-in it came from.

Two places where a good default fights a decision the project has already made. Adapt, don't just paste:

  • The ambiguity filter abstains on formality. Its shipped body treats "the target language needs a formal/informal choice" as grounds to filter a string out. That is right for a project that has not settled its register - and wrong once the user tells you the answer (a project-wide du, or a style guide that says it), because it would then skip a large share of perfectly translatable strings. When the register is settled, take that one reason out of the filter body and say you did; leave the genuine ambiguity reasons alone.

    Settled means they told you, and it is settled per language. Users routinely answer for one target and go quiet on the rest. Your own guess is not an answer: writing an assumed register into the step bodies - or lifting the filter's abstention for a language nobody decided - commits that guess to every string in that language, invisibly, because the output then looks confident and consistent either way. So strip the abstention only for the languages actually answered for, leave it standing for the others, and give an undecided language no per-language step at all. Untranslated strings the user can see and ask about are cheaper than a wrong register they cannot.

  • Later steps can quietly undo a deliberate language choice. Artifact correction and CrowdinQA exist to normalise text, and Crowdin's own checks will flag a dialect's correct forms as errors - Austrian Jänner or Bankomat, Swiss ss for ß. An unscoped correction or QA step that trusts those findings will "fix" them back to standard German, and the result looks clean, which is what makes it hard to notice. When the project targets a dialect or has any deliberate non-standard convention, name those forms in the downstream steps as expected and not to be changed.

Three rules hold for every step prompt:

  1. Every step that works on the batch needs the batch placeholder. {{input.strings}} (or its legacy alias {{inputData}}) is where the batch of strings is injected, so a prompt without it describes a task over no data. The one exception is file-context-extraction, which summarises the file rather than the batch and reads {{input.file.sourceText}} instead.
  2. Never write the words "OUTPUT FORMAT". The app appends the JSON output contract for the node type automatically, and skips doing so if that exact phrase already appears - so writing your own format section silently replaces a correct machine-readable contract with your prose.
  3. Only real placeholders resolve. An unrecognised {{input.…}} token resolves to an empty string with no error, so a typo silently removes the data you meant to include.

The names that resolve are in the catalog's meta.placeholders - dotted for the current {{input.…}} form, legacy for the flat aliases that still work, snippet for the {{custom:<snippet>}} shape of an AI snippet. Read them from there rather than from memory; step 12 also validates every token you used, so a typo surfaces before the write rather than as an empty section at run time.

{{input.upstreamInstruction}} is worth knowing about: it resolves to the verbatim prompt body of the closest preceding translation step that ran this batch, and it is filled in purely because the placeholder appears in the text - any step type can use it. That makes it the clean way for a correction step to enforce the rules the translation was produced under (brand terms, register, forbidden phrasings) without copying them into a second prompt that then has to be kept in sync. It matters most when the translate prompt was hand-negotiated: reference it, do not restate it. That is also when the prompt_adherence catalog body earns a pipeline place - a narrow correction pass built around this placeholder, positioned after translation like any other review. With an untouched translate body it has nothing to enforce; do not add it by default.

Which catalog entry each step starts from. The id column is what you pass to step-prompt-catalog?id=…; the catalog's own use line says what each one is for, so read it before choosing between the correction bodies:

Step Catalog id builtinId to write
translation translate translate
correction (artifact) artifact_correction none - that body is not a built-in's default
correction (prompt adherence) prompt_adherence prompt_adherence
correction (file consistency) file_consistency file_consistency
unambiguous-filter ambiguous_filter ambiguous_filter
context-extraction prepare_context prepare_context
file-context-extraction prepare_file_context prepare_file_context
CrowdinQA qa_check qa_check

A per-language review has no shipped default on purpose: a language step should check that language's rules and nothing else. Write it yourself, short, and leave builtinId off it:

Review the {{input.targetLanguage}} translation for orthography and grammar - spelling, accents,
agreement, punctuation, and the register (formal / informal) appropriate to the language.

German specifics:
- Spelling rules (ß vs ss, compound words)
- Gender agreement (der/die/das) and case (nominative, accusative, dative, genitive)
- Word order
- Formal address: use Sie, never du or ihr

Strings serialized as JSON (with context):
<strings>
{{input.strings}}
</strings>

When several languages each need rules, give every variant the same variantGroup (see step 8) so exactly one runs per language.

settings.builtinId is worth writing even though nothing at run time reads it. It is the marker the pipeline settings UI uses to tell which built-in a step came from; without it the UI guesses from the step's name, and a renamed step guesses wrong. Set it only when the body really is that built-in's default (or a deliberate edit of it), and only where the type matches - a mismatched marker is discarded and step 12's validation flags it.

Step 10 - Model

Copy link

Format is "<providerId>::<modelId>", set per step in settings.model.

Prefer a model an existing pipeline already runs - the inventory carries model on every step, so this is free and it is known to work in this organization. Otherwise take one from the configured-models list, excluding the AI Pipeline provider itself (it advertises only the pseudo-model Crowdin); that call runs to tens of KB in an organization with many providers, so skip it entirely when the inventory already handed you a working model. Show the model in the plan; do not spend a question on it.

A pipeline prompt's own aiModelId is no help here - it is always the literal "Crowdin".

A screenshot step needs a model that can see - check, do not assume. attachScreenshots is honoured only when the step's model accepts images. Whenever the pipeline includes context-extraction, confirm the model you picked with list_all_supported_ai_models and fields: ["modalities"], then read modalities.input.image for that model id. Ask for the field explicitly: it is optional and absent from the default response.

The configured-models list is the wrong place to look - it answers which models this organization has wired up, not what they can accept, so a model that appears there tells you nothing about images.

If modalities.input.image is false, either move context-extraction to a model where it is true or drop the step and say why; do not ship the pair. The failure is quiet and points the wrong way: the step is dropped at run time and the log records the reason as no screenshots attached to the batch, sending whoever reads it hunting for screenshots that are in fact there. If the lookup itself is unavailable, fall back to the model an existing AI Pipeline prompt already runs its own context-extraction step on - vision-capable by demonstration - and if there is nothing to copy, name your choice in the plan as needing a multimodal model so the user can correct it while it is still cheap.

Step 11 - Plan, then confirm

Copy link

One numbered markdown table - number, name, type, scope, one sentence of purpose - plus the model and the prompt name. When two rows share a variantGroup, say so in the scope cell: it is the field that decides whether a language runs one of them or both, and a reader who cannot see it cannot tell a first-match-wins group from a pipeline that quietly pays twice. Write it as text, not through ShowUI: the table and the question have to sit together, and a rendered table has no buttons. Close with an explicit invitation: "Reply with any steps you want dropped or reordered, or a different name - otherwise I'll create it as shown."

If the user wants to cut below Translate + Artifact Correction, make the case once - a single pass never checks its own work - then build what they asked for and note plainly which checks are now absent. Create the prompt only after the plan is approved.

If an answer reshapes the plan materially, show the new shape before you write it. An approval attaches to a step list, not to your judgement in general, and answers routinely change the count - "rules for two of these thirty languages" turns thirty review steps into two. Building the smaller thing straight away is usually right, and saying so afterwards is not the same as being told to: a number that moves that far deserves one line of confirmation first, because the alternative is that you misread which languages were meant and nobody finds out until the run.

Step 12 - Validate, create, verify

Copy link

Validate the config before you write it - every time, create and edit alike. The app checks it for you, on the exact payload you are about to send:

{
  "applicationIdentifier": "ai-pipeline",
  "path": "validate-pipeline",
  "body": { "config": { "…the whole config object, byte for byte as it will go to Crowdin…" } }
}

The answer is { valid, errors, warnings, summary, unchecked }:

  • errors are blocking. Do not write. Fix and re-validate. They are the failures the Crowdin API does not report: a step array in the wrong place, a placeholder that resolves to nothing, a variantGroup whose catch-all switches off the variants after it, retryOnQaIssues: true. Each finding carries the path it is at and, where the fix is not obvious, a fix.
  • warnings are judgement calls, not noise. Read each one and decide. If you keep the behaviour it describes, say so in one line when you report - "the QA step runs before Translate because you asked for that order" - so the user is not left to discover it.
  • summary is what the validator actually read. Compare its step list against your plan; if it is shorter, you sent something other than what you built.
  • unchecked is the part it cannot see - language ids against the project, whether the model is configured, whether the screenshot model accepts images, whether filePathPatterns match real files. Those stay yours, from steps 4, 7 and 10.

A clean validation is not permission to skip the plan approval from step 11. Then create:

{
  "name": "AI Pipeline - Docs (DE, FR)",
  "action": "pre_translate",
  "aiProviderId": 42,
  "aiModelId": "Crowdin",
  "isEnabled": true,
  "enabledProjectIds": [130],
  "config": {
    "mode": "external",
    "identifier": "ai-pipeline",
    "key": "ai-pipeline-ai-prompt-provider",
    "retryOnQaIssues": false,
    "options": {
      "flowNodes": [
        {
          "id": "node_translation_1",
          "type": "translation",
          "settings": {
            "name": "Translate",
            "builtinId": "translate",
            "languages": [],
            "model": "7::gpt-5.2",
            "prompt": "… {{input.strings}} …"
          }
        },
        {
          "id": "node_correction_2",
          "type": "correction",
          "settings": {
            "name": "Artifact Correction",
            "languages": [],
            "model": "7::gpt-5.2",
            "prompt": "… {{input.strings}} …"
          }
        }
      ]
    }
  }
}

aiModelId is the literal string "Crowdin" - the only model the AI Pipeline provider advertises. The real models live in each step's settings.model. key is always "ai-pipeline-ai-prompt-provider" (the app identifier plus the module suffix); when editing an existing prompt, copy its identifier and key rather than retyping them.

The trap worth guarding against. Under config, only mode, identifier, key, options and retryOnQaIssues are accepted - everything else is dropped without an error. Put flowNodes at the config level instead of inside config.options and the call succeeds, creating a prompt with an empty pipeline that fails much later, during pre-translation. The validation above catches exactly this, which is most of why it is not optional. Still confirm the step count in config.options.flowNodes after the write - read it off the create call's own response, which returns the stored prompt in full, and spend a separate get_* only if that response comes back without the config. options may also carry enableAutoCorrection (default true) and maxCorrectionAttempts (default 2); omitting them is fine.

Default enabledProjectIds to the current project unless the user asked for wider availability - a new prompt has no business appearing in projects nobody mentioned. The field needs Manager rights: if the call is rejected for it, retry without it - the prompt is then available to every project, which is worth saying out loud.

Then report in one or two lines: name, number of steps, scope, availability. Offer to run it - and hand the run itself to ai-pipeline-pretranslation.

Step 13 - Editing an existing pipeline

Copy link
  1. Find it in the inventory (step 4) - it lists every pipeline with its id, name and steps, which is exactly what identifying one takes. Several matches → ask which, showing their step lists. None → offer to build one. This is the path that matters most for cost: an organization with a dozen pipelines is where the prompt list overflows, and the inventory answers the same question in a few KB.

  2. Read that one prompt with get_organization_ai_prompt / get_user_ai_prompt by id. You need its full config now, because the edit sends config back whole - but you need exactly one, and you already know which.

  3. A step you are adding is designed like any other: body from the catalog or a harvest (step 9), position from step 8, model from step 10 - most cheaply one an existing step already runs. The edit path changes how the config is written, not where step bodies come from.

  4. Validate the whole config you are about to send (step 12) - not just the part you changed. config is replaced wholesale, so the whole pipeline is what gets stored, and the rules worth having only exist across steps: where your step sits relative to translation, whether a variant group's catch-all now swallows the variants after it, whether an id collides.

  5. Apply it. edit_organization_ai_prompt / edit_user_ai_prompt, with config carrying every step: the ones you changed and the ones you did not, prompt bodies included.

    Both calls are long - thousands of tokens of prompt text each - and that is expected. There is no shorter form: a config missing a step deletes that step. Length is not a reason to summarise, to describe the change instead, or to stop and check in. Send it.

  6. Then write the answer: the pipeline as the same table from step 11, the change as a diff against what it was, and any warning you decided to live with.

There is no step-11 approval gate on this path: the user's request is the approval, and the confirmation is the diff you show after the write, not a question before it. Ask first only when the request is ambiguous about which pipeline or which step it means - not to re-confirm a change the user already described.

The two findings worth expecting on this path come from the config's history rather than your change: retryOnQaIssues stored as true, and steps whose prompts drifted from the shipped default long ago. Fix the first - the API rejects a true and the whole edit fails. Leave the second alone unless the user asked for it.

The trap on this path, and it is the one that costs the user most. A turn that ends with "validation passed - applying the change now" is a turn in which nothing was applied. It reads exactly like success: the analysis is right, the diff is right, the validation is real, and the pipeline is untouched. This is measured, not hypothetical - five runs of this step across two models ended that way, every one after a clean valid: true.

Two checks against it, both cheap:

  • A sentence containing "applying", "will apply", "let me apply" is a signal to make the call instead. There is no state in which announcing the edit is more useful than performing it.
  • Before writing that it is applied, name the evidence: the edit_* response carries the stored prompt, so read the step count out of it (or out of the inventory). "It is applied" is a claim about something you saw, not about something you decided to do.

Pass config as a flat field - do not hand-build the JSON-Patch. The tool takes ordinary arguments and turns each one into a replace operation itself, so an edit is shaped like this:

{
  "aiPromptId": 755,
  "config": {
    "mode": "external",
    "identifier": "ai-pipeline",
    "key": "ai-pipeline-ai-prompt-provider",
    "retryOnQaIssues": false,
    "options": { "flowNodes": [ "…every step, including the ones you are not changing…" ] }
  }
}

Send only the fields you mean to change: passing config alone leaves name, action, aiProviderId, aiModelId, isEnabled and enabledProjectIds exactly as they were.

config is replaced wholesale - that one operation overwrites the whole object. So it must contain every step, including the ones you are not touching; a partial list silently deletes the omitted steps. Untouched steps go back byte-identical, prompt text included. Rewriting a hand-tuned prompt because it "looked improvable" is the hardest failure on this path to undo. The lone exception is retryOnQaIssues, which you set to false on the way through even if the stored config says true. Echoing a stored true back is not neutral - the API refuses it for this provider (Retry on QA issues is not supported for this AI prompt provider, HTTP 400) and the whole edit fails. A stored true was never anyone's decision - it is the default leaking through an earlier write (step 5).

When the reason for a change is speed, check the cause before acting on it. Removing a step does make a run faster, but make sure the step being removed is the one actually costing the time - and if the same request also reports broken output, say which of the two changes addresses which complaint. Removing a validation pass never fixes bad output; it just stops reporting it. Do not quantify the speed-up: you cannot see how the app batches a run, so promise a direction, never a number.

Quality bar

Copy link
  • Every step earns its place, or it does not go in. A step is a full model pass.
  • Translate + Artifact Correction is a recommendation you argue for, never an API requirement.
  • Language-specific steps stay narrow, and share one variantGroup.
  • Prompt bodies come from the catalog, or from an existing pipeline for the steps it actually tuned - never from memory.
  • The model is shown in the plan, not negotiated in a question.
  • Nothing is created before the plan is approved.
  • Nothing is written before the config validates clean.

Edge cases

Copy link
  • QA-check or alignment requested → not a pipeline; this skill ends (step 2). Alignment does not exist on crowdin.com at all.
  • A plain single-pass pre-translation prompt requested → make the self-checking argument once (step 5), then it is a different kind of prompt and this skill ends there too.
  • custom:-prefixed action → the action enum accepts only pre_translate, qa_check, alignment. Say so; never quietly substitute pre_translate.
  • No AI provider / app not installed → stop with the concrete fix, not a designed-but-dead pipeline.
  • list_screenshots unavailable → do not silently drop the screenshot option; ask the user instead.
  • String-based project → no list_files; skip the file-consistency option entirely.
  • "Always translate Cart as Кошик" → that is a glossary term, not a pipeline step. Point at the glossary (glossary-terms-extraction).
  • Formality or punctuation rules spanning a whole language → that is a style guide reached through {{input.styleguide}} (style-guide-creation), not a large correction step. If the pipeline already has a per-language step for that language, putting the rule there is the smaller change and usually the right one.
  • Filtered strings come back untranslated → the ambiguity filter working, not a failure; the per-string reasons (app logs) and the createIssues remedy are both in step 6.
  • Duplicate prompt name → the create call answers 400 notUnique and names the taken name. Add a suffix and retry immediately, and never overwrite the existing prompt. Do not list prompts to hunt for a free name: the error already told you everything you need, and that listing is the one call here that reliably overflows the tool-result cap (step 5). It is how a run that did everything else right still burns a call at the last step.
  • A pipeline with no translation step → technically accepted, behaviour undefined. Re-ask what the user meant. If they are clear about it - vendor-translated content they only want checked - build the pipeline they asked for rather than quietly adding a translate pass back in, and name the two things that decide whether it does anything at all: the run has to be given scope: "translated" (or "all"), because the default untranslated visits only the strings the checks cannot help; and {{input.upstreamInstruction}} resolves to an empty string with no preceding translation step, so a correction body that leans on it silently loses its rules.
  • Only a handful of strings involved → say so; a pipeline is a standing configuration and a disproportionate answer to a one-off handful. Saying it is not licence to run one instead: the cheaper-looking path - reuse the pipeline that exists and pre-translate with it - is a bigger action than the one that was asked for, because a run covers every string in scope rather than the handful named, and the run belongs to the other skill. Name the option, name what it would cover, and wait.
  • The catalog or the validation call is unavailable → say which one, name the reason if the answer gives you one (step 4 has the table - a 404 without an app error code is a stale installation, not a missing app), and carry on with what you can still do honestly. Without the catalog, harvest from an existing pipeline (step 5) or write the short per-language shape yourself; do not reconstruct a shipped default from memory. Without validation, re-read the config against steps 8, 9 and 12 yourself and say plainly that it went out unvalidated. Neither outage is a reason to stop: a pipeline built from a harvested config and checked by hand is still a working pipeline.
Categories
Works with
  • crowdin.com
  • Crowdin Enterprise
Details

Released on Sep 4, 2026

Updated on Sep 4, 2026

Published by Crowdin

Identifier:ai-pipeline-prompt-design

All product and company names are trademarks™ or registered® trademarks of their respective holders. Use of them does not imply any affiliation with or endorsement by them.