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:
targetLocale: fr
expansionPercent: 30
prefix: "["
suffix: "]"
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 .klz workspace, an
interchange import/export, or none — not 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-check
At the CLI the binding comes from -i / -o (a plain path is detected, a
scheme: is explicit), the project / .klz you are in, or auto-detection;
kapi run <flow> --explain shows the resolved source → sink. See
AD-026: 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:
targetLocale: fr
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:
targetLocale: fr
copySource: true
- tool: search-replace
config:
pairs:
- search: "TODO"
replace: ""
target: true
- tool: qa-check
config:
targetLocale: fr
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:
targetLocale: fr
copySource: true
- parallel:
- tool: word-count
label: Count words
config:
targetLocale: fr
- tool: qa-check
label: Quality checks
config:
targetLocale: fr
- tool: chars-listing
label: Character inventory
config:
targetLocale: fr
All three analysis 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: ai-translate
config:
targetLocale: fr
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, AD-020). 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:
- Each sequential step becomes a tool node, chained by edges
- A
parallel:block creates multiple tool nodes, all connected from the previous node (fan-out) - 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 (AD-026), 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 TM leverage, AI translation for new blocks, and quality checks:
steps:
- tool: create-target
config:
targetLocale: fr
copySource: false
- tool: tm-leverage
label: Apply TM matches
config:
targetLocale: fr
fuzzyThreshold: 75
- tool: ai-translate
label: Translate remaining
config:
targetLocale: fr
provider: anthropic
- tool: qa-check
label: Quality checks
config:
targetLocale: fr
Fan-out analysis
Run multiple analysis tools in parallel after pseudo-translation:
steps:
- tool: pseudo-translate
config:
targetLocale: qps-ploc
expansionPercent: 30
- parallel:
- tool: word-count
config:
targetLocale: qps-ploc
- tool: length-check
config:
targetLocale: qps-ploc
maxChars: 200
- tool: qa-check
config:
targetLocale: qps-ploc
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
config:
targetLocale: fr
Running flows
From the CLI
# Run a built-in composed flow
kapi run ai-translate-qa -i input.xliff --target-lang fr
# Run a flow defined in a .kapi project file
kapi run my-flow -p myproject.kapi -i input.json
# List available flows
kapi flows
Programmatically
spec := &flow.StepsSpec{
Input: "json",
Steps: []flow.FlowStep{
{Tool: "pseudo-translate", Config: map[string]any{
"targetLocale": "fr",
"expansionPercent": 30,
}},
{Tool: "qa-check", Config: map[string]any{
"targetLocale": "fr",
}},
},
}
nodes, edges, err := flow.StepsToGraph(spec)
// Build and execute with Executor...