Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -231,7 +231,7 @@ Blocked: block-ssn-demo
Explore working examples for popular frameworks.

- [Customer Support Agent](examples/customer_support_agent/) - PII protection, prompt injection defense, and tool controls
- [Steer Action Demo](examples/steer_action_demo/) - allow, deny, warn, and steer decisions in one workflow
- [Steer Action Demo](examples/steer_action_demo/) - observe, deny, and steer decisions in one workflow
- [LangChain](examples/langchain/) - protect a SQL agent from dangerous queries
- [CrewAI](examples/crewai/) - combine Agent Control with CrewAI guardrails
- [AWS Strands](examples/strands_agents/) - protect Strands workflows and tool calls
Expand Down
152 changes: 76 additions & 76 deletions engine/tests/test_core.py

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This directory contains runnable examples for Agent Control. Each example has it
| DeepEval | Build a custom evaluator using DeepEval GEval metrics. | https://docs.agentcontrol.dev/examples/deepeval |
| Galileo Luna-2 | Toxicity detection and content moderation with Galileo Protect. | https://docs.agentcontrol.dev/examples/galileo-luna2 |
| LangChain SQL Agent | Protect a SQL agent from dangerous queries with server-side controls. | https://docs.agentcontrol.dev/examples/langchain-sql |
| Steer Action Demo | Banking transfer agent showcasing allow, deny, warn, and steer actions. | https://docs.agentcontrol.dev/examples/steer-action-demo |
| Steer Action Demo | Banking transfer agent showcasing observe, deny, and steer actions. | https://docs.agentcontrol.dev/examples/steer-action-demo |
| AWS Strands | Guardrails for AWS Strands agent workflows and tool calls. | https://docs.agentcontrol.dev/examples/aws-strands |
| TypeScript SDK | Consumer-style TypeScript example using the published npm package. | https://docs.agentcontrol.dev/examples/typescript_sdk |

Expand Down
10 changes: 5 additions & 5 deletions examples/crewai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ Tool executes (LLM call, DB query, etc.)
CrewAI Guardrails (quality retries) <- LAYER 3: CrewAI validates quality
|
v
Return to user (or block / steer / warn)
Return to user (or block / steer / observe)
```

**Agent Control** blocks or steers immediately (security). **CrewAI Guardrails** retry with feedback (quality). Controls are defined on the **server** -- update rules without redeploying agents.
Expand Down Expand Up @@ -77,15 +77,15 @@ uv run --active python -m secure_research_crew.main

## Examples

### 1. [Steering Financial Agent](./steering_financial_agent/) -- Deny, Steer & Warn
### 1. [Steering Financial Agent](./steering_financial_agent/) -- Deny, Steer & Observe

Demonstrates all three Agent Control action types in a wire-transfer scenario:

| Action | Behavior | Example |
|--------|----------|---------|
| **DENY** | Hard block, no recovery | Sanctioned country, fraud detected |
| **STEER** | Pause, guide agent to correct, retry | 2FA required, manager approval needed |
| **WARN** | Log for audit, no blocking | New recipient, unusual activity |
| **OBSERVE** | Record for audit, no blocking | New recipient, unusual activity |

```bash
cd examples/crewai/steering_financial_agent
Expand Down Expand Up @@ -131,7 +131,7 @@ A production-quality **3-agent sequential crew** (Researcher, Analyst, Writer) w

