Metadata-Version: 2.4
Name: wytness-sdk
Version: 0.13.1
Summary: Python SDK for Wytness — audit logging for AI agents with cryptographic signing and chain integrity
Project-URL: Homepage, https://wytness.ai
Author-email: Wytness <support@wytness.ai>
License: MIT
License-File: LICENSE
Keywords: agents,ai,audit,compliance,logging,security
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.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: System :: Logging
Requires-Python: >=3.11
Requires-Dist: cryptography==42.0.7
Requires-Dist: pydantic<3,>=2.10.6
Provides-Extra: crewai
Requires-Dist: crewai==1.14.1; extra == 'crewai'
Requires-Dist: langchain-core==1.2.28; extra == 'crewai'
Provides-Extra: dev
Requires-Dist: black==24.4.2; extra == 'dev'
Requires-Dist: crewai==1.14.1; extra == 'dev'
Requires-Dist: langchain-core==1.2.28; extra == 'dev'
Requires-Dist: pytest-asyncio==0.23.6; extra == 'dev'
Requires-Dist: pytest-cov==5.0.0; extra == 'dev'
Requires-Dist: pytest==8.2.0; extra == 'dev'
Requires-Dist: ruff==0.4.4; extra == 'dev'
Provides-Extra: langchain
Requires-Dist: langchain-core==1.2.28; extra == 'langchain'
Description-Content-Type: text/markdown

# Wytness Python SDK

Audit logging for AI agents. Every action your AI agent takes is cryptographically signed, hash-chained, and streamed to the Wytness platform for compliance and observability.

## Installation

```bash
pip install wytness-sdk
```

## One-time setup — generate and configure your signing key

The SDK refuses to construct an `AuditClient` until a signing keypair
is configured — it raises `WytnessSetupError` otherwise. This is by
design: pre-configuring ensures the matching public PEM is already
registered with the platform, so `/ingest` cannot 412 on signature
verification.

