Skip to main content

Policy evaluation layer for AI agent tool calls

Project description


title: Action Guardrail emoji: ๐Ÿ›ก๏ธ colorFrom: blue colorTo: indigo sdk: docker pinned: false

Action Guardrail

test

Live demo: https://AntiSpiral18-action-guardrail.hf.space

A policy engine for AI agents that intercepts tool calls before execution and evaluates them against declarative rules. Built with FastAPI for local development, designed to be deployable to production with a pluggable storage backend.

Live Public Deployment

https://AntiSpiral18-action-guardrail.hf.space

This is a real, publicly accessible API running on Hugging Face Spaces free tier with MongoDB Atlas M0 storage. Anyone can hit it โ€” that's the point. Endpoint: POST /evaluate with X-API-Key and a tool_call payload.

Quick Start โ€” Demo Against the Live Deployment

Prerequisites

pip install -r requirements.txt
pytest -v        # all tests must pass

One-time setup

cp .env.example .env

Then edit .env and fill in:

Variable What to put
GUARDRAIL_API_KEY Must match the API_KEY secret set in the HF Space settings. Currently AryaGuardrail1804.
GROQ_API_KEY Your free key from console.groq.com โ€” only needed for harness/run_all.py.

That's it. No $env: exports, no PowerShell shenanigans.

Two-terminal demo

Terminal 1 โ€” Start the interactive HITL reviewer (polls the live Space):

python scripts\review_pending.py --watch

Terminal 2 โ€” Run all 4 scenarios (requires GROQ_API_KEY in .env):

python harness\run_all.py --no-auto-approve

When a scenario hits a require_hitl rule, the agent will block and print a dashboard URL. Switch to Terminal 1, type a + your name, and the agent unblocks within ~2 seconds.

Dashboard

Open https://AntiSpiral18-action-guardrail.hf.space (or /dashboard) in a browser. Enter the API key, click Connect, and browse the audit log. You can also ask natural-language questions about the log data via the Ask Groq section at the bottom of the page.

Local development server

python -m uvicorn app.main:app --reload

Rule Schema

Each rule declares:

Field Description
id Unique rule identifier
description Human-readable description
priority Lower number = evaluated first
action block, require_hitl, or log_and_allow
match.tool Tool name this rule applies to
match.conditions List of conditions (all must match โ€” AND logic)

Example

rules:
  - id: "block-bulk-delete"
    description: "Block any database delete exceeding 100 records"
    priority: 10
    action: block
    match:
      tool: "delete_records"
      conditions:
        - field: "record_count"
          operator: "gt"
          value: 100

Supported Operators

eq, ne, gt, gte, lt, lte, in, not_in, contains, regex

API Endpoints

POST /evaluate โ€” Core policy evaluation

# Block: delete >100 records
curl -s -X POST http://localhost:8000/evaluate \
  -H "Content-Type: application/json" \
  -d '{
    "tool_call": {"tool": "delete_records", "parameters": {"record_count": 500}},
    "dry_run": false
  }' | python -m json.tool

# Require HITL: email to external domain
curl -s -X POST http://localhost:8000/evaluate \
  -H "Content-Type: application/json" \
  -d '{
    "tool_call": {"tool": "send_email", "parameters": {"recipient_domain": "gmail.com"}},
    "dry_run": false
  }' | python -m json.tool

# Log & allow: confidential file read
curl -s -X POST http://localhost:8000/evaluate \
  -H "Content-Type: application/json" \
  -d '{
    "tool_call": {"tool": "read_file", "parameters": {"path": "/data/confidential/report.pdf"}},
    "dry_run": false
  }' | python -m json.tool

# Default allow: no rule matches
curl -s -X POST http://localhost:8000/evaluate \
  -H "Content-Type: application/json" \
  -d '{
    "tool_call": {"tool": "unknown_tool", "parameters": {}},
    "dry_run": false
  }' | python -m json.tool

# Dry-run mode: simulates without enforcement
curl -s -X POST http://localhost:8000/evaluate \
  -H "Content-Type: application/json" \
  -d '{
    "tool_call": {"tool": "delete_records", "parameters": {"record_count": 500}},
    "dry_run": true
  }' | python -m json.tool

GET /health โ€” Health check

curl -s http://localhost:8000/health | python -m json.tool

GET /hitl/pending โ€” List pending HITL requests

curl -s http://localhost:8000/hitl/pending | python -m json.tool

POST /hitl/{id}/approve โ€” Approve a HITL request

curl -s -X POST http://localhost:8000/hitl/<REQUEST_ID>/approve \
  -H "Content-Type: application/json" \
  -d '{"resolved_by": "admin-1"}' | python -m json.tool

POST /hitl/{id}/reject โ€” Reject a HITL request

curl -s -X POST http://localhost:8000/hitl/<REQUEST_ID>/reject \
  -H "Content-Type: application/json" \
  -d '{"resolved_by": "admin-1"}' | python -m json.tool

GET /audit-log โ€” Query audit log

