Skip to content

symfonic.kernel.rungs

rungs

The kernel-facing names for the three per-round capability rungs.

Split out of :mod:symfonic.kernel.invoker when TA8.50's sixth rung took that module past the repository's 300-line budget. The split is not arbitrary: invoker is the phase operations the runner drives, and this is the thin forwarding layer between them and the three rung modules (:mod:~symfonic.kernel.post_model, :mod:~symfonic.kernel.pre_tool, :mod:~symfonic.kernel.post_tool) that hold the dispatch policy.

They exist at all so a caller reaches one object rather than two -- the same reason open_turn_dispatched does -- and none of them takes a RequestContext: the turn is already open by the time any of them runs, and a second bind would raise.

transcript is threaded through unread. At pre-tool it is HK2's requirement (a capability recording a continuable state needs the messages as well as the reserved ids); at post-tool it is the round already closed by record_tools, which is what makes that seam the place a policy can change what the next round reads.

CapabilityRungs

The four rungs the loop dispatches, as methods on the kernel object.

__slots__ = () for the reason :class:~symfonic.kernel.invoker.InvocationKernel declares it: one shared instance serves unlimited concurrent invocations only because it cannot grow an attribute.

dispatch_finalize async

dispatch_finalize(plan: InvocationPlan, request: TurnRequest, turn: Any, *, resolved: Any = None, transcript: Any = None, handlers: Mapping[str, Any] | None = None) -> tuple[Any, ...]

Dispatch finalize for a turn that ran to completion (TA8.61).

Source code in src/symfonic/kernel/rungs.py
async def dispatch_finalize(
    self,
    plan: InvocationPlan,
    request: TurnRequest,
    turn: Any,
    *,
    resolved: Any = None,
    transcript: Any = None,
    handlers: Mapping[str, Any] | None = None,
) -> tuple[Any, ...]:
    """Dispatch ``finalize`` for a turn that ran to completion (TA8.61)."""
    return await run_finalize(
        plan, request, turn,
        resolved=resolved, transcript=transcript, handlers=handlers,
    )

dispatch_post_model async

dispatch_post_model(plan: InvocationPlan, request: TurnRequest, turn: Any, *, resolved: Any = None, transcript: Any = None, handlers: Mapping[str, Any] | None = None) -> tuple[Any, ...]

Dispatch post-model for one completed round (#23 slice 1).

Source code in src/symfonic/kernel/rungs.py
async def dispatch_post_model(
    self,
    plan: InvocationPlan,
    request: TurnRequest,
    turn: Any,
    *,
    resolved: Any = None,
    transcript: Any = None,
    handlers: Mapping[str, Any] | None = None,
) -> tuple[Any, ...]:
    """Dispatch ``post-model`` for one completed round (#23 slice 1)."""
    return await run_post_model(
        plan, request, turn, resolved=resolved, transcript=transcript,
        handlers=handlers,
    )

dispatch_post_tool async

dispatch_post_tool(plan: InvocationPlan, request: TurnRequest, turn: Any, requests: Sequence[Any], outcomes: Sequence[Any], *, resolved: Any = None, transcript: Any = None, handlers: Mapping[str, Any] | None = None) -> tuple[Any, ...]

Dispatch post-tool for one round's completed calls (TA8.50).

The sixth rung, and the one TA8.39 found declared and dispatched by nobody. outcomes is what the tool port returned, positionally aligned with requests.

Source code in src/symfonic/kernel/rungs.py
async def dispatch_post_tool(
    self,
    plan: InvocationPlan,
    request: TurnRequest,
    turn: Any,
    requests: Sequence[Any],
    outcomes: Sequence[Any],
    *,
    resolved: Any = None,
    transcript: Any = None,
    handlers: Mapping[str, Any] | None = None,
) -> tuple[Any, ...]:
    """Dispatch ``post-tool`` for one round's completed calls (TA8.50).

    The sixth rung, and the one TA8.39 found declared and dispatched by
    nobody. ``outcomes`` is what the tool port returned, positionally
    aligned with ``requests``.
    """
    return await run_post_tool(
        plan, request, turn, requests, outcomes,
        resolved=resolved, transcript=transcript, handlers=handlers,
    )

dispatch_pre_model async

dispatch_pre_model(plan: InvocationPlan, request: TurnRequest, *, resolved: Any = None, transcript: Any = None, handlers: Mapping[str, Any] | None = None) -> tuple[Any, ...]

Dispatch pre-model for one round about to be sent (#147).

Source code in src/symfonic/kernel/rungs.py
async def dispatch_pre_model(
    self,
    plan: InvocationPlan,
    request: TurnRequest,
    *,
    resolved: Any = None,
    transcript: Any = None,
    handlers: Mapping[str, Any] | None = None,
) -> tuple[Any, ...]:
    """Dispatch ``pre-model`` for one round about to be sent (#147)."""
    # Imported here, not at module scope, and the reason is measured: the
    # eager import graph sits at 248 modules against a 248.8 ceiling, so a
    # fifth rung module at the top of this file is a footprint regression
    # rather than a style choice. The ceiling does not move without a named
    # review; the import does.
    from symfonic.kernel.pre_model import run_pre_model  # noqa: PLC0415

    return await run_pre_model(
        plan, request, resolved=resolved, transcript=transcript,
        handlers=handlers,
    )

dispatch_pre_tool async

dispatch_pre_tool(plan: InvocationPlan, request: TurnRequest, turn: Any, requests: Sequence[Any], *, resolved: Any = None, transcript: Any = None, handlers: Mapping[str, Any] | None = None) -> tuple[tuple[Any, ...], tuple[Any, ...]]

Dispatch pre-tool, and answer with the calls to execute.

(traces, requests). The second element is what the loop must run: the reserved calls, or a resolution stage's amendment of them.

Source code in src/symfonic/kernel/rungs.py
async def dispatch_pre_tool(
    self,
    plan: InvocationPlan,
    request: TurnRequest,
    turn: Any,
    requests: Sequence[Any],
    *,
    resolved: Any = None,
    transcript: Any = None,
    handlers: Mapping[str, Any] | None = None,
) -> tuple[tuple[Any, ...], tuple[Any, ...]]:
    """Dispatch ``pre-tool``, and answer with the calls to execute.

    ``(traces, requests)``. The second element is what the loop must run:
    the reserved calls, or a resolution stage's amendment of them.
    """
    return await run_pre_tool(
        plan, request, turn, requests,
        resolved=resolved, transcript=transcript, handlers=handlers,
    )