Skill Tracer
Skills › Scientific Agent Skills › latchbio-integration › ui-and-automation.md

ui-and-automation.md

360 lines · skills/latchbio-integration/references/ui-and-automation.md at 5f2bc03

Open on GitHub
1# Workflow UI, Launch Plans, Messages, Results, and Automations
2
3The workflow signature defines types. `LatchMetadata` defines how users
4understand and enter those values. Treat interface design as part of workflow
5correctness.
6

Workflow UI, Launch Plans, Messages, Results, and Automations

The workflow signature defines types. LatchMetadata defines how users understand and enter those values. Treat interface design as part of workflow correctness.

Raw lines 1 to 6
7## Metadata
8
9```python
10from latch.types.metadata import (
11 LatchAuthor,
12 LatchMetadata,
13 LatchParameter,
14 LatchRule,
15 Params,
16 Section,
17 Spoiler,
18 Text,
21metadata = LatchMetadata(
22 display_name="Bulk RNA-seq QC",
23 author=LatchAuthor(
24 name="Workflow Team",
25 github="https://github.com/example",
26 ),
27 documentation="https://example.org/docs/rnaseq-qc",
28 repository="https://github.com/example/rnaseq-qc",
29 license="MIT",
30 tags=["RNA-seq", "QC"],
31 parameters={
32 "reads": LatchParameter(
33 display_name="Reads",
34 description="FASTQ input files.",
35 samplesheet=True,
36 ),
37 "minimum_quality": LatchParameter(
38 display_name="Minimum quality",
39 description="Minimum accepted Phred score.",
40 rules=[
41 LatchRule(
42 regex=r"^(?:[0-9]|[1-3][0-9]|40)$",
43 message="Choose an integer from 0 through 40.",
44 )
45 ],
46 ),
47 "output_directory": LatchParameter(
48 display_name="Output directory",
49 description="Destination for reports and processed outputs.",
50 output=True,
51 ),
52 },
53 flow=[
54 Section(
55 "Inputs",
56 Text("Select samples and input reads."),
57 Params("reads"),
58 ),
59 Spoiler(
60 "Advanced quality settings",
61 Params("minimum_quality"),
62 ),
63 Section(
64 "Outputs",
65 Params("output_directory"),
66 ),
67 ],
69```
71Apply it:
73```python
74from dataclasses import dataclass
75from pathlib import Path
77from latch import small_task, workflow
78from latch.types import LatchDir, LatchFile, LatchOutputDir
81@dataclass
82class SampleRow:
83 sample_name: str
84 reads: LatchFile
87@small_task
88def run_qc(
89 reads: list[SampleRow],
90 output_directory: LatchOutputDir,
91 minimum_quality: int,
92) -> LatchDir:
93 local_output = Path("/root/rnaseq-qc")
94 local_output.mkdir(parents=True, exist_ok=True)
95 (local_output / "summary.txt").write_text(
96 f"samples={len(reads)}\nminimum_quality={minimum_quality}\n",
97 encoding="utf-8",
98 )
100 remote = output_directory.remote_path
101 if remote is None:
102 raise ValueError("output_directory must have a remote path")
103 return LatchDir(str(local_output), remote)
106@workflow(metadata)
107def rnaseq_qc(
108 reads: list[SampleRow],
109 output_directory: LatchOutputDir,
110 minimum_quality: int = 20,
111) -> LatchDir:
112 return run_qc(
113 reads=reads,
114 output_directory=output_directory,
115 minimum_quality=minimum_quality,
116 )
117```
119Metadata parameter keys must match the signature. The SDK adds default metadata
120for signature parameters omitted from the metadata object, and 2.77.1 silently
121drops unknown keys. Validate the keys before decoration so a misspelling does
122not silently replace a scientific label or validation rule.
124Although `about_page_path` is documented, SDK 2.77.1 can fail while serializing
125its `Path` value. Prefer `documentation=` and a descriptive workflow docstring
126until that defect is fixed.

Metadata

from latch.types.metadata import (
    LatchAuthor,
    LatchMetadata,
    LatchParameter,
    LatchRule,
    Params,
    Section,
    Spoiler,
    Text,
)

