Metadata-Version: 2.4
Name: xenovia-sdk
Version: 0.1.2
Summary: Decision-first Python SDK for policy-aware agent action control
Project-URL: Homepage, https://xenovia.io
Project-URL: Documentation, https://docs.xenovia.io
Project-URL: Source, https://github.com/Xenovia-io/xenovia-sdk-python
Project-URL: Issues, https://github.com/Xenovia-io/xenovia-sdk-python/issues
Keywords: agents,policy,security,governance,sdk
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: License :: Other/Proprietary License
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
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 :: Security
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Provides-Extra: dev
Requires-Dist: build>=1.2.2; extra == "dev"
Requires-Dist: twine>=5.1.1; extra == "dev"
Requires-Dist: ruff>=0.6.9; extra == "dev"
Requires-Dist: mypy>=1.11.2; extra == "dev"

# Xenovia SDK for Python

Decision-first control hook for agent actions.

Full documentation:

- `docs/SDK_REFERENCE.md` (complete API and mode behavior)

## Core Model

Xenovia decides. Your code executes.

- The SDK sends action intent to Xenovia for policy decisioning.
- The SDK returns a typed response with decision and trace context.
- Local execution stays in your environment, never in Xenovia.

## Before vs After SDK

Before (proxy-only wiring, ad-hoc in each tool integration):

- Calls can still go through the proxy, but request/response shape is custom per tool.
- Block/failed/success handling is inconsistent across agents.
- Sequence tracking and result-to-decision mapping are harder to do reliably.

After (SDK-wrapped integration):

- Every call uses one contract: `capability + payload + session_id + identity + env`.
- Every outcome uses one response object: `status + decision + result + trace_id`.
- You can enforce or observe consistently and map call sequence + outcomes deterministically.

```mermaid
flowchart LR
    subgraph BEFORE["Before SDK (manual wiring)"]
      B1["Agent"] --> B2["Custom tool wrapper(s)"]
      B2 --> B3["Xenovia Proxy decision"]
      B3 --> B4["Local execution"]
      B4 --> B5["Raw / inconsistent tool result handling"]
    end

    subgraph AFTER["After SDK (standardized control hook)"]
      A1["Agent"] --> A2["Xenovia SDK execute/guard"]
      A2 --> A3["Xenovia Proxy decision + trace_id"]
      A3 --> A4{"mode"}
      A4 -- "enforce + deny" --> A5["blocked response"]
      A4 -- "allow or observe" --> A6["Local execution"]
      A6 --> A7["Unified XenoviaResponse<br/>status + decision + result + trace_id"]
      A7 --> A8["Sequence mapping + behavior controls"]
    end
```

## Execution Flow

```mermaid
flowchart TD
    A["Agent / App"] --> B["Xenovia SDK (execute/guard)<br/>infers capability from module.function"]
    B --> C["Xenovia Proxy<br/>policy decision + trace_id"]
    C --> D{"Decision"}
    D -- "deny" --> E["Blocked response<br/>status=blocked + reason + trace_id"]
    D -- "allow" --> F["Local tool execution<br/>(your infra)"]
    F --> G["Capture actual result/outcome"]
    G --> H["Return unified response<br/>decision + result + trace_id"]
    H --> I["Sequence mapping<br/>session_id + capability + call order"]
    I --> J["Behavior controls<br/>detect/limit duplicate or multi-call patterns"]
```

## Installation

```bash
pip install xenovia-sdk
```

For local development:

```bash
pip install -e ".[dev]"
```

## Quick Start

Capability is inferred automatically from the calling module and function name — you do not need to name it.

```python
from xenovia_sdk import Xenovia

xenovia = Xenovia(
    api_key="xv_...",
    endpoint="https://api.xenovia.io",
    default_env="prod",
    auto_session=True,
)

# capability inferred as "payments.transfer" when called from payments.py
decision = xenovia.execute({"service": "checkout", "version": "v2"})

if decision.is_blocked():
    print("Blocked:", decision.decision.get("reason"))
elif decision.is_success():
    print("Allowed:", decision.trace_id)
else:
    print("Failed:", decision.error)
```

