Evaluating it on your traces

Before you put the guardrail in front of your tools, run your agent's past calls through it: how many it would have stopped, by which rule, how often it would have asked, and, where you label the calls, how often it was wrong. Everything is read on your machine, and reports hold tool names, rule names, counts and decision ids, never an argument value or a word a person said.

Trace formats

Format What it is
jsonl one call a line: {"conversation", "tool", "arguments", "turns"?, "result"?, "label"?}; or one turn a line: {"conversation", "turn"}, which the calls after it read
openai chat logs: a conversation a line (or a JSON list), each its messages with tool_calls and tool replies
anthropic the same with tool_use and tool_result blocks
langsmith an export of runs: the tool runs (run_type: tool) of each trace, in time order
tau2 a tau2-bench results file: each simulation a conversation

auto tells them apart by the first record. Each call is checked with the person's turns said before it and what tools returned before it, and each conversation is its own session.

Replaying: what would it have done?

replay(trace) in stalwart mcp, or evaluation.replay in Python:

Evaluating: was it right?

Label each call should_allow, should_confirm or should_refuse (a label on each line), and evaluate(labelled):

answered allow answered confirm answered refuse
should allow correct false alarm false alarm
should confirm miss correct false alarm
should refuse miss miss correct

The report gives the matrix, the counts and the calls behind each false alarm and miss, overall and per rule.

Counterfactual: what would it have changed?

counterfactual(trace, "check.py:outcome") scores each conversation with your own outcome check, as run and with the calls the guardrail stopped removed. Your function is given a conversation's id and the calls that ran, and returns a number or a truth value (for example, whether the database it leaves is the one the task expected).

This is an offline counterfactual: the rest of the conversation is kept as recorded. A live agent that met the refusal may have gone on differently.

Your Guardrails AI guard, with and without ours

compare_with_gai(trace, "guard.py:guard") runs every call through your Guardrails AI guard alone, and through it with our validator added: how many each lets through, and the calls only ours stops. It needs stalwart-agents[guardrails] where the server runs.

Holes in your rules

hunt("rules.yaml") runs the bug hunter on your rule sheet: each finding is a way through that your rules allow, with a witness to replay. redteam(server) proves an MCP server's tools behind the gateway for any call a model may make. Both run on the service, with your key. They are a quality check of your own rules, not a certificate.

The tau2-bench benchmark

benchmark("tau2-airline") and benchmark("tau2-retail") fetch the third-party tau2-bench data from its pinned upstream commits into benchmarks/, on demand; nothing is bundled. The published figures (airline: 114 of 1,161 writes blocked; retail: 290 of 3,450) come from Stalwart's own replay of that data, which the package does not yet carry: the report gives them as published, says they were not reproduced here, and names the sources it fetched.