| # | Scenario | Agent | Evaluator | Action | Result |
|---|----------|-------|-----------|--------|--------|
| 1 | Happy path | All 3 | All | allow | Report generated with sources |
| 1 | Happy path | All 3 | All | observe | Report generated with sources |
| 2 | SQL injection | Researcher | SQL | deny | "Multiple SQL statements not allowed" |
| 3 | Restricted table | Researcher | LIST | deny | salary_data access blocked |
| 4 | Missing methodology | Analyst | JSON Schema | steer | Auto-corrected, passes on retry |
Expand Down Expand Up @@ -189,7 +189,7 @@ uv run --active python -m content_publishing_flow.main
| POST-execution checks | Yes | Yes | Yes | Yes |
| **deny** action | Yes | Yes | Yes | Yes |
| **steer** action | Yes | Yes | Yes | Yes |
| **warn** action | Yes | | | |
| **observe** action | Yes | | | |
| Regex evaluator | | Yes | Yes | Yes |
| List evaluator | Yes | Yes | Yes | Yes |
| JSON evaluator | Yes | Yes | Yes | Yes |
Expand Down
2 changes: 1 addition & 1 deletion examples/crewai/content_publishing_flow/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ Agent Control works at the **tool boundary** within each flow stage:
3. The server evaluates controls matching the step name and stage (pre/post)
4. On **deny**: `ControlViolationError` is raised, the stage handles it
5. On **steer**: `ControlSteerError` provides corrective guidance for retry
6. On **allow**: execution proceeds normally
6. On **observe** or no match: execution proceeds normally

This pattern means controls are defined on the **server** (not in code), so you can update rules, add new controls, or change actions without redeploying the agent.

Expand Down
2 changes: 1 addition & 1 deletion examples/crewai/secure_research_crew/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ Each tool's `step_name` matches the `step_names` in its corresponding control sc

| # | Scenario | Agent | Control | Evaluator | Action | Expected |
|---|----------|-------|---------|-----------|--------|----------|
| 1 | Happy path | All | All | All | allow | Report generated |
| 1 | Happy path | All | All | All | observe | Report generated |
| 2 | SQL injection | Researcher | researcher-sql-safety | SQL | deny | Query blocked |
| 3 | Restricted table | Researcher | researcher-restricted-tables | LIST | deny | Query blocked |
| 4 | Missing methodology | Analyst | analyst-methodology-check | JSON Schema | steer | Auto-corrected, then passes |
Expand Down
12 changes: 6 additions & 6 deletions examples/crewai/steering_financial_agent/README.md
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
# CrewAI Financial Agent with Steering Controls

Demonstrates all Agent Control action types in a realistic wire-transfer scenario using CrewAI.
Demonstrates Agent Control actions in a realistic wire-transfer scenario using CrewAI.

## Action Types

| Action | Behavior | Example |
|--------|----------|---------|
| **DENY** | Hard block, agent cannot recover | Sanctioned country, fraud detected |
| **STEER** | Pause execution, guide agent to correct and retry | 2FA required, manager approval needed |
| **WARN** | Log for audit, agent continues uninterrupted | New recipient, unusual activity |
| **OBSERVE** | Record for audit, agent continues uninterrupted | New recipient, unusual activity |

The key difference: **DENY** raises `ControlViolationError` (permanent), **STEER** raises `ControlSteerError` (recoverable), and **WARN** logs silently.
The key difference: **DENY** raises `ControlViolationError` (permanent), **STEER** raises `ControlSteerError` (recoverable), and **OBSERVE** records a non-blocking advisory event.

## Scenarios

| # | Scenario | Amount | Controls Triggered | Outcome |
|---|----------|--------|--------------------|---------|
| 1 | Small transfer to new vendor | $2,500 | warn (new recipient) | ALLOWED + audit log |
| 1 | Small transfer to new vendor | $2,500 | observe (new recipient) | ALLOWED + audit trail |
| 2 | Transfer to North Korea | $500 | deny (sanctioned country) | BLOCKED |
| 3 | Large transfer | $15,000 | steer (2FA required) | STEERED -> verified -> ALLOWED |
| 4 | Very large transfer | $75,000 | steer (2FA + manager) | STEERED -> approved -> ALLOWED |
Expand All @@ -28,8 +28,8 @@ The key difference: **DENY** raises `ControlViolationError` (permanent), **STEER
- `deny-high-fraud-score` — JSON evaluator, blocks fraud_score > 0.8
- `steer-require-2fa` — JSON evaluator with oneOf schema, steers for 2FA
- `steer-require-manager-approval` — JSON evaluator, steers for approval
- `warn-new-recipient` — LIST evaluator, logs unknown recipients
- `warn-pii-in-confirmation` — REGEX evaluator, logs PII in output
- `observe-new-recipient` — LIST evaluator, records unknown recipients
- `observe-pii-in-confirmation` — REGEX evaluator, records PII in output

