Skip to content

symfonic.capabilities.prompting.admission

admission

S01's three declaration-time decisions: gate, grant, and operator override.

Split out of :mod:.compiler because they are a different kind of thing and because the compiler was at its 300-line budget. The compiler owns the fixed sequence; this module owns three questions that sequence asks about a contribution before its bytes are priced:

  • may it render at all (gated / gated_async) -- the request's render_when policy, consulted before any source is read so a declined contribution costs no I/O, which is the ordering PromptBlockResolver.resolve_block documents for the same decision;
  • may this principal see it (permitted) -- agent_permissions against the grants the turn carries;
  • whose text is it (operator_override / overridden) -- the request carries the authority, the contribution carries the permission, and neither half alone changes a byte. Asked before the read, for the same reason the gate is: bytes that are going to be replaced are bytes not worth fetching, and a source that fails closed must not take down a turn whose text the operator had already supplied.

All three are recorded as diagnostics rather than applied silently. A contribution that is missing from a prompt and one that was withheld from this principal are different facts, and only one of them is a reason to look at the sources.

gated

gated(request: PromptCompileRequest, admitted: tuple[PromptContribution, ...], diagnostics: list[PromptDiagnostic]) -> tuple[PromptContribution, ...]

The render gate at the synchronous door. Runs before any source read.

Source code in src/symfonic/capabilities/prompting/admission.py
def gated(
    request: PromptCompileRequest,
    admitted: tuple[PromptContribution, ...],
    diagnostics: list[PromptDiagnostic],
) -> tuple[PromptContribution, ...]:
    """The render gate at the synchronous door. Runs before any source read."""
    gate = request.policy.render_when
    if gate is None:
        return admitted
    kept: list[PromptContribution] = []
    for contribution in admitted:
        verdict = gate(contribution)
        if inspect.isawaitable(verdict):
            _close(verdict)
            raise ContributionContractError(
                f"the render gate returned an awaitable for "
                f"{contribution.contribution_id!r} and this compile went through the "
                "synchronous door. Await compile_prompt_async(request) instead; guessing a "
                "verdict would render a block the host wanted hidden, or hide one it wanted."
            )
        if _gate_verdict(contribution, verdict, diagnostics):
            kept.append(contribution)
    return tuple(kept)

gated_async async

gated_async(request: PromptCompileRequest, admitted: tuple[PromptContribution, ...], diagnostics: list[PromptDiagnostic]) -> tuple[PromptContribution, ...]

The same gate, awaited when the host's predicate asks to be.

Source code in src/symfonic/capabilities/prompting/admission.py
async def gated_async(
    request: PromptCompileRequest,
    admitted: tuple[PromptContribution, ...],
    diagnostics: list[PromptDiagnostic],
) -> tuple[PromptContribution, ...]:
    """The same gate, awaited when the host's predicate asks to be."""
    gate = request.policy.render_when
    if gate is None:
        return admitted
    kept: list[PromptContribution] = []
    for contribution in admitted:
        verdict = gate(contribution)
        if inspect.isawaitable(verdict):
            verdict = await verdict
        if _gate_verdict(contribution, verdict, diagnostics):
            kept.append(contribution)
    return tuple(kept)

operator_override

operator_override(request: PromptCompileRequest, contribution: PromptContribution, diagnostics: list[PromptDiagnostic]) -> SourceRead | None

The authorised operator's text for this contribution, if both halves say so.

Two halves, and neither is sufficient alone: the request carries the authority (a composition root assembled it) and the contribution carries the permission (operator_editable). An override aimed at a contribution that did not declare itself editable is recorded and refused, never applied quietly -- a silent substitution in the system prompt is the least auditable change this compiler could make.