curl -s "http://localhost:8000/audit-log?limit=10&outcome=block" | python -m json.tool

Architecture

evaluate_action(tool_call, rules) โ†’ Decision

Rules are evaluated in priority order. The first matching rule wins. If no rule matches, the default decision is allow.

Storage Abstraction

StorageBackend is an abstract base class with in-memory (InMemoryStorage) and future DynamoDB implementations. Swap by changing STORAGE_BACKEND env var.

Deployment (AWS โ€” always-free tier)

Deploy the guardrail API to AWS Lambda + API Gateway + DynamoDB, all within the AWS Free Tier (no paid services).

Architecture

Agent / Harness  โ”€โ”€HTTPSโ”€โ”€>  API Gateway HTTP API  โ”€โ”€>  Lambda  โ”€โ”€>  DynamoDB
                                  โ”‚                                  โ”‚
                                  โ””โ”€โ”€ SSM Parameter Store             โ”‚
                                     (API key)                  Audit Log +
                                                                 HITL Queue

Prerequisites

Tool Version Install
AWS CLI 2.x pip install awscli or installer
SAM CLI 1.x+ pip install aws-sam-cli
Docker Desktop Required by sam build for dependencies

Configure the AWS CLI with credentials that have sufficient permissions (AdministratorAccess or a scoped policy covering CloudFormation, Lambda, API Gateway, DynamoDB, SSM, IAM, and CloudWatch Logs).

Deploy

# First deployment (guided prompts)
.\deploy.ps1

# Subsequent deployments (uses saved samconfig.toml)
.\deploy.ps1 -DeployOnly

Guided deploy prompts โ€” what to expect:

Prompt Recommended value
Stack Name action-guardrail
AWS Region us-east-1
Parameter ApiKeyParamValue A random string (e.g. pwgen -s 32 1)
Confirm changes before deploy N
Allow SAM CLI IAM role creation Y
Disable rollback N
Save arguments to samconfig.toml Y

After deploy completes, the script prints the API Gateway URL.

Smoke test

.\deploy\smoke_test.ps1 -Endpoint "https://abc123.execute-api.us-east-1.amazonaws.com" -ApiKey "your-api-key"

Expected output:

=== Smoke Test: https://abc123.execute-api.us-east-1.amazonaws.com ===
  [PASS] GET /health
  [PASS] POST /evaluate -> block (delete >100 records)
  [PASS] POST /evaluate -> require_hitl (external email)
  [PASS] POST /evaluate -> log_and_allow (confidential file)
  [PASS] POST /evaluate -> allow (unknown tool)

+-------------------+
| ALL 5 PASSED  โœ“ |
+-------------------+

Run the harness against the cloud endpoint

$env:GUARDRAIL_API_URL = "https://abc123.execute-api.us-east-1.amazonaws.com"
python harness\run_all.py      # requires GROQ_API_KEY in .env

The harness runs all 4 LLM-driven scenarios through the deployed guardrail just as it does locally.

Free-tier cost breakdown

Service Free tier limit Monthly usage at 10k evaluations Charges
Lambda 1M requests + 400,000 GB-seconds ~10k requests ร— 0.5 GB ร— 0.1s = 500 GB-seconds $0
API Gateway HTTP API 1M calls (12 months) 10k calls $0
DynamoDB 25 GB storage + 25 RCU/WCU ~1 MB + <1 RCU/WCU $0
SSM Parameter Store 10,000 parameters (standard) 1 parameter ร— ~4 reads/cold-start $0
CloudWatch Logs 5 GB ingestion (first month) ~1 MB $0
Total $0/month

If traffic exceeds free-tier limits:

  • Lambda: ~$0.20 per million requests + ~$0.0000166667 per GB-second beyond quota
  • API Gateway: ~$1.00 per million requests after 12-month free tier
  • DynamoDB: PAY_PER_REQUEST = ~$1.25 per million writes, ~$0.25 per million reads
  • CloudWatch Logs: ~$0.50 per GB ingested

Project Layout

