Skip to content

Public surface (guardrail_calculus)

The builder/authoring API plus domain scalars. This layer is still being shaped by Phases 2–3, so several builders are documented by signature only for now.

guardrail_calculus

Public guardrail-calculus surface.

This module is mostly public re-export and high-level API assembly. The foundational nodes, predicates, dimensional protocols, and the local proxy _ live in the import-safe :mod:guardrail_kernel; the generic dimension-algebra dispatch engine and symbolic conditional in :mod:guardrail_ir.dimensions; concrete temporal values (duration_hours, clock_hm, ...) in :mod:guardrail_ir.dimension_value_builtins; propositions and the typed check chain in :mod:guardrail_ir.facts; the explanation DAG, solver utilities, and puzzle solving in the optional :mod:guardrail_solver package.

What remains defined here is the builder/authoring surface: Routes, schema-rooted refs, IR serialization, and the agent/graph/subgraph/subject builders. (The last two bespoke domain-scalar classes, Probability and Money, migrated onto the values API — probability/money_gbp/money_usd in :mod:guardrail_ir.dimension_value_builtins.)

money_gbp module-attribute

money_gbp = ctor(GBP)

money_usd module-attribute

money_usd = ctor(USD)

routes module-attribute

routes = RoutesFactory()

ref module-attribute

ref = RefFactory()

agent module-attribute

agent = AgentFactory()

graph module-attribute

graph = GraphFactory()

subgraph module-attribute

subgraph = SubgraphFactory()

subject module-attribute

subject = SubjectFactory()

Routes dataclass

Represents a set of target destinations for graph routing.

Attributes:

Name Type Description
targets frozenset[RouteTarget]

Set of successor vertex references.

is_out bool

True if routing out of a subgraph.

Examples:

>>> from guardrail_calculus import Path, Routes, VertexRef
>>> r = Routes(frozenset([VertexRef(Path(("a",)))]))
>>> r.label
'routes.to(a)'
Source code in packages/guardrail-ir/src/guardrail_ir/routes.py
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
@typing.final
@dataclass(frozen=True)
class Routes:
    """Represents a set of target destinations for graph routing.

    Attributes:
        targets: Set of successor vertex references.
        is_out: True if routing out of a subgraph.

    Examples:
        >>> from guardrail_calculus import Path, Routes, VertexRef
        >>> r = Routes(frozenset([VertexRef(Path(("a",)))]))
        >>> r.label
        'routes.to(a)'
    """

    targets: frozenset[RouteTarget] = frozenset()
    is_out: bool = False

    def includes(self, target: RouteTarget) -> Predicate[_Includes]:
        return Predicate(
            _node=_Includes(self, target),
            label=f"{display_value(self)} includes {display_value(target)}",
        )

    @property
    def label(self) -> str:
        if self.is_out:
            return "routes.out"

        if not self.targets:
            return "routes.none()"

        # Sorted, because `targets` is a `frozenset` and a label is read by
        # people: a code lens, an explanation and a snapshot all render this,
        # and set iteration order is not stable across runs. The set is the
        # right *type* -- routing to a target twice is routing to it once --
        # and the ordering is a decision about the rendering, not about it.
        return (
            "routes.to("
            + ", ".join(sorted(display_value(one) for one in self.targets))
            + ")"
        )

targets class-attribute instance-attribute

targets = frozenset()

is_out class-attribute instance-attribute

is_out = False

label property

label

includes

includes(target)
Source code in packages/guardrail-ir/src/guardrail_ir/routes.py
43
44
45
46
47
def includes(self, target: RouteTarget) -> Predicate[_Includes]:
    return Predicate(
        _node=_Includes(self, target),
        label=f"{display_value(self)} includes {display_value(target)}",
    )

RoutesFactory

Factory to build Routes instances using helper methods.

Examples:

>>> from guardrail_calculus import Path, VertexRef, routes
>>> routes.to(VertexRef(Path(("a",)))).label
'routes.to(a)'
>>> routes.none().label
'routes.none()'
Source code in packages/guardrail-ir/src/guardrail_ir/routes.py
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
class RoutesFactory:
    """Factory to build [Routes][guardrail_calculus.Routes] instances using helper methods.

    Examples:
        >>> from guardrail_calculus import Path, VertexRef, routes
        >>> routes.to(VertexRef(Path(("a",)))).label
        'routes.to(a)'
        >>> routes.none().label
        'routes.none()'
    """

    @property
    def out(self) -> Routes:
        return Routes(is_out=True)

    def none(self) -> Routes:
        return Routes()

    def to(self, *targets: RouteTarget) -> Routes:
        return Routes(targets=frozenset(targets))

