Skip to content

symfonic.platform.wire

wire

Values on the wire, and the domain values they name.

Split from :mod:symfonic.platform.transport, which is the router itself. The router decides who may ask; this decides what a header, a query parameter or a stored record looks like as JSON. Neither needs to change when the other does, and one of them is the file a deployment reads when it wants to know what the API returns.

MemoryIdentityRefused

Bases: MemoryContractError

The declaration raised, returned no MemoryScope, or left the request tenant.

NativeScopeRouteRefused

Bases: MemoryContractError

A v0-only route refused a scope whose Agent composes v1; nothing was read.

declared_identity

declared_identity(identity: Any) -> Iterator[None]

Bind the router's memory_identity for one request's handlers.

Source code in src/symfonic/platform/wire.py
@contextmanager
def declared_identity(identity: Any) -> Iterator[None]:
    """Bind the router's ``memory_identity`` for one request's handlers."""
    token = _DECLARED.set(identity)
    try:
        yield
    finally:
        _DECLARED.reset(token)

kind_of

kind_of(event: Any) -> str

The event's kind as a plain string, whatever type carries it.

Source code in src/symfonic/platform/wire.py
def kind_of(event: Any) -> str:
    """The event's kind as a plain string, whatever type carries it."""
    kind = getattr(event, "kind", None)
    return getattr(kind, "value", None) or str(kind or "chunk")

memory_layer

memory_layer(name: str | None) -> Any

The memory layer a query parameter names, or None for every layer.

Source code in src/symfonic/platform/wire.py
def memory_layer(name: str | None) -> Any:
    """The memory layer a query parameter names, or ``None`` for every layer."""
    if name is None:
        return None
    from symfonic.capabilities.memory import MemoryLayer

    try:
        return MemoryLayer(name)
    except ValueError:
        raise ValueError(f"unknown layer: {name!r}") from None

memory_scope

memory_scope(scope: Any) -> Any

The positional v0 memory scope the remaining HTTP routes name.

Delegated rather than converted here: the memory package owns the one translation between the two spellings of a scope, and a second one in a transport is how they drift. These ports still address v0 storage, so when the router's memory_identity declares that the host's Agent composes kind-aware v1 for scope, the route refuses (CORE-171) before reading or changing a positional namespace that Agent never wrote.

Source code in src/symfonic/platform/wire.py
def memory_scope(scope: Any) -> Any:
    """The positional v0 memory scope the remaining HTTP routes name.

    Delegated rather than converted here: the memory package owns the one
    translation between the two spellings of a scope, and a second one in a
    transport is how they drift. These ports still address v0 storage, so
    when the router's ``memory_identity`` declares that the host's Agent
    composes kind-aware v1 for ``scope``, the route refuses (CORE-171) before
    reading or changing a positional namespace that Agent never wrote.
    """
    from symfonic.capabilities.memory import MemoryContractError, as_memory_scope

    if _DECLARED.get() is not None and record_scope(scope).identity_version != 0:
        raise NativeScopeRouteRefused(NATIVE_ROUTE_REFUSAL)
    result = as_memory_scope(scope)
    if result.identity_version != 0:
        raise MemoryContractError("HTTP memory routes require positional v0 scope")
    return result

record_body

record_body(record: Any) -> dict[str, Any]

One stored record as JSON a browser can render.

Source code in src/symfonic/platform/wire.py
def record_body(record: Any) -> dict[str, Any]:
    """One stored record as JSON a browser can render."""
    return {
        "id": record.record_id,
        "layer": getattr(record.layer, "value", str(record.layer)),
        "content": record.text,
        "scope_path": record.scope_path,
        "salience": record.salience,
        "origin": record.origin,
        "revision": record.revision,
        "metadata": dict(record.metadata or {}),
    }

record_scope

record_scope(scope: Any) -> Any

The memory identity record listing, point read and delete address (CORE-171).

Positional v0, as every route has always used, unless the router was built with memory_identity: then exactly the scope that declaration says the host's Agent composes, so a read or delete reaches the row that Agent wrote -- kind-aware v1 included -- and no positional row that only shares its ids. A declaration that raises, returns anything but a MemoryScope, or roots it at another tenant than the request's is refused (MemoryIdentityRefused) before any store call.

Source code in src/symfonic/platform/wire.py
def record_scope(scope: Any) -> Any:
    """The memory identity record listing, point read and delete address (CORE-171).

    Positional v0, as every route has always used, unless the router was
    built with ``memory_identity``: then exactly the scope that declaration
    says the host's Agent composes, so a read or delete reaches the row that
    Agent wrote -- kind-aware v1 included -- and no positional row that only
    shares its ids. A declaration that raises, returns anything but a
    ``MemoryScope``, or roots it at another tenant than the request's is
    refused (``MemoryIdentityRefused``) before any store call.
    """
    identity = _DECLARED.get()
    if identity is None:
        return memory_scope(scope)
    from symfonic.capabilities.memory import MemoryScope

    try:
        declared = identity(scope)
    except Exception as error:  # noqa: BLE001 - any failure is one refusal
        raise MemoryIdentityRefused(MEMORY_IDENTITY_REFUSAL) from error
    tenant = getattr(scope, "tenant_id", None)
    if type(declared) is not MemoryScope or type(tenant) is not str or declared.tenant != tenant:
        raise MemoryIdentityRefused(MEMORY_IDENTITY_REFUSAL)
    return declared

scope_for_tenant

scope_for_tenant(tenant: str) -> Any

The scope a tenant header names.

One level, because a header names a tenant and nothing deeper. A router that read a principal or a session from headers too would be deriving authorisation from something the caller controls, which is the platform's own rule about where a scope may come from.

Source code in src/symfonic/platform/wire.py
def scope_for_tenant(tenant: str) -> Any:
    """The scope a tenant header names.

    One level, because a header names a tenant and nothing deeper. A router
    that read a principal or a session from headers too would be deriving
    authorisation from something the caller controls, which is the platform's
    own rule about where a scope may come from.
    """
    from symfonic.agent.types import FrameworkTenantScope
    from symfonic.core.scope import ScopeLevel

    return FrameworkTenantScope.from_path([ScopeLevel(kind="tenant", id=tenant)])

text_of

text_of(value: Any) -> str

Whatever the turn produced, as text a JSON body can carry.

Structural rather than typed: the router is a mapper and must not learn the result type, which differs between a run and a stream chunk and is free to gain fields without this file changing.

Source code in src/symfonic/platform/wire.py
def text_of(value: Any) -> str:
    """Whatever the turn produced, as text a JSON body can carry.

    Structural rather than typed: the router is a mapper and must not learn
    the result type, which differs between a run and a stream chunk and is
    free to gain fields without this file changing.
    """
    for attribute in ("content", "text", "final_response", "delta"):
        found = getattr(value, attribute, None)
        if isinstance(found, str) and found:
            return found
    return str(value)