Metadata-Version: 2.4
Name: hitl-guardrail
Version: 0.1.0
Summary: A human-in-the-loop policy guardrail for LLM / AI-agent actions — approve, escalate, or reject with an auditable reason and risk score.
Author-email: Furqan Ali <furqanali2628@gmail.com>
License: MIT
Project-URL: Homepage, https://github.com/furqunali/hitl-guardrail
Project-URL: Repository, https://github.com/furqunali/hitl-guardrail
Project-URL: Issues, https://github.com/furqunali/hitl-guardrail/issues
Keywords: llm,ai-agents,guardrails,human-in-the-loop,crewai,langgraph,agentic,policy,safety
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest>=7; extra == "dev"
Dynamic: license-file

# hitl-guardrail 🛡️

**A human-in-the-loop policy guardrail for LLM / AI-agent actions.**

<p>
  <a href="https://pypi.org/project/hitl-guardrail/"><img src="https://img.shields.io/pypi/v/hitl-guardrail?color=0d9488" alt="PyPI"></a>
  <img src="https://img.shields.io/badge/python-3.9%2B-3776AB" alt="Python 3.9+">
  <img src="https://img.shields.io/badge/deps-zero-2ea44f" alt="Zero dependencies">
  <img src="https://img.shields.io/badge/license-MIT-blue" alt="MIT">
</p>

When an AI agent can take **consequential actions** — send money, email a customer, delete a record, post publicly — you don't want a language model to be the last thing standing between intent and impact. `hitl-guardrail` wraps any such action in a small, **deterministic** policy layer that decides:

**✅ approve automatically · 🧑‍⚖️ escalate to a human · ⛔ reject** — each with an **auditable reason** and a **0–100 risk score**.

- 🔒 **The model advises; policy decides.** Rules live in tested code, not prompts, so behaviour is reproducible and can't drift with a model update.
- 🧩 **Framework-agnostic.** Works with CrewAI, LangGraph, the OpenAI Agents SDK, or plain Python.
- 🪶 **Zero dependencies**, fully typed, tiny.

## Install

```bash
pip install hitl-guardrail
```

## Quickstart

```python
from hitl_guardrail import Guardrail, Rule, Decision

guard = Guardrail(
    rules=[
        Rule("amount_cap", lambda a: a["amount"] <= 2000,
             weight=40, message="amount over $2,000 needs a human"),
        Rule("receipt", lambda a: a.get("has_receipt", False),
             when=lambda a: a["amount"] > 50,           # only checked over $50
             message="receipt required for amounts over $50"),
        Rule("no_fraud", lambda a: not a.get("flagged_fraud", False),
             on_fail=Decision.REJECTED, weight=100, message="flagged as fraud"),
    ],
    auto_approve=lambda a: a["amount"] <= 200,           # small + compliant → auto
)

result = guard.evaluate({"amount": 2500, "has_receipt": True})
print(result.decision)     # Decision.NEEDS_HUMAN_REVIEW
print(result.risk_score)   # 40
print(result.reasons)      # ['amount over $2,000 needs a human']
```

## Gate an action with the `@protect` decorator

The function runs **only if the action is approved**; otherwise your review/reject
handlers take over.

```python
review_queue = []

@guard.protect(on_review=lambda action, res: review_queue.append((action, res)))
def pay_expense(action):
    return charge_card(action)          # only runs when APPROVED

pay_expense({"amount": 5000})           # -> escalated, not charged
```

Without an `on_reject` handler a rejected action raises `GuardrailError`; without an
`on_review` handler a review returns the `GuardrailResult` so you can route it yourself.

## How the decision is made

1. Every applicable rule runs. A rule returns `True` when the action **complies**.
2. A failing rule adds its `weight` to the **risk score** and records a reason.
3. **Precedence:** any `on_fail=REJECTED` failure → **REJECTED**. Otherwise any failed
   review rule, or `risk_score >= risk_threshold` (default 60) → **NEEDS_HUMAN_REVIEW**.
   Otherwise `auto_approve(action)` decides (defaults to approve).
4. A rule that raises is treated as a failure and **fails safe** to human review.

## Use with an agent framework

```python
# CrewAI / LangGraph / OpenAI Agents SDK — same idea: guard the *tool*, not the prompt.
@guard.protect(on_review=queue_for_human, on_reject=notify_and_drop)
def transfer_funds(action: dict):
    banking_api.transfer(**action)
```

## Why this exists

Extracted from a production multi-agent finance system
([agentic-finance-crew](https://github.com/furqunali/agentic-finance-crew)), where the
same principle applies: **an LLM can propose, but a deterministic guardrail authorizes.**

## Development

```bash
pip install -e ".[dev]"
pytest -q
python examples/expense_approval.py     # decision routing
python examples/approval_workflow.py    # human-in-the-loop review queue (approve/reject)
```

See [`examples/approval_workflow.py`](examples/approval_workflow.py) for a complete
human-approval workflow — auto-approve, park-for-review, and reject — built entirely
from the public API.

## License

MIT © Furqan Ali
