Skip to content

Event Format

Every guardrail evaluation requires an event — a structured description of what's happening in your agentic system. This page documents the exact format with copy-paste examples for every scenario.


Structure

Python

from theaios.guardrails import GuardEvent

event = GuardEvent(
    scope="input",                    # Required: what kind of event
    agent="sales-agent",              # Required: which agent
    data={                            # Required: event payload (your rules reference these fields)
        "content": "Hello, world",
    },
    session_id="sess-abc-123",        # Optional: for rate limiting by session
    source_agent="finance-agent",     # Optional: for cross-agent rules
    target_agent="sales-agent",       # Optional: for cross-agent rules
)

CLI (JSON)

guardrails check --config policy.yaml --event '{
  "scope": "input",
  "agent": "sales-agent",
  "data": {
    "content": "Hello, world"
  },
  "session_id": "sess-abc-123",
  "source_agent": "finance-agent",
  "target_agent": "sales-agent"
}'

Fields

Field Type Required Description
scope string Yes Event type: input, output, action, tool_call, cross_agent
agent string Yes Agent identifier. Matched against profile names in your policy.
data dict Yes Freeform payload. Your when clauses reference fields inside this dict.
session_id string No Session identifier. Used for rate_limit rules with key: session.
source_agent string No Sending agent. Used for cross_agent rules (from field).
target_agent string No Receiving agent. Used for cross_agent rules (to field).
timestamp float No Unix timestamp. For logging/audit only — not used in evaluation.

The data Dict

The data dict is freeform — you put whatever fields your rules need. The engine doesn't enforce a schema. Your when clauses reference fields using dot notation, and missing fields resolve to null.

# This rule references data.content
when: "content matches prompt_injection"

# This rule references data.action and data.recipient.domain
when: "action == 'send_email' and recipient.domain != $company_domain"

# This rule references data.resource.domain
when: "resource.domain in $sensitive_domains"

The key principle: the field names in your when clauses must match the field names in your data dict. If your rule says recipient.domain, your event data must include {"recipient": {"domain": "..."}}.


Complete Examples by Scope

input — Prompt or message going into an agent

Use this when a user sends a message to an agent, or when an agent receives a prompt.

# Python
GuardEvent(
    scope="input",
    agent="sales-agent",
    data={
        "content": "What meetings do I have today?",
    },
)
# CLI
guardrails check --config policy.yaml --event '{
  "scope": "input",
  "agent": "sales-agent",
  "data": {"content": "What meetings do I have today?"}
}'

Common data fields for input:

Field Type Description
content string The message/prompt text. Most input rules check this field.
role string Optional: "user", "system", "assistant"
tool_name string Optional: if the input is a tool result being fed back

Rules that typically match input events:

rules:
  - name: block-injection
    scope: input
    when: "content matches prompt_injection"
    then: deny

  - name: block-sensitive-questions
    scope: input
    when: "content contains 'password' or content contains 'secret key'"
    then: deny

output — Response generated by an agent

Use this when an agent produces a response before it's delivered to the user.

GuardEvent(
    scope="output",
    agent="hr-agent",
    data={
        "content": "The employee's SSN is 123-45-6789 and email is john@acme.com",
    },
)
guardrails check --config policy.yaml --event '{
  "scope": "output",
  "agent": "hr-agent",
  "data": {"content": "The employee SSN is 123-45-6789 and email is john@acme.com"}
}'

Common data fields for output:

Field Type Description
content string The response text. PII redaction and content filtering rules check this.
tool_name string Optional: if the output is from a specific tool
tool_result string Optional: raw tool output before agent processing

Rules that typically match output events:

rules:
  - name: redact-pii
    scope: output
    when: "content matches pii"
    then: redact
    patterns: [ssn, email_addr, phone]

  - name: block-confidential-leaks
    scope: output
    when: "content contains 'CONFIDENTIAL' or content contains 'INTERNAL ONLY'"
    then: deny

action — Agent performing an action

Use this when an agent is about to do something: send an email, write a file, call an API, modify a record.

# Sending an email
GuardEvent(
    scope="action",
    agent="sales-agent",
    data={
        "action": "send_email",
        "recipient": {
            "domain": "external-client.com",
            "email": "client@external-client.com",
        },
        "subject": "Proposal follow-up",
    },
)

# Writing to a resource
GuardEvent(
    scope="action",
    agent="finance-agent",
    data={
        "action": "write",
        "resource": {
            "domain": "finance",
            "path": "finance/reports/q3.xlsx",
        },
        "amount": 50000,
    },
)

# Generic action
GuardEvent(
    scope="action",
    agent="sales-agent",
    data={
        "action": "commit_pricing",
        "deal_id": "DEAL-2024-001",
    },
)
# CLI — external email
guardrails check --config policy.yaml --event '{
  "scope": "action",
  "agent": "sales-agent",
  "data": {
    "action": "send_email",
    "recipient": {"domain": "external-client.com"}
  }
}'

# CLI — financial write
guardrails check --config policy.yaml --event '{
  "scope": "action",
  "agent": "finance-agent",
  "data": {
    "action": "write",
    "resource": {"domain": "finance", "path": "finance/reports/q3.xlsx"}
  }
}'

Common data fields for action:

