Skip to content

Function package development

Current Component boundary

The current @px.component responsibility ends at process-local companion metadata and port shapes derived from Python annotations. Existing V1 presentation and settings wire remains compatible, and the production Flow reader presents a saved exact Component target as unsupported. The installer has no safe inactive quarantine, so production third-party Component install admission and activation remain closed. Exact ProjectScope data-path isolation is implemented, but Project authorization and execution-trust closure are not. The successor V2 Canvas mounts only the host-owned component.declarative@2; it does not mount package UI. Settings are declared through component-declared-settings@1, at most eight per declaration, and the host draws each control from its own registry. A rich surface belongs to component-rich-surface-declaration@1, the RFC-016 successor, and this guide does not restate its entry or message shapes.

This guide covers reusable Functions shipped in versioned packages. PXFLOW authors place those Functions explicitly as Function Reference Nodes in a Flow. A Function definition, a Node placement, and a runtime Task are different concepts. @px.function registers the definition; it does not place or execute it.

To expose an installed custom Function through a normal Python import, generated type stubs, PXFLOW, and LLM discovery, see Distribute and use custom Functions.

Register reusable Component companion metadata with @px.component; see Component authoring and container boundary. Existing V1 documents and internal fixtures preserve an exact Component target, which the production reader presents as unsupported. The V2 Canvas projection is host-owned component.declarative@2; package renderer, modal, and settings schema are not V2 contributions.

Keep SDK ownership visible in every example

Use from pipelinexlab import px as the single core import. Current root types and decorators use px.*, including px.Flow, px.Report, px.Results, px.Record, px.Settings, px.input, px.result, px.setting, @px.function, and @px.component. px.Table, px.field, and the @px.record decorator are planned contract_only symbols, not current facade calls. Actions use the object that owns them: flow.* changes one Flow, report.* and section.* author one Report, connection.* maps one Report–Flow connection, and client.* starts Runs through one px.Client.

Inside its own defining module, a local Function or Component may use its ordinary Python name. After the authorization and trust closures, consumer code can keep an admitted target under its package namespace, such as midas_components.model_viewer, before passing it to flow.node(...). This makes core SDK calls, project-local code, and admitted capabilities distinguishable at a glance. The lowercase px.client() helper is reserved for PipelineXLab-hosted app surfaces; ordinary Python uses px.Client(workspace="...", project="...").

Create your first package

Use CPython 3.12 and install the SDK and developer CLI in a virtual environment.

bash
python3.12 -m venv .venv
source .venv/bin/activate
python -m pip install pipelinexlab
pxlab package init structural-checks --package-key structural_checks
cd structural-checks

Keep reusable Functions in an approved package module. Put each Flow in its own .py module and export one top-level flow object by convention. Do not create a separate port manifest or YAML Flow file.

After writing the first Function and Flow, follow this sequence:

bash
python -m pytest
pxlab package check .
pxlab flow materialize . --flow girder_review --output dist/girder_review.pxflow
pxlab studio open . --flow girder_review

package check reads declarations without executing Function bodies. flow materialize writes the direct deterministic .pxflow object without a manifest or YAML conversion. studio open opens the same declaration in SDK-linked mode.

A quickstart Flow with two public inputs, one utilization_check Function Reference Node, and one public result

The Flow boundary lists the two inputs and one result separately from the single executable Canvas Node, keeping the SDK declaration and Flow aligned one-to-one.

For a supported Function package, make it available in a development workspace and then publish a reviewed release:

bash
pxlab package install . --workspace <workspace_key> --development
pxlab package build . --output dist/release
pxlab package publish dist/release --channel stable

Publish shows Function version changes, port compatibility, capabilities, supported runtimes, and verification results before the user confirms the upload. The Component decorator-to-container compiler portion of this workflow remains contract_only. Internal V1 reader/fixture probes consume a separately prepared manifest, one declaration, and an executor container, but that path is not production third-party install admission. Production Component install and activation remain blocked by the authorization and trust closure above.

Define the shared contract once

SDK, Studio, MCP, and Runtime read one canonical Function descriptor; the planned V2 host renderer consumes admitted Component declarations.

