Normandy.Guardrails.Builtins.SemanticScope
(normandy v1.2.0)
View Source
A hybrid scope guard: a cheap deterministic fast path in front of an injected classifier.
Normandy owns only the orchestration shape. It is deliberately provider-agnostic — it knows nothing about LLMs, HTTP, prompts, locales, or block-lists. The host injects those as plain functions, so the same guard can front a keyword heuristic plus a remote inference call without any of that knowledge leaking into the framework.
Options
:classifier(required) —(value, context) -> :allow | {:block, reason}. Called when the fast path defers.reasonis an atom and becomes the violation's:constraint, so the host can map it to localized copy (e.g.:off_topic). Owns all IO.:fast_path(optional) —(value, context) -> :admit | :needs_classifier. A cheap pre-filter.:admitshort-circuits and the classifier never runs;:needs_classifierdefers to the classifier. Defaults to always:needs_classifier.:on_error— handled byNormandy.Guardrails.run/2,3, not here. A classifier that calls a flaky service should seton_error: :openso an outage admits rather than crashing the guard chain. SeeNormandy.Guardrailsfor the policy semantics (default:reraise).
context (the third argument supplied by Normandy.Guardrails.run/3 /
Normandy.Agents.BaseAgent.admit/3) is passed unchanged to both injected
functions. check/2 runs them with an empty context.
Example
classifier = fn message, %{event_id: id} ->
if MyApp.Inference.on_topic?(message, id), do: :allow, else: {:block, :off_topic}
end
fast_path = fn message, _context ->
if MyApp.Keywords.obviously_in_scope?(message), do: :admit, else: :needs_classifier
end
guards = [{SemanticScope, classifier: classifier, fast_path: fast_path, on_error: :open}]
Normandy.Agents.BaseAgent.admit(config, message, %{event_id: 42})