Safety
Boundary-targeted guardrails, constraints, tool policies, validation retry, and prompt-injection-resistant patterns.
Four incidents, four different fixes:
- The model echoed a customer's email address into your logs. That is a guardrail: filter, transform, redact, warn, or block on typed boundaries.
- The output parsed cleanly but cited no sources. That is a constraint: validate output semantics and retry the model with feedback on business-rule violations.
- A tool call tried to delete a production record. That is a tool policy: block, request approval, or screen tool calls/results through the Safety decision model.
- The model returned broken JSON. That is validation retry: Crux text-repairs first, then re-prompts with the Zod error.
Separate concern: Security, patterns to make your prompts injection-resistant.
Boundary targets
Guardrails and constraints bind to boundary.* descriptors instead of a broad "input/output" phase:
import { boundary, constraint, guardrail } from "@use-crux/core/safety";
export const redactPii = guardrail({
id: "pii",
on: [boundary.input.text(), boundary.output.text()],
run: guardrail.pii({ strategy: "mask" }),
});
export const grounded = constraint({
id: "grounded-answer",
on: boundary.output.object<{ answer: string; citations: string[] }>(),
run: constraint.judge({ judge, minScore: 0.8 }),
});Useful targets include boundary.input.text(), boundary.input.media(),
boundary.input.instructions(), boundary.input.tools(),
boundary.output.text(),
boundary.output.media(), boundary.output.object<T>(),
boundary.output.object<T>().path('a.b'), boundary.memory.write<T>(), and
boundary.input.text({ from: "feedback" }).
The optionless input helpers cover all supported sources. Advanced policies can
filter with boundary.input.text({ from: "retrieval" }) or
boundary.input.media({ from: "tool" }). Use toolPolicy.args() and
toolPolicy.result() for raw tool lifecycle decisions.
Think of an input boundary as destination plus provenance. The destination
states what the provider will receive; from states who supplied that canonical
content:
const lifecycleText = boundary.input.text({
from: ["memory", "handoff", "feedback"],
});
const discoveredTools = boundary.input.tools({ from: "discovered" });Rendered managed memory and blackboard state both use source memory, with
distinct ctx.origin.kind values. Handoff context uses handoff; validation
and constraint retry writeback uses feedback. Provenance is safe metadata,
not the guarded text or payload.
See Safety for content primitives for the exact language, structured, stream, image, speech, and transcription coverage matrix.
For an application-oriented walkthrough of input media, generated previews, held deltas, final assets, and required-media strip behavior, see Media safety.
Report mode and streaming
Use safety.tune at a call site to shadow a policy or disable it:
await adapter.generate(prompt, {
input,
safety: {
tune: {
pii: { mode: "report" },
"grounded-answer": { enabled: true },
},
},
});Tune changes only mode and enabled. The streaming unit is not tunable: it is
part of the policy's boundary, so a call site cannot widen what a policy sees.
In report mode, input envelopes, exposed tools, generated candidates, retry
feedback, and memory writes remain unchanged; Crux records what the policy
would have done.
Built-in text guardrails such as guardrail.pii(), guardrail.secrets(), and
guardrail.injection() check sentences by default; guardrail.classifier()
checks the complete subject. Refine the boundary — .sentences(), .lines(),
.deltas(), .complete() — to override that.
Guardrails and constraints do different things on a stream. A guardrail is a
pass over content: it holds a unit, clears or rewrites it, and releases it — it
never re-calls the model. An assert constraint is transactional: while it is
unresolved the stream withholds output, and if it fails the whole attempt is
discarded — the consumer saw none of it — and Crux re-streams with corrective
feedback. That is why a guardrail can act per sentence while a constraint acts per
attempt.
The boundary, plainly
A recap of the four primitives side by side:
| Guardrail | Constraint | Tool policy | Validation retry | |
|---|---|---|---|---|
| Runs on | Semantic model input/output, memory, and validation feedback | One output boundary | Raw tool call, result, or approval lifecycle | Structured output parse/validation |
| What it catches | Forbidden content, PII, secrets, injection, formatting | Semantic / business-rule violations | Unsafe tool execution or unsafe tool I/O | Schema-parse failures, wrong types |
| What happens on a hit | Block, rewrite, warn, or report | Re-call the model with custom feedback | Block, request approval, report, or rewrite tool I/O | Text-repair first, then re-call with the Zod error |
| Knows about retries | No: it is a policy pass | Yes: retry is the purpose | No, except approval suspension | Yes: bounded by maxRetries and maxSteps |
| You write | guardrail({ id, on, run }) | constraint({ id, on, run }) | toolPolicy({ id, match, action }) or toolPolicy.args/result | validationRetry: { maxRetries } |
Rules of thumb:
- Schema parse failed? That's validation retry: built-in, just enable it.
- Output parsed but business rule failed? That's a constraint: write a
check. - Need to filter, redact, or transform content? That's a guardrail.
- Need to gate tool execution? That's a tool policy. Use tool middleware for execution plumbing.
When should I use a guardrail?
- You need to redact PII before logging, before sending to a tool, or before showing output
- You want to block certain inputs entirely (profanity, prompt-injection markers)
- You want to transform outputs (strip leading code-fence markers, normalize whitespace, decode entities)
- You want to warn without blocking (attach metadata for downstream code)
When should I use a constraint?
- The model occasionally returns output that fails a business rule (off-topic, wrong language, missing field)
- You're willing to spend an extra round-trip to get correct output
- Your output schema validates most outputs but a small percentage need a re-prompt with the validation error
When should I use neither?
- The output is structurally invalid (won't parse): Zod schema validation already handles that
- The check needs to read external state (database, RAG): use a tool call, not a guardrail
- The check is about who is asking, not what: that's auth, not safety
Inspect and test safety
Safety decisions emit observability artifacts, Turn Decision Report rows, and
Project Index definitions. Devtools opens runtime interventions in Runs Explain
with model, action, semantic target, source (User, Tool, Retrieval,
Memory, Blackboard, Handoff, Feedback, Authored tool, or
Discovered tool),
privacy-safe origin coordinates, and strip escalation. Catalog shows authored
canonical boundary tuples, strategy helper configuration and action, links from
policies to completed operations, operation-side Safety attachments, and
duplicate policy-id lints. Source filters affect runtime matching but are not
stored as Project Index facts.
Safety sessions currently govern the application-facing model call. Internal judge, summarizer, reranker, and classifier model calls are not automatically wrapped by the originating session; configure their own Safety policy when their implementation exposes one.
In Evals, use safety signal assertions for guardrail/constraint audits or the decision report matcher for the canonical safety read model:
ctx.expect.safety.toHaveBlocked("pii");
ctx.expect.decisionReport.safety.toHaveOutcome("pii", "rewrite", {
reasonCode: "guardrail.redacted",
});Pick a topic
Guardrails
Filter, transform, redact, or warn on inputs and outputs.
Constraints
Semantic output validation with model retry on business-rule violations.
Validation retry
Auto-recover from schema-parse failures and wrong types.
Tool middleware
Execution plumbing and where toolPolicy fits.
Security
Injection-resistant prompt patterns.
How this fits with the rest of Crux
- Eval validates output quality during testing. Guardrails and constraints validate it during execution.
constraint.judge(...)bridges a scoring judge into runtime Safety enforcement; Eval safety and decision-report matchers regression-test the resulting production behavior. - Tools can implement domain-specific safety checks that need external data.
- Devtools trace every guardrail hit and constraint retry.