Each consumer reads the same key, type, shape, unit, description, policy, and capability metadata. Do not recreate those definitions in UI, MCP, or package-specific formats.

The current public syntax keeps the ordinary Python type in parameter annotations and px.Results annotated fields. Port and settings markers are current; the Record field marker and the typed Table remain planned:

PurposeStatusSyntax
Inputcurrentspan: float
Named resultcurrentutilization: float in a px.Results subclass
Collectioncurrentlist[T]
Port metadatacurrentpx.input(...), px.result(...) in the default-value position
Component settingscurrentpx.setting(...) on a px.Settings subclass
Record schemacurrentannotated fields on a px.Record subclass
Record field metadatacontract_only · plannedpx.field(...)
Typed Tablecontract_only · plannedpx.Table[LoadCase]

Write the type once. PipelineXLab-specific types are reserved for shapes that standard Python does not provide, such as tables, artifacts, and evidence.

Write a minimal Function

python
from pipelinexlab import px

class GirderResults(px.Results):
    utilization: float


@px.function(
    key="girder_resistance",
    version="1.0.0",
    description="Calculates girder utilization for the supplied span.",
)
def girder_resistance(span: float) -> GirderResults:
    return GirderResults(utilization=calculate_utilization(span))

Every public Function has an explicit semantic version and a useful description.

Know where each field comes from

InformationSingle source
Function keyExplicit stable @px.function(key=...) value
Function version and description@px.function(...)
Input key and typePlain Function parameter and its Python annotation
Result key and typeNamed px.Results annotated field
Unit and descriptionpx.input(...) or px.result(...) in the default-value position
Constraint and connectionNo current target marker
RequiredAbsence of a plain Python parameter default
DefaultPlain Python parameter default
NullableWhether the type includes None

Authors use ordinary Python types and do not repeat the type inside a marker. Do not repeat required/default/nullability in a second metadata list, and do not parse docstrings to reconstruct the public contract.

Use closed types, explicit shapes, and units

  • Distinguish scalar, record, list, table, and object-reference shapes.
  • Use named result fields instead of tuples, positions, or output0.
  • Use closed records and tables with stable lower_snake_case field keys.
  • Give each engineering number a canonical unit; use "1" for dimensionless values.
  • Declare a domain row key when rows must retain identity.
  • Keep large tables and binary results behind bounded object references with previews.

For multiple results, every named field is an independent result port. AI and future Component presentation select the exact field; declaration order is never a default.

Control external discovery

Input and result ports default to connection=True. Set connection=False only for internal values that should stay outside a planned host-owned Component surface, AI, or cross-Flow discovery.

This setting does not control Project ACLs, package installation, result-resource access, filesystem permission, network access, or secret access. Explicit connections within the same Flow remain available.

Declare execution behavior and capability

This example is contract_only and is not executable with the current SDK: px.policy(...) and px.capabilities(...) are planned symbols the facade does not export. px.input(...) in the default-value position is current.

python
@px.function(
    key="external_section_check",
    version="1.0.0",
    description="Checks a section with an external design service.",
    policy=px.policy(
        deterministic=False,
        parallel_safe=False,
        side_effect="external",
        cache="none",
        retry="never",
        timeout_seconds=60,
    ),
    capabilities=px.capabilities(
        files="none",
        network=("api.example.com",),
        secrets=("structural_api",),
        gpu=False,
    ),
)
def external_section_check(
    section: Section = px.input(description="Section to check."),
) -> SectionCheckResults:
    ...

Declare the narrowest capability ceiling. Runtime combines it with Project policy, user approval, dependency order, and quota. parallel_safe=True permits parallelism but does not force it. Live is an execution trigger, not a worker-count setting.

Build a Flow from Functions

A Flow is a module-level declarative object, not a decorated function. One .py Flow module exports one flow object by convention.

python
flow = px.Flow(
    "bridge_review",
    label="Bridge review",
    description="Combines loads and checks resistance.",
)
span = flow.input("span", float, unit="m", description="Clear span.")
load_cases = flow.input(
    "load_cases",
    list[float],
    description="Load cases.",
)

