Skip to content

symfonic.kernel.contracts.values

values

Kernel-native invocation values — the vocabulary the loop actually speaks.

Everything here is a frozen dataclass built from the standard library alone. That is the whole point of the module: the invocation loop is written against these types, so it never learns a wire format, a provider SDK, or a serialization library. LangChain messages, pydantic results, and provider responses live on the far side of the ports in :mod:.ports, and the adapter that implements those ports is the only code that knows both vocabularies.

InvocationOutcome dataclass

InvocationOutcome(text: str, output: Any = None, messages: tuple[Any, ...] = (), tool_outcomes: tuple[Any, ...] = (), usage: UsageDelta = UsageDelta(), run_id: str = '', duration_ms: float = 0.0, stop_reason: str | None = None)

Everything one invocation produced, before result-shape adaptation.

messages and tool_outcomes are opaque to the kernel: it collects and orders them, and the bound response port decides what they look like to an adopter. That split is why T2.3.3 can change the public result shape without touching the loop.

KernelEvent dataclass

KernelEvent(kind: str, index: int, run_id: str, text: str | None = None, tool_request: ToolRequest | None = None, tool_outcome: Any = None, outcome: InvocationOutcome | None = None, error: str | None = None, stage_id: str | None = None, phase: str | None = None, capability: str | None = None, stage_outcome: str | None = None, stage_reason: str | None = None, counts: Mapping[str, int] = (lambda: EMPTY_COUNTS)(), dropped_kind: str | None = None, dropped_count: int = 0, first_dropped_index: int | None = None, last_dropped_index: int | None = None, termination: str | None = None, interrupt: PendingInterrupt | None = None)

One event on the single internal pipeline (CADR-10, EVT-1…EVT-10).

index is dense and monotonic within a run, and the kernel is the only thing that assigns it — an adapter that renumbered events would break the ordering guarantee every consumer joins on.

ModelDelta dataclass

ModelDelta(kind: str, text: str)

One incremental piece of a streaming round.

ModelTurn dataclass

ModelTurn(text: str = '', tool_requests: tuple[ToolRequest, ...] = (), usage: UsageDelta = UsageDelta(), stop_reason: str | None = None, payload: Any = None)

One completed provider round, in kernel vocabulary.

payload is the adapter's own object — the raw provider message, or whatever else it needs back when the kernel asks it to record the round. The kernel carries it and never reads it: a field the loop cannot inspect is a field the loop cannot come to depend on.

PromptAssembly dataclass

PromptAssembly(instructions: str | None, prompt: str, attachments: tuple[Any, ...] = (), history: tuple[Any, ...] = (), system_blocks: tuple[Any, ...] = ())

The output of the prompt-assembly phase (STG-7: a pure function).

It is a value, not a side effect, precisely so the phase stays pure and the assembled prompt is reproducible from the plan and the request alone.

ToolRequest dataclass

ToolRequest(call_id: str, name: str, arguments: Mapping[str, Any] = (lambda: MappingProxyType({}))())

One tool the model asked for, with a kernel-owned join key.

call_id is assigned by the kernel, not trusted from the provider: providers omit ids and reuse them across rounds, and an ambiguous join key between a tool_call and its tool_result is not a defect a consumer can work around (RES-3).

TurnRequest dataclass

TurnRequest(prompt: str, attachments: tuple[Any, ...] = (), history: tuple[Any, ...] = (), run_id: str = '', root_run_id: str = '', parent_run_id: str | None = None, session_id: str = '', scope: Any = None, properties: Mapping[str, Any] = (lambda: EMPTY_PROPERTIES)())

The caller's half of an invocation: prompt, attachments, history.

Instructions are deliberately absent. They are a plan decision (the kernel-owned kernel.prompt stage carries them), so a caller cannot change the system prompt per call without recompiling — which is IPL-1's compile-once rule expressed in a signature.

UsageDelta dataclass

UsageDelta(input_tokens: int = 0, output_tokens: int = 0, total_tokens: int = 0, cache_read_tokens: int = 0, cache_creation_tokens: int = 0, reasoning_tokens: int = 0, cache_ttl: str | None = None)

What one provider round reported (RES-4).

0 means unreported, never free: the kernel adds what it is told and estimates nothing, so a silent provider leaves the zero value standing.