Metadata-Version: 2.4
Name: lithtrix-langgraph
Version: 0.2.0
Summary: LangGraph BaseStore adapter and swarm client for Lithtrix agent memory
Requires-Python: >=3.11
Description-Content-Type: text/markdown
Requires-Dist: cryptography<45,>=42
Requires-Dist: httpx<1,>=0.27
Requires-Dist: langgraph<1.3,>=1.2.9
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-asyncio>=0.24; extra == "dev"
Requires-Dist: respx>=0.21; extra == "dev"

# lithtrix-langgraph

A [LangGraph](https://github.com/langchain-ai/langgraph) `BaseStore` adapter plus **swarm task sessions** backed by the [Lithtrix](https://lithtrix.ai) API — identity, memory, delegation, and audit traces for AI agents.

## What this is (and isn't)

**Memory:** `LithtrixStore` gives a LangGraph graph a `store=` that reads and writes to Lithtrix's memory API — persistent per-agent memory with semantic search.

**Swarm (0.2.0+):** `LithtrixSwarmClient` wraps spawn, signed delegation, and task trace REST endpoints. A **`LithtrixTaskSession`** carries the **scoped child API key** so worker nodes never need the parent root `ltx_*` key in graph state or LLM prompts.

This package wraps REST; [MCP tools](https://docs.lithtrix.ai) remain available for non-LangGraph adopters. See [swarm docs](https://docs.lithtrix.ai/swarm/spawn) for protocol detail.

## Install

```bash
pip install lithtrix-langgraph
```

Requires Python 3.11+, LangGraph **1.2.x** (`langgraph>=1.2.9,<1.3`), and **`cryptography`** (Ed25519 delegation signing). Use a venv if system Python blocks installs.

## 1. Get an API key

Every Lithtrix agent needs its own identity and key. Register one with a single unauthenticated call — no dashboard, no approval step:

```bash
curl -X POST https://api.lithtrix.ai/v1/register \
  -H "Content-Type: application/json" \
  -H "User-Agent: my-agent/1.0" \
  -d '{
    "agent_name": "my-langgraph-agent",
    "owner_identifier": "you@example.com",
    "agree_to_terms": true
  }'
```

`agent_name` + `owner_identifier` must be unique together — reusing the same pair returns `409`. `agree_to_terms` must be `true` (accepts the [Gentle-Agent Agreement](https://lithtrix.ai/terms)).

The response is a full agent record (identity keys, tier info, etc.) — the only field you need right now is `api_key` (starts with `ltx_`). **Save it now — it is only ever shown once.** Set it as an environment variable:

```bash
export LITHTRIX_API_KEY=ltx_your_key_here
```

Use a **caller-supplied owner email** in examples — never `@lithtrix.internal`. Optional: `"registration_source": "langgraph:package"` when registering from your app.

## 2. Memory quickstart

```python
from lithtrix_langgraph import LithtrixStore

store = LithtrixStore()  # reads LITHTRIX_API_KEY from the environment
```

| Variable | Required | Default |
|----------|----------|---------|
| `LITHTRIX_API_KEY` | Yes | — |
| `LITHTRIX_API_URL` | No | `https://api.lithtrix.ai` |

### Complete memory example

```python
from lithtrix_langgraph import LithtrixStore
from langgraph.graph import StateGraph
from langgraph.config import get_store
from typing_extensions import TypedDict


class State(TypedDict):
    note: str


def remember(state: State) -> State:
    store = get_store()
    store.put(("my-agent",), "last-note", {"text": state["note"]})
    item = store.get(("my-agent",), "last-note")
    return {"note": item.value["text"]}


store = LithtrixStore()
graph = StateGraph(State)
graph.add_node("remember", remember)
graph.set_entry_point("remember")
graph.set_finish_point("remember")
compiled = graph.compile(store=store)

result = compiled.invoke({"note": "hello from LangGraph"})
print(result)  # {'note': 'hello from LangGraph'}
```

## 3. Swarm task session (0.2.0)

Orchestrator holds the **parent root key** and passport private key in environment — not in LangGraph state passed to the LLM.

```python
import os
from cryptography.hazmat.primitives import serialization
from lithtrix_langgraph import LithtrixSwarmClient

parent_id = os.environ["LITHTRIX_PARENT_AGENT_ID"]
root_key = os.environ["LITHTRIX_API_KEY"]
signing_pem = os.environ["LITHTRIX_PASSPORT_PRIVATE_KEY"]

signing_key = serialization.load_pem_private_key(signing_pem.encode(), password=None)

with LithtrixSwarmClient(api_key=root_key) as swarm:
    session = swarm.spawn_and_delegate(parent_id, signing_key=signing_key)
    # session.child_api_key — pass to worker nodes only
    store = session.store()
    store.put(("worker",), "status", {"phase": "running"})
    swarm.trace_append(
        session.task_id,
        proposed_action="memory.put",
        decision="allowed",
        outcome="written",
        delegation_id=session.delegation_id,
    )
```

**Child LangGraph node pattern:** compile a worker graph with `store=session.store()` so the worker uses the **scoped child key** only. See `examples/langgraph_swarm_session.py` for a minimal graph.

**Signing:** Delegation uses canonical bytes `lithtrix.delegation.contract.v1` — same as API/MCP. Offline verification vector: `GET /v1/capabilities` → `swarm.signing_test_vector`.

**D133 cold-run path:** Register parent → `spawn_and_delegate` → child `LithtrixStore.put` with child key → trace append/get. No dependency on `learning/swarm_audit_demo.py` (scratch only, not shipped).

## Key mapping

LangGraph's `(namespace_tuple, key)` gets flattened into a single Lithtrix key, since Lithtrix keys are flat strings (1–128 chars, charset `[a-zA-Z0-9-_.:]`):

| LangGraph call | Lithtrix key |
|----------------|--------------|
| `put(("user", "alice"), "preferences", ...)` | `user:alice:preferences` |
| `put((), "preferences", ...)` | `preferences` |

An empty namespace `()` passes the key through unchanged — useful if you're writing keys that need to match a flat naming convention from another system.

## Values

- **Put:** LangGraph values are `dict` → `PUT /v1/memory/{key}` with body `{"value": <dict>}`. Serialized size is checked locally at **512 KiB** before HTTP (mirrors API `MEMORY_VALUE_TOO_LARGE` / HTTP 413).
- **Get:** JSON objects are returned as-is. String/number/array payloads (DeerFlow Rung 1) are wrapped as `{"content": <raw>}`.
- **Timestamps:** Uses Lithtrix `created_at` / `updated_at` when present; otherwise `datetime.now(UTC)` on read.
- **TTL:** `supports_ttl = False`; `PutOp.ttl` is ignored.

## SearchOp supported subset

| Feature | Support |
|---------|---------|
| `namespace_prefix` | Yes → Lithtrix list `prefix` |
| `query` (semantic) | Yes → `GET /v1/memory/search` |
| `limit` / `offset` | Yes (best-effort pagination) |
| `filter` with `query` | Partial — exact top-level match applied client-side after semantic search |
| `filter` without `query` | Partial — list keys under prefix, fetch values, exact match only |
| `$eq` / `$ne` / `$gt` / … | **No** — raises `NotImplementedError` |
| Cross-namespace search | No |

## HTTP errors

401/403/413/422 responses propagate as `LithtrixAPIError` with `error_code` when the API returns structured JSON (e.g. `MEMORY_VALUE_TOO_LARGE`). 5xx responses are safe to retry — they indicate a transient server-side issue, not a problem with your request.

## Free tier

New agents get rolling-30 free floors (**1,000 memory writes**, **50 searches**, **20 browses**) and **5 MiB KV storage**, no credit card required. See [docs.lithtrix.ai/pricing](https://docs.lithtrix.ai/pricing) for paid tiers.

## Sealed journal (Arc 35 — `0.2.0+`)

Hash journal material on your machine; Lithtrix stores **only** the 32-byte digest:

```python
import asyncio
from lithtrix_langgraph.sealed_journal import commit_sealed_journal
from lithtrix_langgraph.client import LithtrixClient

async def main():
    client = LithtrixClient()  # LITHTRIX_API_KEY
    agent_id = "your-agent-uuid"
    await commit_sealed_journal(client, agent_id, b"notes you never send to the API")

asyncio.run(main())
```

Same domain string as `POST /v1/me/journal/commit` and MCP `lithtrix_journal_commit`. See [custody and recovery docs](https://docs.lithtrix.ai/custody-and-recovery).

## Contributing

The source lives in Lithtrix's main repository, which is private — there's no public repo to file a pull request against. If you hit a bug or want a feature, email [hello@lithtrix.ai](mailto:hello@lithtrix.ai).
