Metadata-Version: 2.4
Name: obex
Version: 0.1.0
Summary: Deterministic, config-driven guardrails for LLM agent tool calls
Project-URL: Homepage, https://github.com/terry-li-hm/obex
Project-URL: Repository, https://github.com/terry-li-hm/obex
Author-email: Terry Li <terry.li.hm@gmail.com>
License-Expression: MIT
License-File: LICENSE
Keywords: agent,ai,audit,compliance,guardrails,llm,tool-calls
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Security
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Provides-Extra: all
Requires-Dist: langchain-core>=0.3; extra == 'all'
Requires-Dist: langgraph>=0.2; extra == 'all'
Requires-Dist: pyyaml>=6.0; extra == 'all'
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == 'dev'
Requires-Dist: pyyaml>=6.0; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Provides-Extra: langgraph
Requires-Dist: langchain-core>=0.3; extra == 'langgraph'
Requires-Dist: langgraph>=0.2; extra == 'langgraph'
Provides-Extra: yaml
Requires-Dist: pyyaml>=6.0; extra == 'yaml'
Description-Content-Type: text/markdown

# obex

> Deterministic, config-driven guardrails for LLM agent tool calls.
> Zero-LLM enforcement. Compliance audit trail built in.

Named after the Latin word for *bolt* or *barrier*. The bolt on the gate that no LLM can argue past.

## Why

LLM agents can now call tools autonomously — execute SQL, send emails, make API calls. Existing guardrail frameworks either use LLM calls in the enforcement path (non-deterministic, slow, expensive) or require writing Python code that compliance teams can't review.

Obex is different:

- **YAML config** — rules live in config files that compliance teams can review, version, and audit without reading Python
- **Zero-LLM enforcement** — every decision is deterministic and reproducible. No AI in the guardrail path.
- **Compliance-first** — OPA-inspired audit trail as a first-class feature, not an afterthought
- **Framework-agnostic** — works standalone or with LangGraph, CrewAI, or any agent framework

| Feature | Obex | NeMo Guardrails | LangChain Middleware | OpenAI Agents SDK |
|---|---|---|---|---|
| Config-driven (YAML) | Yes | Colang DSL | Python code | Python decorators |
| Zero-LLM enforcement | Yes | No (LLM in loop) | Yes | Yes |
| Audit trail | Built-in | Logging only | No | No |
| Framework-agnostic | Yes | LangChain | LangChain only | OpenAI only |
| Dependencies | Zero (stdlib) | Heavy | LangChain | OpenAI SDK |

## Quick Start

```bash
pip install obex[yaml]
```

```python
from obex import Engine, ToolCall

engine = Engine.from_yaml("rules.yaml")

# Evaluate a tool call
result = engine.evaluate(
    ToolCall(name="execute_sql", args={"query": "DROP TABLE users"})
)
print(result.decision)  # Decision.BLOCK
print(result.reason)    # "Pattern matched in field 'query': DROP TABLE"
```

## YAML Config

```yaml
version: "1.0"
policy_version: "1.0.0"

rules:
  # Block dangerous SQL patterns
  - name: block_sql_injection
    type: regex_block
    applies_to: ["execute_sql", "run_query"]
    params:
      fields: ["query"]
      patterns:
        - "(?i)(DROP|DELETE|TRUNCATE)\\s+TABLE"

  # Require confirmation IDs on sensitive operations
  - name: require_confirmation
    type: regex_require
    applies_to: ["send_email", "transfer_funds"]
    params:
      fields: ["confirmation_id"]
      pattern: "^CONF-[A-Z0-9]{8}$"

  # Scan all tool calls for PII leakage
  - name: detect_pii
    type: pii_detect
    applies_to: ["*"]
    params:
      detectors: [email, phone_intl, hk_id, credit_card]
      action: block

  # Role-based tool access
  - name: tool_entitlement
    type: entitlement
    applies_to: ["*"]
    params:
      roles:
        analyst: ["search", "get_data", "summarize"]
        admin: ["*"]
      default: block
```

### Rule Types

| Type | Purpose | Key Params |
|---|---|---|
| `regex_block` | Block if field matches pattern | `fields`, `patterns` |
| `regex_require` | Block if required field is missing/invalid | `fields`, `pattern` |
| `pii_detect` | Scan args for PII (email, phone, HKID, etc.) | `detectors`, `action` |
| `entitlement` | Role-based tool access control | `roles`, `default` |

## Audit Trail

Every evaluation produces a structured JSON record (JSONL format, OPA-inspired):

```python
from obex import AuditLogger, Engine

logger = AuditLogger("audit.jsonl")
engine = Engine.from_yaml("rules.yaml", audit_logger=logger.log)
```

Each record includes: `decision_id`, `timestamp`, `policy_version`, `tool_name`, `tool_args` (redacted), `decision`, `rules_evaluated`, `blocking_rule`, `human_override`, `trace_id`.

## Audit Reports

Generate compliance-ready summaries from audit logs:

```python
from obex import AuditReporter

reporter = AuditReporter("audit.jsonl")
report = reporter.generate()
print(report.to_text())
```

```
========================================
OBEX AUDIT REPORT
========================================
Period: 2026-02-28 10:00 to 2026-02-28 16:00
Policy versions: 1.0.0

Total evaluations: 500
       Allow:    450 (90.0%)
       Block:     50 (10.0%)

Top blocked tools:
  1. execute_sql                    — 25 blocks
  2. send_email                     — 15 blocks

Top triggered rules:
  1. block_sql_injection            — 20 triggers
  2. detect_pii                     — 18 triggers

Human override rate: 4.0% (2 of 50 blocks overridden)
========================================
```

## LangGraph Integration

```bash
pip install obex[langgraph]
```

```python
from langgraph.prebuilt import ToolNode
from obex import Engine
from obex.adapters.langgraph import guarded_tool_node

tools = [search, calculator]
engine = Engine.from_yaml("rules.yaml")
safe_tools = guarded_tool_node(ToolNode(tools), engine)

builder.add_node("tools", safe_tools)
```

Blocked tool calls return a `ToolMessage` with the block reason — the LLM sees why its call was rejected and can adjust.

## Programmatic Use (No YAML)

```python
from obex import Engine, ToolCall
from obex._types import RuleConfig, RuleType

engine = Engine(rules=[
    RuleConfig(
        name="block_drops",
        rule_type=RuleType.REGEX_BLOCK,
        params={"fields": ["query"], "patterns": [r"(?i)DROP\s+TABLE"]},
        applies_to=["execute_sql"],
    ),
])

result = engine.evaluate(ToolCall(name="execute_sql", args={"query": "SELECT 1"}))
assert result.decision.value == "allow"
```

Zero dependencies — the core engine runs on stdlib alone.

## Design Philosophy

- **Hooks > prompts** for mechanical rules. If a rule is regex-matchable, enforce it in code, not in the system prompt.
- **Fail closed.** If a rule errors, the tool call is blocked.
- **No LLM in the enforcement path.** Every decision is deterministic and reproducible.
- **Config is reviewable.** Compliance teams review YAML, not Python.
- **Audit schema inspired by OPA.** Decision logs follow established policy-engine conventions.

## License

MIT
