Types¶
Kedi types are runtime contracts, provider schemas, and editor information. They are used in output fields, variable initializations, procedure signatures, custom fields, tool schemas, evals, and the Python API. Assignments reuse the target binding's established type contract.
Built-In Types¶
The type environment includes model-facing Python and typing primitives such as:
str,int,float,bool, andbytes;list[T],dict[K, V], andset[T];Union,Optional,Literal, andAnnotated;datetime,date,time, andtimedelta;Regex,Email,HttpUrl, andFileUrl.
~Window(start: datetime, duration: timedelta)
@window(start: datetime, duration: timedelta) -> Window:
= `Window(start=start, duration=duration)`
Native Kedi annotations reject tuple, Tuple, Sequence, Mapping,
Iterable, object, bytearray, slice, and range. They remain available
to ordinary Python expressions and blocks; only their use as Kedi type
contracts is rejected. Use list, dict, or a named custom type for
model-facing schemas.
Unannotated outputs, variable initializations, parameters, returns, and custom
fields default to str. Add an annotation whenever a value is intentionally
not text.
Container, Union, and Literal Types¶
Types can be nested:
>> The model scores are [scores: dict[str, list[float]]].
>> The current state is [state: Literal["open", "closed", "blocked"]].
>> The responsible owner is [owner: str | None].
Choose a shape that the model and adapter can reliably represent. Deeply nested unions may be valid Python but poor model interfaces; a named custom type often produces a clearer schema.
Inline Python Type Expressions¶
Backtick-wrapped annotations are evaluated at runtime:
```
from typing import Literal
Severity = Literal["low", "medium", "high"]
```
@triage(level: `Severity`) -> `Severity`:
= `level`
They have the same validation behavior as direct annotations. Prefer direct syntax for built-ins and Kedi custom types. Use runtime expressions only when the type is computed or imported through Python.
Define Custom Types¶
Declare a Pydantic-compatible model with ~Name(fields):
~Owner(
name: Annotated[str, "Human-readable display name"],
email: Email
)
~Issue(
id: int,
title: str,
owner: Owner | None,
labels: list[str] = `[]`
)
Fields without annotations are str. Field names must be unique valid
identifiers. Required fields must come before fields with defaults, and a
defaulted custom field must have an explicit annotation.
Custom types are lexically scoped. A type declared inside a procedure is visible to that procedure and nested scopes, but not after the call. A nested type may shadow an outer type with the same name.
Defaults¶
Field defaults are single-line Python expressions evaluated at definition time:
~Job(name: str, retries: int = `3`, tags: list[str] = `[]`)
Kedi deep-copies mutable defaults for each model instance, so instances do not share the same list or dict. Required-after-default and untyped-default fields are parse errors.
Field Description Metadata¶
Annotated[T, "description"] keeps T as the runtime type and adds the string
to the generated schema:
~Finding(
path: Annotated[str, "Repository-relative POSIX path"],
confidence: Annotated[float, "Value from 0.0 through 1.0"]
)
Descriptions should state semantic constraints or interpretation. They are
forwarded to model-facing schemas. Annotated[T] works as T but lacks useful
metadata and is warned about. The first string metadata item supplies the field
description when it is the first metadata argument. Non-string metadata from
Python type aliases is preserved for validation and integrations; it is not
discarded. Descriptions guide the model but do not enforce a numeric range.
Validation Constraints¶
Use kedi.Constraints inside a Python type alias when bounds must be enforced:
```
from typing import Annotated
from kedi import Constraints
Rating = Annotated[
float,
"Rating on a zero-to-two scale",
Constraints(ge=0, le=2),
]
```
[rating: `Rating`] = `1.5`
= `rating`
The Python alias supplies actual validation metadata. Kedi's native annotation
grammar does not accept arbitrary constructor calls such as
Annotated[float, Constraints(ge=0)]; define the alias in Python and reference
it with a backtick type expression. These constraints validate values, not the
truth of model judgements. See Jev for decision
metadata and supported schemas.
Pydantic-Compatible Models¶
Generated custom types subclass Pydantic BaseModel. Construct them with
positional fields in declaration order, keyword arguments, or a mixture:
~Person(age: int, name: str, city: str)
[person: Person] = `Person(30, name="Ada", city="London")`
= `person.model_dump_json()`
Pydantic APIs such as model_dump(), model_dump_json(), and
model_json_schema() are available. Prefer keyword construction in public code
because it remains readable if field order changes.
Custom Types in Model Outputs¶
Use a custom type when one output has a meaningful structured shape:
~Decision(
approved: bool,
reason: str,
conditions: list[str] = `[]`
)
>> The request review decision is [decision: Decision].
= `decision.model_dump_json()`
The adapter receives the nested JSON schema and Kedi validates the model response. Use multiple primitive outputs when the fields are local and simple; use a named type when the structure is reused, nested, returned, or exposed to a tool.
Custom Types in Procedures¶
Custom types can cross procedure boundaries natively:
~Ticket(id: int, title: str)
@format_ticket(ticket: Ticket) -> str:
= \#<`ticket.id`> <`ticket.title`>
= `format_ticket(Ticket(id=7, title="Parser error"))`
Passing rendered JSON text is not equivalent to passing a Ticket. Construct
or validate the model in Python when converting external data.
Adapter Schema Compatibility¶
Provider support is narrower than Python's type system. Kedi validates known schema limitations before contacting the model:
- Codex supports formats including
date,date-time,duration,email, andtime. - Codex rejects
Regex,HttpUrl, andFileUrlmodel-output schemas. - Claude accepts the listed built-in formats.
- Framework adapters may impose their own provider and model restrictions.
When strict wire validation is not essential, use
Annotated[str, "Exact HTTPS URL ..."] instead of an unsupported format. This
keeps the schema portable while retaining model guidance.
Resolution and Validation Errors¶
Unknown type names fail loudly; they do not fall back to str. Values are
validated without implicit string-to-number coercion. Errors also identify
duplicate fields, invalid default ordering, incompatible initialization,
assignment, or return values, and unsupported adapter schema formats.
Types declared later are not available to earlier prelude or runtime expressions. Define or import a type before the statement that resolves it.