Skip to content

Human Message Guardrail

The Human Message Guardrail node matches the current user message against a list of regex patterns (one per string in a string storage) and branches the graph on the result. Typical use: short-circuit disallowed, off-topic, or jailbreak-style inputs to a canned-response node before they reach a Generate node.

Patterns are compiled once at agent creation and matched with std::regex_search — i.e. a pattern hits if it matches anywhere in the message, case-insensitively.

NodeType: HumanMessageGuardrail.

String storage kinds

HumanMessageGuardrailNode accepts any string storage kind (List, Map, Multimap) and uses the list view — each value is treated as a regex pattern regardless of any key. Topic-tagged deflection (writing the matched pattern's category key onto the interaction for use by a downstream CannedResponseNode) is planned as future work.

Parameters

Param Type Default Range Structural Description
string_storage Optional[str] inherit model default Named session string storage. Mutable — rebinds the underlying storage.
inline_strings Optional[Any] inherit model default Inline pattern list. Structural because it materialises the underlying StringStorage at construction; rebinding goes through string_storage.

Exits

Each exit is a structural string field on the node's params; its value names the target node (empty = END).

Exit Param field Description
triggered triggered_exit Exit taken when the human message matches a guardrail pattern. Empty string = END.
not_triggered not_triggered_exit Exit taken when no pattern matches. Empty string = END.

Source resolution

Priority order: string_storage (named session storage) → inline_strings (authored inline) → server default file (default_guardrail_patterns_path in server-config.json). Patterns are matched case-insensitively. Compilation errors at agent-creation time fail graph validation with error 3003.

Exit routes

Route Fires when
triggered At least one pattern matched the user message.
not_triggered No patterns matched.

Both routes must be wired.

Side effects

None on the dialog. The node does not modify the current interaction, does not emit AnswerText frames, and does not touch the KV cache. It is pure routing.

Minimum working example

from tryll_client.graph import (
    GraphDescription, HumanMessageGuardrailParams,
    CannedResponseParams, GenerateParams,
)

client.create_string_storage(
    name="jailbreak_patterns",
    strings=[
        r"ignore\s+previous\s+instructions?",
        r"you\s+are\s+DAN",
    ],
)

graph = (
    GraphDescription()
    .add_node("guard", HumanMessageGuardrailParams(
        string_storage="jailbreak_patterns",
        triggered_exit="refuse",
        not_triggered_exit="answer",
    ))
    .add_node("refuse", CannedResponseParams(
        default_exit="",   # uses server default; empty = END
    ))
    .add_node("answer", GenerateParams(
        default_exit="",   # empty = END
    ))
    .set_start_node("guard")
    .set_default_model_name("My Local Model")
)

agent = client.create_agent(graph)
using namespace Tryll::Client;
using namespace Tryll::NodeParams;

client.CreateStringStorage("jailbreak_patterns", {
    R"(ignore\s+previous\s+instructions?)",
    R"(you\s+are\s+DAN)",
});

HumanMessageGuardrailParamsT guardP;
guardP.string_storage    = "jailbreak_patterns";
guardP.triggered_exit    = "refuse";
guardP.not_triggered_exit = "answer";

// refuseP and answerP use defaults (server default canned responses / END)
CannedResponseParamsT refuseP;
GenerateParamsT       answerP;

GraphDescription graph;
graph.AddHumanMessageGuardrail("guard",  std::move(guardP))
     .AddCannedResponse("refuse",         std::move(refuseP))
     .AddGenerate("answer",               std::move(answerP))
     .SetStartNode("guard")
     .SetDefaultModelName("My Local Model");

auto agent = client.CreateAgent(graph);
using Tryll.Client;

var graph = new TryllGraphBuilder()
    .AddHumanMessageGuardrail("guard", new TryllHumanMessageGuardrailParams
    {
        StringStorage    = "jailbreak_patterns",
        TriggeredExit    = "refuse",
        NotTriggeredExit = "answer",
    })
    .AddCannedResponse("refuse", new TryllCannedResponseParams())
    .AddGenerate("answer",       new TryllGenerateParams())
    .SetStartNode("guard")
    .SetDefaultModelName("My Local Model")
    .Build();
#include "Generated/TryllGraphBuilder.Nodes.h"
#include "Generated/TryllNodeParamsFactory.h"

auto* Subsystem = GetGameInstance()->GetSubsystem<UTryllSubsystem>();
Subsystem->RequestCreateStringStorage(
    TEXT("jailbreak_patterns"),
    { TEXT(R"(ignore\s+previous\s+instructions?)"),
      TEXT(R"(you\s+are\s+DAN)") });

UTryllHumanMessageGuardrailParams* GuardP = UTryllNodeParamsFactory::MakeHumanMessageGuardrailParams(this);
GuardP->bOverrideStringStorage = true;
GuardP->StringStorage    = TEXT("jailbreak_patterns");
GuardP->TriggeredExit    = TEXT("refuse");
GuardP->NotTriggeredExit = TEXT("answer");

FTryllGraphDescription Graph = FTryllGraphBuilder()
    .AddNode(TEXT("guard"),  GuardP)
    .AddNode(TEXT("refuse"), UTryllNodeParamsFactory::MakeCannedResponseParams(this))
    .AddNode(TEXT("answer"), UTryllNodeParamsFactory::MakeGenerateParams(this))
    .SetStartNode(TEXT("guard"))
    .SetDefaultModelName(TEXT("My Local Model"))
    .Build();

See the full flow in How to use canned responses and guardrails.

Client bindings

  • C++: GraphDescription::AddHumanMessageGuardrail(name, HumanMessageGuardrailParamsT)GraphDescription.h
  • Python: GraphDescription.add_node(name, HumanMessageGuardrailParams(...))tryll_client.graph
  • Unity: TryllGraphBuilder.AddHumanMessageGuardrail(name, new TryllHumanMessageGuardrailParams{...})Runtime/Generated/TryllGraphBuilder.Nodes.cs
  • Unreal: AddHumanMessageGuardrailNode(builder, name, UTryllHumanMessageGuardrailParams*)Generated/TryllGraphBuilder.Nodes.h