punky-client 0.1.0
Python SDK for Punky — self-hosted enterprise memory for AI agents
Requires Python: >=3.9
Keywords: agents, ai, encryption, enterprise, gdpr, knowledge-graph, llm, mcp, memory, self-hosted

# punky-client

[![CI/CD](https://github.com/swhtker/punky-client/actions/workflows/ci.yml/badge.svg)](https://github.com/swhtker/punky-client/actions/workflows/ci.yml)
[![PyPI](https://img.shields.io/pypi/v/punky-client)](https://pypi.org/project/punky-client/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)

Python SDK for [Punky](https://punky.jspriggins.com) — self-hosted enterprise memory for AI agents.

**E2E encrypted. GDPR-compliant. Knowledge graphs. $0/month.**

```bash
pip install punky-client
```

## Quick Start

```python
from punky import PunkyClient

async with PunkyClient("https://punky.example.com", api_key="pk_...") as client:
    # Store a memory (auto-encrypted, PII-detected)
    await client.store("User prefers dark mode", user_id="alice")

    # Semantic search
    results = await client.search("UI preferences", user_id="alice")
    for memory in results:
        print(f"{memory.content} (score: {memory.score})")

    # Knowledge graph
    graph = await client.graph.query("alice", "What does alice prefer?")

    # GDPR data export (Article 20)
    export = await client.compliance.gdpr_export("alice")
```

## Features

| Feature | Description |
|---------|-------------|
| **Memory CRUD** | Store, search, get, delete with auto-encryption |
| **Semantic Search** | Vector similarity search over encrypted memories |
| **Knowledge Graph** | Query, traverse, detect contradictions |
| **GDPR Compliance** | Articles 15 (access), 17 (erasure), 20 (portability) |
| **PII Detection** | Detect and redact personally identifiable information |
| **Memory Sharing** | Encrypted sharing between users with accept/decline |
| **Versioning** | Full version history with rollback |
| **Intelligence** | Extraction, deduplication, proactive suggestions |
| **Webhooks** | Register endpoints for memory events |
| **Resilience** | Retry with backoff, circuit breaker, connection pooling |

## API Reference

### Client

```python
client = PunkyClient(
    url="https://punky.example.com",
    api_key="pk_...",
    timeout=30.0,       # request timeout (seconds)
    max_retries=3,      # retry on 429, 502, 503, 504
)
```

### Memory

```python
# Store
result = await client.store("content", user_id="alice",
    memory_type="fact",     # episodic|semantic|procedural|fact|preference|...
    importance=0.8,         # 0.0–1.0
    tags=["coding"],
    metadata={"source": "chat"})

# Search
memories = await client.search("query", user_id="alice", limit=10, min_score=0.5)

# Get all user memories
all_mems = await client.memory.get_user_memories("alice")

# List with filtering (dashboard API)
page = await client.memory.list(limit=20, memory_type="fact", tag="coding")

# Pin / delete
await client.memory.pin("mem_123", pinned=True)
await client.memory.delete("mem_123")

# Version history & rollback
versions = await client.memory.history("mem_123")
await client.memory.rollback("mem_123", version_number=2)
```

### Knowledge Graph

```python
# Natural-language query
result = await client.graph.query("alice", "What programming languages?")
for node in result.nodes:
    print(f"{node.label} ({node.type})")

# Add relationship
await client.graph.add("Python", "relates_to", "FastAPI", user_id="alice")

# Find path between nodes
path = await client.graph.find_path("Python", "Deployment")

# Get contradictions
contradictions = await client.graph.contradictions()

# Statistics
stats = await client.graph.stats()
print(f"{stats.total_nodes} nodes, {stats.total_edges} edges")
```

### Compliance

```python
# GDPR Article 15 — Right of Access
data = await client.compliance.gdpr_access("alice")

# GDPR Article 17 — Right to Erasure
await client.compliance.gdpr_erase("alice", confirm=True)

# GDPR Article 20 — Data Portability
export = await client.compliance.gdpr_export("alice")

# PII detection & redaction
pii = await client.compliance.detect_pii("My SSN is 123-45-6789")
clean = await client.compliance.redact_pii("My SSN is 123-45-6789")
print(clean.redacted_text)  # "My SSN is [REDACTED]"

# Tamper-evident audit logs
logs = await client.compliance.audit_logs(limit=100, user_id="alice")
integrity = await client.compliance.verify_audit()

# Retention policies
policies = await client.compliance.retention_policies()
preview = await client.compliance.apply_retention(dry_run=True)
```

### Sharing

```python
# Share a memory (re-encrypted with recipient's key)
share = await client.sharing.share("mem_123", "alice", "bob", permission="read")

# Accept / decline
await client.sharing.accept(share["share_id"])
await client.sharing.decline(share["share_id"])

# Read shared memory
content = await client.sharing.read(share["share_id"])

# List shares
pending = await client.sharing.pending()
active = await client.sharing.active()
```

### Intelligence

```python
# Extract structured knowledge
extracted = await client.intelligence.extract(
    "I've been using Python for 10 years and prefer FastAPI over Flask"
)
for mem in extracted.memories:
    print(f"{mem['type']}: {mem['content']}")

# Deduplicate memories
dupes = await client.intelligence.deduplicate("alice", threshold=0.85, dry_run=True)

# Proactive suggestions
suggestions = await client.intelligence.suggestions("alice", context="building an API")
for s in suggestions:
    print(f"[{s.type}] {s.content} (confidence: {s.confidence})")
```

### Webhooks

```python
# Register (HMAC-SHA256 signed payloads)
await client.register_webhook("wh1", "https://example.com/hook",
    events=["memory.created", "gdpr.erasure"], secret="signing-key")

# Check delivery log
deliveries = await client.webhook_deliveries(limit=20)
```

### Health & System

```python
health = await client.health()          # Basic health check
ready  = await client.readiness()       # All 14 modules
status = await client.security_status() # Security config

# Database
db = await client.db_health()
integrity = await client.db_integrity()
backup = await client.db_backup()

# Rate limiting (updated after each request)
print(f"Rate limit: {client.rate_remaining}/{client.rate_limit} remaining")
```

## Integrations

### OpenAI Agents SDK

Drop-in replacement for `SQLiteSession` with encryption and GDPR compliance:

```python
from punky import PunkyClient
from punky.integrations.openai_agents import PunkySession
from agents import Agent, Runner

client = PunkyClient("https://punky.example.com", api_key="pk_...")
session = PunkySession(client, user_id="alice")

agent = Agent(name="assistant", instructions="You help users.")
result = await Runner.run(agent, "Hello!", session=session)
```

```bash
pip install punky-client[openai-agents]
```

### LangChain

Drop-in memory backend for any LangChain chain:

```python
from punky import PunkyClient
from punky.integrations.langchain import PunkyMemory
from langchain.chains import ConversationChain
from langchain_openai import ChatOpenAI

client = PunkyClient("https://punky.example.com", api_key="pk_...")
memory = PunkyMemory(client, user_id="alice")

chain = ConversationChain(llm=ChatOpenAI(), memory=memory)
response = chain.predict(input="What's my preferred language?")
```

```bash
pip install punky-client[langchain]
```

## Error Handling

```python
from punky import PunkyClient, AuthenticationError, RateLimitError, PunkyError

try:
    await client.store("content", user_id="alice")
except AuthenticationError:
    print("Invalid API key")
except RateLimitError as e:
    print(f"Rate limited — retry in {e.retry_after}s")
except PunkyError as e:
    print(f"Error {e.status_code}: {e.detail}")
```

| Exception | HTTP Status | When |
|-----------|-------------|------|
| `AuthenticationError` | 401 | Missing or invalid API key |
| `AuthorizationError` | 403 | Insufficient permissions |
| `NotFoundError` | 404 | Resource doesn't exist |
| `ValidationError` | 422 | Invalid request payload |
| `RateLimitError` | 429 | Rate limit exceeded |
| `ServerError` | 5xx | Server-side error |
| `ConnectionError` | — | Network failure or circuit breaker open |

## Resilience

The SDK automatically handles transient failures:

- **Retry** — 429, 502, 503, 504 are retried up to 3 times with exponential backoff
- **Circuit breaker** — After 5 consecutive connection failures, requests are blocked for 60s
- **Connection pooling** — Reuses HTTP connections for performance
- **Rate-limit awareness** — Parses `X-RateLimit-*` headers, available via `client.rate_remaining`

## Requirements

- Python 3.9+
- `httpx` ≥ 0.25
- `pydantic` ≥ 2.0

## License

MIT