combinations = flow.node("load_combinations", combine_loads)
resistance = flow.node("resistance_check", girder_resistance)

flow.connect(load_cases, combinations.inputs.load_cases)
flow.connect(span, resistance.inputs.span)
flow.connect(
    combinations.results.design_cases,
    resistance.inputs.design_cases,
)
flow.result(
    "governing_utilization",
    resistance.results.governing_utilization,
)
  • Only flow.node(node_key, target, *, settings={...}) creates a durable Node placement; Function targets render on the Canvas and current Component targets appear as unsupported.
  • Every executable Python Node target uses @px.function with an explicit stable key. Undecorated functions are implementation helpers.
  • An @px.function target becomes a Function Reference Node managed in Definitions; changing it opens an impact review for every placement.
  • An existing V1 artifact can store an exact seven-field ComponentRef; the production reader presents it as unsupported. New production third-party placement is blocked. Successor V2 rendering is host-owned component.declarative@2 only.
  • Every value relationship uses flow.connect(source, node.inputs.port).
  • Every public result uses flow.result("result_key", source).
  • flow.group(...) changes same-Flow presentation membership only.
  • An imported exported Flow object uses flow.subflow(...) and opens read-only in the caller.

Function and Component decorators register definitions and metadata only. They never place or run a Node and never own nodeKey, layout, wiring, or a Run trigger. Complex Flows remain top-level declarative compositions rather than large decorated functions.

Publish the boundary used by a Report

flow.input(...) and flow.result(...) define the saved Flow's public boundary. px.Report and Report Workbench use those public ports when an author connects a .pxreport document to the Flow.

Dotted lines are Report-to-Flow mappings owned by `.pxreport`; the solid line is the internal Flow connection owned by `.pxflow` and `flow.connect(...)`. Creating a Report connection does not change the Flow Python source.

The Report owns both mappings in .pxreport. In Python, connection = report.connect_flow(...) returns a Connected Flow handle. Its .bind() method connects a Report value to a Flow input, and .show() connects a Flow result to a Report position. flow.connect(...) continues to describe only a connection inside the Flow, so adding or moving a Report mapping leaves this Flow module unchanged. See Author a Flow and Report with Python.

Design for Table inputs and batch Runs

A px.Table[RecordType] input receives one typed Table value in one Flow Run. If a caller needs several scalar input sets, it submits explicit Runs against the same saved Flow revision. A document table does not create hidden row Runs or Canvas Nodes.

An N×1 value is not compatible with a scalar port. Use a declared list or Table port for one Run, or create an explicit Batch Run with complete scalar input sets. Do not infer execution count from the selected shape or visual position.

Version the public meaning

ChangeTypical Function SemVer
Description correction with unchanged meaningpatch
Optional input or compatible named result additionminor, after compatibility tests
Required input additionmajor
Port key removal or renamemajor
Type, shape, unit, or nullability changemajor
Calculation standard or formula changeminor or major according to semantic impact
Performance improvement within the same numerical contractpatch after regression tests

Package version, Function version, SDK contract version, and Runtime compatibility range remain separate. A package rebuild does not silently change Function meaning.

Test the whole user path

Before publishing, verify:

  1. Function unit, boundary, failure, cancellation, and numerical-tolerance cases.
  2. Descriptor keys, types, units, descriptions, defaults, policy, and capabilities.
  3. SDK declaration → direct object → deterministic .pxflow → load round-trip.
  4. Native, WASM, and Python validation parity.
  5. PXFLOW canvas and SDK-linked source-safe editing parity.
  6. Scalar/list/Table shape validation and explicit batch Run; V2 Component bindings stay within the host-owned declarative projection and accept settings declared through component-declared-settings@1.
  7. MCP search, describe, plan, run, result, error, and permission behavior.
  8. Worker isolation, resource limits, network/file/secret policy, timeout, and cancellation.
  9. Large-result bounds, partial row failure, recovery, and representative performance budgets.
  10. Signed package provenance, license notices, and rollback information.

A release is available in the catalog only when every supported consumer uses the same Flow, Node, port, Function version, Flow revision, diagnostic, and Run provenance.

Continue