Metadata-Version: 2.4
Name: underwrit-client
Version: 0.3.0
Summary: Client for an Underwrit data plane: decide, claim, record, and produce evidence for what an agent did.
License: MIT
Project-URL: Homepage, https://github.com/underwrit-io/dataplane
Classifier: Programming Language :: Python :: 3
Classifier: License :: OSI Approved :: MIT License
Classifier: Topic :: Security
Requires-Python: >=3.9
Description-Content-Type: text/markdown

# underwrit-client

Integrate an agent runtime with an Underwrit data plane. Standard library only.

```python
import os
from underwrit_client import Underwrit, Held

underwrit = Underwrit("http://127.0.0.1:8787", token=os.environ["UNDERWRIT_AGENT_TOKEN"])

with underwrit.session(agent="ops-assistant", environment="production",
                  intent="Restart api during incident 4417") as s:
    logs = s.call("k8s/get_k8s_logs", get_logs, pod="api-1")
    try:
        s.call("k8s/rollout_restart", restart, deployment="api", namespace="prod")
    except Held as h:
        park(h.decision["id"])            # a person answers in the console, then:
        s.resume(h.decision["id"], restart, deployment="api", namespace="prod",
                 verify=lambda r: {"status": "passed", "checks": [{"name": "rollout", "ok": True}]})
```

- `call()` decides, runs the function, and records the outcome. It raises `Held` when the data
  plane says `act: "hold"` — enforcement on, verdict held or denied. In shadow mode nothing is
  raised and the verdict is still recorded.
- `resume()` claims an approval with the same arguments immediately before executing. The claim is
  refused if the arguments or the stated preconditions changed, the approval expired, the policy
  now refuses it, or it was already used.
- A `TimeoutError` from the wrapped function is recorded as `uncertain`, and the data plane will not
  let a retry overwrite that without `reconcile=True`.
- `verify=` records what was observed afterwards, separately from whether the call succeeded.

The full routes are in the Underwrit README; this client adds nothing the service does not do.

## Also

- `open_session(..., task_policy={...})` attaches a t=0 task policy; `guarded(session, tool)` wraps a
  function so every call goes through the session.
- `decide(..., signals={"taint": True, "risk": 80, "source": "my-classifier"})` lets an external
  classifier raise taint or risk; it can never lower them.
- `receipt(seq)` returns the per-entry receipt (leaf index, tree size, root, inclusion proof, signed
  checkpoint) once a checkpoint covers the entry; `Decision["receipt"]` carries the chain position now.
- 429 and 503 are retried with `Retry-After` (three times by default, capped at thirty seconds).
- `Held` exposes `id`, `verdict`, `budgeted`, `duplicate`, `quorum` and `risk`.
