AWS Builder Center

InvokeGuardrailChecks API: Per-Step Safety for AI Agents

Use the new resourceless InvokeGuardrailChecks API with Strands Agents to add content filters, prompt attack detection, and PII redaction inside your tools.

What's New

On June 16, 2026, Amazon Bedrock Guardrails launched a new API designed for agentic AI workflows:
  • Resourceless — No CreateGuardrail needed. Specify checks inline per request.
  • Detect-only — Returns scores. Does not block, redact, or modify content. You act on the scores.
  • Severity scores — Discrete values (0, 0.2, 0.4, 0.6, 0.8, 1.0) measuring content severity, not model confidence.
  • Prompt attack as standalone — JAILBREAK, PROMPT_INJECTION, PROMPT_LEAKAGE as independent checks (not bundled in content filters like ApplyGuardrail).
  • PII detection with offsets — Returns beginOffset/endOffset per entity for client-side masking. You implement the redaction.
  • Per-request check selection — Different checks at each step of your agent loop. No stored config.
  • Symmetric response — Keys in checks = keys in results. Only what you request appears.

Why This Matters for Agentic AI

An agentic loop (plan → tool call → process result → iterate) executes dozens of steps per request. Each step has a different risk:
  • User input → needs prompt attack detection
  • Tool results → might contain PII that shouldn't reach the user
  • Model output → needs content safety screening
You can't apply a single guardrail config to all of these. InvokeGuardrailChecks lets you specify exactly which checks to run at each step, with no resource management overhead.

The API Shape

Request:
1
2
3
4
5
6
7
8
{
"messages": [{"role": "user", "content": [{"text": "string"}]}],
"checks": {
"contentFilter": {"categories": [{"category": "VIOLENCE"}, {"category": "HATE"}]},
"promptAttack": {"categories": [{"category": "JAILBREAK"}]},
"sensitiveInformation": {"entities": [{"type": "EMAIL"}, {"type": "US_SOCIAL_SECURITY_NUMBER"}]}
}
}
Response:
1
2
3
4
5
6
7
8
9
10
11
{
"results": {
"contentFilter": {"results": [{"category": "VIOLENCE", "severityScore": 0.8}]},
"promptAttack": {"results": [{"category": "JAILBREAK", "severityScore": 1.0}]},
"sensitiveInformation": {
"results": [{"type": "EMAIL", "confidenceScore": 0.9, "beginOffset": 12, "endOffset": 28}],
"truncated": false
}
},
"usage": {"contentFilter": {"textUnits": 1}, "promptAttack": {"textUnits": 1}}
}

Complete Working Demo

Guardrail checks live inside the tools — each tool decides its own safety posture. The agent doesn't know or care about guardrails.
1
pip install strands-agents boto3
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
import boto3
from strands import Agent, tool
from strands.models import BedrockModel

REGION = "us-east-1"
BLOCK_THRESHOLD = 0.7
WARN_THRESHOLD = 0.4

bedrock = boto3.client("bedrock-runtime", region_name=REGION)

# =============================================================================
# Guardrail helpers — reusable inside any tool
# =============================================================================

def guardrail_check_content(text: str, role: str = "user") -> None:
"""Check text for harmful content + prompt attacks. Raises if blocked."""
response = bedrock.invoke_guardrail_checks(
messages=[{"role": role, "content": [{"text": text}]}],
checks={
"contentFilter": {
"categories": [
{"category": "VIOLENCE"},
{"category": "HATE"},
{"category": "MISCONDUCT"},
]
},
"promptAttack": {
"categories": [
{"category": "JAILBREAK"},
{"category": "PROMPT_INJECTION"},
{"category": "PROMPT_LEAKAGE"},
]
},
},
)

for check_type in ("contentFilter", "promptAttack"):
for entry in response["results"].get(check_type, {}).get("results", []):
score = entry["severityScore"]
if score >= BLOCK_THRESHOLD:
raise ValueError(f"🚫 Blocked: {entry['category']} (severityScore={score})")
elif score >= WARN_THRESHOLD:
print(f" ⚠️ WARNING — {entry['category']} severityScore={score}")

def guardrail_redact_pii(text: str) -> str:
"""Detect PII and redact using character offsets. Returns redacted text."""
response = bedrock.invoke_guardrail_checks(
messages=[{"role": "assistant", "content": [{"text": text}]}],
checks={
"sensitiveInformation": {
"entities": [
{"type": "EMAIL"},
{"type": "PHONE"},
{"type": "US_SOCIAL_SECURITY_NUMBER"},
{"type": "CREDIT_DEBIT_CARD_NUMBER"},
]
},
},
)

