AFP engineering series · Chapter 14 of 25
Design input, retrieval, dialogue, execution, and output controls with explicit policy ownership and testable business invariants.
A conversation flow can guide behavior without proving permission
A customer-service assistant may need to stay within supported topics, ask for missing information, and refuse certain content. Those are conversational requirements. It also needs to prevent unauthorized refunds and cross-tenant access. Those are resource and transaction requirements. A dialogue policy should not be asked to carry both responsibilities by itself.
NVIDIA NeMo Guardrails describes rails at input, retrieval, dialogue, execution, and output stages, with configuration and custom actions. That vocabulary is useful for organizing checks. The reference architecture here pairs conversational rails with a separate policy service that owns authorization and action constraints.
Write policy in terms of claims and evidence
Avoid a rule such as “approve a refund when the customer sounds credible.” Credibility is not an observable entitlement. Instead require an authenticated principal, an owned order, an eligible balance, and a valid approval for the exact action. Content classification can decide whether a conversation needs review, but it should not create these facts.
Define the policy engine's input schema and reject missing fields. Include the source and freshness of each verified attribute. A boolean named is_admin supplied by an agent is not equivalent to a role loaded from the identity service. The policy engine must know which component is authorized to assert each attribute.
Assign responsibilities by rail
| Rail | Example responsibility | Independent control |
|---|---|---|
| Input | Reject unsupported request types | Authentication and quotas |
| Retrieval | Label untrusted source material | Document access control |
| Dialogue | Ask for missing evidence | Case-state validation |
| Execution | Check proposed operation | Backend authorization |
| Output | Screen and encode final content | Release policy and renderer |
Do not assume every rail sees the same data or runs in the same order for every integration. Document the actual execution path and test it. A custom action that calls an external service should inherit the parent deadline and return a typed result rather than a persuasive paragraph.
Keep side-effecting custom actions especially narrow. A conversational flow should not expose a general-purpose shell or database administrator merely because it needs one small business operation. The service receiving the request should remain safe when called without the conversational layer.
Use a policy result that cannot be mistaken for prose
Illustrative python; adapt the persistence and identity adapters to your service.
def refund_policy(facts):
required = {"owned", "eligible", "approval_matches", "fresh"}
if not required <= facts.keys():
return {"outcome": "invalid", "reason": "missing_evidence"}
if not all(type(facts[k]) is bool for k in required):
return {"outcome": "invalid", "reason": "bad_evidence_type"}
if not all(facts[k] for k in required):
return {"outcome": "deny", "reason": "requirements_not_met"}
return {"outcome": "allow", "reason": "verified_requirements"}This toy function assumes its facts have already been verified by trusted adapters. In production, that assumption must be enforced through authenticated service calls and protected data stores. Do not let the model manufacture the dictionary and then claim the policy engine approved it.
Choose a policy language or rules service that your team can review and test. The important property is a stable, auditable decision contract, not a particular syntax. Separate rule changes from generated natural-language explanations so a wording improvement cannot accidentally change authorization.
Test conflicts and precedence
Two controls can disagree. A content classifier may allow a request while the action policy denies it. A dialogue rule may ask for more information while the backend reports that the operation is permanently prohibited. Define precedence: a hard authorization denial cannot be overridden by a conversational allow, and an unavailable required service cannot be treated as an ordinary pass.
Use a decision table for combinations of allow, deny, review, and unavailable. Test every combination rather than only the happy path. Record which control determined the final outcome and which controls were not evaluated. That makes later analysis more reliable than a single generic “guardrail blocked” event.
Control configuration and custom-action changes
Review rail configuration and action code together. A configuration file can enable a new external action even when the application binary is unchanged. Include configuration hashes, model identifiers, and custom-action versions in release records. Require tests when a new action or output channel is added.
Run a staging environment with synthetic identities and denied destinations. Verify that a malicious document cannot cause an execution rail to call an unapproved endpoint. Test cancellation, timeouts, and malformed custom-action results. A flow that works during a demo may behave differently when an external dependency fails.
Worked policy conflict: dialogue allows, execution denies
A dialogue rule identifies a request as a supported refund conversation and guides the customer through the next step. The execution policy discovers that the order belongs to another tenant. The correct final outcome is denial of the action, even though the conversation is on topic. “Supported topic” and “authorized transaction” are different claims.
Build a decision combiner that preserves those distinctions. Content eligibility permits the assistant to discuss the workflow. Resource authorization permits access to a specific order. Transaction eligibility permits a specific amount under current policy. Approval expresses intent for an exact proposal. An allow at one stage must not overwrite a deny or unavailable result at another mandatory stage.
Test the full Cartesian set of meaningful decision combinations using small fixtures. For example, content allow plus authorization deny must produce no tool effect; content allow plus authorization unavailable must hold the action; all required allows may proceed to atomic execution checks. Store the decisive reason and the checks that were skipped after an early denial.
A custom action then returns a malformed result missing its status field. The combiner should reject it as invalid rather than interpreting absence of an error as success. This catches a common integration mistake when a service changes its response schema. Version the adapter and add a contract test using the new response before deploying it.
The team can still use conversational rails to make the experience helpful. The assistant may explain which evidence is missing or direct the user to an authorized process. It should not suggest that rephrasing the request will change an ownership decision. This worked case shows how dialogue controls improve usability while a separate policy contract prevents conversational success from becoming unauthorized authority.
Measure coverage rather than counting rails
Five rails do not imply five independent defenses. Several may use the same classifier and share the same blind spot. Track which threat each rail addresses, what evidence it receives, and what effect it can prevent. Measure correlated misses and false blocks on legitimate work.
The release criterion should connect each protected outcome to an enforceable control and a test. For example, no unauthorized order can be retrieved, no unapproved refund can execute, and no unscreened candidate can reach a buffered output channel. Conversational rails improve the interaction; policy and resource services preserve authority when the conversation goes wrong.
Practice and continue
Open the interactive companion lab to inspect the assumptions and outcomes with synthetic data. Then return to the 25-chapter AFP learning path.
Sources and implementation scope
Primary references checked October 15, 2026. Framework details follow the linked documentation; the Northstar cases, policy contracts, and test designs are original illustrative designs. Python snippets labeled illustrative are not complete production integrations. ADK package metadata checked for this series reported 2.12.0; pin and test the version used by your application.