Types¶
Kedi types are runtime contracts, provider schemas, and editor information. They are used in output fields, assignments, procedure signatures, custom fields, tool schemas, evals, and the Python API.
Built-In Types¶
The type environment includes Python and typing primitives such as:
str,int,float,bool,bytes, andobject;list[T],dict[K, V],tuple[...], andset[T];Union,Optional,Literal, andAnnotated;datetime,date,time, andtimedelta;Regex,Email,HttpUrl, andFileUrl.
@window(start: datetime, duration: timedelta) -> dict[str, object]:
= `{"start": start, "duration": duration}`
Unannotated outputs, assignments, 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:
>> Model scores: [scores: dict[str, list[float]]].
>> Current state: [state: Literal["open", "closed", "blocked"]].
>> Responsible owner: [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; extra metadata is ignored.
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] = `[]`
)
>> Request review decision: [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 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.