Sandbox — LSD is in active testing. All systems operational·payments are test-mode (no real charges).
Overview Use Cases News Docs Tool Store Support Community Get LSD
All docs
Docs/ Building Tools/ Parameters vs ports
Building Tools

Parameters vs ports

The one classification rule behind every LSD node — how a step's parameters split into input ports, injected context, and form fields, all checked at import.

TL;DRLSD reads a step's signature and sorts each parameter into one of four kinds by its annotation. A Table is an input port; a Source is an injected reader (load only); a StepContext is injected capabilities; anything else is a typed form field. The rules are enforced at import time, so mistakes surface when the package loads — never mid-click.

Every @tool.* step is a plain function. LSD reads its signature and sorts each parameter into one of four kinds — by its annotation, not its name — to decide what becomes a wire, what gets injected, and what becomes a form field.

Usage#

Python
from lsdtools import Tool, Table, StepContext

tool = Tool("demo")

@tool.shape
def clip(t: Table, lo: float, hi: float = 1.0, ctx: StepContext = None) -> Table:
    ctx.progress(0.5, "clipping")
    return t.filter((t["v"] >= lo) & (t["v"] <= hi))
# → t: input PORT · lo: required FIELD · hi: optional FIELD · ctx: INJECTED

The classification rule#

Annotation Becomes Notes
Table / pa.Table an input port wired from an upstream node; a load step may have none
Source an injected reader @tool.load only; bound to the file/query the user picks
StepContext injected capabilities progress, cancel, scratch, emit; excluded from the cache key
anything else (int, str, bool, float, …) a typed parameter a form field; no default ⇒ required, default ⇒ optional

The annotation is the whole signal. Rename t to left and it is still a port; drop the Table annotation off it and it becomes a plain field.

Ports: Table and pa.Table#

A parameter annotated Table (or a raw pyarrow.Table) is an input port — an edge you wire from an upstream node on the canvas, or pass positionally in Python:

Python
@tool.shape
def merge(a: Table, b: Table) -> Table: # two ports
    return a.join(b, on="id")

merge(load_a(), load_b()) # wire both ports

A load step is the source of data, so it may have no ports — see Load steps. Shape and deliver steps have one or more.

Injected: StepContext (and why not Context)#

A StepContext parameter is filled in by the engine at run time — never by the user, and never part of the cache key. Use it for ctx.progress(...), ctx.cancelled, ctx.set_output(...), ctx.emit(...), and ctx.output_dir / ctx.scratch_dir.

A Context (the richer object exposing live project state) is rejected on a step at import:

Python
@tool.shape
def bad(t: Table, ctx: Context) -> Table: # TypeError at import!
    ...
# → "a step must not read Context … use StepContext for progress/cancel/scratch"

A step must stay pure so the cache can trust it; Context is for commands, actions and views, not steps.

Everything else is a parameter#

Any other annotation is a typed parameter that renders as a form field on the node. The type drives the widget (int / float → number, bool → checkbox, str → text). A parameter with no default is required; a parameter with a default is optional:

Python
@tool.load
def rows(n: int, seed: int = 0) -> Table: # n required, seed optional
    ...

To go beyond a plain auto-generated field — a form with its own buttons, or a custom editor — a tool contributes a view: a View of param() fields and @action buttons the desktop docks beside the node. A domain view can also ride on a single parameter via Annotated[...] — see the file loader.

Checked at import time#

All of this is validated when the module is imported (when the package loads), so a mistake never waits until someone clicks the node. The three guards:

  • a Table port on @tool.load"a load step takes no Table input ports";
  • a Source on @tool.shape / @tool.deliver"a Source parameter is only valid on @tool.load";
  • a Context on any step → "a step must not read Context … use StepContext".

Fix the signature and the whole package loads clean.

LearnThe Tool object · Tables & data flow · Load steps

APITool · Context · Source

ExamplesYour first tool · Live node metrics

Frequently asked questions

Why is my Source parameter rejected on a shape step?

A Source produces data, so it is only valid on @tool.load. On a shape or deliver step it raises a TypeError at import time. Wire a Table input port from an upstream load node instead.

Why can't I annotate a step parameter Context?

A step must stay pure so the cache can trust it. A Context exposes live project state the cache can't see, so it is rejected at import. Use StepContext for progress, cancel, scratch and emit; use Context only in commands, actions and views.

By LSD Team · Last updated Jul 18, 2026 Ask on Discord View as Markdown