Metadata-Version: 2.4
Name: warrantd-openai-agents
Version: 0.1.0
Summary: Earned autonomy for the OpenAI Agents SDK — warrantd drives the approval switch.
Project-URL: Homepage, https://github.com/moritzkazooba-wq/warrantd
Project-URL: Repository, https://github.com/moritzkazooba-wq/warrantd
Author: Moritz
License: MIT
License-File: LICENSE
Keywords: agents,approval,autonomy,guardrails,openai-agents,trust
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: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: openai-agents<1.0,>=0.17
Requires-Dist: warrantd-core<0.4.0,>=0.3.0
Provides-Extra: gateway
Requires-Dist: warrantd-gateway<0.3,>=0.2; extra == 'gateway'
Description-Content-Type: text/markdown

# warrantd-openai-agents

> Your framework has the approval switch; warrantd is the brain that flips it.

The [OpenAI Agents SDK](https://github.com/openai/openai-agents-python)
already ships human-in-the-loop tool approval: `needs_approval`,
interruptions, approve/reject, resume. What it doesn't ship is a policy for
*when* a tool needs approval — that's usually a hardcoded bool.

This adapter makes the switch dynamic: **earned autonomy** from
[warrantd](https://github.com/moritzkazooba-wq/warrantd). A tool starts at
MANUAL (every call interrupted for a human), earns SUPERVISED through clean
approvals (auto-runs within a value cap), and is bounded by ceilings no
metric can move.

```bash
pip install warrantd-openai-agents
```

## Quickstart

```python
from decimal import Decimal
from agents import Agent, Runner
from warrantd import (
    ActionClass, ApprovalHistorySignal, AutonomyState, GraduationThresholds,
    InMemoryApprovalHistory, RiskTier, TrustLayer, TrustPolicy,
)
from warrantd_openai_agents import ToolBinding, WarrantdGate

policy = TrustPolicy(
    actions={"issue_refund": ActionClass(
        name="issue_refund", risk=RiskTier.REVERSIBLE_WRITE,
        auto_cap=Decimal("100"), hard_cap=Decimal("1000"),
        max_state=AutonomyState.SUPERVISED,
    )},
    thresholds=GraduationThresholds(
        pass_rate={AutonomyState.SUPERVISED: 1.0, AutonomyState.AUTONOMOUS: 1.0},
        adversarial_pass_rate={AutonomyState.SUPERVISED: 0.0, AutonomyState.AUTONOMOUS: 0.9},
        min_samples={AutonomyState.SUPERVISED: 5, AutonomyState.AUTONOMOUS: 1000},
    ),
)
evidence = InMemoryApprovalHistory()
trust = TrustLayer(policy, audit=my_audit_sink,
                   signals=[ApprovalHistorySignal(evidence, window=10)])
gate = WarrantdGate(trust, policy=policy, evidence=evidence,
                    bindings={"issue_refund": ToolBinding(value_param="amount")})

agent = gate.guard_agent(Agent(name="billing", tools=[issue_refund]))

result = await Runner.run(agent, "refund invoice INV-1 by $50")
if result.interruptions:                      # REQUIRE_APPROVAL: run paused
    state = result.to_state()
    for item in result.interruptions:
        print(gate.context_for(item).sentence())
        # -> "this class is 4/10 approvals from SUPERVISED — your decision
        #     feeds its trust record"
        gate.approve(state, item, approver="alice")   # or gate.reject(...)
    result = await Runner.run(agent, state)
```

After enough clean approvals the same call **stops interrupting** — it
auto-runs within `auto_cap`. A call above `hard_cap` never interrupts either:
it is blocked outright, with the reason returned as tool output so the model
can adapt. **Ceilings never move.**

> **Call `gate.approve()`/`gate.reject()`, not `state.approve()` directly** —
> the SDK has no approval callback, so the gate is the hook that turns the
> human's decision into trust evidence. Bypass it and nothing is learned.

## What this adapter does NOT do

- **No decision logic.** Every verdict comes from `warrantd-core`'s
  `TrustLayer`; this package only translates SDK events. (Verify it:
  `GraduationEngine` appears only in the display-only progress helper, and
  no `Decision`/`Verdict` is ever constructed here.)
- **No approval UI.** Your interruption handler is the UI; `context_for()`
  gives you the graduation sentence to render.
- **No MCP gating.** For gating MCP servers with zero agent changes, use
  [warrantd-gateway](https://pypi.org/project/warrantd-gateway/).
- **No persistence by default.** `InMemoryApprovalHistory` resets on restart.
  For hash-chained, restart-proof evidence:

```bash
pip install "warrantd-openai-agents[gateway]"
```

```python
from warrantd_openai_agents.stores import sqlite_store
evidence = sqlite_store(".warrantd/evidence.db")   # also usable as audit=
```

That store is the gateway's chained audit log: `warrantd verify-audit`
checks it and `warrantd dashboard` displays it read-only.

## Learn more

- Why earned autonomy: [`DESIGN.md` — *Trust is earned, not configured*](https://github.com/moritzkazooba-wq/warrantd/blob/main/DESIGN.md)
- Runnable example (no API key needed): [`examples/openai_agents/earned_autonomy.py`](https://github.com/moritzkazooba-wq/warrantd/blob/main/examples/openai_agents/earned_autonomy.py)
- API promises: [`STABILITY.md`](https://github.com/moritzkazooba-wq/warrantd/blob/main/STABILITY.md)
