Skill Tracer
Skills › Scientific Agent Skills › latchbio-integration › workflow-creation.md

workflow-creation.md

292 lines · skills/latchbio-integration/references/workflow-creation.md at 5f2bc03

Open on GitHub

Most marks come from matching words in the file, so treat them as pointers.

1# Python Workflow Creation
2
3This reference targets `latch==2.77.1`. Check the installed SDK with
4`scripts/inspect_latch_sdk.py` before using version-sensitive symbols.
5

Python Workflow Creation

This reference targets latch==2.77.1. Check the installed SDK with scripts/inspect_latch_sdk.py before using version-sensitive symbols.

Raw lines 1 to 5
Runs code scripts/inspect_latch_sdk.py :4
6## Mental Model
7
8- A function decorated with `@workflow` defines a graph.
9- A function decorated with a task decorator runs in its own containerized task.
10- Calling a task inside a workflow creates a node; it does not execute ordinary
11 Python at graph-construction time.
12- Workflow inputs and outputs must be typed.
13- Put file I/O, subprocesses, network calls, secret lookup, and scientific
14 computation inside tasks.
15- Keep module import time deterministic and free of external side effects.

Mental Model

  • A function decorated with @workflow defines a graph.
  • A function decorated with a task decorator runs in its own containerized task.
  • Calling a task inside a workflow creates a node; it does not execute ordinary Python at graph-construction time.
  • Workflow inputs and outputs must be typed.
  • Put file I/O, subprocesses, network calls, secret lookup, and scientific computation inside tasks.
  • Keep module import time deterministic and free of external side effects.
Raw lines 6 to 16
17## Initialize a Project
19```bash
20latch init my-workflow --template subprocess
21```
23Current template choices are exposed by `latch init --help`; common choices
24include `subprocess`, `conda`, `r`, and `empty`. A project commonly contains a
25`wf/` Python package, a `version` file, dependency inputs, and optional
26`Dockerfile` and metadata files.
28Do not assume a hand-written Dockerfile is required. Registration can generate
29one. If a Dockerfile is present at the project root, registration uses it.

Initialize a Project

latch init my-workflow --template subprocess

Current template choices are exposed by latch init --help; common choices include subprocess, conda, r, and empty. A project commonly contains a wf/ Python package, a version file, dependency inputs, and optional Dockerfile and metadata files.

Do not assume a hand-written Dockerfile is required. Registration can generate one. If a Dockerfile is present at the project root, registration uses it.

Raw lines 17 to 30
31## Tasks and Workflows
33```python
34from latch import small_task, workflow
37@small_task
38def normalize_name(sample_name: str) -> str:
39 return sample_name.strip().replace(" ", "_")
42@workflow
43def normalize_sample(sample_name: str) -> str:
44 """Normalize a sample identifier.
46 Replace embedded spaces with underscores.
47 """
48 return normalize_name(sample_name=sample_name)
49```
51In SDK 2.77.1, an explicit workflow docstring must contain a newline: the
52decorator splits it into a title and description. A one-line docstring raises
53`ValueError` during import. Use a multiline docstring, as above, or omit it.
55Use keyword arguments for task calls. They make graph wiring explicit and
56survive parameter reordering.

Tasks and Workflows

from latch import small_task, workflow


@small_task
def normalize_name(sample_name: str) -> str:
    return sample_name.strip().replace(" ", "_")


@workflow
def normalize_sample(sample_name: str) -> str:
    """Normalize a sample identifier.

    Replace embedded spaces with underscores.
    """
    return normalize_name(sample_name=sample_name)

In SDK 2.77.1, an explicit workflow docstring must contain a newline: the decorator splits it into a title and description. A one-line docstring raises ValueError during import. Use a multiline docstring, as above, or omit it.

Use keyword arguments for task calls. They make graph wiring explicit and survive parameter reordering.

Raw lines 31 to 57
58### File input and output
60`LatchFile` and `LatchDir` remain the most established workflow signature types.
61Inputs are staged onto the task machine. Returned values pair a local path with
62a remote destination.
64```python
65from pathlib import Path
67from latch import small_task, workflow
68from latch.types import LatchFile, LatchOutputDir
71@small_task
72def count_lines(input_file: LatchFile, output_dir: LatchOutputDir) -> LatchFile:
73 source = Path(input_file.local_path)
74 destination = Path("/root/line-count.txt")
76 with source.open("r", encoding="utf-8") as handle:
77 count = sum(1 for _ in handle)
78 destination.write_text(f"{count}\n", encoding="utf-8")
80 remote_dir = output_dir.remote_path
81 if remote_dir is None:
82 raise ValueError("output_dir must have a remote Latch path")
84 return LatchFile(
85 str(destination),
86 f"{remote_dir.rstrip('/')}/line-count.txt",
87 )
90@workflow
91def line_count_workflow(
92 input_file: LatchFile,
93 output_dir: LatchOutputDir,
94) -> LatchFile:
95 return count_lines(input_file=input_file, output_dir=output_dir)
96```
98For new imperative remote-path operations, prefer `LPath`; see
99`references/data-management.md`. Do not silently replace typed workflow
100parameters with plain strings merely to avoid learning the file types.

