Metadata-Version: 2.5
Name: decidio
Version: 0.1.4
Summary: One-line approval gate for AI-agent actions — the agent suspends for human approval and resumes, sealing a portable Authority Receipt the customer owns. Decidio gates + records; the agent executes its own action.
Project-URL: Homepage, https://decidioai.com/developers/
Author: OmniTwin Technologies Inc.
License: Apache-2.0
License-File: LICENSE
Keywords: agent-governance,ai-agents,approvals,audit,authority,human-in-the-loop
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Security
Requires-Python: >=3.9
Provides-Extra: dev
Provides-Extra: langgraph
Requires-Dist: langgraph>=0.2; extra == 'langgraph'
Provides-Extra: openai
Requires-Dist: openai-agents>=0.1; extra == 'openai'
Provides-Extra: signing
Requires-Dist: cryptography>=42; extra == 'signing'
Provides-Extra: temporal
Requires-Dist: temporalio>=1.6; extra == 'temporal'
Provides-Extra: verify
Requires-Dist: cryptography>=42; extra == 'verify'
Description-Content-Type: text/markdown

# decidio (Python)

The one-line approval gate for AI-agent actions — the agent **suspends** for human approval and **resumes**, sealing a portable Authority Receipt the customer owns. Decidio gates (proceed | route | block) + records; **the agent executes its own action** on resume. Decidio never executes and holds no downstream credentials. Python-first, with a TS twin (`@decidio/sdk`) that emits an identical request + receipt (conformance-asserted).

```python
from decidio import guard

# one line — same surface in every runtime
create_opp = guard.protect(
    create_opp_raw,
    lambda o: {"action": "createOpportunity", "amount": o["Amount"], "scope": "Opportunity"},
)
```

- **proceed** → runs immediately (auto-approved under a named, versioned policy rule), sealed.
- **route** → **suspends** (`DecidioSuspended`): parks the call args in an agent-side store, the process may exit; resumes when a human approves and re-runs *your* function.
- **block** → raises `DecidioBlocked`; your function never runs.

## Setup
```bash
pip install 'decidio[signing]'
export DECIDIO_API_URL=https://decidio-api.onrender.com   # the hosted sandbox
python -m decidio init my-agent   # sign in, register the agent, mint its API token, write .env
```
Every later command reads `.env` from the same directory. First run tip: pass
`mode="blocking"` to `guard.protect(...)` to watch the whole loop live (trigger → route to a
human → approve with `python -m decidio approvals approve <id>` → your function executes).
A brand-new agent matches no auto-approve rule, so **every** request routes to a human —
deny-by-default is the product working, not a misconfiguration.
Other commands: `doctor` (config + connectivity + token scope), `receipt <id>` (download the
sealed Authority Receipt). The core is **stdlib-only**; adapters and the verifier are extras.

## Durable resume (real approvals take minutes to days)
Without `mode="blocking"`, a routed action suspends: it parks its call args locally and raises
`DecidioSuspended`; the process may exit. **Self-serve transport — start here:**
`guard.worker()` — a durable poll worker that re-executes parked actions on approval, exactly
once. No inbound URL, no shared secrets; this is the transport for the hosted sandbox.

**Operator deployments** can use the signed webhook instead — Decidio POSTs a verdict to your
resume URL and the handler verifies the HMAC fail-closed:
```python
# FastAPI
@app.post("/decidio/resume")
async def decidio_resume(req: Request):
    return guard.resume.handle(await req.body(), req.headers.get("x-decidio-signature"))
```
Honest requirement: webhook signing uses a shared secret configured on BOTH sides — your
`DECIDIO_WEBHOOK_SECRET` must equal the Decidio server's, and self-hosted production also
allow-lists resume hosts. Against the hosted sandbox, use the worker. Either way, re-execution
is **single-use** (no double-write).

## Engine adapters (durable suspend on the engine you already run)
Thin translators onto each engine's native durable wait — `pip install decidio[langgraph|temporal|openai]`:
```python
# LangGraph — true drop-in (interrupt() is contextvar-based)
create_opp = guard.protect(create_opp_raw, describe, adapter="langgraph")

# Inngest / Temporal / OpenAI Agents — pass the engine handle:
await decidio.adapters.inngest.gate(step, guard, ctx, run=lambda: create_opp_raw(o))
await decidio.adapters.temporal.gate(wf, guard, ctx, run=..., )
resolved, pending = decidio.adapters.openai.gate_interruptions(guard, run_state, describe)
```

## Own the record — verify it yourself
Every outcome is a sealed W3C-VC (Ed25519 did:key), tamper-evident and **offline-verifiable with no Decidio dependency**:
```bash
pip install decidio[verify]
python -m decidio.verify receipt.json
```

## Invariants
Decidio never executes downstream / holds no downstream credentials (the only downstream touch is the opt-in, read-only read-back tier) · holds none of the parked payload · fail-closed signatures · single-use idempotent resume · deny-by-default policy · request-bound identity proof. The agent executes; Decidio gates, records, and signals.