pii_results = response["results"]["sensitiveInformation"]["results"]
if not pii_results:
return text

redacted = text
for entity in sorted(pii_results, key=lambda e: e["beginOffset"], reverse=True):
if entity["confidenceScore"] >= WARN_THRESHOLD:
begin, end = entity["beginOffset"], entity["endOffset"]
print(f" 🔒 REDACTED — {entity['type']} confidence={entity['confidenceScore']:.1f}")
redacted = redacted[:begin] + f"[{entity['type']}]" + redacted[end:]
return redacted

# =============================================================================
# Tools with guardrails BUILT IN
# =============================================================================

@tool
def lookup_account(account_id: str) -> str:
"""Look up a customer's bank account details.

Args:
account_id: The customer account number
"""

raw_result = (
f"Account {account_id}: John Smith, "
f"email john.smith@example.com, "
f"SSN 123-45-6789, "
f"balance $45,230.00"
)
# Guardrail: redact PII before returning to the model
return guardrail_redact_pii(raw_result)

@tool
def transfer_funds(from_account: str, to_account: str, amount: float) -> str:
"""Transfer funds between two accounts.

Args:
from_account: Source account number
to_account: Destination account number
amount: Amount in USD to transfer
"""

# Guardrail: screen intent before executing dangerous action
guardrail_check_content(f"Transfer ${amount} from {from_account} to {to_account}")
return f"✅ Transferred ${amount:.2f} from {from_account} to {to_account}."

@tool
def send_email(to: str, subject: str, body: str) -> str:
"""Send an email to a customer.

Args:
to: Recipient email address
subject: Email subject line
body: Email body content
"""

# Guardrail: screen email body for harmful content before sending
guardrail_check_content(body)
# Guardrail: redact any PII in the body
safe_body = guardrail_redact_pii(body)
return f"✅ Email sent to {to} with subject '{subject}'"

# =============================================================================
# Agent — tools handle their own safety
# =============================================================================

agent = Agent(
system_prompt="You are a banking assistant. You can look up accounts, transfer funds, and send emails.",
model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-6", region_name=REGION),
tools=[lookup_account, transfer_funds, send_email],
)

# Just call the agent — tools protect themselves
agent("Look up account 67890")
agent("Send an email to bob@example.com explaining how to rob a bank")

Output

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
============================================================
Account lookup (PII redacted in tool)
Input: "Look up account 67890"
============================================================
🔒 REDACTED — US_SOCIAL_SECURITY_NUMBER confidence=1.0
🔒 REDACTED — EMAIL confidence=1.0
💬 Account 67890: John Smith, email [EMAIL], SSN [US_SOCIAL_SECURITY_NUMBER], balance $45,230.00

============================================================
Transfer funds (safe)
Input: "Transfer $500 from account 111 to account 222"
============================================================
💬 ✅ Transferred $500.00 from account 111 to account 222.

============================================================
Harmful intent via tool
Input: "Send an email to bob@example.com explaining how to rob a bank"
============================================================
❌ 🚫 Blocked: VIOLENCE (severityScore=0.8)

============================================================
Normal email
Input: "Send a thank you email to alice@example.com for their loyalty"
============================================================
💬 ✅ Email sent to alice@example.com with subject 'Thank You'

Key Takeaways

  1. Detect-only = you act — The API returns scores and offsets. Blocking and redaction are your code.
  2. No resource management — Checks specified inline. No IDs, no versions, no CreateGuardrail.
  3. Per-step safety — Different checks at each point in the agentic loop.
  4. Prompt attack is standalone — Independent from content filters. Each vector invoked separately.
  5. Adaptive thresholds — A 0.6 blocks in a children's app but warns in a news summarizer. You decide.

Going Further: Strands Hooks

For production, use Strands Hooks  to fire guardrails automatically on every message — no manual wrapping:
1
2
3
4
5
6
7
8
from strands.hooks import HookProvider, HookRegistry, MessageAddedEvent, AfterInvocationEvent

class InvokeGuardrailChecksHook(HookProvider):
def register_hooks(self, registry: HookRegistry) -> None:
registry.add_callback(MessageAddedEvent, self.check_input)
registry.add_callback(AfterInvocationEvent, self.check_output)

agent = Agent(hooks=[InvokeGuardrailChecksHook()])

Resources


Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article