## Prerequisites

Expand Down
2 changes: 1 addition & 1 deletion examples/crewai/steering_financial_agent/pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
[project]
name = "steering-financial-agent"
version = "0.1.0"
description = "CrewAI financial agent with steering, deny, and warn actions via Agent Control"
description = "CrewAI financial agent with steering, deny, and observe actions via Agent Control"
requires-python = ">=3.12"
dependencies = [
"agent-control-sdk>=6.3.0",
Expand Down
28 changes: 14 additions & 14 deletions examples/crewai/steering_financial_agent/setup_controls.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
"""
Setup script for the CrewAI Financial Agent steering example.

Creates controls that demonstrate all three non-blocking action types:
Creates controls that demonstrate the three supported action types:
- DENY: Hard-block sanctioned countries and fraud (no recovery)
- STEER: Guide the agent through 2FA and manager approval workflows
- WARN: Flag new recipients and unusual hours for audit (no blocking)
- OBSERVE: Flag new recipients and unusual hours for audit (no blocking)

Run once before running the agent:
uv run --active python setup_controls.py
Expand Down Expand Up @@ -222,15 +222,15 @@ async def setup():
},
),
# ┌─────────────────────────────────────────────────────────┐
# │ WARN: New Recipient │
# │ OBSERVE: New Recipient │
# │ Uses LIST evaluator with "not in" logic. │
# │ Logs a warning when recipient is unknown — but does │
# │ NOT block the transfer. Useful for audit trails. │
# │ Records an advisory event when recipient is unknown │
# │ but does NOT block the transfer. Useful for audit. │
# └─────────────────────────────────────────────────────────┘
(
"warn-new-recipient",
"observe-new-recipient",
{
"description": "Log warning for transfers to unknown recipients",
"description": "Observe transfers to unknown recipients",
"enabled": True,
"execution": "server",
"scope": {
Expand All @@ -251,18 +251,18 @@ async def setup():
"case_sensitive": False,
},
},
"action": {"decision": "warn"},
"action": {"decision": "observe"},
},
),
# ┌─────────────────────────────────────────────────────────┐
# │ WARN: PII in Output │
# │ OBSERVE: PII in Output │
# │ Uses REGEX evaluator to detect leaked PII in the │
# │ transfer confirmation. Logs for compliance review. │
# │ transfer confirmation. Records for compliance review. │
# └─────────────────────────────────────────────────────────┘
(
"warn-pii-in-confirmation",
"observe-pii-in-confirmation",
{
"description": "Log warning if transfer confirmation contains PII patterns",
"description": "Observe PII patterns in transfer confirmation output",
"enabled": True,
"execution": "server",
"scope": {
Expand All @@ -277,7 +277,7 @@ async def setup():
"pattern": r"(?:\b\d{3}-\d{2}-\d{4}\b|\b\d{4}[\s-]?\d{4}[\s-]?\d{4}[\s-]?\d{4}\b)"
},
},
"action": {"decision": "warn"},
"action": {"decision": "observe"},
},
),
]
Expand Down Expand Up @@ -322,7 +322,7 @@ async def setup():
print(" - 2FA required for >= $10k (json evaluator)")
print(" - Manager approval for >= $50k (json evaluator)")
print()
print(" WARN (log for audit, no blocking):")
print(" OBSERVE (audit trail, no blocking):")
print(" - New/unknown recipient (list evaluator)")
print(" - PII in confirmation output (regex evaluator)")
print()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
"""
CrewAI Financial Agent with Steering, Deny, and Warn actions.
CrewAI Financial Agent with Steering, Deny, and Observe actions.

Demonstrates the three key Agent Control action types in a realistic
wire-transfer scenario using a CrewAI crew:

DENY - Sanctioned country or fraud score blocks the transfer immediately.
STEER - Large transfers pause execution and guide the agent through
2FA verification or manager approval before retrying.
WARN - New recipients and PII in output are logged for audit
without blocking the transfer.
OBSERVE - New recipients and PII in output are recorded for audit
without blocking the transfer.

PREREQUISITE:
Run setup_controls.py first:
Expand All @@ -20,7 +20,7 @@
$ uv run --active steering_financial_agent

Scenarios:
1. Small legitimate transfer -> ALLOW (warn on new recipient)
1. Small legitimate transfer -> OBSERVE (new recipient)
2. Sanctioned country -> DENY (hard block)
3. Large transfer ($15k) -> STEER (2FA required, then allowed)
4. Very large transfer ($75k) -> STEER (manager approval, then allowed)
Expand Down Expand Up @@ -67,7 +67,7 @@ def verify_server():
"deny-high-fraud-score",
"steer-require-2fa",
"steer-require-manager-approval",
"warn-new-recipient",
"observe-new-recipient",
]
missing = [n for n in required if n not in names]
if missing:
Expand Down Expand Up @@ -104,7 +104,7 @@ def run_scenario(crew, number, title, request, expected):
def main():
print("=" * 60)
print(" CrewAI Financial Agent")
print(" Steering, Deny & Warn with Agent Control")
print(" Steering, Deny & Observe with Agent Control")
print("=" * 60)
print()