Field Type Description
action string The action name. Matched against profile allow/deny lists and rule conditions.
recipient dict For communication actions: domain, email, name
resource dict For data operations: domain, path, type
amount number For financial actions: transaction amount

The action field is special. It's checked against the agent's profile deny list before any rules run. If the action name is in the deny list, it's immediately blocked.

Rules that typically match action events:

rules:
  - name: external-email-approval
    scope: action
    when: "action == 'send_email' and recipient.domain != $company_domain"
    then: require_approval
    tier: soft

  - name: financial-writes
    scope: action
    when: "action == 'write' and resource.domain in $sensitive_domains"
    then: require_approval
    tier: strong

  - name: large-transactions
    scope: action
    when: "action == 'transfer' and amount > 10000"
    then: require_approval
    tier: strong

tool_call — Agent calling a specific tool

Use this when an agent invokes a tool (MCP tool, function call, API endpoint).

GuardEvent(
    scope="tool_call",
    agent="data-agent",
    data={
        "tool_name": "execute_sql",
        "arguments": {
            "query": "SELECT * FROM employees WHERE salary > 100000",
            "database": "hr_prod",
        },
    },
)
guardrails check --config policy.yaml --event '{
  "scope": "tool_call",
  "agent": "data-agent",
  "data": {
    "tool_name": "execute_sql",
    "arguments": {"query": "SELECT * FROM employees", "database": "hr_prod"}
  }
}'

Common data fields for tool_call:

Field Type Description
tool_name string The tool being called
arguments dict The arguments passed to the tool
tool_description string Optional: what the tool does

Rules that typically match tool_call events:

rules:
  - name: block-dangerous-sql
    scope: tool_call
    when: "tool_name == 'execute_sql' and arguments.query contains 'DROP'"
    then: deny
    reason: "Destructive SQL operations are not allowed"

  - name: approve-prod-db-access
    scope: tool_call
    when: "tool_name == 'execute_sql' and arguments.database ends_with '_prod'"
    then: require_approval
    tier: strong

cross_agent — Agent-to-agent communication

Use this when one agent sends data to another agent. Requires source_agent and target_agent fields.

GuardEvent(
    scope="cross_agent",
    agent="finance-agent",
    data={
        "message": "Q3 revenue was $42M with 18% profit margin",
        "message_type": "data_share",
    },
    source_agent="finance-agent",
    target_agent="sales-agent",
)
guardrails check --config policy.yaml --event '{
  "scope": "cross_agent",
  "agent": "finance-agent",
  "data": {"message": "Q3 revenue was $42M with 18% profit margin"},
  "source_agent": "finance-agent",
  "target_agent": "sales-agent"
}'

Common data fields for cross_agent:

Field Type Description
message string The content being shared between agents
message_type string Optional: "data_share", "query", "delegation"
payload dict Optional: structured data being transferred

Cross-agent rules use from and to to specify which agent pair they apply to:

rules:
  - name: no-finance-to-sales
    scope: cross_agent
    from: finance-agent         # Matched against event.source_agent
    to: sales-agent             # Matched against event.target_agent
    when: "message matches financial_data"
    then: deny
    reason: "Financial data cannot be shared with sales"

How data Fields Map to when Clauses

The connection between your event data and your rules is through field names:

Event data                          Rule condition
──────────                          ──────────────
data.content          ←→            when: "content matches ..."
data.action           ←→            when: "action == 'send_email'"
data.recipient.domain ←→            when: "recipient.domain != $company_domain"
data.resource.path    ←→            when: "resource.path starts_with 'finance/'"
data.amount           ←→            when: "amount > 10000"

You design your own schema. There is no required structure for data beyond what your rules reference. If you want to check data.department, write when: "department == 'engineering'" and include "department": "engineering" in your event data.

Nested fields use dot notation: recipient.domain accesses data["recipient"]["domain"]. Fields that don't exist resolve to null:

# This event has no "approval" field
event = GuardEvent(scope="action", agent="test", data={"action": "write"})

# This rule checks for a missing field — evaluates to True
# when: "approval == null"

Putting It All Together

A typical agent pipeline evaluates multiple events:

engine = Engine(load_policy("policy.yaml"))

# 1. User sends a message → check input
input_decision = engine.evaluate(GuardEvent(
    scope="input",
    agent="sales-agent",
    data={"content": user_message},
    session_id=session_id,
))

# 2. Agent decides to call a tool → check tool call
tool_decision = engine.evaluate(GuardEvent(
    scope="tool_call",
    agent="sales-agent",
    data={"tool_name": "search_crm", "arguments": {"query": "Henderson"}},
    session_id=session_id,
))

# 3. Agent wants to send an email → check action
action_decision = engine.evaluate(GuardEvent(
    scope="action",
    agent="sales-agent",
    data={
        "action": "send_email",
        "recipient": {"domain": "henderson-corp.com"},
        "subject": "Follow-up on proposal",
    },
    session_id=session_id,
))

# 4. Agent generates a response → check output
output_decision = engine.evaluate(GuardEvent(
    scope="output",
    agent="sales-agent",
    data={"content": agent_response},
    session_id=session_id,
))

Each evaluation takes ~0.005ms. The total overhead for all four checks: ~0.02ms.