Skip to content

Blocks & Roles

Block Hierarchy

The composition algebra is sealed — only 5 concrete Block types exist:

  • AtomicBlock — leaf node (domain packages subclass this)
  • StackComposition (>>) — sequential, validates token overlap
  • ParallelComposition (|) — independent, no type validation
  • FeedbackLoop (.feedback()) — backward within evaluation
  • TemporalLoop (.loop()) — forward across temporal boundaries, enforces COVARIANT only

GDS Roles

Block roles subclass AtomicBlock and add interface constraints:

Role forward_in forward_out backward_in backward_out Purpose
BoundaryAction MUST be () any any any Exogenous observation
Policy any any any any Decision logic
ControlAction any any any any Output observable y = C(x, d)
Mechanism any any MUST be () MUST be () State update

Violating the MUST constraints raises GDSCompositionError immediately at construction time.

BoundaryAction

Models exogenous observations — the system boundary. Has no forward inputs.

from gds import BoundaryAction, interface

sensor = BoundaryAction(
    name="Temperature Sensor",
    interface=interface(forward_out=["Temperature"]),
)

Policy

Core decision logic — maps observations to actions. No port restrictions.

from gds import Policy, interface

controller = Policy(
    name="PID Controller",
    interface=interface(
        forward_in=["Temperature", "Setpoint"],
        forward_out=["Heater Command"],
        backward_in=["Energy Cost"],
    ),
    params_used=["Kp", "Ki", "Kd", "setpoint"],
)

ControlAction

Output observable — the system's observable output y = C(x, d) for composition with other systems. From the plant (inside) perspective, this is what the system emits. From the controller (outside) perspective at a >> boundary, it becomes a control action on the next system.

from gds import ControlAction, interface

plant = ControlAction(
    name="Room Plant",
    interface=interface(
        forward_in=["Heater Command"],
        forward_out=["Room State"],
        backward_out=["Energy Cost"],
    ),
)

Mechanism

State update — the only blocks that write to state. Cannot have backward ports.

from gds import Mechanism, interface

update = Mechanism(
    name="Update Room",
    interface=interface(forward_in=["Room State"]),
    updates=[("Room", "temperature"), ("Room", "energy_consumed")],
)

Composition Operators

Sequential (>>)

pipeline = sensor >> controller >> plant

Auto-wires by token overlap between forward_out and forward_in ports. Raises GDSTypeError if no overlap.

Parallel (|)

updates = update_s | update_i | update_r

Independent blocks — no type validation.

Feedback (.feedback())

from gds import Wiring
from gds.ir.models import FlowDirection

system = pipeline.feedback([
    Wiring(
        source_block="Room Plant", source_port="Energy Cost",
        target_block="PID Controller", target_port="Energy Cost",
        direction=FlowDirection.CONTRAVARIANT,
    )
])

Within-evaluation backward flow. Requires CONTRAVARIANT direction.

Temporal Loop (.loop())

system = pipeline.loop([
    Wiring(
        source_block="Update Prey", source_port="Population",
        target_block="Observe", target_port="Population",
        direction=FlowDirection.COVARIANT,
    )
])

Cross-boundary forward flow. COVARIANT is mandatory -- CONTRAVARIANT raises GDSTypeError.

Tagged Mixin

All blocks inherit from Tagged, providing semantic annotations:

sensor = BoundaryAction(
    name="Sensor",
    interface=interface(forward_out=["Temperature"]),
    tags={"domain": "Observation"},
)

sensor.has_tag("domain")       # True
sensor.get_tag("domain")       # "Observation"

Tags are inert — stripped at compile time, never affect verification or composition.