## Guard Modes

`guard()` wraps existing tool functions with decision-aware control. Capability is inferred from the decorated function's module and name.

```python
# capability inferred as "k8s.deploy" when defined in k8s.py
@xenovia.guard(mode="enforce")
def deploy(payload):
    return kubectl_apply(payload)
```

Supported modes:

- `enforce` (recommended): execute local function only when decision outcome is `allow`.
- `observe`: always execute local function, but still return Xenovia's decision for visibility.

Migration pattern:

1. Start with `observe`.
2. Review would-have-blocked events.
3. Move sensitive capabilities to `enforce`.

## API Surface

```python
Xenovia(
    api_key: str,
    endpoint: str = "https://api.xenovia.io",
    default_env: str = "prod",
    raise_on_block: bool = False,
    timeout: int = 5,
    auto_session: bool = False,
    debug: bool = False,
    identity_id: str | None = None,
    identity_type: str = "agent",
    unreachable_mode: str = "error",
    allow_insecure_http: bool = False,
)
```

Primary methods:

- `execute(payload, capability=None, session_id=None, env=None) -> XenoviaResponse`
- `guard(capability=None, mode="enforce") -> decorator`

Capability is inferred as `module.function_name` when not provided. It can always be overridden explicitly.

`XenoviaResponse` fields:

- `status`: `success` | `blocked` | `failed`
- `result`: local function output (if executed via `guard`) or backend-provided result
- `error`: string error when present
- `decision`: decision payload (`outcome`, `rule_id`, `reason`, ...)
- `trace_id`: request trace identifier

## Wire Contract

SDK request envelope:

```json
{
  "capability": "payments.transfer",
  "payload": { "...": "..." },
  "session_id": "sess_123",
  "identity": { "type": "agent", "id": "agent_7" },
  "env": "prod",
  "sdk": { "version": "0.1.2", "language": "python" }
}
```

Typical response shape:

```json
{
  "status": "blocked",
  "result": null,
  "error": "policy_violation",
  "decision": {
    "outcome": "deny",
    "rule_id": "SEQ_001",
    "reason": "unsafe_sequence"
  },
  "trace_id": "xnv_123"
}
```

## Security Defaults

The SDK ships with opinionated safety defaults:

- HTTPS endpoint enforcement by default (`allow_insecure_http=False`).
- If `allow_insecure_http=True`, only localhost/loopback HTTP endpoints are permitted.
- No secret or payload logging in normal operation.
- Explicit fail-mode for Xenovia outages:
  - `unreachable_mode="error"` (default)
  - `unreachable_mode="allow"` (fail-open)
  - `unreachable_mode="block"` (fail-closed)
- Structured identity metadata on every request.
- Timeout required and validated.

## Production Configuration Example

```python
import os
from xenovia_sdk import Xenovia

xenovia = Xenovia(
    api_key=os.environ["XENOVIA_API_KEY"],
    endpoint=os.environ.get("XENOVIA_ENDPOINT", "https://api.xenovia.io"),
    default_env=os.environ.get("XENOVIA_ENV", "prod"),
    identity_id=os.environ.get("XENOVIA_AGENT_ID", "payments-agent"),
    auto_session=True,
    raise_on_block=False,
    unreachable_mode="block",  # fail closed for sensitive environments
)
```

## Development

Run tests:

```bash
python -m unittest discover -s tests -v
```

## Publishing and Security Docs

- See `PUBLISHING.md` for release workflow and publishing checklist.
- See `SECURITY.md` for vulnerability reporting and operational controls.
- See `CHANGELOG.md` for release history.
- See `docs/SDK_REFERENCE.md` for full mode matrix and behavior reference.

## CI and Release Automation

- `.github/workflows/ci.yml` runs tests, builds the package, and checks metadata.
- `.github/workflows/publish.yml` is manual (`workflow_dispatch`) and supports separate targets: `testpypi` and `pypi`.
- `.github/dependabot.yml` keeps dependencies and GitHub Actions updated weekly.
