Metadata-Version: 2.4
Name: wytness-sdk
Version: 0.10.0
Summary: Python SDK for Wytness — audit logging for AI agents with cryptographic signing and chain integrity
Project-URL: Homepage, https://www.wytness.dev
Author-email: Wytness <hello@wytness.dev>
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 — register your signing public key

On first run, the SDK generates an Ed25519 keypair at `./keys/signing.key`.
The Wytness API rejects events with **412 Precondition Failed** until the
matching public key is registered against your org. Print the public PEM:

```bash
wytness-show-public            # default ./keys/signing.key
# or:
python -m wytness.show_public
```

Paste the output into the **Signing Key** section of
[https://app.wytness.dev/keys](https://app.wytness.dev/keys). Wytness never
sees your private key. Alternatively, click *Generate Signing Key* on the
Keys page and drop the downloaded private key into `./keys/signing.key`.

## 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.dev 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.dev 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.dev",
    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 (for serverless) |
| `signing_key_path` | `str` | `"./keys/signing.key"` | Path to Ed25519 private key (auto-generated if missing). Ignored when `signing_key` is set. |
| `http_api_key` | `str\|None` | `None` | API key for HTTP endpoint |
| `http_endpoint` | `str` | `"https://api.wytness.dev"` | HTTP API endpoint URL |
| `fallback_log_path` | `str` | `"./audit_fallback.jsonl"` | Local fallback file path |

### `@audit_tool(client, task_id="default")`

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

### `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
