Metadata-Version: 2.4
Name: jettson
Version: 0.1.0
Summary: Official Python SDK for Jettson — build AI agents in 3 lines of code.
Project-URL: Homepage, https://jettson.dev/docs/sdks/python
Project-URL: Repository, https://github.com/jettsondev/jettson-sdk-python
Project-URL: Issues, https://github.com/jettsondev/jettson-sdk-python/issues
Author: Jettson
License: MIT
License-File: LICENSE
Keywords: agents,ai,automation,jettson,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
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: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.9
Requires-Dist: httpx>=0.25.0
Provides-Extra: dev
Requires-Dist: mypy>=1.0; extra == 'dev'
Requires-Dist: pytest-asyncio>=0.21; extra == 'dev'
Requires-Dist: pytest>=7.0; extra == 'dev'
Requires-Dist: ruff>=0.1; extra == 'dev'
Description-Content-Type: text/markdown

# Jettson SDK for Python

Build AI agents in 3 lines of code.

```bash
pip install jettson
```

```python
from jettson import Jettson

client = Jettson(api_key=os.environ["JETTSON_API_KEY"])

agent = client.agents.spawn(task="Research linear.app and tell me what they do.")
result = client.agents.wait(agent.agent_id)

print(result.final_result)
```

That's it. No vector database, no container orchestration, no agent loop you have to maintain.

---

## Installation

Requires Python 3.9 or newer.

```bash
pip install jettson
```

The SDK has one runtime dependency (`httpx`) and uses synchronous I/O. Async support ships in a follow-up release.

## Quickstart

Get an API key at [jettson.dev/console/api-keys](https://jettson.dev/console/api-keys), then:

```python
import os
from jettson import Jettson

client = Jettson(api_key=os.environ["JETTSON_API_KEY"])

agent = client.agents.spawn(
    task="Browse https://news.ycombinator.com and return the top story title.",
)

# `wait` polls until completion with gentle exponential backoff.
result = client.agents.wait(agent.agent_id)
print(result.status)        # "completed"
print(result.final_result)  # The agent's answer
```

## Authentication

```python
Jettson(
    api_key="jett_sk_live_…",
    base_url="https://jettson.dev/api/v1",   # optional — this is the default
    max_retries=3,                            # optional — retries on 429/5xx
)
```

The API key is bearer-auth'd on every request. Store it in `os.environ["JETTSON_API_KEY"]` (or your platform's secret store), never commit it.

## Examples

### Spawn → wait → use the result

```python
agent = client.agents.spawn(
    task="Summarize the latest version of the OpenTelemetry spec in 3 bullets.",
)
final = client.agents.wait(
    agent.agent_id,
    timeout_seconds=300,
    poll_interval_seconds=1.0,
)
print(final.final_result)
```

### Spawn in a specific region

Multi-region warm pool is live in `iad` (US East), `lhr` (Europe), and `syd` (Asia Pacific):

```python
agent = client.agents.spawn(
    task="…",
    region="lhr",   # or pass metadata={"region": "lhr"}
)
```

