symfonic.core.prompt.blocks.injection_parts¶
injection_parts ¶
The block-injection vocabulary: placement, layer split, revision identity.
Split out of :mod:symfonic.core.prompt.blocks.injection (398 lines against
the 300-line budget). injection is one class -- PromptBlockInjector,
which resolves, renders and memoises. Everything here is what the three prompt
paths agree on around it: where the fragment is spliced, which layer renders,
what a turn's output looks like, and what identity its bytes have.
Keeping these separate matters beyond the line count: splice_blocks_part
and BLOCKS_PART_INDEX are called by paths that never construct an injector,
and the byte-identity tests drive them directly.
injection re-exports every name below, so existing imports are unchanged.
BLOCKS_PART_INDEX
module-attribute
¶
Where the blocks fragment is spliced into l1_parts.
Index 1 is "after the bundled L1 template body, before the plugin contributions". The value is a named constant because all three prompt paths must agree on it: the same fragment landing at a different offset on the legacy path would make the legacy/stratigraphic comparison tests pass while shipping two different prompts.
CACHED_LAYER
module-attribute
¶
The only layer this module renders into -- the cached L0 + L1 prefix.
DEFAULT_MAX_SCOPES
module-attribute
¶
Scopes retained in the render memo before the oldest is evicted.
NO_BLOCKS
module-attribute
¶
What every path sees when the feature is off. Shared, immutable.
BlockParts
dataclass
¶
One turn's rendered blocks, and the revisions that produced them.
l1 is None rather than "" when nothing renders: the
prompt paths filter on truthiness, and None is the value that
makes "no blocks" and "the feature is off" the same code path.
cached_layer_blocks ¶
cached_layer_blocks(blocks: Iterable[ResolvedBlock]) -> tuple[tuple[ResolvedBlock, ...], tuple[str, ...]]
Split blocks into the L1-layer ones and the names of the rest.
Returns (renderable, skipped_names). skipped_names is
reported by the caller rather than logged here so the message can be
emitted once per block per process instead of once per turn.
Source code in src/symfonic/core/prompt/blocks/injection_parts.py
revision_key ¶
The identity of the bytes blocks would render to.
Every component is durable -- a content hash, a
mem:<count>:<max updated_at> stamp, a UTC date -- so a write
made by another process (the onboarding router, the consolidation
worker) changes the key on this process's next resolve. An
in-process counter would not, and this agent would go on serving a
block the user already corrected elsewhere.
now is normalised through :func:_as_utc -- not
now.astimezone(UTC) -- so a naive clock rolls the date component
over at the same instant the renderer rolls the age phrase over; see
:func:_as_utc.
Source code in src/symfonic/core/prompt/blocks/injection_parts.py
splice_blocks_part ¶
splice_blocks_part(parts: Sequence[str | None], blocks_part: str | None, *, index: int = BLOCKS_PART_INDEX) -> list[str]
Return the non-empty parts with blocks_part spliced in.
The one function all three prompt paths call, so "where do blocks go" has a single answer rather than three that drift.
blocks_part=None -- the default state of a deployment that
declares no blocks -- returns exactly [p for p in parts if p],
which is the pre-existing comprehension at every call site. That is
what makes the byte-identity guarantee structural: with the feature
off there is no added element, no added separator, and no added
branch that could reorder anything.