Rail Outcomes
Rail actions return a RailOutcome to express an allow, block, or transform decision. This contract separates a rail’s decision from the way a runtime presents or enforces that decision.
Actions declared by a rail manifest must return RailOutcome. Ordinary custom actions can return other Python values when a Colang flow consumes them explicitly. The runtime does not infer a rail decision from a boolean, number, tuple, or dictionary.
Breaking Change
The output_mapping parameter and its default boolean and numeric mappings have been removed from @action. Passing output_mapping now raises TypeError. Migrate rail decisions to RailOutcome. Do not rely on implicit return-value interpretation.
Decisions
Each outcome contains one of the following decisions:
Import the outcome and transform target types from the actions package:
Allow Content
Return an allow outcome:
Block Content
Return a block outcome:
A block outcome does not contain a refusal message, exception type, bot intent, or localized text. The runtime or Colang flow owns those presentation choices.
Transform Content
Use a transform outcome when the rail rewrites checked content. A transform must include at least one rewrite and cannot repeat a target.
The supported transform targets are:
TransformTarget.USER_MESSAGETransformTarget.BOT_MESSAGETransformTarget.RELEVANT_CHUNKS
Transform outcomes apply to non-streaming processing. Streaming output paths do not apply the rewrite.
Evidence Fields
Use reason for a neutral, human-readable explanation of the decision. Use metadata for structured evidence such as categories, scores, detections, or redacted provider status and error codes.
Do not make metadata load-bearing for the decision. Consumers should use decision, is_blocked, or is_transform to determine the outcome. Treat metadata as potentially observable data. Do not store secrets, credentials, raw provider payloads, or unnecessary user content.
Consume an Outcome in a Flow
The action decides whether content is allowed, blocked, or transformed. The flow decides how to handle that result.
For a transform, read the replacement by its target name:
The following convenience properties are available:
Migrate From Output Mapping
Replace the mapping function with an explicit decision at the action’s return site. This makes the action’s meaning visible to every runtime and avoids conventions such as whether True means safe or blocked.
Safe Boolean Results
Previously, an action could return whether content was safe and negate that result through output_mapping:
Return the decision directly:
Unsafe Boolean Results
For a detector where True means unsafe, return block when the detector matches:
Numeric Thresholds and Structured Results
Apply thresholds in the action and preserve useful evidence in metadata:
The migration follows these mappings:
Update the consuming flow to inspect the outcome rather than the original scalar value:
If the action is not a rail decision, keep its ordinary return type and branch on that value explicitly in the flow. RailOutcome is not required for general-purpose actions.
Validation Rules
RailOutcome validates its state when you construct it:
reasonmust be a string orNone.metadatamust be a mapping with string keys.- Transform outcomes must contain one or more transforms.
- Allow and block outcomes cannot contain transforms.
- Each transform target can appear only once in an outcome.
- Transform replacement values must be strings.
Use the allow, block, and transform class methods instead of constructing decisions directly. These methods make the intended outcome clear at the action’s return site.