Skip to content

symfonic.kernel.contracts.groups

groups

Plan field groups G1–G9 (INV-ADR §2), as frozen value types.

Each group is a separate type rather than a bag of plan attributes because IPL-12 makes adding a group a contract change: a group with a name and a type is something a reviewer can see arriving in a diff.

EventAdapter dataclass

EventAdapter(name: str, buffer: BufferClass = 'rendezvous', policy: BufferPolicy = 'block', capacity: int = 0, byte_capacity: int = 0, terminal_policy: TerminalPolicy = 'reserve', owner: str = 'kernel', emits: frozenset[str] = frozenset(), sheddable: frozenset[str] = frozenset(), coalescible: frozenset[str] = frozenset(), text_reconstruction: Literal['deltas', 'terminal-only', 'transport-dependent'] = 'deltas')

One G9 adapter row, including every BP-1 policy decision.

capacity and byte_capacity are per run. Rendezvous adapters use zero for both. text_reconstruction='transport-dependent' means streaming transport publishes deltas while blocking transport publishes its complete round text as one text_delta. terminal_policy='shed' exists only so compilation can reject it with a useful BP-4 error.

EventProgram dataclass

EventProgram(emitted: frozenset[str] = frozenset(), adapters: tuple[EventAdapter, ...] = ())

G9 — the declared event types and every adapter attached to them.

A buffer that is not declared here may not exist at run time (BP-1), which is only enforceable because the declaration is compiled data.

ModelResolution dataclass

ModelResolution(provider_family: str = 'unknown', model_name: str | None = None, sampling: Mapping[str, Any] = (lambda: MappingProxyType({}))(), response_format: ResponseFormat = ResponseFormat())

G3 — the model decision, already made. The router executes it (CADR-02).

PlanIdentity dataclass

PlanIdentity(plan_id: str, config_digest: str, compiled_at: float, schema_version: int = 1, parent_plan_id: str | None = None)

G1 — who this plan is, and which plan it was derived from.

PlanLimits dataclass

PlanLimits(max_model_rounds: int = 10, max_recursion_depth: int = 1, deadline_seconds: float | None = None, max_event_buffer: int = 256, teardown_grace_seconds: float = 5.0)

G8 — every ceiling this invocation runs under, including the round bound.

max_model_rounds lives here rather than in a module constant on purpose: a bound that a plan cannot express is a bound no adopter can tune and no test can shorten.

teardown_grace_seconds is the same argument applied to unwinding: it bounds each finalizer, the background-work drain, and terminal delivery (RCX-10, BP-8, BP-10). A run whose consumer has gone away must still finish dying in a knowable amount of time.

RequestScope dataclass

RequestScope(tenant: str | None = None, principal: str | None = None, grants: frozenset[str] = frozenset())

G2 — the scope the platform derived, carried and never recomputed.

narrows

narrows(parent: RequestScope) -> bool

True when this scope is the parent's or a strict narrowing of it.

Source code in src/symfonic/kernel/contracts/groups.py
def narrows(self, parent: RequestScope) -> bool:
    """True when this scope is the parent's or a strict narrowing of it."""
    if self == parent:
        return True
    tenant_ok = parent.tenant is None or self.tenant == parent.tenant
    principal_ok = parent.principal is None or self.principal == parent.principal
    return tenant_ok and principal_ok and self.grants <= parent.grants

ResponseFormat dataclass

ResponseFormat(mode: Literal['text', 'structured'] = 'text', schema_name: str | None = None)

How this invocation's response is handled.

A descriptor, not a schema object: the live Pydantic model (or whatever the adopter's schema library produces) is reachable only through the bound response port, which is what keeps IPL-7's "a plan is serializable minus G6" true when structured output is in play.

ServiceBindings dataclass

ServiceBindings(conversation: Any = None, model: Any = None, palette: Any = None, tools: Any = None, response: Any = None, event_sink: Any = None, stage_handlers: Mapping[str, Any] = (lambda: MappingProxyType({}))(), tool_preconditions: tuple[Any, ...] = ())

G6 — live port objects, fixed in identity at freeze time (IPL-4).

These are the only collaborators the kernel ever calls. Rebinding requires a new plan; there is no setter, and no code path that swaps one mid-run.

StageProgram dataclass

StageProgram(stages: tuple[CompiledStage, ...] = ())

G5 — the compiled, totally ordered stage list.

instructions property

instructions: str | None

The system prompt the kernel-owned prompt stage carries (STG-7).

ToolDescriptor dataclass

ToolDescriptor(name: str, description: str = '', policy: Mapping[str, Any] = (lambda: MappingProxyType({}))())

One allowlisted tool, described rather than imported.

ToolManifest dataclass

ToolManifest(tools: tuple[ToolDescriptor, ...] = ())

G4 — the single manifest source. Enforcement reads it; nothing rebuilds it.