1. Generate the keypair at [https://app.wytness.ai/keys](https://app.wytness.ai/keys).
   The private key is created in your browser and offered for download
   as the literal string `wyt_pk_sign_<base64>` — Wytness never sees it.
   The matching public PEM is registered with the platform automatically.

2. Configure it for the SDK. **Pick one — env var is recommended:**

   **Env var (recommended for every environment):** set the downloaded
   value as the `WYTNESS_SIGNING_KEY` env var in your secrets manager /
   `.env` / deployment platform's env config. Since 0.12.3 the SDK reads
   it automatically — no boilerplate in the constructor:

   ```python
   client = AuditClient(
       agent_id="my-agent",
       http_api_key=os.environ["WYTNESS_API_KEY"],
   )
   ```

   To pass the key explicitly instead, `signing_key=os.environ["WYTNESS_SIGNING_KEY"]`
   still works and wins precedence over the env-var auto-read.

   **File on disk (local prototyping only):** save the downloaded file
   to `./keys/signing.key`. The SDK reads this path by default.
   ⚠️ **Add `keys/` to your `.gitignore` first** — the private signing
   key is the root of trust for the entire audit guarantee; if it leaks
   into git an attacker can fabricate events that look like they came
   from your agent. Treat it like a database password.

3. Verify the local key matches what the portal has registered:

   ```bash
   wytness-show-public
   # or:
   python -m wytness.show_public
   ```

## Quick Start

```python
from wytness import AuditClient, audit_tool

# Initialize the client
client = AuditClient(
    agent_id="my-agent",
    human_operator_id="user-123",
    http_api_key="wyt_api_live_...",   # from app.wytness.ai settings
)

# Decorate any tool your agent calls
@audit_tool(client=client)
def send_email(to: str, subject: str, body: str):
    # Your tool implementation
    pass

# Every call is automatically logged, signed, and chained
send_email(to="team@company.com", subject="Report", body="Weekly summary")
```

## Features

- **Response capture** — AI agent responses are automatically captured, truncated to 5K chars, and PII-redacted
- **PII redaction** — emails, SSN, TFN, credit cards, and phone numbers are automatically redacted from responses and parameter values
- **Cryptographic signing** — every event is signed with Ed25519
- **Hash chaining** — tamper-evident chain of events per agent session
- **Automatic secret redaction** — secrets in parameter names are automatically redacted
- **HTTP transport** — stream events to api.wytness.ai over HTTPS
- **File fallback** — events are saved locally if the API is unreachable

## HTTP Emitter (Recommended)

Send events directly to the Wytness API using your API key:

```python
client = AuditClient(
    agent_id="my-agent",
    human_operator_id="user-123",
    http_endpoint="https://api.wytness.ai",
    http_api_key="wyt_api_live_...",
)
```

## API

### `AuditClient(agent_id, **kwargs)`

| Parameter | Type | Default | Description |
|---|---|---|---|
| `agent_id` | `str` | required | Unique identifier for the agent |
| `agent_version` | `str` | `"0.1.0"` | Version of the agent |
| `human_operator_id` | `str` | `"unknown"` | ID of the human overseeing the agent |
| `signing_key` | `str\|None` | `None` | Base64-encoded Ed25519 private key — typically `os.environ["WYTNESS_SIGNING_KEY"]`. **Recommended in every environment** (no risk of accidental git commit). Accepts the portal-emitted `wyt_pk_sign_<base64>` string with the prefix included. |
| `signing_key_path` | `str` | `"./keys/signing.key"` | Path to the Ed25519 private key on disk. SDK raises `WytnessSetupError` if no key exists at this path. Save the file downloaded from `https://app.wytness.ai/keys` here for **local prototyping only** — add `keys/` to your `.gitignore` first. Ignored when `signing_key` is set. |
| `http_api_key` | `str\|None` | `None` | API key for HTTP endpoint |
| `http_endpoint` | `str` | `"https://api.wytness.ai"` | HTTP API endpoint URL |
| `fallback_log_path` | `str` | `"./audit_fallback.jsonl"` | Local fallback file path |

### `@audit_tool(client, task_id="default", prompt=None, reasoning_summary=None, inputs_classification=None, auto_extract=True)`

Decorator that wraps a function to automatically log audit events. Works with both sync and async functions. Captures:
- Function name, parameters (with secret redaction), and return value hash
- **Response text** (truncated to 5K chars, PII-redacted) — the function's return value as a string
- Execution duration, success/failure status, and error codes
- Cryptographic signature and hash chain link
- **Per-process `agent_instance_id`** (since 0.13.0) — UUID persisted to `./keys/.instance_id`; the Wytness Agents page uses it to count distinct instances per `agent_id`.

**Auto-captured fields by convention** (since 0.13.0; disable with `auto_extract=False`):
- `prompt` — picked from any wrapped-function argument named one of `user_input`, `prompt`, `question`, `query`, `input_text`, `message`, `user_message` (first match, truncated to 2000 chars).
- `reasoning_summary` — extracted by duck-typing the return value. Anthropic-style `.content` text blocks and OpenAI-style `.choices[0].message.content` recognised, truncated to 500 chars. Plain string returns ignored (no false positives).
- `inputs_classification` — set to `"restricted"` when any kwarg name contains `ssn`/`tfn`/`credit_card`/`card_number`/`passport` (case-insensitive).

All three accept an explicit string OR a callable `(args, kwargs) -> str` that wins precedence over the convention.

### `client.session_id`

Read-only property returning the UUID for this client instance.

### `client.flush(timeout=5.0)`

Wait for pending HTTP requests to complete. Call before process exit in serverless/short-lived environments.

### `hash_value(value)`

SHA-256 hash of any JSON-serialisable value. Use this when recording events manually to compute `inputs_hash` / `outputs_hash`.

```python
from wytness import hash_value

result = my_tool(data)
outputs_hash = hash_value(result)
```

## Logging

The SDK emits diagnostics (HTTP ingest errors, fallback replay info,
key-generation warnings, record-path errors) through Python's standard
`logging` module, under the `wytness` logger. Control verbosity the same
way you would for any other library:

```python
import logging

# Silence the SDK completely:
logging.getLogger("wytness").setLevel(logging.CRITICAL)

# Or just silence the info-level replay messages, keep warnings + errors:
logging.getLogger("wytness").setLevel(logging.WARNING)

# Or route everything to your own handler:
logging.getLogger("wytness").addHandler(my_handler)
```

If you haven't configured Python's logging at all, SDK warnings and errors
appear on stderr via the default last-resort handler.

## License

MIT
