symfonic.platform.core_preference_policy¶
core_preference_policy ¶
Explicit, per-app enforcement of declared core preferences at egress.
The legacy critic reviewed every draft against DomainTemplate.core_preferences
without the app asking for it. That implicit behaviour is deliberately not
restored. An app that wants its preferences enforced says so here, supplies
the reviewer that judges a draft against them, and gets a native governance
capability whose egress review is consulted on every governed turn.
The defaults are explicit too:
enforce=False(the default) composes exactly what the migration already gave an app -- the sensitive-term watch -- and decides nothing about the preference text. Supplying a reviewer without declaring enforcement is refused rather than guessed at.enforce=Truewith no reviewer, with no preferences, or with a reviewer that has noreviewmethod is refused at composition. An app that declared enforcement never serves a turn that is not enforced.- A reviewer that raises, or returns something other than a
Reflection, asks for a revision. The stage underneath fails open on error, so the fail-closed answer has to be produced here.
The preferences are bound at composition, per app. Nothing is read from a tenant, and the adapter holds no per-scope state. The reviewer receives the turn's governance context; an app needing tenant-specific policy must bind that scope through its own reviewer rather than assuming a scope path is set.
CorePreferencePolicyError ¶
PreferenceReviewer ¶
Bases: Protocol
Judges a draft against the app's declared preferences.
core_preference_governance ¶
core_preference_governance(migration: CorePreferenceMigration, *, enforce: bool = False, reviewer: PreferenceReviewer | None = None, **ports: Any) -> GovernanceStages
Build the governance capability for an app's declared preferences.
build_agent(governance_capability=core_preference_governance(
core_preference_migration(PREFERENCES),
enforce=True,
reviewer=MyReviewer(),
))
Remaining keywords go to :func:~symfonic.platform.governance. Enforcement
owns the egress reviewer and its trigger, so reflector and
trigger_policy are refused alongside it. Caller sensitive_terms
extend, never replace, the migrated preference tags.
Source code in src/symfonic/platform/core_preference_policy.py
critic_preference_reviewer ¶
critic_preference_reviewer(provider: Any, *, model_config: Any = None, prompt_template: str | None = None) -> PreferenceReviewer
Build the ported critic as an enforcement reviewer.
core_preference_governance(
migration,
enforce=True,
reviewer=critic_preference_reviewer(provider),
)
provider answers the hidden reflection call (get_chat_model +
ainvoke); model_config passes through to get_chat_model.
prompt_template overrides the critic's stock prompt, with the same
{draft}, {core_preferences}, {procedural_rules} and
{sensitive_tag_hit} slots; None keeps the stock text.