out property

out

none

none()
Source code in packages/guardrail-ir/src/guardrail_ir/routes.py
84
85
def none(self) -> Routes:
    return Routes()

to

to(*targets)
Source code in packages/guardrail-ir/src/guardrail_ir/routes.py
87
88
def to(self, *targets: RouteTarget) -> Routes:
    return Routes(targets=frozenset(targets))

RefFactory

Source code in src/guardrail_calculus/__init__.py
623
624
625
class RefFactory:
    def __getitem__[T](self, schema_type: type[T]) -> T:
        return cast(T, Proxy(schema_type))

AgentBuilder dataclass

Builder for constructing an AgentBlock.

Attributes:

Name Type Description
output_type type[OutputsT] | None

Optional class representing outputs.

rules tuple[AgentRule, ...]

Tuple of accumulated rules.

pending_given Prop[Any] | None

The pending given predicate.

Examples:

>>> from guardrail_calculus import agent
>>> builder = agent().given(True).then(True)
>>> len(builder.rules)
1
Source code in src/guardrail_calculus/__init__.py
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
@dataclass(frozen=True)
class AgentBuilder[OutputsT]:
    """Builder for constructing an [AgentBlock][guardrail_calculus.AgentBlock].

    Attributes:
        output_type: Optional class representing outputs.
        rules: Tuple of accumulated rules.
        pending_given: The pending given predicate.

    Examples:
        >>> from guardrail_calculus import agent
        >>> builder = agent().given(True).then(True)
        >>> len(builder.rules)
        1
    """

    output_type: type[OutputsT] | None = None
    rules: tuple[AgentRule, ...] = ()
    pending_given: Prop[Any] | None = None

    def given(self, value: Boolish_Bound | Prop[Any]) -> AgentBuilder[OutputsT]:
        return dataclasses.replace(self, pending_given=prop(value))

    def then(
        self,
        *values: Boolish_Bound | Prop[Any],
    ) -> AgentBuilder[OutputsT]:
        rule = AgentRule(
            given=self.pending_given,
            consequences=tuple(prop(value) for value in values),
        )
        return AgentBuilder(
            output_type=self.output_type,
            rules=(*self.rules, rule),
            pending_given=None,
        )

    def block(self) -> AgentBlock[OutputsT]:
        return AgentBlock(output_type=self.output_type, rules=self.rules)

output_type class-attribute instance-attribute

output_type = None

rules class-attribute instance-attribute

rules = ()

pending_given class-attribute instance-attribute

pending_given = None

given

given(value)
Source code in src/guardrail_calculus/__init__.py
743
744
def given(self, value: Boolish_Bound | Prop[Any]) -> AgentBuilder[OutputsT]:
    return dataclasses.replace(self, pending_given=prop(value))

then

then(*values)
Source code in src/guardrail_calculus/__init__.py
746
747
748
749
750
751
752
753
754
755
756
757
758
def then(
    self,
    *values: Boolish_Bound | Prop[Any],
) -> AgentBuilder[OutputsT]:
    rule = AgentRule(
        given=self.pending_given,
        consequences=tuple(prop(value) for value in values),
    )
    return AgentBuilder(
        output_type=self.output_type,
        rules=(*self.rules, rule),
        pending_given=None,
    )

block

block()
Source code in src/guardrail_calculus/__init__.py
760
761
def block(self) -> AgentBlock[OutputsT]:
    return AgentBlock(output_type=self.output_type, rules=self.rules)

AgentBlock dataclass

A constructed agent block with a set of rules.

Attributes:

Name Type Description
output_type type[OutputsT] | None

Optional class representing outputs.

rules tuple[AgentRule, ...]

Tuple of rules inside the agent.

Examples:

>>> from guardrail_calculus import AgentBlock
>>> block = AgentBlock(output_type=None, rules=())
>>> len(block.rules)
0
Source code in src/guardrail_calculus/__init__.py
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
@dataclass(frozen=True)
class AgentBlock[OutputsT]:
    """A constructed agent block with a set of rules.

    Attributes:
        output_type: Optional class representing outputs.
        rules: Tuple of rules inside the agent.

    Examples:
        >>> from guardrail_calculus import AgentBlock
        >>> block = AgentBlock(output_type=None, rules=())
        >>> len(block.rules)
        0
    """

    output_type: type[OutputsT] | None
    rules: tuple[AgentRule, ...]

