symfonic.capabilities.prompting¶
prompting ¶
The prompt and context capability (T3.2.1, REQ-S3.2).
One compiler owns everything that decides what the model reads: domain
instructions, the JIT and stratified strategies, the stratigraphic layers,
standing contributions and their sources, scope boundaries and inheritance,
render gates, budgets, and cache annotations. Those nine concerns used to live
in five packages that each knew a little about the others' ordering; here they
are one pipeline with one entry point, :func:compile_prompt.
The three deliverables of this task map onto three modules:
- :mod:
.contractsโ the prompt contribution contract: what a capability declares, and (just as load-bearing) what it may not. - :mod:
.cacheโ the cache-region model: derived regions, the volatile-content invariant, and the non-increasing TTL ladder. - :mod:
.compilerโ the prompt/context compiler: accept, validate, admit, resolve, gate, order, budget, region, freeze.
Nothing here imports the kernel runtime, a provider SDK, a memory backend, or a
tokenizer download. The one outward edge is :mod:.assembly, which speaks
kernel contracts only.
AsyncContributionSource ¶
Bases: Protocol
The asynchronous half of the same port (S01, TA8.51).
read stays exactly what it was. This is a second member rather than a
coroutine version of the first, and the choice is the whole design:
- every source written against :class:
ContributionSourcekeeps working, and the synchronous :func:~.compiler.compile_promptkeeps compiling them without an event loop; - a source whose content lives behind an
await-- the shape everysymfonic.core.prompt.blockssource already has, whereBlockSource.loadis a coroutine functionPromptBlockResolverawaits -- declaresareadand is awaited by :func:~.compiler.compile_prompt_async.
A synchronous bridge was the other option and it was refused on purpose:
asyncio.run inside read explodes on a loop that is already running,
and a thread hop per contribution per turn would trade a missing port for a
latency defect on every turn that has nothing to do with blocks.
A source may declare both. Declaring both means the synchronous door can
still compile it; declaring only aread means the synchronous door
refuses it by name rather than dropping it silently.
BudgetReport
dataclass
¶
BudgetReport(limit: int | None = None, total_tokens: int = 0, admitted: tuple[str, ...] = (), dropped: tuple[str, ...] = ())
The budget arithmetic, kept alongside the prompt it explains.
CacheDirective
dataclass
¶
One contribution's (or region's) cache annotation.
ttl without cacheable is inert by construction rather than by
convention: :meth:marker reads cacheable first, so a directive that
names a tier it never earned cannot leak a marker onto the wire.
marker ¶
The wire-level cache_control value, or None when uncached.
The marker is reconstructed from ttl rather than passed through
from anything a caller built, so the wire shape stays canonical however
the directive was assembled.
Source code in src/symfonic/capabilities/prompting/cache.py
CacheRegion
dataclass
¶
CacheRegion(index: int, layer: Layer, directive: CacheDirective, text: str, contribution_ids: tuple[str, ...], digest: str)
One cache-addressable span of the compiled prompt.
annotation ¶
This region as a provider content block, marker included when cached.
Source code in src/symfonic/capabilities/prompting/cache.py
CacheTtl ¶
Bases: StrEnum
The two TTL tiers a cache breakpoint can advertise.
CompiledPrompt
dataclass
¶
CompiledPrompt(strategy: str, regions: tuple[CacheRegion, ...] = (), contributions: tuple[RenderedContribution, ...] = (), budget: BudgetReport = BudgetReport(), diagnostics: tuple[PromptDiagnostic, ...] = (), digest: str = '')
One compiled prompt: regions, survivors, budget, diagnostics, digest.
selected_line_candidates
property
¶
Non-authoritative hydration rows that survived the compiler budget.
cache_annotations ¶
The prompt as provider content blocks, cache markers included.
region_of ¶
The region carrying contribution_id, or None when it was dropped.
Source code in src/symfonic/capabilities/prompting/values.py
ContextStrategy ¶
Bases: StrEnum
How much context the compiler assembles, and whether it may be cached.
ContributionScope ¶
Bases: StrEnum
How widely one contribution's content is shared.
ContributionSource ¶
Bases: Protocol
Reads the current content of one contribution for one scope.
scope_aware and offline_safe are declared members, so an
isinstance check requires them to be present: a source that never
decided whether it keys on scope does not satisfy this protocol.
DomainPersona
dataclass
¶
DomainPersona(name: str, role: str = 'assistant', tone: str = 'helpful', description: str = '', onboarding: Sequence[str] = tuple())
Who a domain's assistant is, in the four fields that reach the model.
Frozen for the reason load_plugin() disappears: a plan compiled from
mutable inputs is a plan nobody can trust, and a persona that could be
edited after composition would reintroduce that one field at a time.
Only name is required, because only name was. A domain that names
itself and says nothing else is the ordinary case, and requiring a
checklist to get an identity would make the smallest scaffold carry the
largest one's shape.
ExtractionDirectiveSource
dataclass
¶
ExtractionDirectiveSource(template_path: Path | None = None, cadence: int = 1, scope_path: str = '', untrusted: bool = False, offline_safe: bool = True, scope_aware: bool = True)
Render a custom or bundled extraction directive on its declared cadence.
GateResult
dataclass
¶
What a gate decided for one contribution.
Guardrail
dataclass
¶
One rule a deployment wants honoured, and the tags that watch it.
Frozen, like every other composition input: a plan compiled from mutable rules is a plan whose rules nobody can quote afterwards.
HeuristicTokenEstimator
dataclass
¶
Characters-per-token arithmetic โ the offline default.
Deliberately an estimate with a name that says so. A provider-exact counter is a legitimate substitution (T3.2.2 owns that seam), but it must arrive as a bound port rather than as a network call this module makes, which is why the estimator is a protocol and this is only its default.
InMemoryLastKnownGood
dataclass
¶
A per-process store of the last revision each contribution served.
Keyed by :func:~.boundaries.isolation_key, so one tenant's stored profile
can never be served into another tenant's prompt โ the failure mode that
makes a "just use the cached copy" fallback dangerous in a shared process.
JitManifestSource
dataclass
¶
JitManifestSource(manifest: Sequence[str], policy: RegionalPromptPolicy, untrusted: bool = False, offline_safe: bool = True, scope_aware: bool = False)
Render a fixed tool manifest under the policy's independent JIT cap.
The caller supplies the manifest it has already authorised for the turn. This source deliberately does not discover tools or inspect providers.
Layer ¶
Bases: StrEnum
The three stratigraphic layers, most stable first.
ProceduralSkillsSource
dataclass
¶
ProceduralSkillsSource(layer: Any, scope: Any, cue: str = '', limit: int = DEFAULT_LIMIT, include_drafts: bool = False, untrusted: bool = True, include_platform: bool = False, offline_safe: bool = False, scope_aware: bool = True)
The active skills for a turn's scope, rendered for the prompt.
aread
async
¶
The skills this scope may act on, or nothing at all.
A store fault renders an empty block rather than raising. The turn has an answer to give and a missing procedure block costs the model a hint; a raised exception costs the user the turn.
Source code in src/symfonic/capabilities/prompting/procedural.py
PromptBudget
dataclass
¶
The ceiling one compiled prompt runs under.
None means unbounded, which is the honest default: inventing a ceiling
the adopter did not ask for would silently truncate prompts that fit.
narrows ¶
True when this budget is the parent's or a strictly tighter one.
Source code in src/symfonic/capabilities/prompting/budget.py
PromptCompileRequest
dataclass
¶
PromptCompileRequest(instructions: str | None = None, contributions: tuple[PromptContribution, ...] = (), strategy: ContextStrategy = ContextStrategy.STRATIFIED, budget: PromptBudget = PromptBudget(), policy: RenderPolicy = DEFAULT_POLICY, scope_path: str = '', turn: int = 0, session_id: str = '', last_known_good: InMemoryLastKnownGood | None = None, estimator: object | None = None, principal_grants: frozenset[str] = frozenset(), operator_overrides: Mapping[str, str] | None = None)
Everything one prompt compile is allowed to depend on.
PromptContribution
dataclass
¶
PromptContribution(contribution_id: str, source: ContributionSource, capability: str = 'prompting', layer: Layer = Layer.L1, tier: TrustTier = TrustTier.OPERATING, scope: ContributionScope = ContributionScope.DEPLOYMENT, order: int = 0, inherit: bool = True, pinned: bool = False, requires_hydration: bool = False, jit_only: bool = False, cache: CacheDirective | None = None, on_source_failure: SourceFailurePolicy | None = None, agent_permissions: frozenset[str] = frozenset(), operator_editable: bool = False, label_prefix: str = '', profile_fields: frozenset[str] = frozenset())
One capability's declaration of context it contributes to the prompt.
failure_policy
property
¶
The declared policy, or the tier's default when none was declared.
validate ¶
Shape validation, performed the moment a contribution is declared.
Resolution against the whole contribution set (duplicate ids, ordering, budget) happens later in the compiler, where the set is knowable.
Source code in src/symfonic/capabilities/prompting/contracts.py
PromptContributor ¶
Bases: Protocol
What a capability registers with the prompt compiler.
A contributor is asked once per compile and answers with declarations, not with text: the compiler decides ordering, gating, budgeting, and caching over the whole set, which it can only do if nothing has rendered yet.
PromptDiagnostic
dataclass
¶
One recorded compile decision.
kind is the stage that made it (input, strategy, source,
gate, budget, cache), subject the contribution it concerns.
Diagnostics are data rather than log lines because the caller that needs
them most โ a test asserting a block was dropped for the right reason โ has
no access to a log handler.
PromptingCapability
dataclass
¶
PromptingCapability(sources: Sequence[Any] = (), priority: int = -900, options: dict[str, Any] = dict())
Compile the layered prompt and contribute it to the turn's assembly.
Usage is the whole point of the phase::
Agent(provider, capabilities=[PromptingCapability(sources=[...])])
sources are the prompt contributions this capability compiles โ the
same values the capability's own compiler has always taken. They are held
on the config rather than read from a global, so two agents in one process
do not share a prompt.
contribute ¶
Declare the stage and supply the handler that answers it.
Both in one value, which is what lets fold_contributions refuse a
declaration nothing runs. The compile happens here, at contribution
time, so the stage descriptor can carry the compiled digest โ that is
what makes "this plan compiled this prompt" checkable from the plan
alone, without the plan carrying the prompt text.
Source code in src/symfonic/capabilities/prompting/capability.py
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 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | |
RegionRow ¶
Bases: NamedTuple
One rendered contribution, as the region planner sees it.
A narrow tuple rather than the full rendered value: the planner must not be able to read a tier, a trust flag, or a source, because any of those would become a second place cache decisions get made.
RegionalBudgetSource
dataclass
¶
RegionalBudgetSource(source: _ReadableSource, policy: RegionalPromptPolicy, region: str = 'hms_system_prompt')
Apply an advisory or strict boundary to an authored prompt source.
RegionalPromptPolicy
dataclass
¶
RegionalPromptPolicy(hms_system_prompt_token_budget: int | None = None, jit_manifest_token_budget: int = 0, hms_budget_mode: Literal['warn', 'strict'] = 'warn', on_hms_budget_exceeded: BudgetObserver | None = None, domain_description_max_chars: tuple[int, int] = (2000, 400))
The explicit legacy-region limits a native composition can honour.
hms_system_prompt_token_budget guards only the authored HMS source.
jit_manifest_token_budget controls only the manifest source. The
compiler's own PromptBudget remains a distinct total-prompt ceiling.
RenderPolicy
dataclass
¶
RenderPolicy(max_learned_chars: int = 500, render_when: Callable[[PromptContribution], Any] | None = None)
The limits applied to learned content, and the set-wide render gate.
Authored content is deliberately uncapped: it is operator configuration, already reviewed by whoever deployed it, and a cap there would silently delete instructions the operator can see in their own config file.
render_when is S01's counterpart to the legacy per-block predicate, and
it lives here rather than on the contribution for the reason
PROMPT_BLOCK_CONTRACT published ahead of the port: the kernel gate is a
policy over the whole set, so a host predicate migrates as a policy rule.
It is consulted before any source is read, so a gated-off contribution
costs no I/O -- the same ordering PromptBlockResolver.resolve_block
documents. It may return an awaitable, in which case only the asynchronous
door can honour it; the synchronous one refuses by name.
RenderedContribution
dataclass
¶
RenderedContribution(contribution_id: str, layer: Layer, tier: TrustTier, text: str, revision: str, tokens: int, directive: CacheDirective, pinned: bool, degraded: bool = False, selected_line_candidates: tuple[object, ...] = ())
One contribution after gating, with its cost and cache annotation fixed.
SourceFailurePolicy ¶
Bases: StrEnum
What the compiler does when a contribution's source cannot be read.
SourceRead
dataclass
¶
SourceRead(text: str, revision: str = '', untrusted: bool = False, fields: Mapping[str, str] | None = None)
What a source answered.
untrusted is the source's own declaration about its payload. A source
that fetches a web page or reads a user-writable row says so here, and the
render gate then refuses to place it at an authored tier.
SourceRequest
dataclass
¶
SourceRequest(contribution_id: str, scope_path: str = '', turn: int = 0, session_id: str = '', scope: ContributionScope = ContributionScope.DEPLOYMENT)
What a source is asked for: one contribution, in one scope, on one turn.
SourceResolution
dataclass
¶
SourceResolution(read: SourceRead | None, diagnostics: tuple[PromptDiagnostic, ...] = (), degraded: bool = False)
The outcome of reading one contribution's source.
StaticSource
dataclass
¶
StaticSource(text: str, revision: str = 'static', untrusted: bool = False, scope_aware: bool = False, offline_safe: bool = True)
A source whose content is fixed at declaration time.
Deployment-global by construction: scope_aware is False because one
literal string is the same for every tenant, and saying so is what lets the
scope-pairing check reject a tenant-scoped block backed by it.
TokenEstimator ¶
Bases: Protocol
Estimates the token cost of a rendered string.
TrustTier ¶
Bases: StrEnum
Authority tiers, highest first.
platform and operating are authored: a human wrote that text.
profile and session are learned: their content is aggregated from
what the system recorded about a user, so it is attacker-influenced input.
admit_within_budget ¶
admit_within_budget(rows: Sequence[BudgetRow], budget: PromptBudget) -> tuple[tuple[BudgetRow, ...], BudgetReport]
Admit as much as the budget allows, dropping in a fixed, total order.
The returned rows keep their original (compiled) order; only membership is decided here. Reordering the prompt to fit would change what the model reads for reasons the adopter never declared.
Source code in src/symfonic/capabilities/prompting/budget.py
bounded_domain_description ¶
Return the model-visible domain description for the selected surface.
Source code in src/symfonic/capabilities/prompting/regional.py
cache_directive_for ¶
The cache annotation this contribution carries under strategy.
JIT caches nothing โ including a contribution that declared a directive,
since a prefix that changes shape per turn cannot be a cache hit and the
breakpoint would be pure cost. Under stratified a declared directive
wins, and the default is derived from volatility: stable layers cache at
the provider's default tier, the volatile layer never does.
Source code in src/symfonic/capabilities/prompting/strategies.py
compile_prompt ¶
Compile one prompt from one request. Reads sources; performs nothing else.
The synchronous door onto the one sequence. A contribution whose source
only offers aread is refused here by name (see
:func:~.sources.resolve_source) rather than dropped.
Source code in src/symfonic/capabilities/prompting/compiler.py
compile_prompt_async
async
¶
The same sequence, with step 4 awaited (S01, TA8.51).
This is not the forbidden second pipeline, and the difference is worth
stating because the module docstring above forbids one by name. A second
pipeline is a second place ordering, gating, budgeting and region planning
are decided; those steps are :func:_prepare and :func:_finish here, and
both doors call the same two functions with the same arguments. What
differs is one line: whether the source read is called or awaited. A
contribution's position in the prompt cannot depend on which door compiled
it, because neither door decides positions.
The alternative -- asyncio.run or a thread hop inside a synchronous
read -- was refused for the reason S01 states: it would block the loop
every turn, trading a missing port for a latency defect.
Source code in src/symfonic/capabilities/prompting/compiler.py
compiled_instructions ¶
The compiled prompt as kernel instructions, or None when it is empty.
None rather than "" because the kernel's instructions field is
optional, and an empty string is a system prompt that says nothing โ
indistinguishable downstream from a deployment that meant to send one and
lost it.
Source code in src/symfonic/capabilities/prompting/assembly.py
default_failure_policy ¶
The policy a contribution of tier gets when it declares none.
fail_closed for the authored tiers: losing BOUNDARIES or RULES removes
the agent's constraints. omit for the learned tiers: losing a profile
costs personalisation for a turn, which does not justify failing the turn.
Source code in src/symfonic/capabilities/prompting/contracts.py
derive_child_request ¶
derive_child_request(parent: PromptCompileRequest, *, contributions: Sequence[PromptContribution] | None = None, budget: PromptBudget | None = None, scope_path: str | None = None, instructions: str | None = None, principal_grants: frozenset[str] | None = None, operator_overrides: Mapping[str, str] | None = None) -> PromptCompileRequest
Derive a delegated child's request by narrowing parent.
Omitting contributions inherits the parent's inheritable set โ the safe
default, since a child silently losing its boundaries is the failure that
matters. Naming them narrows: every named id must already be inheritable on
the parent, so a child cannot promote a contribution its parent marked
non-inheritable by re-declaring it under the same name.
The two S01 access axes inherit, and the rule is stated rather than
implied. Delegation here is the same principal on the same turn, so a
child that dropped principal_grants would silently withhold from the
subagent a contribution the parent was entitled to, and one that dropped
operator_overrides would serve the source's own text where the operator
had replaced it โ a divergence between what the parent and the child read
that no diagnostic would explain. Inheriting is therefore the default, and
both axes narrow the same way everything else here does:
principal_grantsmust be a subset of the parent's. A child cannot hand itself a verb the turn's principal was not granted.operator_overridesmust be a sub-mapping of the parent's โ the same text under a subset of the same ids. A child may drop an override; it may not introduce one, nor change the text of one, because either would make the operator's decision for the parent turn something the child rewrites.
Delegation is a block-visibility boundary elsewhere in this repo
(tests/agent/subagents/test_child_block_lockdown.py), and that boundary
is drawn by inherit on the contribution, which is enforced above. These
two axes are about the principal and the operator, not about which blocks a
child may see.
Source code in src/symfonic/capabilities/prompting/request.py
extraction_contribution ¶
Declare the directive in L1, after stable system instructions.
Source code in src/symfonic/capabilities/prompting/extraction.py
guardrail_sources ¶
The prompt contributions guardrails render through.
Ordered by descending priority, because a budget drops from the end and priority already existed on this value while deciding nothing. If two rules cannot both fit, the deployment has said which one matters.
No guardrails contribute no source -- not an empty section. A heading with nothing under it reads as "this deployment has no rules", which is a claim rather than an absence.
Source code in src/symfonic/capabilities/prompting/guardrails.py
hms_source_for_domain ¶
hms_source_for_domain(state: Mapping[str, object], domain: object, policy: RegionalPromptPolicy) -> _ReadableSource
Bind the supported domain.description field into an HMS source.
The caller still supplies the template's other authored values. Domain fields that belong to tools or governance are intentionally not projected into this prompt source.
Source code in src/symfonic/capabilities/prompting/regional.py
inheritable ¶
The subset of contributions a delegated child may see.
is_async_source ¶
is_sync_source ¶
True when source offers the synchronous member.
A coroutine read is deliberately not one: it cannot be called on
the synchronous door and calling it on the asynchronous one would produce a
coroutine where the compiler expects bytes. See :func:is_coroutine_read.
Source code in src/symfonic/capabilities/prompting/ports.py
is_volatile ¶
isolation_key ¶
The canonical storage key for one contribution in one scope.
The scope half is the full root-first path verbatim, not a tenant id: keying on the tenant alone would collapse every brand and conversation under an organisation into one bucket, so two sibling scopes would share โ and overwrite โ one another's stored revisions.
Source code in src/symfonic/capabilities/prompting/boundaries.py
jit_manifest_contribution ¶
Declare the manifest in L1; callers use it only in a JIT composition.
Source code in src/symfonic/capabilities/prompting/regional.py
layer_index ¶
neutralise_delimiters ¶
Replace every delimiter-shaped sequence in value.
Two rules, because "looks like the closing delimiter" is a question with a lexical answer and a visual one, and content that defeats either has escaped the wrapper.
Source code in src/symfonic/capabilities/prompting/gates.py
normalise_ttl_ladder ¶
Promote earlier cached regions to the highest tier appearing to their right.
Right-to-left scan tracking the high-water tier. Uncached regions are skipped โ they carry no marker and so do not participate in the provider's ladder โ which is why a volatile region between two cached ones does not reset the rule.
Source code in src/symfonic/capabilities/prompting/cache.py
ordering_key ¶
Total order over contributions: layer, then declared order, then id.
Totality is the point. Two contributions that tie on layer and order still have a defined relative position, so the compiled prompt is byte-stable across runs and across dict/set iteration orders upstream.
Source code in src/symfonic/capabilities/prompting/contracts.py
persona_sources ¶
The contribution sources persona renders through.
Ordered identity first: it is the shortest, the most stable, and the one whose absence changes how every other contribution reads. A budget that has to drop something should drop the description before it drops who the assistant is.
Source code in src/symfonic/capabilities/prompting/persona.py
plan_regions ¶
Group rows into regions, breaking wherever the annotation changes.
Rows arrive already ordered by the compiler. Empty rows are skipped rather than emitted: a region whose only content is an empty string still costs a breakpoint, and breakpoints are the scarce resource here.
Source code in src/symfonic/capabilities/prompting/cache.py
prompt_stage_descriptor ¶
prompt_stage_descriptor(compiled: CompiledPrompt, *, priority: int = -900, awaited_sources: int = 0, deferred_render_gate: bool = False) -> StageDescriptor
The stage this capability contributes, carrying the compile's identity.
No effects and no emitted events are declared: this is a compilation stage (STG-7), a pure function of plan, request and the resolved-input snapshot. A stage that claimed an effect it does not perform would widen its own admission for nothing, and under the reformulated STG-7 it would also be refused outright.
static_prompt_digest identifies this compile, not the final prompt.
The name is deliberate and the distinction is not pedantic. The digest
covers sources โ what the capability was configured with, compiled here
at contribution time. It does not cover:
Agent(instructions=...), which the kernel's own stage puts on the assembly and this capability composes onto afterwards;- anything a resolution stage put in the turn's snapshot, which by definition does not exist yet when this descriptor is built;
- any contribution whose source must be awaited (S01).
contribute()is synchronous, so anaread-only source cannot be read here at all.awaited_sourcescounts them in the same config, because "the digest does not cover N of my sources" is a fact a plan reader needs and a silently narrower digest is exactly the false "checkable from the plan alone" claim the paragraph below retires. - the request's
render_whenpolicy, when one is set (S01).deferred_render_gaterecords that the digest compile ran without it. The gate is a turn-time decision over the whole set and may be awaited, so the synchronous declaration-time compile cannot honour it; running it here would refuse the plan outright for a policy the turn supports.
The field was called prompt_digest and read as "this plan compiled this
prompt, checkable from the plan alone". That claim was already false before
memory existed โ the adopter's instructions were outside it โ and the
reformulated STG-7 makes the gap structural rather than accidental. Naming
it for what it actually covers is the honest half; recording the final
digest is a runtime artifact and needs a carrier the kernel does not have
yet (see the debt table in the phase-4 sizing note).
Source code in src/symfonic/capabilities/prompting/assembly.py
regional_hms_contribution ¶
regional_hms_contribution(source: _ReadableSource, policy: RegionalPromptPolicy, *, order: int = -1000) -> PromptContribution
Declare the HMS section as a pinned authored L0 region.
Source code in src/symfonic/capabilities/prompting/regional.py
regional_prompting_capability ¶
regional_prompting_capability(hms_source: _ReadableSource, policy: RegionalPromptPolicy, *, strategy: Literal['jit', 'stratified'] = 'stratified', manifest: Sequence[str] = ()) -> object
Build the supported regional composition with explicit JIT intent.
A non-zero manifest budget is only effective when a JIT manifest is
supplied and the composition declares strategy='jit'. Refusing the
other combinations prevents a deployment from carrying a configuration
value that cannot affect the model request.
Source code in src/symfonic/capabilities/prompting/regional.py
render_contribution ¶
render_contribution(contribution: PromptContribution, read: SourceRead, policy: RenderPolicy = DEFAULT_POLICY) -> GateResult
Gate one read and return the text that may render, or None.
Raises only on a trust/tier mismatch: that is the one failure where continuing means putting attacker-influenced text where the model reads operator instruction. Every other refusal drops with a diagnostic.
Source code in src/symfonic/capabilities/prompting/gates.py
resolve_source ¶
resolve_source(contribution: PromptContribution, *, scope_path: str = '', turn: int = 0, session_id: str = '', store: InMemoryLastKnownGood | None = None) -> SourceResolution
Read one contribution's source, applying its declared failure policy.
The synchronous door. A source that offers only aread is refused by
name here rather than dropped: a silent drop is the defect this whole seam
exists to avoid, and a compile that accepted the declaration and rendered
none of it would be exactly the "accepted, validated, and never resolved"
failure TA8.38 refused to ship.
Source code in src/symfonic/capabilities/prompting/sources.py
resolve_source_async
async
¶
resolve_source_async(contribution: PromptContribution, *, scope_path: str = '', turn: int = 0, session_id: str = '', store: InMemoryLastKnownGood | None = None) -> SourceResolution
Read one contribution's source, awaiting it when it asks to be awaited.
The asynchronous door, and the only new failure mode it introduces is
cancellation, which is named ahead of the policy arm on purpose.
CancelledError derives from BaseException rather than Exception,
so the arm below would not have caught it anyway -- it is written out
because a later edit widening that arm to BaseException would otherwise
absorb a cancelled turn into on_source_failure and leave the turn
running. PromptBlockResolver.resolve_block names the same hazard in the
same words, and this seam is the one that brings that hazard onto the kernel
line.
Source code in src/symfonic/capabilities/prompting/sources.py
resolve_strategy ¶
Resolve the strategy from an explicit name, falling back to the legacy flag.
Precedence is stated once, here, so the facade and the compiler cannot disagree about which strategy a given configuration selects โ the classic "two factories, two answers" defect.
Source code in src/symfonic/capabilities/prompting/strategies.py
sensitive_terms ¶
The terms a governance stage should watch, from the rules themselves.
MetacognitionStage takes terms and guardrails carry tags; deriving one
from the other is what stops them drifting. Threaded by hand, a tag added
to a rule and forgotten in the stage's configuration is a rule that renders
and is never enforced.
Sorted and deduplicated, because an unordered set makes a stage's configuration differ between runs for no reason a reader can see.
Source code in src/symfonic/capabilities/prompting/guardrails.py
strategy_admits ¶
True when strategy assembles this contribution at all.
The only axis is hydration: JIT declines contributions whose content only exists after a retrieval pass, because performing that pass is exactly what JIT exists to avoid.
Source code in src/symfonic/capabilities/prompting/strategies.py
to_prompt_assembly ¶
to_prompt_assembly(compiled: CompiledPrompt, *, prompt: str, attachments: tuple[Any, ...] = (), history: tuple[Any, ...] = (), append_user_message: bool = True) -> PromptAssembly
Project a compiled prompt into the kernel's prompt-assembly value.