Skip to main content

YAML Flow Authoring

Flows define processing pipelines as YAML files. The steps-based format is the human-authored representation that compiles to an internal graph (nodes + edges) for execution.

Steps format

A flow is a list of sequential steps. Each step references a tool by name and optionally provides configuration:

steps:
- tool: pseudo-translate
config:
expansionPercent: 30
prefix: "["
suffix: "]"

The target locale is not part of the flow. The run supplies it to every step (--target-lang at the CLI, or the project's declared targets under kapi run / kapi up), so one flow serves every locale; a targetLocale written into a step's config is outranked by the run's language.

Source and sink

A flow carries only its steps. Where content enters and leaves are bindings resolved when the flow runs (a file, the project store, a .kpz workspace, an interchange import/export, or none) rather than fields of the flow document. A flow declares a binding only when it is intrinsic to the flow (e.g. a check flow that produces no document sets sink: none), and never a path:

sink: none # only when intrinsic; otherwise omit and let the run decide
steps:
- tool: qa

At the CLI the binding comes from -i / -o (a plain path is detected, a scheme: is explicit), the project / .kpz you are in, or auto-detection; kapi run <flow> --explain shows the resolved source → sink. See E-04: Flow I/O Binding.

Step labels

Add a label for readability in the UI graph view:

steps:
- tool: pseudo-translate
label: Generate test translations
config:
expansionPercent: 30

Sequential steps

Steps execute in order. The output channel of one tool feeds into the input channel of the next:

steps:
- tool: create-target
config:
copySource: true

- tool: search-replace
config:
pairs:
- search: "TODO"
replace: ""
target: true

- tool: qa

This creates a three-step pipeline: create the target, clean up placeholder text, then run quality checks.

Parallel blocks for fan-out

Use parallel: to run multiple tools concurrently on the same stream of Parts. Each branch receives a copy of the input and produces independent output:

steps:
- tool: create-target
config:
copySource: true

- parallel:
- tool: qa
label: Quality checks
- tool: term-check
label: Terminology checks
- tool: xml-validation
label: XML validation

All three check tools run at the same time, each in its own goroutine.

Transformers

Tools that rewrite the source (redaction, whitespace/markup normalization, a source-mutating script) are ordinary steps in the same ordered list as everything else. A transformer returns an edit plan; the framework applier performs the rewrite inline and in order, rebasing surviving run-anchored overlays (segmentation, terms) onto the new runs, so each transformer settles the source before later steps observe it.

steps:
- tool: redact
config:
detectors: [rules]
rulesPath: redaction-rules.yaml

- tool: translate

A placement pass (core/flow/placement.go) validates transformer positions beside data-flow validation at every flow build and load:

  • Error: a transformer follows a step that produces a committed target (rewriting source orphans the targets). A transformer that produces targets itself, such as unredact, is exempt.
  • Error: a recoverable transformer (redact) follows a step with the remote-source-egress side effect, except a step producing an input the transformer's config-resolved contract requires (a cloud NER step feeding entity-driven redaction is the documented detection trade-off, C-10). AI tools configured with a local provider (ollama, demo) carry no egress effect.
  • Warning: a transformer placed later than its earliest valid slot, since every overlay present at apply time must be rebased.

A flow that declares the removed source_transforms: field is rejected at load with a migration error directing you to list the transformers as ordered steps. See the tool-system AD for the immutability model and the producer/applier split.

How steps compile to the graph

The StepsToGraph() function transforms a StepsSpec into FlowNode and FlowEdge slices:

  1. Each sequential step becomes a tool node, chained by edges
  2. A parallel: block creates multiple tool nodes, all connected from the previous node (fan-out)
  3. After a parallel block, subsequent steps connect from all branch endpoints (fan-in)

The graph is tool nodes only. The flow's source and sink are bindings supplied at run time (E-04), not nodes in the graph.

The resulting graph is what the Executor runs: each node becomes a goroutine connected by buffered channels.

Example flows

Translation pipeline

A typical translation flow with content-memory leverage, AI translation for new blocks, and quality checks:

steps:
- tool: create-target
config:
copySource: false

- tool: recycle
label: Apply memory matches
config:
fuzzyThreshold: 75

- tool: translate
label: Translate remaining
config:
provider: anthropic

- tool: qa
label: Quality checks

Run it for each locale you ship:

kapi run my-translate -i input.xliff --target-lang fr
kapi run my-translate -i input.xliff --target-lang de

Fan-out analysis

Run multiple analysis tools in parallel after pseudo-translation:

steps:
- tool: pseudo-translate
config:
expansionPercent: 30

- parallel:
- tool: term-check
- tool: qa
config:
checkAbsoluteMaxCharLength: true
absoluteMaxCharLength: 200
kapi run fan-out -i input.json --target-lang qps

Script filtering

Use the JavaScript script step to filter or transform parts programmatically:

steps:
- tool: script
label: Skip short blocks
config:
code: |
if (part.type === 'block') {
var text = part.block.source[0].content.text;
if (text.length < 3) {
skip();
}
}

- tool: pseudo-translate

Running flows

From the CLI

# Run a built-in composed flow
kapi run translate-qa -i input.xliff --target-lang fr

# Run a flow defined in a kapi.yaml project file
kapi run my-flow -p kapi.yaml -i input.json --target-lang fr

# List available flows
kapi flows

Programmatically

An ad-hoc programmatic run has no --target-lang to inherit, so the locale is passed in each step's config:

spec := &flow.StepsSpec{
Input: "json",
Steps: []flow.FlowStep{
{Tool: "pseudo-translate", Config: map[string]any{
"targetLocale": "fr",
"expansionPercent": 30,
}},
{Tool: "qa", Config: map[string]any{
"targetLocale": "fr",
}},
},
}

nodes, edges, err := flow.StepsToGraph(spec)
// Build and execute with Executor...