output_type instance-attribute

output_type

rules instance-attribute

rules

AgentRule dataclass

A rule inside an agent containing a premise and a set of consequences.

Attributes:

Name Type Description
given Prop[Any] | None

Optional premise proposition.

consequences tuple[Prop[Any], ...]

Sequence of consequences to apply.

Examples:

>>> from guardrail_calculus import AgentRule
>>> rule = AgentRule(given=None, consequences=())
>>> rule.given is None
True
Source code in src/guardrail_calculus/__init__.py
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
@dataclass(frozen=True)
class AgentRule:
    """A rule inside an agent containing a premise and a set of consequences.

    Attributes:
        given: Optional premise proposition.
        consequences: Sequence of consequences to apply.

    Examples:
        >>> from guardrail_calculus import AgentRule
        >>> rule = AgentRule(given=None, consequences=())
        >>> rule.given is None
        True
    """

    given: Prop[Any] | None
    consequences: tuple[Prop[Any], ...]

given instance-attribute

given

consequences instance-attribute

consequences

GraphBlock dataclass

A constructed graph block.

Attributes:

Name Type Description
vertices Mapping[str, object]

Mapping from vertex names to blocks.

starts frozenset[str] | None

The vertices a run starts at, as the graph declares them (its positional arguments), or None where it declares none and lower_graph infers them (architecture.md dp-declared-start).

Examples:

>>> from guardrail_calculus import graph
>>> block = graph(a="vertex_a")
>>> block.vertices["a"]
'vertex_a'
Source code in src/guardrail_calculus/__init__.py
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
@dataclass(frozen=True)
class GraphBlock:
    """A constructed graph block.

    Attributes:
        vertices: Mapping from vertex names to blocks.
        starts: The vertices a run starts at, as the graph declares them
            (its positional arguments), or ``None`` where it declares none
            and `lower_graph` infers them (architecture.md
            `dp-declared-start`).

    Examples:
        >>> from guardrail_calculus import graph
        >>> block = graph(a="vertex_a")
        >>> block.vertices["a"]
        'vertex_a'
    """

    vertices: Mapping[str, object]
    starts: frozenset[str] | None = None

vertices instance-attribute

vertices

starts class-attribute instance-attribute

starts = None

SubgraphBlock dataclass

A constructed subgraph block.

Attributes:

Name Type Description
output_type type[OutputsT] | None

Optional class representing outputs.

vertices Mapping[str, object]

Mapping from vertex names to sibling blocks.

starts frozenset[str] | None

The vertices a run of it starts at, as declared, or None where they are inferred; a subgraph means what a workflow means (architecture.md dp-declared-start).

input_type type[InputsT] | None

What its vertices read as _.input, or None for a subgraph that declares no input and is not checked.

bound InputsT | None

The input its parent hands it (bind), or None until bound; a subgraph with an input type is embedded only bound.

Examples:

>>> from guardrail_calculus import subgraph
>>> block = subgraph(a="vertex_a")
>>> block.vertices["a"]
'vertex_a'
Source code in src/guardrail_calculus/__init__.py
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
@dataclass(frozen=True)
class SubgraphBlock[OutputsT, InputsT = None]:
    """A constructed subgraph block.

    Attributes:
        output_type: Optional class representing outputs.
        vertices: Mapping from vertex names to sibling blocks.
        starts: The vertices a run of it starts at, as declared, or ``None``
            where they are inferred; a subgraph means what a workflow means
            (architecture.md `dp-declared-start`).
        input_type: What its vertices read as ``_.input``, or ``None`` for a
            subgraph that declares no input and is not checked.
        bound: The input its parent hands it (`bind`), or ``None`` until
            bound; a subgraph with an input type is embedded only bound.

    Examples:
        >>> from guardrail_calculus import subgraph
        >>> block = subgraph(a="vertex_a")
        >>> block.vertices["a"]
        'vertex_a'
    """

    output_type: type[OutputsT] | None
    vertices: Mapping[str, object]
    starts: frozenset[str] | None = None
    input_type: type[InputsT] | None = None
    bound: InputsT | None = None

    def bind(self, value: InputsT) -> SubgraphBlock[OutputsT, InputsT]:
        """This subgraph, handed ``value`` as its input by its parent.

        ``value`` is an instance of the input type, its fields built from what
        the parent can read: a sibling's ``r.v.outputs.k``, or the parent's own
        ``_.input.k``.
        """
        if self.input_type is None:
            raise TypeError("bind() needs a subgraph that declares an input type")
        if not isinstance(value, self.input_type):
            raise TypeError(
                f"a subgraph over {self.input_type.__name__} is bound to one, "
                f"got {value!r}"
            )
        return replace(self, bound=value)