metadata = LatchMetadata(
    display_name="Bulk RNA-seq QC",
    author=LatchAuthor(
        name="Workflow Team",
        github="https://github.com/example",
    ),
    documentation="https://example.org/docs/rnaseq-qc",
    repository="https://github.com/example/rnaseq-qc",
    license="MIT",
    tags=["RNA-seq", "QC"],
    parameters={
        "reads": LatchParameter(
            display_name="Reads",
            description="FASTQ input files.",
            samplesheet=True,
        ),
        "minimum_quality": LatchParameter(
            display_name="Minimum quality",
            description="Minimum accepted Phred score.",
            rules=[
                LatchRule(
                    regex=r"^(?:[0-9]|[1-3][0-9]|40)$",
                    message="Choose an integer from 0 through 40.",
                )
            ],
        ),
        "output_directory": LatchParameter(
            display_name="Output directory",
            description="Destination for reports and processed outputs.",
            output=True,
        ),
    },
    flow=[
        Section(
            "Inputs",
            Text("Select samples and input reads."),
            Params("reads"),
        ),
        Spoiler(
            "Advanced quality settings",
            Params("minimum_quality"),
        ),
        Section(
            "Outputs",
            Params("output_directory"),
        ),
    ],
)

Apply it:

from dataclasses import dataclass
from pathlib import Path

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


@dataclass
class SampleRow:
    sample_name: str
    reads: LatchFile


@small_task
def run_qc(
    reads: list[SampleRow],
    output_directory: LatchOutputDir,
    minimum_quality: int,
) -> LatchDir:
    local_output = Path("/root/rnaseq-qc")
    local_output.mkdir(parents=True, exist_ok=True)
    (local_output / "summary.txt").write_text(
        f"samples={len(reads)}\nminimum_quality={minimum_quality}\n",
        encoding="utf-8",
    )

    remote = output_directory.remote_path
    if remote is None:
        raise ValueError("output_directory must have a remote path")
    return LatchDir(str(local_output), remote)


@workflow(metadata)
def rnaseq_qc(
    reads: list[SampleRow],
    output_directory: LatchOutputDir,
    minimum_quality: int = 20,
) -> LatchDir:
    return run_qc(
        reads=reads,
        output_directory=output_directory,
        minimum_quality=minimum_quality,
    )

Metadata parameter keys must match the signature. The SDK adds default metadata for signature parameters omitted from the metadata object, and 2.77.1 silently drops unknown keys. Validate the keys before decoration so a misspelling does not silently replace a scientific label or validation rule.

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

Raw lines 7 to 127
128## Flow Elements
130Current flow classes include:
132- `Section(title, *children)`
133- `Text(markdown)`
134- `Title(markdown_title)`
135- `Params(*parameter_names)`
136- `Spoiler(title, *children)`
137- `Fork(parameter_name, display_name, **branches)`
138- `ForkBranch(display_name, *children)`
140`flow` replaces the default top-to-bottom layout. Include every visible
141parameter intentionally.
143Use a `Fork` only with a matching string parameter in the workflow signature:
145```python
146from latch.types.metadata import Fork, ForkBranch, Params
148read_type_flow = Fork(
149 "read_type",
150 "Read layout",
151 paired=ForkBranch("Paired-end", Params("read1", "read2")),
152 single=ForkBranch("Single-end", Params("read1")),
154```

Flow Elements

Current flow classes include:

  • Section(title, *children)
  • Text(markdown)
  • Title(markdown_title)
  • Params(*parameter_names)
  • Spoiler(title, *children)
  • Fork(parameter_name, display_name, **branches)
  • ForkBranch(display_name, *children)

flow replaces the default top-to-bottom layout. Include every visible parameter intentionally.

Use a Fork only with a matching string parameter in the workflow signature:

from latch.types.metadata import Fork, ForkBranch, Params

read_type_flow = Fork(
    "read_type",
    "Read layout",
    paired=ForkBranch("Paired-end", Params("read1", "read2")),
    single=ForkBranch("Single-end", Params("read1")),
)
Raw lines 128 to 155
156## Parameter Guidance
158Use:
160- `display_name` and `description` for scientific meaning
161- `rules` for syntactic validation
162- `hidden` for rarely changed values, not security
163- `batch_table_column` for high-value bulk-run fields
164- `samplesheet=True` for dataclass-backed tabular inputs
165- `allowed_tables` to restrict Registry imports
166- `output=True` or `LatchOutputFile` / `LatchOutputDir` for new destinations
168Do not use form defaults to hide scientific assumptions. State reference
169genome, strandedness, units, and algorithm defaults explicitly.

Parameter Guidance

Use:

  • display_name and description for scientific meaning
  • rules for syntactic validation
  • hidden for rarely changed values, not security
  • batch_table_column for high-value bulk-run fields
  • samplesheet=True for dataclass-backed tabular inputs
  • allowed_tables to restrict Registry imports
  • output=True or LatchOutputFile / LatchOutputDir for new destinations

Do not use form defaults to hide scientific assumptions. State reference genome, strandedness, units, and algorithm defaults explicitly.

