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.