guardrail/
โ”œโ”€โ”€ app/
โ”‚   โ”œโ”€โ”€ main.py           # FastAPI app + routes
โ”‚   โ”œโ”€โ”€ lambda_handler.py # Lambda entrypoint (Mangum)
โ”‚   โ”œโ”€โ”€ models.py         # Pydantic models
โ”‚   โ”œโ”€โ”€ evaluator.py      # Core evaluation logic
โ”‚   โ”œโ”€โ”€ policy_loader.py  # YAML rule loader
โ”‚   โ”œโ”€โ”€ storage.py        # Storage interface + InMemory / DynamoDB
โ”‚   โ”œโ”€โ”€ audit.py          # Audit log logic
โ”‚   โ”œโ”€โ”€ hitl.py           # HITL queue logic
โ”‚   โ””โ”€โ”€ config.py         # Settings (env / SSM)
โ”œโ”€โ”€ harness/
โ”‚   โ”œโ”€โ”€ agent.py          # LLM agent loop (Groq)
โ”‚   โ”œโ”€โ”€ tools.py          # Mock tool schemas + executors
โ”‚   โ”œโ”€โ”€ guardrail_client.py
โ”‚   โ”œโ”€โ”€ scenarios.py      # Scripted test scenarios
โ”‚   โ”œโ”€โ”€ run_all.py        # Bootstrap + run all scenarios
โ”‚   โ””โ”€โ”€ run_scenarios.py  # Standalone scenario runner
โ”œโ”€โ”€ policies/
โ”‚   โ””โ”€โ”€ example_rules.yaml
โ”œโ”€โ”€ deploy/
โ”‚   โ”œโ”€โ”€ smoke_test.ps1    # Post-deploy smoke test
โ”œโ”€โ”€ tests/
โ”‚   โ”œโ”€โ”€ conftest.py
โ”‚   โ”œโ”€โ”€ test_evaluator.py
โ”‚   โ”œโ”€โ”€ test_policy_loader.py
โ”‚   โ”œโ”€โ”€ test_audit.py
โ”‚   โ”œโ”€โ”€ test_hitl.py
โ”‚   โ”œโ”€โ”€ test_main.py
โ”‚   โ””โ”€โ”€ test_harness.py
โ”œโ”€โ”€ template.yaml         # SAM deployment template
โ”œโ”€โ”€ deploy.ps1            # Deployment script
โ”œโ”€โ”€ .env.example          # Environment variable template
โ””โ”€โ”€ requirements.txt

Production Considerations

This project is deployed on Hugging Face Spaces free tier (Docker + MongoDB Atlas M0) and AWS Lambda free tier (DynamoDB + API Gateway). The following hardening measures are implemented:

Feature Implementation Details
API key authentication X-API-Key header checked in middleware Applied to /evaluate, /hitl/*, /audit-log, /policies. /health, /docs, and / are public.
Rate limiting In-memory sliding window, 60 req/min per key Returns 429 Too Many Requests with Retry-After header when exceeded. Sliding window per unique X-API-Key value. Applies only to /evaluate. In-memory store resets on container restart โ€” acceptable for single-container deployment.
Correlation IDs X-Request-ID header Generated as UUID if not provided by caller. Returned in response headers and present in all structured log output. Every EVALUATE log line includes request_id=....
Request size limit 100KB content-length check 413 Payload Too Large returned before any processing for oversized requests. Applied in middleware before routing.
Graceful degradation Audit/HITL writes wrapped in try/except If MongoDB is unreachable, /evaluate still returns a policy decision (evaluator doesn't need DB). audit_written=false is flagged in the response. WARNING-level logs capture the failure detail for manual recovery.

What a larger-scale production deployment would add next

Area Next step
API key rotation Real key rotation with overlapping validity periods (e.g. two active keys, deprecate old after rotation window). Currently a single static key.
Persistent rate limiting Redis or MongoDB-backed rate counter so limits survive container restarts. Currently in-memory only.
Multi-region DB MongoDB Atlas M10+ with replica sets for HA and cross-region reads. Currently single-region M0.
Dedicated observability Datadog / Grafana / Prometheus for metrics (latency histograms, error rates, rate-limit hit counts). Currently only HF container logs.
Autoscaling Kubernetes or multiple HF Spaces replicas behind a load balancer. Currently single container.
Audit reconciliation Background queue (e.g. Celery + Redis) for retrying failed audit writes. Currently logs to WARNING only.
Webhook notifications Outbound webhooks on HITL creation/resolution. Currently requires polling GET /hitl/{id}.
RBAC / multi-tenant Isolated rule sets per tenant, scoped API keys with read/write/admin roles. Currently single-tenant.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

action_guardrail-0.1.0.tar.gz (22.7 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

action_guardrail-0.1.0-py3-none-any.whl (10.0 kB view details)

Uploaded Python 3

File details

Details for the file action_guardrail-0.1.0.tar.gz.

File metadata

  • Download URL: action_guardrail-0.1.0.tar.gz
  • Upload date:
  • Size: 22.7 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.11.0

File hashes

Hashes for action_guardrail-0.1.0.tar.gz
Algorithm Hash digest
SHA256 b2f015a5c6ec3e5d3c979fca0edf012ee2ab3d7af317b38ad78b0d0ec13e2f68
MD5 28aa2f793ed61ac12b2aa14d56ec46f3
BLAKE2b-256 466ec998c3874400fd5fedb0c901f622700458b5a4bb26292c8ff5f9f907f154

See more details on using hashes here.

File details

Details for the file action_guardrail-0.1.0-py3-none-any.whl.

File metadata

File hashes

Hashes for action_guardrail-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6f3b56a6b37951019e07460ba54b644aa5c8637a9cafc54c49f74e5b71d5d8a9
MD5 3f95bdc4ecd5ad204f2c23b20d907b92
BLAKE2b-256 bcd5efbfeded7d6de08f043d2de689bfba284258e529885245f9cc7e0a039bc3

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page