Raw lines 156 to 170
171## Launch Plans
173`LaunchPlan` creates a named set of defaults shown under Test Data in the
174Console:
176```python
177from latch.resources.launch_plan import LaunchPlan
178from latch.types import LatchFile, LatchOutputDir
180LaunchPlan(
181 rnaseq_qc,
182 "Small public example",
183 {
184 "reads": [
185 SampleRow(
186 sample_name="example",
187 reads=LatchFile("latch:///test-data/example.fastq.gz"),
188 )
189 ],
190 "minimum_quality": 20,
191 "output_directory": LatchOutputDir(
192 "latch:///test-results/rnaseq-qc"
193 ),
194 },
195 description="Small input for interface and integration testing.",
197```
199Constructor:
201```text
202LaunchPlan(
203 workflow,
204 name,
205 default_params,
206 *,
207 description=None,
209```
211Rules:
213- Define launch plans at module scope so registration discovers them.
214- Parameter keys and values must match the workflow.
215- A launch plan name cannot contain `.`.
216- Use small public or workspace-safe test data.
217- Avoid destinations shared by concurrent users.
218- Never put secret values in a launch plan.

Launch Plans

LaunchPlan creates a named set of defaults shown under Test Data in the Console:

from latch.resources.launch_plan import LaunchPlan
from latch.types import LatchFile, LatchOutputDir

LaunchPlan(
    rnaseq_qc,
    "Small public example",
    {
        "reads": [
            SampleRow(
                sample_name="example",
                reads=LatchFile("latch:///test-data/example.fastq.gz"),
            )
        ],
        "minimum_quality": 20,
        "output_directory": LatchOutputDir(
            "latch:///test-results/rnaseq-qc"
        ),
    },
    description="Small input for interface and integration testing.",
)

Constructor:

LaunchPlan(
    workflow,
    name,
    default_params,
    *,
    description=None,
)

Rules:

  • Define launch plans at module scope so registration discovers them.
  • Parameter keys and values must match the workflow.
  • A launch plan name cannot contain ..
  • Use small public or workspace-safe test data.
  • Avoid destinations shared by concurrent users.
  • Never put secret values in a launch plan.
Raw lines 171 to 219
220## Execution Messages
222Use messages for concise user-facing signals, not raw logs:
224```python
225from latch import message, small_task
228@small_task
229def validate_columns(columns: list[str]) -> bool:
230 required = {"sample_id", "condition"}
231 missing = sorted(required.difference(columns))
233 if missing:
234 message(
235 typ="error",
236 data={
237 "title": "Missing required columns",
238 "body": ", ".join(missing),
239 },
240 )
241 raise ValueError(f"missing columns: {missing}")
243 message(
244 typ="info",
245 data={
246 "title": "Input validated",
247 "body": "Required sample columns are present.",
248 },
249 )
250 return True
251```
253Valid message types are `info`, `warning`, and `error`. The data dictionary
254requires `title` and `body`. Since 2.76.10, message-delivery errors do not
255terminate the task. An `error` message alone is not task failure: raise an
256exception after reporting invalid scientific inputs, as above. Delivery is
257best-effort; do not treat a successful `message()` return as delivery confirmation.
259Do not put secrets, signed URLs, patient identifiers, or excessive raw data in
260messages.

Execution Messages

Use messages for concise user-facing signals, not raw logs:

from latch import message, small_task


@small_task
def validate_columns(columns: list[str]) -> bool:
    required = {"sample_id", "condition"}
    missing = sorted(required.difference(columns))

    if missing:
        message(
            typ="error",
            data={
                "title": "Missing required columns",
                "body": ", ".join(missing),
            },
        )
        raise ValueError(f"missing columns: {missing}")

    message(
        typ="info",
        data={
            "title": "Input validated",
            "body": "Required sample columns are present.",
        },
    )
    return True

Valid message types are info, warning, and error. The data dictionary requires title and body. Since 2.76.10, message-delivery errors do not terminate the task. An error message alone is not task failure: raise an exception after reporting invalid scientific inputs, as above. Delivery is best-effort; do not treat a successful message() return as delivery confirmation.

Do not put secrets, signed URLs, patient identifiers, or excessive raw data in messages.

Raw lines 220 to 261
262## Results Page
264Publish the most useful result locations:
266```python
267from latch import small_task
268from latch.executions import add_execution_results
269from latch.types import LatchOutputDir
272@small_task
273def publish_results(output_directory: LatchOutputDir) -> None:
274 remote = output_directory.remote_path
275 if remote is None:
276 raise ValueError("output directory must have a remote path")
278 add_execution_results(
279 [
280 remote,
281 f"{remote.rstrip('/')}/multiqc_report.html",
282 ]
283 )
284```
286For Nextflow parameters, `NextflowParameter.results_paths` can publish selected
287subpaths under an output directory.
289Result links improve discoverability; they do not upload missing outputs or
290verify scientific validity.

Results Page

Publish the most useful result locations:

from latch import small_task
from latch.executions import add_execution_results
from latch.types import LatchOutputDir