Expand All @@ -118,19 +118,19 @@ def main():
crew = create_financial_crew()

# ── Scenario 1: Small Legitimate Transfer ───────────────────────
# Amount < $10k, known country, low fraud → ALLOW
# Unknown recipient → WARN (logged, not blocked)
# Amount < $10k, known country, low fraud → OBSERVE only
# Unknown recipient → OBSERVE (recorded, not blocked)
run_scenario(
crew,
1,
"Small Legitimate Transfer (ALLOW + WARN)",
"Small Legitimate Transfer (OBSERVE)",
{
"amount": 2500,
"recipient": "New Vendor XYZ",
"destination_country": "Germany",
"fraud_score": 0.1,
},
"ALLOWED with warning (new recipient logged for audit)",
"Allowed with an observe event (new recipient recorded for audit)",
)

# ── Scenario 2: Sanctioned Country ──────────────────────────────
Expand All @@ -149,26 +149,26 @@ def main():
)

# ── Scenario 3: Large Transfer Requiring 2FA ────────────────────
# Amount $15k → STEER (2FA required), agent verifies, retries → ALLOW
# Amount $15k → STEER (2FA required), agent verifies, retries successfully
run_scenario(
crew,
3,
"Large Transfer - 2FA Steering (STEER then ALLOW)",
"Large Transfer - 2FA Steering (STEER then success)",
{
"amount": 15000,
"recipient": "Acme Corp",
"destination_country": "United Kingdom",
"fraud_score": 0.2,
},
"STEERED (2FA), then ALLOWED after verification",
"STEERED (2FA), then succeeded after verification",
)