File input and output

LatchFile and LatchDir remain the most established workflow signature types. Inputs are staged onto the task machine. Returned values pair a local path with a remote destination.

from pathlib import Path

from latch import small_task, workflow
from latch.types import LatchFile, LatchOutputDir


@small_task
def count_lines(input_file: LatchFile, output_dir: LatchOutputDir) -> LatchFile:
    source = Path(input_file.local_path)
    destination = Path("/root/line-count.txt")

    with source.open("r", encoding="utf-8") as handle:
        count = sum(1 for _ in handle)
    destination.write_text(f"{count}\n", encoding="utf-8")

    remote_dir = output_dir.remote_path
    if remote_dir is None:
        raise ValueError("output_dir must have a remote Latch path")

    return LatchFile(
        str(destination),
        f"{remote_dir.rstrip('/')}/line-count.txt",
    )


@workflow
def line_count_workflow(
    input_file: LatchFile,
    output_dir: LatchOutputDir,
) -> LatchFile:
    return count_lines(input_file=input_file, output_dir=output_dir)

For new imperative remote-path operations, prefer LPath; see references/data-management.md. Do not silently replace typed workflow parameters with plain strings merely to avoid learning the file types.

Raw lines 58 to 101
102## Custom Workflow Metadata
104Without metadata, the SDK derives a basic interface from the workflow
105signature. Add `LatchMetadata` when the form needs deliberate design.
107```python
108from pathlib import Path
110from latch import small_task, workflow
111from latch.types import LatchFile
112from latch.types.metadata import (
113 LatchAuthor,
114 LatchMetadata,
115 LatchParameter,
116 LatchRule,
120@small_task
121def validate_task(reads: LatchFile) -> bool:
122 return Path(reads.local_path).stat().st_size > 0
125metadata = LatchMetadata(
126 display_name="FASTQ Validator",
127 author=LatchAuthor(name="Workflow Team"),
128 license="MIT",
129 repository="https://github.com/example/fastq-validator",
130 parameters={
131 "reads": LatchParameter(
132 display_name="Reads",
133 description="Input FASTQ or compressed FASTQ file.",
134 rules=[
135 LatchRule(
136 regex=r".*\.(fastq|fq)(\.gz)?$",
137 message="Choose a .fastq, .fq, .fastq.gz, or .fq.gz file.",
138 )
139 ],
140 )
141 },
145@workflow(metadata)
146def validate_fastq(reads: LatchFile) -> bool:
147 return validate_task(reads=reads)
148```
150Metadata keys must match workflow parameter names. In SDK 2.77.1, the
151decorator drops unmatched metadata keys before its mismatch check; a typo can
152silently lose labels or rules. Compare the key sets before applying the
153decorator instead of relying on an exception.
155Although `about_page_path` is documented, SDK 2.77.1 can fail while serializing
156its `Path` value. Use `documentation=` and a descriptive workflow docstring
157until that defect is fixed.
159See `references/ui-and-automation.md` for sections, samplesheets, launch plans,
160messages, and result links.

Custom Workflow Metadata

Without metadata, the SDK derives a basic interface from the workflow signature. Add LatchMetadata when the form needs deliberate design.

from pathlib import Path

from latch import small_task, workflow
from latch.types import LatchFile
from latch.types.metadata import (
    LatchAuthor,
    LatchMetadata,
    LatchParameter,
    LatchRule,
)


@small_task
def validate_task(reads: LatchFile) -> bool:
    return Path(reads.local_path).stat().st_size > 0


metadata = LatchMetadata(
    display_name="FASTQ Validator",
    author=LatchAuthor(name="Workflow Team"),
    license="MIT",
    repository="https://github.com/example/fastq-validator",
    parameters={
        "reads": LatchParameter(
            display_name="Reads",
            description="Input FASTQ or compressed FASTQ file.",
            rules=[
                LatchRule(
                    regex=r".*\.(fastq|fq)(\.gz)?$",
                    message="Choose a .fastq, .fq, .fastq.gz, or .fq.gz file.",
                )
            ],
        )
    },
)