output_type instance-attribute

output_type

vertices instance-attribute

vertices

starts class-attribute instance-attribute

starts = None

input_type class-attribute instance-attribute

input_type = None

bound class-attribute instance-attribute

bound = None

bind

bind(value)

This subgraph, handed value as its input by its parent.

value is an instance of the input type, its fields built from what the parent can read: a sibling's r.v.outputs.k, or the parent's own _.input.k.

Source code in src/guardrail_calculus/__init__.py
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
def bind(self, value: InputsT) -> SubgraphBlock[OutputsT, InputsT]:
    """This subgraph, handed ``value`` as its input by its parent.

    ``value`` is an instance of the input type, its fields built from what
    the parent can read: a sibling's ``r.v.outputs.k``, or the parent's own
    ``_.input.k``.
    """
    if self.input_type is None:
        raise TypeError("bind() needs a subgraph that declares an input type")
    if not isinstance(value, self.input_type):
        raise TypeError(
            f"a subgraph over {self.input_type.__name__} is bound to one, "
            f"got {value!r}"
        )
    return replace(self, bound=value)

probability

probability(value)

Construct a probability literal in [0, 1].

A thin authoring name only: Probability is comparison-only with no registered constructors, so this direct builder is its one authoring path — but it restates no bounds. checked_real gates finiteness (the REAL carrier's wire precondition), and DimensionValue itself evaluates the dimension's declared registry invariant (0 <= v <= 1) at construction — the same node tree the solver asserts on every Probability slot.

Parameters:

Name Type Description Default
value float

The probability, between 0 and 1 inclusive.

required

Returns:

Type Description
DisplacementValue[Literal['Probability'], float]

The constructed probability value.

Source code in packages/guardrail-ir/src/guardrail_ir/dimension_value_builtins.py
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
def probability(value: float) -> DisplacementValue[Literal["Probability"], float]:
    """Construct a probability literal in [0, 1].

    A thin authoring name only: Probability is comparison-only with no
    registered constructors, so this direct builder is its one authoring
    path — but it restates *no* bounds. ``checked_real`` gates finiteness
    (the REAL carrier's wire precondition), and ``DimensionValue`` itself
    evaluates the dimension's declared registry invariant (``0 <= v <= 1``)
    at construction — the same node tree the solver asserts on every
    Probability slot.

    Args:
        value: The probability, between 0 and 1 inclusive.

    Returns:
        The constructed probability value.
    """
    # Probability is ``Vector``-structured, hence a displacement, and this is
    # a constructor, so the concrete class is both what it builds and what it
    # declares -- no cast, and no union for a caller's arithmetic to widen on.
    return DisplacementValue[Literal["Probability"], float](
        _node=checked_real(value), unit=None, _dimension=PROBABILITY
    )

validate_routes_out

validate_routes_out(block, *, in_subgraph=False)

Validate that routes.out is only used inside subgraphs.

Parameters:

Name Type Description Default
block AgentBlock[Any]

The agent block to validate.

required
in_subgraph bool

True if nested inside a subgraph.

False

Raises:

Type Description
ValueError

If routes.out is used outside a subgraph.

Source code in src/guardrail_calculus/__init__.py
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
def validate_routes_out(
    block: AgentBlock[Any],
    *,
    in_subgraph: bool = False,
) -> None:
    """Validate that `routes.out` is only used inside subgraphs.

    Args:
        block: The agent block to validate.
        in_subgraph: True if nested inside a subgraph.

    Raises:
        ValueError: If `routes.out` is used outside a subgraph.
    """
    for rule in block.rules:
        for consequence in rule.consequences:
            if _is_routes_out_assignment(consequence) and not in_subgraph:
                raise ValueError("routes.out is only valid inside a subgraph.")