Outputs, Initialization, and Assignment¶
Square brackets are write syntax in Kedi. In a >> template they declare
fields the model must produce; on the left of = they initialize variables
with deterministic values. Neither form is the same as <name>, which reads a
value.
Output Fields as L-Values¶
An output field embedded in a template is an L-value:
>> The incident severity is [severity: str]. The summary is [summary: str].
= <severity>: <summary>
Kedi builds a structured output schema, asks the active adapter to fill it, and stores each field in the current environment. Output names must be valid Python identifiers: they start with a letter or underscore and contain only letters, digits, and underscores.
Use model outputs for semantic extraction or generation. Do not use them for values that Python can determine exactly.
Simple and Typed Outputs¶
An untyped output defaults to str:
>> A concise headline is [headline].
A typed output asks the adapter for a native value and validates it:
>> The priority is [priority: int]. The owners are [owners: list[str]]. It is [blocked: bool] that the work is blocked.
Kedi does not merely include the annotation in the prompt. It resolves the type, creates the provider schema, and validates the returned value. Unsupported provider schema formats fail before a model call where the adapter can detect them.
Inline Python Output Types¶
Wrap a type expression in backticks when the type comes from runtime Python state:
```
from typing import Literal
Priority = Literal["low", "medium", "high"]
```
>> The ticket priority is [priority: `Priority`].
For ordinary built-in or custom Kedi types, prefer the direct form
[priority: Priority]. Runtime expressions are useful for types created by a
prelude, import, or factory, but they make static tooling less certain.
Field Description Metadata¶
Use Annotated[T, "description"] to explain a field to the model while keeping
T as its runtime type:
>> The country code is [code: Annotated[str, "Uppercase ISO 3166-1 alpha-2 country code"]].
The description becomes JSON Schema metadata for adapters that expose schemas.
The native description form uses a single-line string literal. Annotated[T]
is accepted as T but triggers an editor warning because it carries no
description. Python type aliases can carry additional non-string validation or
decision metadata; Kedi preserves that metadata. See
Validation Constraints.
Use descriptions for constraints the base type cannot express clearly. Do not
repeat obvious information such as Annotated[int, "An integer"].
Multiple Outputs and Same-Scope Initialization¶
One template can fill several fields:
>> Version [version: str] was released on [date: date].
It includes [changes: list[str]].
All continuation lines in that >> block belong to one model request and one
combined schema. A later output or = initialization with the same name
replaces the value in the current scope:
[status] = draft
>> The document status after review is [status: str].
= <status>
This does not update an outer lexical binding. Prefer a new name such as
reviewed_status when both values matter.
Variable Initialization¶
Initialization does not contact a model:
[title] = Release <version>
[attempts: int] = `2 + 1`
[copy] = <title>
The right-hand side has two evaluation modes:
- A sole native Python segment preserves its native result.
- A sole procedure call preserves that procedure's native result.
- Mixed literal text and substitutions render to
str.
This distinction matters:
[native: int] = `40 + 2`
[rendered] = Answer: <`40 + 2`>
native is the integer 42; rendered is the string "Answer: 42".
= always declares in the current lexical scope. A declaration inside an
if, else, or loop iteration shadows a visible outer name and disappears
when that child scope ends.
Assignment¶
Use := to assign a new value to the nearest visible Kedi binding instead of
shadowing it:
[attempts: int] = `0`
> if: `True`:
[attempts] := `attempts + 1`
= `attempts`
The target must already exist. := does not accept a type annotation because
the existing binding owns its type contract; Kedi validates the new value
against that contract. Unknown and reserved targets fail.
A fenced Python result can be assigned without changing the target type:
[total: int] = `0`
[total] := ```
return sum([1, 2, 3])
```
Typed Initialization¶
A typed initialization validates the resulting value without coercing it:
[ports: list[int]] = `[8000, 8001]`
[enabled: bool] = `True`
[count: int] = five fails instead of converting text. If parsing user text is
required, do it explicitly in Python:
[raw_count] = 5
[count: int] = `int(raw_count)`
Initialization from a Python Block¶
Use a fenced block when computing a value requires statements:
[durations: list[int]] = `[14, 8, 21]`
[p95: int] = ```
ordered = sorted(durations)
index = round(0.95 * (len(ordered) - 1))
return ordered[index]
```
= p95\=<p95>
The block must execute return to supply the initialization value. New helper names
inside the block remain local; the initialized result is the supported way to
surface one of them.
Output Capture versus Raw Capture¶
Use >> output fields when you need typed or multiple structured values:
>> The detected language is [language: str]. The confidence is [confidence: float].
Use raw capture when the complete model response should remain an unstructured string:
[explanation] << Explain why the build failed in one paragraph.
Raw capture accepts no embedded output fields and no type other than optional
str. A plain >> request with no output fields runs the model and discards
its response.
Resolution and Validation Errors¶
Kedi rejects duplicate output names within an invalid schema, malformed identifiers, unknown types, values that do not match their initialization or assignment contracts, and adapter schemas the active backend cannot represent. Type validation is strict: a numeric-looking string is still a string.
Choose annotations that match the wire capability of the selected adapter. For
example, a descriptive Annotated[str, "..."] can be more portable than a
provider-specific URL or regex format.