symfonic.kernel.dispatch¶
dispatch ¶
Running the stages the plan compiled — W2/1b.
Before this, StageProgram was compiled, totally ordered, carried in the
plan, and executed by nothing. invoker.finish said so:
The kernel compiles a total StageProgram but dispatches no stage callable
yet ... a registered stage affects ordering and diagnostics and nothing
else.
This is the "and nothing else" ending. One phase's stages run here, in compiled order, each as a transactional unit: invoke, validate, apply, publish.
Four decisions are frozen into this module, and each is written where it is enforced rather than in a document that drifts:
- A stage returns; it does not mutate (decision 2). The dispatcher owns application, so "did this stage change anything?" has one answer and one place that knows it.
- Events ride with the result (decision 3). One execution publishes one contiguous block, after its contribution is applied. A stage that fails publishes nothing — no event announcing a change that never landed.
emitsis a checked upper bound. A stage may emit fewer events than it declared; an undeclared event is a contract violation. This is what stopsCompiledStage.emitsfrom becoming the next declared-and-never-read field.- Every executable stage resolves exactly one handler. The distinction is executable stage vs structural marker, not kernel-owned vs capability-owned — a kernel stage that runs is as obliged as anyone's.
Cardinality (decision 1) is fixed per phase and lives at the call sites in
runner, not here: this module runs one phase's stages once, and the caller
decides how often a phase happens.
StageDispatcher ¶
Runs one phase's compiled stages, in order, against their handlers.
Source code in src/symfonic/kernel/dispatch.py
run_phase
async
¶
run_phase(program: StageProgram, phase: Phase, *, context_for: Callable[[CompiledStage], Any], apply: Callable[[CompiledStage, Any], Awaitable[None]] | None = None, publish: Callable[[Any], Awaitable[None]] | None = None, grants: frozenset[str] | None = None, kind: StageKind | None = None, stop_on_failure: bool = False) -> tuple[StageTrace, ...]
Execute the stages compiled into phase, in order, until one's
contribution cannot be applied.
By default a handler that fails stops nothing: it applied no returned
contribution, so the stages after it run on
untouched state. An apply that fails failed partway through adopting a
contribution, so the phase ends there rather than handing the next stage
its remains. Commit-capable phases opt into stop_on_failure because
their handlers can perform effects without an apply hook.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
program
|
StageProgram
|
the plan's compiled stage program. |
required |
phase
|
Phase
|
which phase to run. Stages outside it are untouched. |
required |
context_for
|
Callable[[CompiledStage], Any]
|
builds the per-stage context. A callable rather than a value because a phase that runs per tool call needs a different context per stage invocation. |
required |
apply
|
Callable[[CompiledStage, Any], Awaitable[None]] | None
|
applies an APPLIED result's contribution. |
None
|
publish
|
Callable[[Any], Awaitable[None]] | None
|
publishes one event. |
None
|
kind
|
StageKind | None
|
run only stages of this kind, in compiled order. |
None
|
grants
|
frozenset[str] | None
|
the invocation's effect grants (STG-8). A stage declaring an
effect outside them is not invoked — the check is before the
call, because a handler that ran already performed whatever it
was going to perform. |
None
|
stop_on_failure
|
bool
|
stop before any later stage after a FAILED trace, including an absent handler or denied grant. FINALIZE uses this because handlers can commit effects directly. Other phases retain their existing continuation policy by default. |
False
|
Returns:
| Name | Type | Description |
|---|---|---|
One |
StageTrace
|
class: |
...
|
— a prefix of the phase when an apply failed, not one trace per |
|
tuple[StageTrace, ...]
|
compiled stage. Nothing is fabricated for the stages that never |
|
started |
tuple[StageTrace, ...]
|
:class: |
tuple[StageTrace, ...]
|
of the four outcomes truthfully says "never reached". The terminal |
|
tuple[StageTrace, ...]
|
FAILED trace is what explains why the prefix is short, and exposing |
|
tuple[StageTrace, ...]
|
skips deliberately would need a status this model does not have. |
Source code in src/symfonic/kernel/dispatch.py
80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 | |
StageHandler ¶
Bases: Protocol
What a stage actually is: a callable returning a :class:StageResult.
Async by contract even when the work is synchronous. A handler that could be either forces every call site to branch, and the one place that forgets is the one that blocks the loop.
StageTrace
dataclass
¶
StageTrace(stage_id: str, phase: Phase, capability: str, outcome: StageOutcome, reason: str = '', declared_events: tuple[str, ...] = (), returned_events: int = 0, published_events: int = 0, counts: Mapping[str, int] = (lambda: EMPTY_COUNTS)(), error: BaseException | None = None)
One stage execution, as the record a reader needs to explain a turn.
declared/returned/published are separate on purpose. Equal
numbers are the boring case; the interesting bugs are a stage that declared
events and returned none, or returned some the dispatcher refused to
publish.
inert
property
¶
Ran, changed nothing, emitted nothing.
Not an error — a stage can legitimately have nothing to do. But it is the shape worth counting, because a capability whose stages are always inert is a capability that is wired and doing nothing, which is exactly the state RCH-1 catches statically and this catches at runtime.
UndeclaredEventError ¶
Bases: Exception
A stage returned an event kind it never declared in emits.