The Complete Policy Config YAML Reference
The complete policy-config.yaml reference in Keeptrusts is simpler than it first looks: start with pack, define policies.chain, add only the policy.<kind> blocks you actually need, and then use optional providers and agents sections where routing or usage limits belong. The important rule is that the file is a strict contract, so unknown keys and unsupported shapes are rejected rather than ignored.
Use this page when
- You need a practical reading guide to the declarative config instead of jumping between field-level docs.
- You are authoring a new policy config and want to avoid the common schema mistakes.
- You need to understand how chain order, providers, and per-policy blocks work together.
Primary audience
- Primary: Technical Engineers and AI platform maintainers
- Secondary: Technical Leaders and reviewers who approve config changes
The main structure
Most production configs use the normal config-document shape.
At the top level, Keeptrusts expects pack and policies. It can also accept policy, providers, and agents.
That separation is useful:
packidentifies the config itself.policies.chaindecides execution order.policyholds the detailed configuration for each policy kind.providersdefines upstream targets, routing, fallback, and related fleet behavior.agentscan carry usage constraints for agent-level control.
Keeptrusts also supports single-policy documents, but only for a small, explicitly documented set of policy kinds: prompt-injection, agent-firewall, citation-verifier, and quality-scorer. If you try to use the single-policy shape for other controls, the schema rejects it.
That is the first useful mental model: this is not an open-ended YAML bag. It is a typed config surface.
Example: a realistic skeleton
The easiest way to understand the structure is to see a compact example that includes routing and several commonly paired policy kinds.
pack:
name: complete-config-reference
version: 1.0.0
enabled: true
description: Reference-grade Keeptrusts gateway config
providers:
routing:
strategy: ordered
fallback:
enabled: true
triggers:
- rate_limit
- server_error
- timeout
max_fallback_attempts: 2
targets:
- id: openai-primary
provider: openai
model: gpt-5.4-mini
secret_key_ref:
env: OPENAI_API_KEY
- id: anthropic-backup
provider: anthropic
model: claude-sonnet-4-20250514
secret_key_ref:
env: ANTHROPIC_API_KEY
policies:
chain:
- prompt-injection
- dlp-filter
- data-routing-policy
- financial-compliance
- human-oversight
- audit-logger
policy:
prompt-injection:
use_embedding: false
detection:
attack_patterns:
- "ignore.*previous.*instructions"
- "reveal.*system.*prompt"
encoding:
decode_base64: true
normalize_unicode: true
detect_homoglyphs: true
boundaries:
enforce_delimiters: true
reject_fake_boundaries: true
dlp-filter:
blocked_terms:
- internal use only
action: block
data-routing-policy:
require_zero_data_retention: true
require_no_training: true
on_no_compliant_provider: block
financial-compliance:
blocked_patterns:
- guaranteed return
required_disclaimers:
- This is not financial advice.
human-oversight:
action: escalate
audit-logger:
retention_days: 365
This single file demonstrates the main pattern: top-level identity, provider fleet, chain order, and per-policy detail.
Why policies.chain matters more than the policy block
Many authoring mistakes happen because people focus on policy.<kind> blocks first.
The execution engine starts from policies.chain, not from the existence of a block under policy.
That means:
- If a policy kind appears in
chainbut has nopolicy.<kind>block, schema defaults apply. - If a
policy.<kind>block exists but the kind is not present inchain, the block can still validate but it does not execute. - Order is semantic, not decorative. Earlier policies can block or shape the path for later ones.
This is why Prompt Injection Detection belongs early, why DLP Filter is usually an input-side companion, and why output controls such as Financial Compliance or Human Oversight belong later in the flow.
The provider section is part of the same contract
Another common mistake is treating providers as separate from the policy system. It is not separate. It is one of the core parts of the same YAML contract.
providers.targets tells the gateway which upstreams exist.
providers.routing determines how selection works.
providers.fallback decides when the gateway should try another target.
providers.model_groups can hide vendor-specific models behind stable names.
And once you declare providers.targets[].data_policy, Data Routing Policy can exclude targets that do not satisfy the retention or training guarantees required for the request.
That is a good example of why the config should be read as one contract instead of separate YAML islands. Routing behavior and policy behavior depend on each other.
Strict validation is a feature
Keeptrusts rejects unknown keys, unknown policy kinds, and unsupported nested fields. That can feel strict the first time you hit a lint error, but it is one of the reasons the config is safe to review and automate.
A silent typo in a governance config is worse than a loud one.
Strict validation is also what keeps field-level docs meaningful. If the docs say detection.attack_patterns, you do not want a misspelled top-level attack_patterns field to be accepted and ignored.
A small workflow that prevents most mistakes
Write the file in this order:
- Define
packfirst so the document has identity and versioning. - Write
policies.chainbefore detailed policy blocks so execution order is explicit. - Add only the
policy.<kind>blocks for the controls that are actually in the chain. - Add
providersonce you know which upstreams and routing rules are required. - Lint the file before trying to run the gateway.
- Query the live gateway config after startup to confirm the loaded shape.
kt policy lint --file policy-config.yaml
curl http://localhost:41002/keeptrusts/config | jq .
That second command is useful because it turns the YAML you wrote into a running-state check. You are not only validating syntax. You are validating what the gateway actually loaded.
Common misconceptions
One misconception is that every control listed in broad product docs must be part of the customer declarative schema. The catalog explicitly calls out exceptions such as response-rewriter, which is documented as a runtime-only response transformation control rather than part of the customer declarative config surface.
Another misconception is that the YAML should contain ad hoc metadata for human readers. That is exactly what strict validation is meant to prevent.
The third misconception is that one file can express any policy as a single-policy document. It cannot. The docs are explicit about the limited set supported there.
Key takeaways
- Read
policy-config.yamlas a strict contract with a small number of top-level sections. - Let
policies.chaindrive the design first; detailedpolicy.<kind>blocks come second. - Treat routing, failover, and policy enforcement as parts of the same config, not separate concerns.
- Use linting and live-config verification as part of normal authoring, not just debugging.
- Avoid undocumented shapes even if they seem intuitive. Keeptrusts validates against the documented schema, not guesswork.