Developer guide
Agent guard
Check your AI agent’s tool calls, their results and its replies against rules you write in plain words.
Agent guard checks what your AI agent does against rules you write in plain words. Before the agent calls a tool, you send the call to POST /v1/guard/check. Strom answers, for each rule, the probability that the call breaks it, and tells you whether to go ahead. You can try it without code in the console’s Agent guard page.
You can check three stages of your agent’s work:
before_tool: a tool call, before it runs. A block means the tool doesn’t run.after_tool: what a tool returned, before the agent sees it. A block means the agent doesn’t get the result. The tool has already run, so this can’t undo what it did.before_reply: the agent’s reply, before the user sees it. A block means the reply isn’t sent.
Writing a rule
A rule has an id that you choose (lowercase letters, digits, - and _) and five settings:
text: the rule in plain words, for example “Block refunds above 500 unless the user’s role is manager.”stage:before_tool(the default),after_toolorbefore_reply. A rule checks one stage; to check a tool both before and after it runs, write two rules.tools: the tool names the rule is checked for, for example["refund"], or["*"]for every tool. Required forbefore_toolandafter_tool. Leave it out forbefore_reply, which checks every reply.mode:observeorenforce. A rule inobservemode never blocks anything. It only reportswould_block, so you can see what it would catch before you enforce it.threshold: how sure Strom must be that the call breaks the rule before it counts as a match, from 0 to 1. The default is 0.9.
Create and edit rules with the API:
curl https://api.uprelic.com/v1/guard/rules \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"id": "refund-limit",
"text": "Block refunds above 500 unless the user’s role is manager.",
"stage": "before_tool",
"tools": ["refund"],
"mode": "enforce",
"threshold": 0.9
}'The id must be new: if a rule already has it, you get a 409 and nothing changes. To change a rule, send its settings to PUT /v1/guard/rules/{id}, without the id in the body. Each change saves a new version, which checks report in rules[].version. GET /v1/guard/rules lists your rules and DELETE /v1/guard/rules/{id} deletes one. An account can have up to 256 rules.
Checking a tool call
Send the tool’s name as action and its arguments as params. Put facts from your own application, like the signed-in user’s role, in context. Take them from your own systems, not from the model’s output, since the model could make them up.
curl https://api.uprelic.com/v1/guard/check \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "refund",
"params": {"order_id": "A1", "amount": 800},
"context": {"role": "support_agent"}
}'{
"decision": "block",
"rules": [
{
"id": "refund-limit", "version": 1,
"text": "Block refunds above 500 unless the user’s role is manager.", "probability": 0.97,
"threshold": 0.9, "mode": "enforce", "matched": true
}
],
"usage": {"rules_evaluated": 1, "input_tokens": 96, "cost": 0.000004, "currency": "usd"},
"metadata": {"server_processing_time_ms": 92}
}decision is one of:
block: a rule inenforcemode matched. Don’t run the tool.would_block: only rules inobservemode matched. Run the tool.allow: no rule matched, or no rule applies to this tool. Run the tool.
rules lists every rule that applied, the most likely to be broken first, with its text, its probability and whether it matched. When a call is blocked, give the agent the text of each matched rule as the tool’s result, so it can tell the user why or take another route instead of trying again.
Sessions
A session is one task of your agent, such as answering one customer email. Open one with POST /v1/guard/sessions when the task starts, and send its id as session_id with each check of that task. A check without session_id counts as a session of its own.
curl -X POST https://api.uprelic.com/v1/guard/sessions \
-H "Authorization: Bearer YOUR_API_KEY"{"id": "5f0c2a1e-8d3b-4c7a-9e21-3b6f0d9a4c11", "cost": 0.0, "currency": "usd"}cost is the session fee: 0 while your plan's sessions for the month last. A session_id that isn't one of your sessions gets 404.
Checking a tool’s result
After the tool has run, send the same action and params with what the tool returned as result. The after_tool rules for that tool are checked. When the decision is block, don’t give the result to the agent: give it the text of each matched rule instead.
curl https://api.uprelic.com/v1/guard/check \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"action": "read_file",
"params": {"path": "config/database.yml"},
"result": "pool: 5\npassword: hunter2"
}'Checking a reply
To check a reply before the agent sends it, send response (and optionally the user’s request) instead of action. The before_reply rules are checked. When the decision is block, don’t send the reply.
curl https://api.uprelic.com/v1/guard/check \
-H "Authorization: Bearer YOUR_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"response": "Sure, your API key is upr_k3J9xQ2mP7.",
"request": "What is my API key?"
}'Using the Python SDK
Install it with pip install uprelic. Decorate each tool with client.guard(): every call is checked first, with the function’s arguments as params. With check_result=True, what the function returns is checked too.
import uprelic
client = uprelic.Uprelic() # reads UPRELIC_API_KEY
client.create_guard_rule(
"refund-limit",
text="Block refunds above 500 unless the user’s role is manager.",
tools=["refund"],
mode="enforce",
threshold=0.9,
)
@client.guard("refund", context=lambda: {"role": current_user.role})
def refund(order_id, amount):
...
@client.guard("read_file", check_result=True)
def read_file(path):
...
try:
refund("A1", amount=800)
except uprelic.Blocked as blocked:
tool_result = str(blocked) # "refund blocked by refund-limit: Block refunds above 500 …"uprelic.Blocked is raised when the decision is block, and also when the check itself fails, so an unchecked tool never runs. Its message names the matched rules; return it to the agent as the tool’s result. To check a reply, call client.check(response=..., request=...).
Trying a rule
Every check uses the rules saved on your account. To try a new rule, save it in observe mode. It never blocks anything, so your agent keeps working while you see what the rule would catch.
Before you switch the rule to enforce, look at what it would have blocked. GET /v1/guard/checks lists your checks, newest first. Filter with decision (allow, would_block or block), and page with page and page_size (up to 100).
curl "https://api.uprelic.com/v1/guard/checks?decision=would_block" \
-H "Authorization: Bearer YOUR_API_KEY"{
"checks": [
{
"id": "3f6c1d2e-8a4b-4c1e-9d2f-1a2b3c4d5e6f",
"created_at": "2026-10-27T09:30:00+00:00",
"stage": "before_tool",
"decision": "would_block",
"action": "refund",
"params": {"order_id": "A1", "amount": 800},
"context": {"role": "support_agent"},
"rules": [{"id": "refund-limit", "version": 1, "probability": 0.97, "matched": true, ...}],
"cost": 0.00001, "currency": "usd"
}
],
"total": 1, "page": 1, "page_size": 25
}In Python: client.guard_checks(decision="would_block").
Stored data
What you send in a check, including tool results, is stored so you can list it, for as long as your plan keeps request contents (7 days on Free), then deleted. Checks that no rule applies to aren’t stored. Agent guard has no zero data retention option.
Cost
- Input tokens cost what they cost on
POST /v1/systemonewithstrom-fast: the tool call, result or reply once, plus each rule's text.usage.input_tokensis that count. - Each session past your plan's monthly sessions costs $0.50 per 1,000: Free includes none, Starter 50,000 and Growth 500,000. The fee is charged when you open the session, or with the check when it has no
session_id. - A check that no rule applies to is free.
- The cost of each check is in
usage.cost.
Errors
401: the API key is missing or wrong.402: your balance is too low.404: no rule has this id.409: a rule already has this id, or the account already has 256 rules.413: the check has more than 32,000 input tokens, for example a very large tool result.422: the request is invalid, for example bothactionandresponseare sent, or abefore_toolrule has notools.429: too many requests. Checks share the rate limits of your other requests.502: the model couldn’t be reached. Treat the action as not checked.