Metadata-Version: 2.5
Name: outhad-contextkit-sdk
Version: 1.10.0
Summary: Python client for the Outhad ContextKit hosted memory API: long-term memory for AI agents
Project-URL: Homepage, https://contextkit.outhad.com
Project-URL: Documentation, https://contextkit.outhad.com/docs/cloud/
Project-URL: Changelog, https://contextkit.outhad.com/changelog/
Project-URL: Issues, https://github.com/tanzil7890/contextkit-site/issues
Author-email: Outhad <idrisitanzil@gmail.com>
License: Apache-2.0
License-File: LICENSE
Keywords: agents,ai,contextkit,long-term-memory,memory,outhad,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Operating System :: OS Independent
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.27.0
Requires-Dist: typing-extensions>=4.8.0; python_version < '3.11'
Provides-Extra: test
Requires-Dist: pytest-asyncio>=0.23.7; extra == 'test'
Requires-Dist: pytest>=8.2.2; extra == 'test'
Requires-Dist: respx>=0.21.0; extra == 'test'
Description-Content-Type: text/markdown

# Outhad ContextKit SDK

<div align="center">

**Long-term memory for AI agents, as an API: one `pip install`, no databases or models to run**

[![PyPI version](https://img.shields.io/pypi/v/outhad-contextkit-sdk.svg)](https://pypi.org/project/outhad-contextkit-sdk/)
[![Python 3.9+](https://img.shields.io/badge/python-3.9+-blue.svg)](https://www.python.org/downloads/)
[![License](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](https://www.apache.org/licenses/LICENSE-2.0)

[Quick Start](#quick-start) • [Features](#features) • [Configuration](#configuration) • [All Methods](#all-methods) • [Docs](https://contextkit.outhad.com/docs/cloud/)

</div>

> **Status:** the hosted ContextKit cloud is not open for sign-ups yet. If you run your own ContextKit server, point this client at it with `host=` (see [Configuration](#configuration)).

---

## Why the SDK?

Agents forget everything between sessions. Giving them memory usually means running a vector database, a graph database and an LLM extraction pipeline, and keeping all of it in sync.

**ContextKit runs all of that for you.** This package is the client:

- **Tiny**: one dependency (`httpx`); no torch, no vector drivers, no model SDKs
- **Everything server-side**: fact extraction, embeddings, vector + graph storage, re-ranking
- **Sync and async**: `MemoryClient` and `AsyncMemoryClient` share the same methods
- **Your models**: choose the LLM and embedder per project, or override them per call

---

## Quick Start

### Installation

```bash
pip install outhad-contextkit-sdk
```

### Get an API key

In the [ContextKit dashboard](https://app.contextkit.outhad.com/dashboard/api-keys), open **API keys** → **Create key**. Keys look like `ock_live_...` (production) or `ock_test_...` (sandbox).

```bash
export OUTHAD_CONTEXTKIT_API_KEY="ock_live_..."
```

### 60-Second Setup

```python
from openai import OpenAI
from outhad_contextkit_sdk import MemoryClient

memory = MemoryClient()  # reads OUTHAD_CONTEXTKIT_API_KEY
openai_client = OpenAI()

# Remember
memory.add(messages=[{"role": "user", "content": "I'm allergic to peanuts"}], user_id="alice")

# Recall only what matters
hits = memory.search(query="What should I avoid eating?", user_id="alice", limit=3)
facts = "\n".join(hit["memory"] for hit in hits["results"])

# Give your LLM the facts, not the whole transcript
response = openai_client.chat.completions.create(
    model="gpt-4o-mini",
    messages=[
        {"role": "system", "content": f"What you know about the user:\n{facts}"},
        {"role": "user", "content": "Suggest a snack for me"},
    ],
)
print(response.choices[0].message.content)
```

---

## Features

### 1. Memory That Manages Itself

**Facts, not transcripts**: the cloud extracts, de-duplicates and updates memories for you.

```python
# Conversations become facts automatically
memory.add(messages=conversation, user_id="alice")

# Short-lived facts forget themselves
memory.add(messages=[{"role": "user", "content": "Pizza party at 7pm tonight"}],
           user_id="alice", expiration_date="2026-11-01T19:00:00Z")

memory.get_all(user_id="alice", page=1, page_size=50)
memory.history(memory_id)  # every change, in order
memory.pin(memory_id)      # lock a critical fact against updates and deletes

# Safe retries: the same key always returns the same result
memory.add_idempotent("signup-12345", messages=conversation, user_id="alice")
```

**What makes it powerful:**
- **Automatic Fact Extraction**: conversations become short, de-duplicated facts
- **Scoped Memory**: separate memory per `user_id`, `agent_id` or `run_id`
- **Expiry & Pinning**: `expiration_date` for temporary facts; `pin()` protects the ones that must never change
- **Safe Retries**: `add_idempotent()` makes network retries byte-identical

### 2. Retrieval Beyond Vector Search

**Graph, time and keyword retrieval in one call.** Turn on **Graph mode** for your project in Dashboard → Settings to unlock the graph features.

```python
# Context graph: related memories come along with the best match
memory.search(query="What should I eat?", user_id="alice", use_context_graph=True)

# Temporal + causal (TCMGM): ask *when* and *why*
memory.search(query="When did the API slow down?", user_id="dev_team", use_tcmgm=True,
              time_window={"start": "2026-10-01T00:00:00Z", "end": "2026-10-15T00:00:00Z"})

# Hybrid keyword search: rare names, typos, acronyms
memory.search(query="Acme", user_id="alice", keyword_search=True)

# LLM relevance gate: drop hits that don't actually answer the question
memory.search(query="What food do I like?", user_id="alice", filter_memories=True)
```

**What makes it powerful:**
- **Context Graph**: memories link to each other, so related context arrives together
- **Temporal + Causal**: timeline filtering plus cause → effect chains
- **Hybrid Search**: keyword matches fused with vector hits (reciprocal-rank fusion)
- **Fine Control**: `threshold`, `top_k`, `categories`, `filters`, `includes` / `excludes`

### 3. Personalisation That Learns

**Results improve as your users react to them.**

```python
hits = memory.search(query="revenue forecast", user_id="alice", use_context_graph=True)

# Tell ContextKit what helped. Future searches for this query rank it higher.
memory.feedback(hits["results"][0]["id"], helpful=True,
                user_id="alice", query="revenue forecast")
```

**What makes it powerful:**
- **Feedback Loop**: helpful / unhelpful verdicts boost or bury memories for that user
- **Learns What Worked**: memories that answered a query before get lifted for the same query
- **Usage-Aware**: memories your agent keeps using rank higher
- **Per-Project Tuning**: custom instructions, categories and retrieval weights in the dashboard

### 4. Versioning & Lifecycle

**Versions, rollback and freshness**: inspect history, restore, archive.

```python
memory.update(memory_id, text="I love sushi and ramen")

memory.versions(memory_id)                # the version chain
memory.diff_versions(old_id, new_id)      # what changed
memory.rollback(memory_id, to_version=1)  # restore an earlier version

memory.decay_score(memory_id)             # 0.0 (stale) → 1.0 (fresh)
memory.archive_low(threshold=0.05)        # archive what has gone stale
memory.demote_to_cold(memory_id)          # cheap storage, restorable any time
```

**What makes it powerful:**
- **Version Chain**: diff any two versions; a rollback is written as a new version
- **Decay Scores**: recency, usage and feedback blended into one freshness number
- **Safe Archival**: stale memories leave search results without being deleted
- **Cold Storage**: demote rarely used memories, then `promote_from_cold()` when needed

> **Note:** `update()` currently edits a memory in place, so new versions come from `rollback()` only.

### 5. Workspace & Security

**Production controls built in.**

```python
# Sandbox keys write to a separate collection from live keys
sandbox = MemoryClient(api_key="ock_test_...")

# Projects, members and webhooks are managed in the dashboard and readable here
memory.list_projects()
memory.list_webhooks(memory.project_id)

# Who did what, and when (needs a key with the audit.read scope)
memory.audit_log(limit=20)
```

**What makes it powerful:**
- **Projects & Roles**: per-project models and settings; `READER` / `WRITER` / `ADMIN` / `OWNER` members
- **Live & Test Environments**: `ock_test_` keys never touch production data
- **Scoped Keys**: read-only keys, IP allowlists, and key rotation with a grace period
- **Signed Webhooks**: `memory.created` / `memory.updated` / `memory.deleted`, HMAC-SHA256 signed

### 6. Observability

**See what your agents remember and how they use it.**

```python
memory.usage()                          # request counts, latency, endpoint mix
memory.requests(min_status=400)         # recent failures
memory.top_users(limit=5)               # your most active end-users
memory.graph_snapshot(user_id="alice")  # one user's memory graph: nodes + edges
```

**What makes it powerful:**
- **Usage**: counts, latencies and endpoint mix over any `since` / `until` window
- **Request Log**: filter by path, status or end-user to debug a single call
- **Top Users**: see which end-users drive your traffic
- **Graph Snapshot**: export a user's memory graph to inspect or visualise

### 7. Bring Your Own Model

**Project defaults, per-call overrides.**

```python
# Run one call on a different model; your key is never stored server-side
memory.add(
    messages=conversation,
    user_id="alice",
    llm={"provider": "anthropic", "model": "claude-haiku-4-5-20251001", "api_key": "sk-ant-..."},
    embedder={"provider": "openai", "model": "text-embedding-3-large"},
)
```

**What makes it powerful:**
- **Dashboard Defaults**: set the project's LLM and embedder once in Settings
- **Per-Call Override**: `llm=` / `embedder=` on `add`, `search` and `summary`
- **Bring Your Own Key**: pass `api_key` inside the dict for a single call; it is never persisted
- **Embedder-Safe**: each embedder gets its own vector collection, so switching models never mixes dimensions

---

## Async

`AsyncMemoryClient` has the same methods as `MemoryClient`; just `await` them.

```python
import asyncio
from outhad_contextkit_sdk import AsyncMemoryClient

async def main():
    async with AsyncMemoryClient() as memory:
        await memory.add(messages=[{"role": "user", "content": "I prefer dark mode"}], user_id="alice")
        hits = await memory.search(query="UI preferences?", user_id="alice")
        print(hits["results"])

asyncio.run(main())
```

---

## Configuration

| Setting | Argument | Environment variable | Default |
|---|---|---|---|
| API key | `api_key=` | `OUTHAD_CONTEXTKIT_API_KEY` | required |
| Server | `host=` | `OUTHAD_CONTEXTKIT_HOST` | `https://api.contextkit.outhad.com` |
| Timeout | `timeout=` | | 30 seconds |
| Local fallback | `allow_dev_fallback=` | | `False`. Set `True` during local development to retry `http://localhost:8000` when the default server is unreachable |

**Self-hosted server:**

```python
memory = MemoryClient(api_key="ock_live_...", host="https://memory.your-company.com")
```

Requests carry `Authorization: Token <key>`. When calling the HTTP API directly, `Bearer <key>` and `X-API-Key: <key>` work too.

---

## Errors

```python
from outhad_contextkit_sdk import APIError, AuthenticationError

try:
    memory.update(memory_id, text="...")
except AuthenticationError:
    ...  # 401: key missing, malformed, revoked or expired
except APIError as error:
    print(error.status, error.body)  # e.g. 409 when the memory is pinned
```

---

## All Methods

<details>
<summary><b>Memories</b>: add, search, get, update, delete, history</summary>

`add` · `add_idempotent` · `search` · `get` · `get_all` · `get_all_filtered` · `update` · `history` · `delete` · `delete_all` · `pin` · `unpin` · `batch_update` · `batch_delete` · `summary` · `export` · `users` · `delete_users` · `reset`

</details>

<details>
<summary><b>Personalisation & graph</b>: feedback, backfill, resets</summary>

`feedback` · `backfill` · `reset_cgl` · `reset_feedback`

</details>

<details>
<summary><b>Versioning & lifecycle</b>: versions, rollback, decay, cold storage</summary>

`versions` · `latest_version` · `diff_versions` · `rollback` · `decay_score` · `tick_decay` · `archive_low` · `record_reference` · `demote_to_cold` · `promote_from_cold`

</details>

<details>
<summary><b>Workspace</b>: projects, members, webhooks, API keys, audit log</summary>

`list_projects` · `get_project` · `create_project` · `update_project` · `delete_project` · `list_members` · `add_member` · `update_member` · `remove_member` · `list_webhooks` · `create_webhook` · `update_webhook` · `delete_webhook` · `list_api_keys` · `create_api_key` · `revoke_api_key` · `rotate_api_key` · `update_api_key` · `audit_log`

Creating or changing projects, members, webhooks and keys requires a dashboard sign-in; API keys can read them.

</details>

<details>
<summary><b>Observability</b>: usage, requests, top users, graph snapshot</summary>

`usage` · `requests` · `top_users` · `graph_snapshot`

</details>

---

## Documentation

- **[Cloud guide →](https://contextkit.outhad.com/docs/cloud/)**
- **[SDK API reference →](https://contextkit.outhad.com/docs/reference/sdk/)**
- **[Changelog →](https://contextkit.outhad.com/changelog/)**

## License

Apache-2.0.
