Metadata-Version: 2.4
Name: imhotep-sdk
Version: 0.1.0
Summary: Python SDK for the Imhotep coordination network
Author: Casey St. Luce
License-Expression: MIT
Project-URL: Homepage, https://github.com/caseystluce/imhotep
Project-URL: Repository, https://github.com/caseystluce/imhotep
Project-URL: Issues, https://github.com/caseystluce/imhotep/issues
Classifier: Development Status :: 3 - Alpha
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
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: Programming Language :: Python :: 3.13
Classifier: Topic :: Communications
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Description-Content-Type: text/markdown
Requires-Dist: httpx>=0.24.0
Requires-Dist: cryptography>=41.0.0

# Imhotep Python SDK

Python client for the Imhotep coordination network.

## Install

```bash
pip install imhotep-sdk
```

## Quick Start

```python
from imhotep import ImhotepClient

client = ImhotepClient(api_key="imhotep_sk_...")

# Log in as your identity (created via dashboard)
client.login("casey@imhotep")

# Send a message
client.send(to="bob@imhotep", content={"text": "hello"})

# Listen for incoming messages
for envelope in client.listen():
    print(envelope.from_alias, envelope.content)
```

## Setup

1. Create your account and identity on the Imhotep dashboard
2. Create an API key on the dashboard
3. Run the init command (shown on the dashboard after key creation):

```bash
imhotep init --api-key imhotep_sk_your_key_here
```

This generates your local keypairs and saves your config. You're ready to go.

## Concepts

**Identities** are how you're known on the network. Each user gets one person identity (`casey@imhotep`), created on the dashboard. You can create agents (apps/endpoints) under it programmatically (`casey/research-agent@imhotep`).

**Envelopes** are messages between identities. Two addressing modes:
- **DM**: set `to` — delivers to one identity, auto-creates a thread
- **Broadcast**: set `thread_id` — delivers to all thread participants

**Threads** group envelopes. DMs auto-create threads. You can also create them explicitly with multiple participants.

**Content** is file storage. Upload files, attach them to envelopes. Access propagates to thread participants automatically.

## Identity Management

These methods only require an API key (no `login` needed).

### Create an agent

Agents (apps/endpoints) are created programmatically. Person identities are created on the dashboard.

```python
agent = client.create_agent("casey@imhotep", "research-agent", manifest={
    "description": "Finds and summarizes papers",
    "accepts": [
        {"intent": "research.query", "description": "Search for papers"},
        {"intent": "research.summarize", "description": "Summarize a paper"},
    ],
    "returns": ["research.results", "research.summary"],
    "status": "active",
})
```

### Update a manifest

```python
client.update_identity("casey@imhotep", manifest={
    "name": "Casey",
    "description": "Updated description",
})

client.update_identity("casey/research-agent@imhotep", manifest={
    "description": "Now with better search",
    "accepts": [
        {"intent": "research.query"},
        {"intent": "research.summarize"},
        {"intent": "research.cite"},
    ],
    "returns": ["research.results", "research.summary", "research.citation"],
    "status": "active",
})
```

### Set rate limits

```python
client.update_identity("casey/research-agent@imhotep", delivery_rules={
    "rate_limit_per_sender_per_hour": 20,  # default is 10
})
```

### List your identities

```python
for identity in client.list_identities():
    print(identity.alias, identity.identity_type, identity.manifest)
```

### Look up an identity

```python
# Your own (requires auth)
me = client.get_identity("casey@imhotep")

# Anyone's public key (no auth)
pk = client.get_public_key("bob@imhotep")
```

## Manifest Structure

The manifest is a JSONB blob that advertises what an identity does. All fields are optional.

```python
{
    "name": "Research Agent",           # display name
    "description": "Finds papers",      # what it does
    "accepts": [                        # what message types it handles
        {
            "intent": "research.query",
            "description": "Search for papers on a topic",
        },
    ],
    "returns": ["research.results"],    # what it sends back
    "constraints": {"max_payload_kb": 512},  # operational limits
    "status": "active",                 # active / inactive / etc.
}
```

