Repository navigation
reference workflow
lclang.workflow follows the same separation as Modules and Frames. A workflow
is a strong reusable format: an immutable tree of valid task IDs, typed
dataclass boundaries, explicit variable flows, ordered contexts, and ordered
children. Workflow.execute supplies flexibility only inside that format:
actions are ordinary async Python, contexts may own arbitrary dismissible
resources, and status managers may add run-specific detail.
The definition never owns execution state. One execution borrows a shared
Frame, derives short-lived task Frames, records argument and output values,
and creates a fresh status tree. Reusing a Workflow is therefore analogous to
reusing a Module; each call is analogous to creating a new per-run Frame.
Create variables through the subscribed factory:
import lclang.workflow as wf
source = wf.define_variable[int]("source", "Required source value")
secret = wf.define_variable[str]("service.token", "Service token", is_masked=True)
result = wf.define_variable[float]("result")TaskVar.quote is statically typed as the represented value but returns the
variable marker at runtime. It can therefore occupy a normally typed dataclass
field in a definition mapping. Argument fields containing a marker are resolved
from the current task Frame. Other fields retain their literal value, including
values supplied by dataclass defaults.
Output publication is opt-in. With no output mapping, nothing is published.
Within a mapping, only fields containing TaskVar.quote are copied from the
returned dataclass into a Frame; other fields are retained in task_outputs
but remain unmapped. Masked variables use the same exact-name redaction model
as Modules, Presets, configuration, CLI bindings, and Frame mixins.
An action has the exact async form:
async def action(
context: wf.TaskContext,
args: Args,
status_mgr: wf.ExecutionStatusManager,
) -> Outputs:
...Args and Outputs are dataclasses with plain annotated fields. lclang checks
the callable boundary and dataclass shape but deliberately does not impose
runtime field validation. Application code remains responsible for semantic
types received from external systems.
Each TaskNode may have ordered context tasks and ordered child tasks. Context factories accept the same three arguments and return an async context manager. They enter left-to-right before the action and exit right-to-left after all children. Their mapped resources enter only the current task Frame. Every task Frame is derived directly from the shared execution Frame, so a context resource never leaks to another task. Mapped action outputs enter the shared Frame and are visible to later parent-first, depth-first tasks.
Each derived task-node Frame contains __task_id_branch__, the dot-connected
root-to-node ID path. A context-task lifecycle log appends its own ID, but its
shared task Frame continues to expose the owning task-node branch.
FailureCoveringContextTask is the standard template for recovery contexts.
Subclasses implement acquire, handle_exception, and optionally release.
Successful handling and cleanup suppress the ordinary exception and mark the
context FAILURE_COVERED. Covered failures remain visible and still stop later
siblings; covering means the failure was wrapped up, not that execution can be
resumed safely.
await workflow.execute(context) returns WorkflowExecutionResult with the
finalized execution_status, the borrowed execution_frame, and dictionaries
of successfully materialized action arguments and returned outputs. A key is
added to task_args after argument construction and to task_outputs after a
valid action return. Structural, unreached, and failed stages are absent.
Raised exceptions become WorkflowException under the shared Frame name
__exception__. The value retains the ordinary exception and originating
task or context-task ID. Explicit FAILURE, ERROR, or FAILURE_COVERED
statuses also stop execution and receive a deterministic synthetic exception.
All unexecuted declared branches appear as SKIPPED.
Severity is ERROR, FAILURE, FAILURE_COVERED, then PENDING. Clean work
retains the existing success/all-skipped aggregation rules. Process-control
BaseException values unwind task contexts and Frames, then propagate.
workflow.to_lines() renders the definition without executing it. Task and
context lines show IDs, titles, argument flows (field <- $variable), output
flows (field -> $variable), literals, defaults, and unmapped fields. Context
entry lines nest above child tasks and matching exit lines appear below them in
reverse order.
workflow.to_cli(name, summary, preset=None) infers external variables by the
same scope-aware traversal. A context output is visible only to later contexts
and the action in its task; an action output is visible globally after that
action. Variables first read without a visible assignment become required CLI
parameters. Intermediate variables are omitted and cannot be directly
overridden. Missing descriptions render as NO HELP MESSAGE PROVIDED.
The generated command logs lifecycle records around every reached task and
context task. task start is INFO and appends (dryrun) in dry-run mode. An
exception produces one task error record with the original traceback, then
finalization still produces task complete. Branches use the injected
dot-connected path. Completion severity follows the task status.
Verbose mode adds one aligned, sorted args mapping DEBUG record after
materialization and one outputs mapping record after completion when an output
exists. Runtime types remain visible for masked values. Sources and targets are
labelled as LCL variables, defaults, literals, or unused outputs.
The finalized workflow tree is one severity-aware multi-line record headed
workflow complete: [command.path] STATUS: and includes the existing tree root.
Exit codes are success 0, failure 1, error 2, and covered failure 3.
lclang.cli.scan_commands(module, name, description) imports a package and its
submodules deterministically, collects public module-level Command objects,
deduplicates re-exports by identity, and rejects distinct duplicate names.
Published from ac045a6487d6. Documentation is maintained and tested in the source repository.