# ── Scenario 4: Very Large Transfer Requiring Manager Approval ──
# Amount $75k → STEER (2FA + manager approval), agent handles both → ALLOW
# Amount $75k → STEER (2FA + manager approval), agent handles both successfully
run_scenario(
crew,
4,
"Very Large Transfer - Manager Approval (STEER then ALLOW)",
"Very Large Transfer - Manager Approval (STEER then success)",
{
"amount": 75000,
"recipient": "Global Suppliers Inc",
Expand Down Expand Up @@ -206,13 +206,13 @@ def main():
STEER 2FA verification (Scenario 3) - pause, verify, retry
Manager approval (Scenario 4) - pause, collect + approve, retry

WARN New recipient (Scenario 1) - logged for audit, not blocked
PII in output (if triggered) - logged for compliance, not blocked
OBSERVE New recipient (Scenario 1) - recorded for audit, not blocked
PII in output (if triggered) - recorded for compliance, not blocked

Key Differences:
DENY = ControlViolationError (agent cannot recover)
STEER = ControlSteerError (agent corrects and retries)
WARN = Logged silently (agent continues uninterrupted)
OBSERVE = Recorded silently (agent continues uninterrupted)
""")


Expand Down
4 changes: 2 additions & 2 deletions examples/customer_support_agent/demo.sh
Original file line number Diff line number Diff line change
Expand Up @@ -247,7 +247,7 @@ show_observability_stats() {
echo "Last 5 minutes:"
echo "---------------"
local stats_5m=$(curl -s "${server_url}/api/v1/observability/stats?agent_name=${agent_name}&time_range=5m")
if echo "$stats_5m" | python3 -c "import sys, json; d=json.load(sys.stdin); print(f\" Total executions: {d.get('total_executions', 0)}\"); print(f\" Matches: {d.get('total_matches', 0)}\"); print(f\" Non-matches: {d.get('total_non_matches', 0)}\"); print(f\" Errors: {d.get('total_errors', 0)}\"); actions=d.get('action_counts', {}); print(f\" Actions: allow={actions.get('allow', 0)}, deny={actions.get('deny', 0)}, warn={actions.get('warn', 0)}, log={actions.get('log', 0)}\")" 2>/dev/null; then
if echo "$stats_5m" | python3 -c "import sys, json; d=json.load(sys.stdin); print(f\" Total executions: {d.get('total_executions', 0)}\"); print(f\" Matches: {d.get('total_matches', 0)}\"); print(f\" Non-matches: {d.get('total_non_matches', 0)}\"); print(f\" Errors: {d.get('total_errors', 0)}\"); actions=d.get('action_counts', {}); print(f\" Actions: observe={actions.get('observe', 0)}, deny={actions.get('deny', 0)}, steer={actions.get('steer', 0)}\")" 2>/dev/null; then
:
else
echo " No data or error fetching stats"
Expand All @@ -259,7 +259,7 @@ show_observability_stats() {
echo "Last 1 hour:"
echo "------------"
local stats_1h=$(curl -s "${server_url}/api/v1/observability/stats?agent_name=${agent_name}&time_range=1h")
if echo "$stats_1h" | python3 -c "import sys, json; d=json.load(sys.stdin); print(f\" Total executions: {d.get('total_executions', 0)}\"); print(f\" Matches: {d.get('total_matches', 0)}\"); print(f\" Non-matches: {d.get('total_non_matches', 0)}\"); print(f\" Errors: {d.get('total_errors', 0)}\"); actions=d.get('action_counts', {}); print(f\" Actions: allow={actions.get('allow', 0)}, deny={actions.get('deny', 0)}, warn={actions.get('warn', 0)}, log={actions.get('log', 0)}\")" 2>/dev/null; then
if echo "$stats_1h" | python3 -c "import sys, json; d=json.load(sys.stdin); print(f\" Total executions: {d.get('total_executions', 0)}\"); print(f\" Matches: {d.get('total_matches', 0)}\"); print(f\" Non-matches: {d.get('total_non_matches', 0)}\"); print(f\" Errors: {d.get('total_errors', 0)}\"); actions=d.get('action_counts', {}); print(f\" Actions: observe={actions.get('observe', 0)}, deny={actions.get('deny', 0)}, steer={actions.get('steer', 0)}\")" 2>/dev/null; then
:
else
echo " No data or error fetching stats"
Expand Down
Loading
Loading