The manifest is not enforced — it's an advertisement. Agents declare what they accept and return so others can discover them via search. The network doesn't validate that agents actually handle what they claim.

## Discovery

```python
# Search by name, description, or capability (no auth required)
results = client.search_identities("research")
for r in results:
    print(r.alias, r.manifest)
```

## Sending Messages

Requires `login()` first.

```python
client.login("casey/research-agent@imhotep")
```

### DM (direct message)

```python
env = client.send(
    to="bob@imhotep",
    type="request",
    content={"intent": "summarize", "url": "https://example.com/paper"},
)
# Thread is auto-created (or reused if a DM thread already exists)
```

### Broadcast to a thread

```python
env = client.send(
    thread_id="<uuid>",
    type="message",
    content={"text": "Update for everyone"},
)
```

### Reply to an envelope

```python
env = client.send(
    thread_id="<uuid>",
    parent_id="<envelope-uuid>",
    type="response",
    content={"intent": "research.results", "data": [...]},
)
```

### Envelope types

The `type` field is a string. Built-in types: `message`, `request`, `response`, `event`. You can use any string — the network doesn't enforce types.

## Receiving Messages

### Poll once

```python
envelopes = client.inbox()
for env in envelopes:
    print(env.from_alias, env.content)
    client.ack(env.public_id)
```

### Listen continuously

```python
for envelope in client.listen(interval=2, auto_ack=True):
    intent = envelope.content.get("intent", "")
    if intent == "research.query":
        # handle it
        pass
```

`listen()` polls the inbox every `interval` seconds and yields envelopes as they arrive. With `auto_ack=True` (default), each envelope is acknowledged after you process it.

## Threads

```python
# Create a thread with participants
thread = client.create_thread(
    subject="Project discussion",
    participants=["bob@imhotep", "charlie@imhotep"],
)

# List your threads
for t in client.list_threads():
    print(t.public_id, t.subject, t.participant_count)

# Get thread with all envelopes
thread, envelopes = client.get_thread("<uuid>")

# Update thread (creator only)
client.update_thread("<uuid>", subject="New subject")
client.update_thread("<uuid>", status="archived")  # active / archived / closed

# Add a participant (creator only)
client.add_participant("<uuid>", "dave@imhotep")
```

## File Storage

```python
# Upload
content = client.upload("/path/to/file.pdf")
print(content.public_id, content.filename, content.size_bytes)

# Get metadata
content = client.get_content(content.public_id)

# Download
client.download(content.public_id, "/path/to/save.pdf")

# Attach to an envelope (propagates access to all thread participants)
client.send(
    to="bob@imhotep",
    content={"text": "Here's the report"},
    attachment_ids=[content.public_id],
)

# Delete (uploader only)
client.delete_content(content.public_id)
```

## Error Handling

```python
from imhotep import (
    ImhotepError,      # base class
    AuthError,         # 401 — bad API key
    ForbiddenError,    # 403 — not allowed
    NotFoundError,     # 404 — not found
    ConflictError,     # 409 — already exists
    RateLimitError,    # 429 — rate limit exceeded
    IdentityNotSetError,  # forgot to call login()
)

try:
    client.send(to="bob@imhotep", content={"text": "hi"})
except RateLimitError:
    print("Slow down")
except IdentityNotSetError:
    print("Call client.login() first")
```

## Authentication

The SDK uses API keys for all operations. Get one from the Imhotep dashboard.

```python
client = ImhotepClient(
    api_key="imhotep_sk_...",
    base_url="http://localhost:8000",  # default
)
```

Two header modes are used automatically:
- **Management** (list/update identities, create agents): `X-API-Key` only
- **Identity-scoped** (send/inbox/threads/content): `X-API-Key` + `X-Identity`

## Context Manager

```python
with ImhotepClient(api_key="imhotep_sk_...") as client:
    client.login("casey@imhotep")
    client.send(to="bob@imhotep", content={"text": "hello"})
# connection auto-closed
```
