Agentic Loop Guardrails

Screens each newly produced local tool result before it can enter the next inference request

Versions marked “overview” do not contain this page. Selecting one opens that version’s documentation overview.

Category: Setup-dependent integration
Task: Screens each newly produced local tool result before it can enter the next inference request

Prerequisites: The external service, credentials, or certificates referenced by this configuration.

This configuration comes from the selected release. The example has not been run here; external services are not bundled.

Download the source file.

# Responses Agentic Loop with NeMo Tool-Result Guardrails
# Requires `--features openai-agentic-loop,openai-mcp-tools` (experimental and off by default).
#
# Screens each newly produced local tool result before it can enter the next
# inference request. The guardrail belongs inside the IRR step, after every
# request-phase dispatcher and before openai_agentic_loop. Filters outside the
# IRR run only on the original client request and cannot protect continuations.
#
# This focused example uses MCP dispatch. The same ai_guardrails placement also
# covers results appended by openai_web_search_dispatch and openai_file_search_dispatch
# when those dispatchers are added before it.
#
# NeMo's /v1/checks endpoint evaluates Chat-style user messages. Praxis converts
# only the newly appended canonical function_call_output values to that check
# shape; it does not rescan earlier conversation history or reinterpret the raw
# IRR request body.
#
# Start a Responses-compatible model backend on 127.0.0.1:3001 and NeMo
# Guardrails 0.24.0+ on 127.0.0.1:3002 before starting this config.

listeners:
  - name: guarded-agentic-gateway
    address: "127.0.0.1:8080"
    filter_chains: [guarded-agentic-pipeline]

filter_chains:
  - name: guarded-agentic-pipeline
    filters:
      # Publishes the typed operation consumed by openai_responses_request.
      - filter: ai_operation

      # Pure fact publisher: classifies the body and promotes format/model/stream
      # for the guardrail and agentic filters downstream.
      - filter: openai_responses_request
        initialize_state: false
        on_invalid: reject
        headers:
          format: x-praxis-ai-format
          model: x-praxis-ai-model
          stream: x-praxis-ai-stream

      # Managed owner: validates parameters, generates response IDs, and mints
      # the request-scoped ResponsesState that carries the agentic
      # retained-payload budget. openai_agentic_loop installs that budget policy,
      # so a managed owner must run here to attach the budget before MCP
      # discovery and the loop; without it those filters fail closed.
      - filter: openai_responses_request
        on_invalid: reject
        headers:
          format: ~
          model: ~
          stream: ~
          mode: ~

      - filter: openai_tool_parse

      # Resolves request-supplied MCP server_url tools before the first model
      # round. Production deployments should prefer configured connector_id
      # destinations and appropriate outbound policy.
      - filter: openai_mcp_tool_resolve

      - filter: iterative_request_router
        initial_step: inference
        max_iterations: 11
        timeout_ms: 360000
        step_timeout_ms: 300000
        max_response_bytes: 67108864
        max_stream_response_bytes: 67108864
        steps:
          - name: inference
            filters:
              # Required for one logical SSE lifecycle across model rounds and
              # for delivering a post-commit guardrail failure as an SSE error.
              - filter: openai_stream_events

              # Executes MCP assignments from the preceding model response.
              - filter: openai_mcp_dispatch
                max_calls_per_round: 32
                max_parallel_calls: 8
                max_result_bytes: 1048576
                max_total_result_bytes: 8388608

              # Must remain after all local dispatchers and before the loop
              # owner. request:false makes the first IRR round inert: general
              # Responses request-format coverage is tracked separately in
              # ai#1043. tool_results:true evaluates only new local results.
              - filter: ai_guardrails
                provider:
                  type: nemo
                  endpoint: "http://127.0.0.1:3002/v1/checks"
                  guardrails:
                    config_ids: ["your-config"]
                  timeout_ms: 5000
                  max_message_checks: 32
                phase:
                  request: false
                  response: false
                  tool_results: true

              # Sole continuation and terminal-response owner. It consumes a
              # guardrail failure before openai_responses_proxy can issue the
              # next model request.
              - filter: openai_agentic_loop
                max_infer_iters: 10

              - filter: openai_responses_proxy
              - filter: router
                routes:
                  - path_prefix: "/"
                    cluster: inference-backend
              - filter: load_balancer
                clusters:
                  - name: inference-backend
                    endpoints: ["127.0.0.1:3001"]
                    read_timeout_ms: 300000
            on_result:
              - filter: openai_agentic_loop
                key: action
                value: loop
                next: inference
              - default: true
                done: true

insecure_options:
  allow_private_endpoints: true
  # This local example calls loopback MCP and NeMo services. Keep disabled in
  # production unless private upstreams are an intentional trust boundary.
  allow_private_upstreams: true