@workflow(metadata)
def validate_fastq(reads: LatchFile) -> bool:
    return validate_task(reads=reads)

Metadata keys must match workflow parameter names. In SDK 2.77.1, the decorator drops unmatched metadata keys before its mismatch check; a typo can silently lose labels or rules. Compare the key sets before applying the decorator instead of relying on an exception.

Although about_page_path is documented, SDK 2.77.1 can fail while serializing its Path value. Use documentation= and a descriptive workflow docstring until that defect is fixed.

See references/ui-and-automation.md for sections, samplesheets, launch plans, messages, and result links.

Raw lines 102 to 161
162## Parallel Mapping
164Use `map_task` when one task should run over a list. Mapping produces a single
165map node rather than thousands of graph nodes.
167```python
168from pathlib import Path
170from latch import map_task, small_task, workflow
171from latch.types import LatchFile
174@small_task
175def validate_one(reads: LatchFile) -> bool:
176 return Path(reads.local_path).stat().st_size > 0
179@workflow
180def validate_batch(reads: list[LatchFile]) -> list[bool]:
181 return map_task(validate_one)(reads=reads)
182```
184Map task inputs must line up with the mapped function's parameter names.
185Resource overrides are Flyte overrides and should be tested against the
186installed SDK.

Parallel Mapping

Use map_task when one task should run over a list. Mapping produces a single map node rather than thousands of graph nodes.

from pathlib import Path

from latch import map_task, small_task, workflow
from latch.types import LatchFile


@small_task
def validate_one(reads: LatchFile) -> bool:
    return Path(reads.local_path).stat().st_size > 0


@workflow
def validate_batch(reads: list[LatchFile]) -> list[bool]:
    return map_task(validate_one)(reads=reads)

Map task inputs must line up with the mapped function's parameter names. Resource overrides are Flyte overrides and should be tested against the installed SDK.

Raw lines 162 to 187
188## Conditional Nodes
190Ordinary Python `if` statements cannot branch on task promises. Build a
191conditional node:
193```python
194from latch import create_conditional_section, small_task, workflow
197@small_task
198def double(value: float) -> float:
199 return value * 2
202@small_task
203def square(value: float) -> float:
204 return value**2
207@workflow
208def transform(value: float) -> float:
209 doubled = double(value=value)
210 return (
211 create_conditional_section("choose-transform")
212 .if_(doubled < 0.0)
213 .then(double(value=doubled))
214 .elif_(doubled > 0.0)
215 .then(square(value=doubled))
216 .else_()
217 .fail("Zero is not accepted")
218 )
219```
221Use `&` and `|` for compound promise expressions. For a task-produced Boolean,
222use its promise truth checks rather than Python unary `not`.

Conditional Nodes

Ordinary Python if statements cannot branch on task promises. Build a conditional node:

from latch import create_conditional_section, small_task, workflow


@small_task
def double(value: float) -> float:
    return value * 2


@small_task
def square(value: float) -> float:
    return value**2


@workflow
def transform(value: float) -> float:
    doubled = double(value=value)
    return (
        create_conditional_section("choose-transform")
        .if_(doubled < 0.0)
        .then(double(value=doubled))
        .elif_(doubled > 0.0)
        .then(square(value=doubled))
        .else_()
        .fail("Zero is not accepted")
    )

Use & and | for compound promise expressions. For a task-produced Boolean, use its promise truth checks rather than Python unary not.

