> ## Documentation Index
> Fetch the complete documentation index at: https://docs.artaios.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Type Notation

> The BAML-style type notation shared by structured output schemas and the variables system, including coercion and nullability rules

Both [Structured Output](https://docs.artaios.ai/amas/advanced/structured-output.md) schema fields and [Variables](https://docs.artaios.ai/amas/advanced/variables-system.md) share a single type system. A `VariableDefinition` extends the same `SchemaFieldDefinition` base that structured output fields use — that's why a `type` string written for a schema field and a `type` string written for a variable follow identical rules, are parsed by the same type parser, and are coerced by the same logic. This page is the single reference for that notation.

> [!NOTE]
>
> Type strings are parsed with Python's `ast` module (no `eval`), then handed to Pydantic for runtime validation. Only the forms documented below are recognized — anything else raises a parse error.

## Scalars

**str** `type`

Text data.

**int** `type`

Integer numbers.

**float** `type`

Decimal numbers.

**bool** `type`

Boolean values (`true`/`false`).

**Any** `type`

Accepts any value, no validation. Both `Any` and `any` are accepted spellings.

> [!NOTE]
>
> **Enum types are not supported.** Use `str` with a clear description, and steer allowed values through the system prompt.

## Collections

**list\[T]** `type`

A list of items of type `T`, e.g. `list[str]`, `list[int]`.

**dict\[K, V]** `type`

A mapping from keys of type `K` to values of type `V`, e.g. `dict[str, int]`.

Bare `list` and `dict` (with no type arguments) are also valid.

Collections nest to arbitrary depth — the parser recurses through subscripts, so `dict[str, list[int]]`, `list[dict[str, float]]`, and deeper combinations all parse correctly:

```yaml theme={null}
scores_by_category:
  type: "dict[str, list[float]]"
  description: "Category name to list of scores"
```

## Nullability

**T | None** `type`

Optional/nullable value of type `T`. `None | T` (reversed order) works identically.

**Optional\[T]** `type`

Equivalent alternate spelling for `T | None`.

A third spelling is also accepted because the parser maps `Union` directly: `Union[T, None]` parses to the same nullable type as `T | None` and `Optional[T]`.

> [!WARNING]
>
> Capitalization matters. `Optional` and `Union` must be capitalized exactly as shown — `optional[str]` or `union[str, None]` are rejected as unknown types. Likewise, the generic container keywords are lowercase: `list` and `dict`, not `List` or `Dict`. Typing-module-style capitalized generics (`List[str]`, `Dict[str, int]`) are **not** supported — the parser only knows the lowercase builtins.

General, non-nullable unions are also accepted (e.g. `str | int`), though in practice nullable forms (`T | None`) are by far the most common use.

## Coercion rules

Coercion only applies to **variables** — specifically when a value is assigned to a variable via `variable_assignments`, a runtime input, or a checkpoint-restored value. Structured output fields are produced by the model directly and validated by BAML; they are not run through this coercion step.

| Target type | Accepted inputs | Behavior |
| - | - | - |
| `int` | `int`, numeric string (`"42"`) | `int(value)`. A string like `"3.14"` fails (not an int literal) and falls back to the raw value. |
| `float` | `int`, `float`, numeric string (`"3.14"`) | `float(value)`. |
| `bool` | `"true"` / `"1"` → `True`; `"false"` / `"0"` → `False` (case-insensitive, whitespace-trimmed) | Any other string falls through to Python's `bool(value)` truthiness — e.g. `"no"` becomes `True` because it's a non-empty string. Non-string values use plain `bool(value)`. |
| `dict`, `list`, `list[T]` | JSON string, or already the right Python type | If the value is a string, it's parsed with `json.loads`. If that fails, the original string is returned unchanged — silently, with no warning logged. |
| `str` | Anything | `str(value)`. This essentially always succeeds. |
| `T \| None` / `Optional[T]` | Same as `T`, plus `None` | `None` short-circuits immediately and is returned as-is. Otherwise the `None` member is stripped and the value is coerced against the inner type `T`. |

> [!WARNING]
>
> If coercion raises (only possible for `int`/`float`/`bool` conversions), the system logs:
>
> ```
> Type coercion failed for variable '{var_name}'. Expected '{expected_type}', got '{type(value).__name__}'. Falling back to raw value.
> ```
>
> and keeps the original, uncoerced value rather than failing the run.

## Nullability interaction: variables vs. structured output

This is the one interaction worth understanding before you rely on `null` values flowing between agents.

**1. A null field does not overwrite a non-nullable variable**

When a `variable_assignments` entry reads a structured output field that the model set to `null`, and the **variable's** declared type is not nullable (no `None`/`Optional[...]` in its `type`), the assignment is skipped entirely — the variable keeps whatever value it already had. The system logs:

```
Agent '{agent_name}' returned null for '{field_path}'; keeping previous value of non-nullable variable '{var_name}'
```

This exists because a `None` value assigned into a non-nullable variable would otherwise be rejected the next time variables are assembled (e.g. on the following execution, once it's been through a checkpoint), failing the whole session.

**2. The structured output layer tolerates null on any field, regardless of notation**

Internally, every structured output field is marked optional at the BAML schema-building layer, even if you declared it as a plain non-nullable type like `str`. This is deliberate: without it, a model returning `null` for a field typed `str` would hard-fail the generation call (`Expected string, got null`) for the entire agent turn. So declaring a structured output field as `T | None` is **not** required merely to keep BAML from rejecting the model's `null` — the runtime already tolerates that for every field.

**3. Declare both sides nullable anyway, if null is meaningful**

To actually *store and use* a null result, declare the **variable's** type nullable (`float | None`, not `float`) — that's what determines whether the assignment in Step 1 is skipped or allowed through. Declaring the structured output field as `T | None` too keeps the schema's intent explicit and matches the variable it feeds, even though the BAML layer would tolerate `null` either way.

```yaml theme={null}
structured_output:
  schema:
    discount:
      type: "float | None"          # explicit: this field may legitimately be absent
      description: "Discount percentage, or null if none applies"

variables:
  applied_discount:
    type: "float | None"            # must be nullable, or a null result is silently skipped
    default: 0.0

agents:
  - name: "pricer"
    variable_assignments:
      applied_discount: "pricer.output.discount"
```

## Common errors

> **Cannot parse type string '...'**
>
> The type string isn't valid Python expression syntax at all. Exact message:
>
> ```
> Cannot parse type string '{type_str}': {error}. Valid examples: 'str', 'list[str]', 'dict[str, int]'
> ```

> **Unknown type '...'**
>
> The base name isn't one of the recognized keywords (`str`, `int`, `float`, `bool`, `list`, `dict`, `any`/`Any`, `Optional`, `Union`). This is the error you'll hit for things like `List[str]`, `enum[...]`, or a typo. Exact message:
>
> ```
> Unknown type '{type_name}'. Valid types: ['str', 'int', 'float', 'bool', 'list', 'dict', 'any', 'Any', 'Optional', 'Union']
> ```

> **Invalid type '...'**
>
> The string parsed into a type, but Pydantic couldn't build a validator for it. Exact message: `Invalid type '{type_str}': {error}`.

> **Variable has invalid BAML type**
>
> Raised at config-validation time for a variable whose `type` doesn't parse. Exact message:
>
> ```
> Variable '{var_name}' has invalid BAML type '{type}'
>    💡 Common types: str, int, float, bool, str[], int[], dict
>    Error: {error}
> ```
>
> > [!NOTE]
> >
> > The hint text in this message is misleading — `str[]`/`int[]` are not valid notation. Use `list[str]`/`list[int]` instead.

> **'\{field}' expected \{type}, got \{type}**
>
> Raised when a variable's current value doesn't match its declared type after coercion (for example, a non-nullable variable ended up holding `None`, or a static assignment can't be coerced into the declared type).

## Used by

> **[Structured Output](https://docs.artaios.ai/amas/advanced/structured-output.md)**
>
> Define typed fields the agent must extract.

> **[Variables](https://docs.artaios.ai/amas/advanced/variables-system.md)**
>
> Define typed values threaded through prompts and assignments.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.