If you don't specify a region the server picks `iad`. The pool's hit rate per region is on the [Console overview](https://jettson.dev/console).

### Cancel a running agent

```python
client.agents.cancel(agent.agent_id)
```

Idempotent — already-terminal agents return 200 unchanged.

### Memory: write, search, recall

Memory is user-scoped — every agent you spawn under the same account shares the same pool.

```python
client.memory.put(
    key="brand_color",
    value="Brand primary color is #FF5733",
    namespace="user_profile",
    importance=8,
    tags=["brand"],
)

results = client.memory.search(
    query="what color is the brand?",
    namespace="user_profile",
    mode="hybrid",   # 'hybrid' | 'semantic' | 'keyword'
)

for r in results:
    print(f"{r.key}: {r.value} (score={r.score:.2f})")
```

### Idempotent spawn (defensive retries)

If your code might retry a spawn because of a network blip on the way to Jettson, pass an idempotency key.

```python
import time

request_id = f"spawn:{user_id}:{int(time.time())}"
client.agents.spawn(task="…", idempotency_key=request_id)
```

### List + paginate

```python
cursor = None
while True:
    page = client.agents.list(limit=50, cursor=cursor)
    for a in page.data:
        print(a.agent_id, a.status)
    cursor = page.next_cursor
    if not cursor:
        break
```

### Use as a context manager

```python
with Jettson(api_key=os.environ["JETTSON_API_KEY"]) as client:
    agent = client.agents.spawn(task="…")
    final = client.agents.wait(agent.agent_id)
# httpx connection pool released cleanly on exit
```

## API Reference

### `Jettson(api_key, ...)`

| Field | Type | Description |
| --- | --- | --- |
| `api_key` **(required)** | `str` | Your Jettson API key. |
| `base_url` | `str` | Defaults to `https://jettson.dev/api/v1`. |
| `max_retries` | `int` | Retries on 429 / 5xx (default 3). |
| `timeout_seconds` | `float` | Per-request timeout (default 60). |

### `client.agents`

| Method | Description |
| --- | --- |
| `spawn(task, ...)` | POST `/agents`. Returns immediately with `status="spawning"`. |
| `get(agent_id)` | GET `/agents/{id}`. |
| `list(limit=, cursor=)` | Paginated list. |
| `cancel(agent_id)` | DELETE `/agents/{id}`. Idempotent. |
| `wait(agent_id, ...)` | Poll until `completed` / `error` / `stopped`. Raises `AgentTimeoutError` on timeout. |

### `client.memory`

| Method | Description |
| --- | --- |
| `put(key, value, ...)` | Create or version-update a memory. |
| `get(key, namespace=)` | Returns the memory or `None` if not found. |
| `delete(key, namespace=, hard_delete=False)` | Soft delete by default. |
| `search(query, ...)` | Hybrid semantic + keyword search. |
| `list(namespace=, tags=, limit=)` | Newest-first, no ranking. |
| `dedupe(...)` | Soft-delete near-duplicates. |
| `consolidate(...)` | Cluster + summarize related memories. |
| `namespaces()` | Per-namespace live counts. |
| `export()` | Dump every live memory. |
| `import_(memories)` | Bulk insert. (Trailing underscore avoids the `import` keyword.) |

## Error handling

Every HTTP failure surfaces as a typed exception. All extend `JettsonError`.

```python
from jettson import (
    JettsonAuthError,
    JettsonRateLimitError,
    JettsonQuotaExceededError,
    JettsonValidationError,
    JettsonNotFoundError,
    JettsonServerError,
    JettsonNetworkError,
    AgentTimeoutError,
)

try:
    client.agents.spawn(task="…")
except JettsonRateLimitError as err:
    print(f"Backing off {err.retry_after_seconds}s")
except JettsonQuotaExceededError as err:
    print(f"Hit quota: {err.used}/{err.limit} on plan {err.plan}")
except JettsonAuthError:
    print("Bad API key.")
```

`memory.get()` is the one exception: instead of raising on 404, it returns `None` (so the "lookup or fallback" pattern stays clean).

## Retries and rate limits

The HTTP client retries automatically:

- **429** — waits the duration in `Retry-After`, then retries (up to `max_retries`, default 3)
- **5xx** — exponential backoff (1s → 2s → 4s), up to `max_retries`
- **Network failure** — one retry after the initial backoff window

Disable retries with `Jettson(api_key=..., max_retries=0)` if you need first-failure semantics.

## Development

```bash
git clone https://github.com/jettsondev/jettson-sdk-python
cd jettson-sdk-python
pip install -e ".[dev]"
pytest
```

The SDK is intentionally small (~1k LOC + tests). Read the source.

## License

MIT — see [LICENSE](./LICENSE).
