Metadata-Version: 2.4
Name: leo-soul-client
Version: 0.1.0
Summary: Official Python client for the LEO Soul hosted API (metacognitive layer for LLM agents).
Author: Kadropic Labs
License: Proprietary
Project-URL: Homepage, https://soul.kadropiclabs.com
Project-URL: Documentation, https://soul.kadropiclabs.com/documentation
Project-URL: Repository, https://github.com/Kadropic-Labs/Soul
Project-URL: Bug Tracker, https://soul.kadropiclabs.com/contact
Keywords: llm,agents,metacognition,leo-soul,api-client
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Provides-Extra: dev
Requires-Dist: pytest>=8.0; extra == "dev"

# LEO Soul — Python client

Official, zero-dependency Python client for the [LEO Soul](https://soul.kadropiclabs.com)
hosted API — the stateless metacognitive layer for LLM agents.

```bash
pip install leo-soul-client
```

## Quickstart

```python
from leo_soul_client import LeoSoul

soul = LeoSoul(api_key="sk_live_...")             # from your dashboard

result = soul.turn(
    messages=[{"role": "user", "content": "Delete all my production data now."}],
    backend="openai",
    backend_kwargs={"api_key": "sk-...", "model": "gpt-4o-mini"},  # your model + key
)

print(result.action)      # answer | asked | confirmed | held | refused | escalated
print(result.reply)       # the processed reply to send to your user
store(result.soul_state)  # persist this; pass it back next turn — that's the learning
```

### The one rule: round-trip `soul_state`

```python
state = None
for user_msg in conversation:
    result = soul.turn(messages=[*history, user_msg], soul_state=state, backend="openai",
                       backend_kwargs={"api_key": OPENAI_KEY, "model": "gpt-4o-mini"})
    state = result.soul_state   # store it (a few KB of JSON) and send it back next turn
```

We keep none of it — `soul_state` is your agent's memory and it lives on your side.

## Features

- **Zero dependencies** — pure standard library. No native build, installs anywhere.
- **Exactly-once retries** — automatic retries on `429`/`5xx` reuse a single
  `Idempotency-Key`, so a retried turn is never double-metered.
- **Typed results** — `result.reply`, `.soul_state`, `.trace`, `.action`,
  `.rate_limit` (limit/remaining/reset), `.request_id`.
- **Clear errors** — `AuthError` (401/403), `QuotaExceeded` (402),
  `RateLimitError` (429, with `.retry_after`), `LeoSoulError` (everything else),
  each carrying `.status`, `.type`, and `.request_id`.

## Self-hosted / on-prem

Point the client at your own deployment:

```python
soul = LeoSoul(api_key="sk_live_...", base_url="https://soul.your-company.internal")
```

## Personas

```python
result = soul.turn(
    messages=msgs,
    persona={
        "identity": "a careful financial-support assistant",
        "values": ["truthfulness over agreeableness"],
        "red_lines": ["never give individualized investment advice"],
    },
    backend="openai",
    backend_kwargs={"api_key": OPENAI_KEY, "model": "gpt-4o-mini"},
)
```

Or reference a saved persona from your dashboard with `persona_id=...`.

## Error handling

```python
from leo_soul_client import RateLimitError, QuotaExceeded, AuthError, LeoSoulError

try:
    result = soul.turn(messages=msgs, backend="mock")
except QuotaExceeded as e:
    ...  # upgrade the plan; e.request_id for support
except RateLimitError as e:
    time.sleep(e.retry_after)
except AuthError:
    ...  # bad key / IP not allowlisted
```

---

© Kadropic Labs. Part of Project LEO.