Raw lines 188 to 223
224## Caching, Retries, and Timeouts
226Named task decorators forward Flyte task options. This skeleton is illustrative;
227replace the task body before execution:
229```python
230from datetime import timedelta
232from latch import medium_task
233from latch.types import LatchFile
236@medium_task(
237 cache=True,
238 cache_version="reference-index-v2",
239 retries=2,
240 timeout=timedelta(hours=2),
242def build_index(reference: LatchFile) -> LatchFile:
243 ...
244```
246- **Resume versus new execution:** Console **Retry from failed task** reuses
247 completed upstream outputs. Before resuming, verify those outputs still match
248 the intended inputs, reference data, and code. If an upstream scientific step
249 changed, launch an execution that recomputes that step and invalidate its cache
250 explicitly; a downstream retry cannot repair stale upstream results. See the
251 [official caching guide](https://wiki.latch.bio/workflows/sdk/python/caching).
252- Change `cache_version` whenever output-affecting logic or dependencies change.
253- Cache only deterministic tasks.
254- Retries should cover transient failures, not malformed inputs.
255- Set a timeout with scientific runtime variance in mind.
256- See `references/resource-configuration.md` before selecting a larger instance.

Caching, Retries, and Timeouts

Named task decorators forward Flyte task options. This skeleton is illustrative; replace the task body before execution:

from datetime import timedelta

from latch import medium_task
from latch.types import LatchFile


@medium_task(
    cache=True,
    cache_version="reference-index-v2",
    retries=2,
    timeout=timedelta(hours=2),
)
def build_index(reference: LatchFile) -> LatchFile:
    ...
  • Resume versus new execution: Console Retry from failed task reuses completed upstream outputs. Before resuming, verify those outputs still match the intended inputs, reference data, and code. If an upstream scientific step changed, launch an execution that recomputes that step and invalidate its cache explicitly; a downstream retry cannot repair stale upstream results. See the official caching guide.
  • Change cache_version whenever output-affecting logic or dependencies change.
  • Cache only deterministic tasks.
  • Retries should cover transient failures, not malformed inputs.
  • Set a timeout with scientific runtime variance in mind.
  • See references/resource-configuration.md before selecting a larger instance.
Raw lines 224 to 257
258## Task-Specific Dockerfiles
260Since SDK 2.57.0, a task decorator's `dockerfile` argument must be a string
261literal so registration can discover it through static AST inspection:
263```python
264@small_task(dockerfile="Dockerfile.validation")
265def validate_with_custom_image(reads: LatchFile) -> bool:
266 ...
267```
269Do not pass a `Path`, variable, function call, or computed expression to this
270argument.

Task-Specific Dockerfiles

Since SDK 2.57.0, a task decorator's dockerfile argument must be a string literal so registration can discover it through static AST inspection:

@small_task(dockerfile="Dockerfile.validation")
def validate_with_custom_image(reads: LatchFile) -> bool:
    ...

Do not pass a Path, variable, function call, or computed expression to this argument.

Raw lines 258 to 271
272## Workflow Design Checklist
274- Every input and output has a concrete type.
275- Workflow bodies only compose task, map, conditional, and reference nodes.
276- Tasks return actual values rather than undefined placeholder variables.
277- Output destinations do not collide across concurrent runs.
278- Subprocess calls use argument lists and `check=True`.
279- Dependencies and tool versions are pinned for releases.
280- Metadata keys match the workflow signature.
281- Cache versions reflect code and dependency changes.
282- The image has been tested with staging registration and `latch develop`.

Workflow Design Checklist

  • Every input and output has a concrete type.
  • Workflow bodies only compose task, map, conditional, and reference nodes.
  • Tasks return actual values rather than undefined placeholder variables.
  • Output destinations do not collide across concurrent runs.
  • Subprocess calls use argument lists and check=True.
  • Dependencies and tool versions are pinned for releases.
  • Metadata keys match the workflow signature.
  • Cache versions reflect code and dependency changes.
  • The image has been tested with staging registration and latch develop.
Raw lines 272 to 283
284## Official Sources
286- Python SDK overview: https://wiki.latch.bio/workflows/sdk/python/overview
287- Quick start: https://wiki.latch.bio/workflows/sdk/python/quick-start
288- Conditional sections: https://wiki.latch.bio/workflows/sdk/python/conditional-sections
289- Map tasks: https://wiki.latch.bio/workflows/sdk/python/map-task
290- Caching: https://wiki.latch.bio/workflows/sdk/python/caching
291- Workflow environment: https://wiki.latch.bio/workflows/sdk/python/workflow-environment/overview
292- SDK source at the 2.77.1 release commit: https://github.com/latchbio/latch/tree/b3768e65c6d496868f6e530f11977d857ad85dc7

Official Sources

  • Python SDK overview: https://wiki.latch.bio/workflows/sdk/python/overview
  • Quick start: https://wiki.latch.bio/workflows/sdk/python/quick-start
  • Conditional sections: https://wiki.latch.bio/workflows/sdk/python/conditional-sections
  • Map tasks: https://wiki.latch.bio/workflows/sdk/python/map-task
  • Caching: https://wiki.latch.bio/workflows/sdk/python/caching
  • Workflow environment: https://wiki.latch.bio/workflows/sdk/python/workflow-environment/overview
  • SDK source at the 2.77.1 release commit: https://github.com/latchbio/latch/tree/b3768e65c6d496868f6e530f11977d857ad85dc7
Raw lines 284 to 292