Source code in src/symfonic/capabilities/prompting/admission.py
def operator_override(
    request: PromptCompileRequest,
    contribution: PromptContribution,
    diagnostics: list[PromptDiagnostic],
) -> SourceRead | None:
    """The authorised operator's text for this contribution, if both halves say so.

    Two halves, and neither is sufficient alone: the request carries the
    *authority* (a composition root assembled it) and the contribution carries
    the *permission* (``operator_editable``). An override aimed at a
    contribution that did not declare itself editable is **recorded and
    refused**, never applied quietly -- a silent substitution in the system
    prompt is the least auditable change this compiler could make.
    """
    overrides = request.operator_overrides or {}
    text = overrides.get(contribution.contribution_id)
    if text is None:
        return None
    if not contribution.operator_editable:
        diagnostics.append(
            PromptDiagnostic(
                "operator",
                contribution.contribution_id,
                "refused: an operator override was supplied, and this contribution does "
                "not declare operator_editable, so the source's own text renders",
            )
        )
        return None
    diagnostics.append(
        PromptDiagnostic(
            "operator",
            contribution.contribution_id,
            "served the operator override instead of the source's own text",
        )
    )
    return SourceRead(text=text, revision="operator")

overridden

overridden(request: PromptCompileRequest, contribution: PromptContribution, diagnostics: list[PromptDiagnostic]) -> SourceResolution | None

The operator's text for this contribution, decided before the read.

None means "no authorised override applies, read the source". The ordering is the point, and it is the same one :mod:.admission states for the render gate: a contribution whose bytes are not going to be used costs no I/O. Consulting the override after the read had two consequences, both wrong for a contribution the operator has fully replaced -- the backing source was still read or awaited every turn, and a source that raised under fail_closed took the whole compile down even though its bytes were about to be discarded.

A refused override (one aimed at a contribution that did not declare operator_editable) still returns None here, so the source is read and its own text renders. The refusal is recorded either way.

Source code in src/symfonic/capabilities/prompting/admission.py
def overridden(
    request: PromptCompileRequest,
    contribution: PromptContribution,
    diagnostics: list[PromptDiagnostic],
) -> SourceResolution | None:
    """The operator's text for this contribution, decided **before** the read.

    ``None`` means "no authorised override applies, read the source". The
    ordering is the point, and it is the same one :mod:`.admission` states for
    the render gate: a contribution whose bytes are not going to be used costs
    no I/O. Consulting the override *after* the read had two consequences, both
    wrong for a contribution the operator has fully replaced -- the backing
    source was still read or awaited every turn, and a source that raised under
    ``fail_closed`` took the whole compile down even though its bytes were
    about to be discarded.

    A *refused* override (one aimed at a contribution that did not declare
    ``operator_editable``) still returns ``None`` here, so the source is read
    and its own text renders. The refusal is recorded either way.
    """
    read = operator_override(request, contribution, diagnostics)
    return None if read is None else SourceResolution(read=read)

permitted

permitted(request: PromptCompileRequest, admitted: tuple[PromptContribution, ...], diagnostics: list[PromptDiagnostic]) -> tuple[PromptContribution, ...]

Step 3b: what the turn's principal is allowed to be shown (S01).

Recorded rather than silently filtered, for the reason _admit records its own declines: a contribution missing from the prompt and a contribution withheld from this principal are different facts.

Source code in src/symfonic/capabilities/prompting/admission.py
def permitted(
    request: PromptCompileRequest,
    admitted: tuple[PromptContribution, ...],
    diagnostics: list[PromptDiagnostic],
) -> tuple[PromptContribution, ...]:
    """Step 3b: what the turn's principal is allowed to be shown (S01).

    Recorded rather than silently filtered, for the reason ``_admit`` records
    its own declines: a contribution missing from the prompt and a contribution
    withheld from this principal are different facts.
    """
    kept: list[PromptContribution] = []
    for contribution in admitted:
        missing = contribution.agent_permissions - request.principal_grants
        if missing:
            diagnostics.append(
                PromptDiagnostic(
                    "permission",
                    contribution.contribution_id,
                    f"withheld: it requires {sorted(missing)} which this principal was "
                    f"not granted (granted: {sorted(request.principal_grants)})",
                )
            )
            continue
        kept.append(contribution)
    return tuple(kept)