@small_task
def publish_results(output_directory: LatchOutputDir) -> None:
    remote = output_directory.remote_path
    if remote is None:
        raise ValueError("output directory must have a remote path")

    add_execution_results(
        [
            remote,
            f"{remote.rstrip('/')}/multiqc_report.html",
        ]
    )

For Nextflow parameters, NextflowParameter.results_paths can publish selected subpaths under an output directory.

Result links improve discoverability; they do not upload missing outputs or verify scientific validity.

Raw lines 262 to 291
292## Automations
294Automations are configured in the Console and can trigger from:
296- A child added at any depth beneath a watched Latch Data directory
297- A recurring time interval

Automations

Automations are configured in the Console and can trigger from:

  • A child added at any depth beneath a watched Latch Data directory
  • A recurring time interval
Raw lines 292 to 298
299### Data-added workflow contract
301The automation workflow must have exactly one parameter:
303```python
304from latch import small_task, workflow
305from latch.types import LatchDir
308@small_task
309def process_new_data(input_directory: LatchDir) -> None:
310 children = list(input_directory.iterdir())
311 print(f"observed {len(children)} children")
314@workflow
315def automation_workflow(input_directory: LatchDir) -> None:
316 process_new_data(input_directory=input_directory)
317```
319The trigger fires after the configured follow-up update period following the
320last addition. Modification or deletion alone does not trigger it.

Data-added workflow contract

The automation workflow must have exactly one parameter:

from latch import small_task, workflow
from latch.types import LatchDir


@small_task
def process_new_data(input_directory: LatchDir) -> None:
    children = list(input_directory.iterdir())
    print(f"observed {len(children)} children")


@workflow
def automation_workflow(input_directory: LatchDir) -> None:
    process_new_data(input_directory=input_directory)

The trigger fires after the configured follow-up update period following the last addition. Modification or deletion alone does not trigger it.

Raw lines 299 to 321
322### Interval workflow contract
324The workflow must have no parameters:
326```python
327from latch import small_task, workflow
330@small_task
331def run_scheduled_check() -> None:
332 print("scheduled check started")
335@workflow
336def scheduled_workflow() -> None:
337 run_scheduled_check()
338```

Interval workflow contract

The workflow must have no parameters:

from latch import small_task, workflow


@small_task
def run_scheduled_check() -> None:
    print("scheduled check started")


@workflow
def scheduled_workflow() -> None:
    run_scheduled_check()
Raw lines 322 to 339
340### Automation safety
342- Automations can create recurring paid compute. Review frequency, map width,
343 and worst-case cost before activation.
344- Make processing idempotent. A Registry record or durable marker can prevent
345 duplicate work.
346- Do not hard-code secrets. Retrieve them inside tasks with `get_secret()`.
347- Test the workflow manually before enabling the trigger.
348- Start with a long enough debounce/update period to avoid launch storms.
349- Disable the automation while changing its workflow contract.
350- Ensure the watched directory does not include the automation's own outputs.

Automation safety

  • Automations can create recurring paid compute. Review frequency, map width, and worst-case cost before activation.
  • Make processing idempotent. A Registry record or durable marker can prevent duplicate work.
  • Do not hard-code secrets. Retrieve them inside tasks with get_secret().
  • Test the workflow manually before enabling the trigger.
  • Start with a long enough debounce/update period to avoid launch storms.
  • Disable the automation while changing its workflow contract.
  • Ensure the watched directory does not include the automation's own outputs.
Raw lines 340 to 351
352## Official Sources
354- UI definition: https://wiki.latch.bio/workflows/sdk/ui/latch-metadata
355- Launch plans: https://wiki.latch.bio/workflows/sdk/ui/launch-plans
356- Messages: https://wiki.latch.bio/workflows/sdk/ui/messages
357- Results: https://wiki.latch.bio/workflows/sdk/ui/results
358- Automation overview: https://wiki.latch.bio/workflows/sdk/automation/overview
359- Data-added trigger: https://wiki.latch.bio/workflows/sdk/automation/example-data-addition
360- Interval trigger: https://wiki.latch.bio/workflows/sdk/automation/example-interval

Official Sources

  • UI definition: https://wiki.latch.bio/workflows/sdk/ui/latch-metadata
  • Launch plans: https://wiki.latch.bio/workflows/sdk/ui/launch-plans
  • Messages: https://wiki.latch.bio/workflows/sdk/ui/messages
  • Results: https://wiki.latch.bio/workflows/sdk/ui/results
  • Automation overview: https://wiki.latch.bio/workflows/sdk/automation/overview
  • Data-added trigger: https://wiki.latch.bio/workflows/sdk/automation/example-data-addition
  • Interval trigger: https://wiki.latch.bio/workflows/sdk/automation/example-interval
Raw lines 352 to 360