# How to Implement GuardRail Validation for Agent Input and Output in Embabel

> Implement GuardRail validation in Embabel to enforce safety, quality, and policy rules on LLM input and output. Ensure secure and reliable agent interactions.

- Repository: [Embabel/embabel-agent](https://github.com/embabel/embabel-agent)
- Tags: how-to-guide
- Published: 2026-08-08

---

**Embabel provides a plug‑in validation mechanism called Guard Rails that lets you enforce safety, quality, and policy rules on both user input reaching an LLM and assistant output returned by the model.**

The `embabel/embabel-agent` repository includes a comprehensive validation system within the **embabel‑agent‑api** module. This system intercepts messages at the API boundary, allowing you to implement custom validation logic that runs automatically before requests are sent to language models and after responses are received.

## Core GuardRail Interfaces

The validation framework centers on three interfaces located in `embabel-agent-api/src/main/kotlin/com/embabel/agent/api/validation/guardrails/`.

**`GuardRail`** serves as the base contract. It defines the `validate(String, Blackboard): ValidationResult` method that all implementations must provide. This interface lives in [[`GuardRail.kt`](https://github.com/embabel/embabel-agent/blob/main/GuardRail.kt)](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-api/src/main/kotlin/com/embabel/agent/api/validation/guardrails/GuardRail.kt).

**`UserInputGuardRail`** extends the base interface specifically for incoming user messages. The source file [[`UserInputGuardRail.kt`](https://github.com/embabel/embabel-agent/blob/main/UserInputGuardRail.kt)](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-api/src/main/kotlin/com/embabel/agent/api/validation/guardrails/UserInputGuardRail.kt) marks the entry point for input validation.

**`AssistantMessageGuardRail`** handles outgoing assistant messages and adds an overload that receives a `ThinkingResponse`. This allows validation of both the final text and intermediate reasoning steps. See [[`AssistantMessageGuardRail.kt`](https://github.com/embabel/embabel-agent/blob/main/AssistantMessageGuardRail.kt)](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-api/src/main/kotlin/com/embabel/agent/api/validation/guardrails/AssistantMessageGuardRail.kt).

## Global Registration via GlobalGuardRailsRegistry

All guard‑rails are discovered and instantiated by the `GlobalGuardRailsRegistry` class, a Spring `@Component` located in [[`GlobalGuardRailsRegistry.kt`](https://github.com/embabel/embabel-agent/blob/main/GlobalGuardRailsRegistry.kt)](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-api/src/main/kotlin/com/embabel/agent/spi/support/guardrails/GlobalGuardRailsRegistry.kt).

The registry reads two comma‑separated configuration properties:

- `embabel.agent.guardrails.user-input`
- `embabel.agent.guardrails.assistant-message`

During its `@PostConstruct init()` method, the registry calls `instantiateGuardRails<T>()` for each configured class list. This method uses reflection via `ClassUtils.forName` to load classes and validates that they implement the required interface. If a class cannot be instantiated and `embabel.agent.guardrails.fail-on-error=true` is set, the registry throws a `GuardRailInstantiationException`. Otherwise, errors are logged and the offending guard‑rail is skipped.

The registry exposes the created singletons through static helpers: `getUserInputGuardRails()` and `getAssistantMessageGuardRails()`.

## Where GuardRails Are Enforced

**User‑input validation** occurs in the `GuardedOperationsProxyFactory` (located in the `embabel-agent-common` module). This factory wraps LLM calls and iterates over the global user‑input guard‑rails before sending the request to the model. If any guard returns a failure `ValidationResult`, the pipeline immediately throws a `GuardRailViolationException`, aborting the request.

**Assistant‑output validation** runs after the model produces a response. Production code in `embabel-agent-openai` and other model modules follows the pattern demonstrated in [[`StreamingChatClientOperationsGuardRailTest.kt`](https://github.com/embabel/embabel-agent/blob/main/StreamingChatClientOperationsGuardRailTest.kt)](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-api/src/test/kotlin/com/embabel/agent/spi/support/springai/streaming/StreamingChatClientOperationsGuardRailTest.kt). After receiving an `AssistantMessage` or `ThinkingResponse`, each registered `AssistantMessageGuardRail` is invoked sequentially.

## Creating Custom GuardRail Implementations

Follow these steps to add custom validation logic:

1. **Implement the appropriate interface** — Choose `UserInputGuardRail` for input validation or `AssistantMessageGuardRail` for output validation.
2. **Override the `validate` method** — Return `ValidationResult.success()` on pass or `ValidationResult.failure("reason")` on violation.
3. **Register the class** — Add the fully‑qualified class name to the appropriate property in `application.properties`.
4. **Configure error handling** — Optionally set `embabel.agent.guardrails.fail-on-error=true` to force startup failure if instantiation fails.

### Example: Profanity Filter for User Input

```kotlin
package com.mycompany.guardrails

import com.embabel.agent.api.validation.guardrails.UserInputGuardRail
import com.embabel.common.core.validation.ValidationResult
import com.embabel.common.util.loggerFor
import com.embabel.common.core.Blackboard

class ProfanityFilterGuardRail : UserInputGuardRail {
    private val logger = loggerFor<ProfanityFilterGuardRail>()
    private val banned = setOf("badword1", "badword2")

    override val name: String = "ProfanityFilter"

    override fun validate(input: String, blackboard: Blackboard): ValidationResult {
        val containsBanned = banned.any { input.contains(it, ignoreCase = true) }
        return if (containsBanned) {
            logger.warn("Profanity detected in user input")
            ValidationResult.failure("Profanity is not allowed")
        } else {
            ValidationResult.success()
        }
    }
}

```

Register in `application.properties`:

```properties
embabel.agent.guardrails.user-input=com.mycompany.guardrails.ProfanityFilterGuardRail

```

### Example: Output Length Guard for Assistant Messages

```kotlin
package com.mycompany.guardrails

import com.embabel.agent.api.validation.guardrails.AssistantMessageGuardRail
import com.embabel.common.core.validation.ValidationResult
import com.embabel.common.util.loggerFor
import com.embabel.common.core.Blackboard

class MaxLengthGuardRail : AssistantMessageGuardRail {
    private val logger = loggerFor<MaxLengthGuardRail>()
    private val MAX_LENGTH = 500

    override val name: String = "MaxLengthGuard"

    override fun validate(input: String, blackboard: Blackboard): ValidationResult =
        if (input.length > MAX_LENGTH) {
            logger.warn("Assistant message exceeds max length")
            ValidationResult.failure("Response too long")
        } else {
            ValidationResult.success()
        }
}

```

Register in `application.properties`:

```properties
embabel.agent.guardrails.assistant-message=com.mycompany.guardrails.MaxLengthGuardRail

```

## Handling GuardRail Violations

When a guard‑rail signals failure, the core pipeline throws a `GuardRailViolationException` defined in [[`GuardRailViolationException.kt`](https://github.com/embabel/embabel-agent/blob/main/GuardRailViolationException.kt)](https://github.com/embabel/embabel-agent/blob/main/embabel-agent-api/src/main/kotlin/com/embabel/agent/api/validation/guardrails/GuardRailViolationException.kt). You should catch this exception in your higher‑level service layer to return user‑friendly error messages or trigger alternative handling workflows.

## Summary

- **Guard Rails** in Embabel provide a plug‑in mechanism for validating LLM inputs and outputs via the `embabel-agent-api` module.
- **Three core interfaces** define the contracts: `GuardRail`, `UserInputGuardRail`, and `AssistantMessageGuardRail`.
- **GlobalGuardRailsRegistry** automatically discovers and instantiates guard‑rails from the `embabel.agent.guardrails.user-input` and `embabel.agent.guardrails.assistant-message` properties.
- **Enforcement points** include `GuardedOperationsProxyFactory` for input validation and model-specific handlers for output validation.
- **Implementations** must return `ValidationResult` objects and can trigger `GuardRailViolationException` on failure.

## Frequently Asked Questions

### What is the difference between UserInputGuardRail and AssistantMessageGuardRail?

**`UserInputGuardRail`** validates raw text strings sent by users before they reach the LLM, while **`AssistantMessageGuardRail`** validates text generated by the model before it returns to the user. The assistant variant includes an additional overload `validate(ThinkingResponse<*>, Blackboard)` that allows inspection of intermediate reasoning steps, not just the final text output.

### How do I register multiple GuardRails for the same stage?

Add multiple fully‑qualified class names as a comma‑separated list in the appropriate property. For example: `embabel.agent.guardrails.user-input=com.example.FirstGuardRail,com.example.SecondGuardRail`. The `GlobalGuardRailsRegistry` will instantiate and apply each guard‑rail in the order specified.

### What happens if a GuardRail class cannot be instantiated at startup?

If `embabel.agent.guardrails.fail-on-error` is set to `true`, the application will fail to start with a `GuardRailInstantiationException`. If left as `false` (the default), the registry logs the error and continues without that specific guard‑rail, allowing the application to start with reduced validation coverage.

### Can I access the conversation history or metadata inside a GuardRail?

Yes. Every `validate` method receives a **`Blackboard`** parameter that provides access to contextual data, conversation history, and other metadata stored in the current execution context. You can use this blackboard to implement stateful validation rules that depend on previous messages or session-specific information.