Workflow mapping shapes and best practices

Mappings describe data flow using dataclass structure and .quote markers. A quote is a typed marker while defining a workflow; actions receive its materialized value. Context tasks follow the same mapping rules.

Choose the smallest mapping

Shape Best use Input behavior Output behavior
args_mapping=record.quote Transfer a complete cohesive record Preserve a concrete record or construct it from a scope Publish the record, or its direct fields into an existing scope
Args(source.quote, literal) Select independent inputs Resolve quotes; preserve ordinary literal references Publish only quoted destinations
Args(Options(source.quote)) Compose nested inputs Rebuild dataclass templates recursively Follow nested fields to explicit targets
Args(options.quote) Reuse a complete nested record Resolve the nested whole-record quote Publish the nested record at its quoted destination
Args(source.field("name", str).quote) Read a declared field Read-only projection retaining the root dependency Invalid as an output target

From direct to nested data flow

import asyncio
import logging
from dataclasses import dataclass
from datetime import date

from lclang.lang import define_frame
from lclang.workflow import (
    ExecutionStatusManager, TaskContext, WorkflowExecutionContext,
    define_task, define_variable, define_workflow,
)


@dataclass
class Options:
    encoding: str = "utf-8"


@dataclass
class Request:
    options: Options
    labels: list[str]


async def echo(
    context: TaskContext, args: Request, status_mgr: ExecutionStatusManager,
) -> Request:
    return args


async def main() -> None:
    source = define_variable[Request]("source")
    target = define_variable[Request]("target")
    encoding = define_variable[str]("encoding", default="ascii")
    options = define_variable[Options]("options")
    published = define_variable[str]("published")
    record = Request(Options("utf-16"), ["shared"])
    direct = define_task(
        "direct", "Transfer a complete record", task_action=echo,
        args_mapping=source.quote, outputs_mapping=target.quote,
    )
    nested = define_task(
        "nested", "Compose nested fields", task_action=echo,
        args_mapping=Request(Options(encoding.quote), record.labels),
        outputs_mapping=Request(Options(published.quote), []),
    )
    whole = define_task(
        "whole", "Reuse a nested record", task_action=echo,
        args_mapping=Request(options.quote, record.labels),
    )
    projected = define_task(
        "projected", "Select a field", task_action=echo,
        args_mapping=Request(
            Options(source.field("options", Options).field("encoding", str).quote),
            record.labels,
        ),
    )
    async with define_frame(preset={"source": record, "options": record.options}) as frame:
        context = WorkflowExecutionContext(
            False, date(2026, 9, 23), False, logging.getLogger("mapping-example"), frame,
        )
        await define_workflow("Direct", direct).execute(context)
        first = await define_workflow("Nested", nested).execute(context)
        second = await define_workflow("Whole", whole).execute(context)
        third = await define_workflow("Projected", projected).execute(context)
        assert await frame.get("target") is record
        assert first.task_outputs["nested"].options.encoding == "ascii"
        assert first.task_outputs["nested"].labels is record.labels
        assert await frame.get("published") == "ascii"
        assert not frame.has("labels")
        assert second.task_outputs["whole"].options is record.options
        assert third.task_outputs["projected"].options.encoding == "utf-16"


asyncio.run(main())

The direct quote preserves the whole record. The nested template builds a new Options from the variable default while preserving the ordinary shared list. Only the quoted encoding output is published; the literal list is unmapped. The nested whole-record quote preserves the supplied Options instance. Finally, the chained projection reads the original record without defining new variables.

Use separate top-level field quotes when a task reads independent variables; put the same markers inside nested dataclass templates when grouping those inputs. Use a nested whole-record quote when the group already has one owner. Containers are leaves: markers hidden inside lists or dictionaries are not recursively resolved. Put quotes in dataclass fields instead.

Defaults, publication and ownership

Effective Frame/configuration values outrank variable defaults. Variable factories are lazy and shared per execution. A missing field quote can finally use the constructor field's default; an existing definition that fails never falls back. Scope-to-record conversion retains constructor defaults. Use factories for independent mutable defaults.

Whole-record mappings must exactly match the callable's dataclass annotation, including generic arguments. Concrete records keep identity. Template records are rebuilt without mutating the template; ordinary non-record literal values keep their references. Cyclic templates are rejected, but acyclic shared subtemplates may be reused.

Whole-record output publication is shallow. An existing target scope receives direct fields atomically; otherwise it receives the record itself. Nested dataclasses remain values, not recursively flattened bindings. Action outputs target the shared workflow Frame; context outputs stay within the owning task subtree. task_outputs retains the complete action record even when only a subset is published.

Output names must be unique after normalization, projections remain input-only, and failed validation publishes no partial set. Prefer explicit destinations to mutation of input objects. See variable types and boxes for opt-in conversion at individual quote boundaries.

See errors and diagnostic codes for exception fields, specific codes, cause chains, and executable troubleshooting examples. Ordinary library validation uses LclValidationError; native callback and operation failures retain their original exception in __cause__.