Metadata-Version: 2.4
Name: unshadow
Version: 0.2.0
Summary: Python client for Unshadow Engine and agent banks.
Author-email: Unshadow <support@unshadow.dev>
License-Expression: MIT
Project-URL: Homepage, https://unshadow.dev
Project-URL: Documentation, https://unshadow.dev/docs/api/sdk
Project-URL: Repository, https://github.com/unshadow-ai/unshadow
Project-URL: Issues, https://github.com/unshadow-ai/unshadow/issues
Keywords: unshadow,memory,langchain,llamaindex,rag
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
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 :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: langchain
Requires-Dist: langchain-core>=0.3; extra == "langchain"
Provides-Extra: llamaindex
Requires-Dist: llama-index-core>=0.11; extra == "llamaindex"
Dynamic: license-file

# Unshadow (Python)

Stdlib client for **Unshadow Engine**, plus an agent-bank client. PyPI name: `unshadow`.

```bash
pip install unshadow
```

From this repo, before the PyPI upload:

```bash
pip install ./packages/unshadow
```

LangChain is optional:

```bash
pip install "./packages/unshadow[langchain]"
```

## Engine

`Unshadow` matches the TypeScript SDK: `context`, `search`, `extract`, `forget`, `profile`.

```python
from unshadow import Unshadow

unshadow = Unshadow(api_key="unshadow_…")
packed = unshadow.inject_context("What am I working on?", token_budget=1500)
hits = unshadow.search("pnpm", limit=8, category="preference")
unshadow.extract(
    "User prefers pnpm",
    source_type="ai_conversation",
    idempotency_key="turn-1",
)
unshadow.forget(["11111111-1111-1111-1111-111111111111"])
```

`extract(..., idempotency_key=)` sends `Idempotency-Key`. Reads retry once on 429/5xx. `extract` retries only when that key is set. A new fact can supersede, contradict, or coexist with an older one. Search rows include `supersedes` and `superseded_by`. `UnshadowRetriever` copies `id`, `category`, `source_type`, and those lists into document metadata. Filter with `unshadow.search("editor", category="preference")`.

### LangChain

```python
from langchain_unshadow import UnshadowRetriever
from unshadow import Unshadow

unshadow = Unshadow(api_key="unshadow_…")
retriever = UnshadowRetriever(client=unshadow, k=8, prefer_context=True)
docs = retriever.invoke("What did we decide about pnpm?")
```

`prefer_context` uses `POST /context` and splits `[mem:uuid]` lines. This is not `ConversationBufferMemory`.

### LlamaIndex

```bash
pip install "./packages/unshadow[llamaindex]"
```

```python
from llama_index_unshadow import UnshadowRetriever
from unshadow import Unshadow

unshadow = Unshadow(api_key="unshadow_…")
retriever = UnshadowRetriever(unshadow, k=8, prefer_context=True)
nodes = retriever.retrieve("What did we decide about pnpm?")
```

## Agent bank

`UnshadowBank` is retain / recall / authorize. Those calls still use `/lattice/*`.

```python
from unshadow import UnshadowBank

bank = UnshadowBank(api_key="unshadow_…")
bank.remember("Ship on Fridays is forbidden.", conversation_key="agent-main")
packed = bank.context("release policy")
receipt = bank.authorize(["memory-id"], query="deploy production")
bank.explain(packed["operation_id"])
```

| Name | HTTP |
|------|------|
| `remember` | `POST /lattice/retain` (delta, 32k cap, 409 retry) |
| `explain` | `POST /lattice/explain` |
| `authorize` | `POST /lattice/recall` with `purpose: tool_arg` |

`retain` is an alias of `remember`. Hermes still uses `packages/lattice-hermes`.

The bank client does not ingest files. Capture stores pages and repo notes. Link that project into the agent bank. `remember(..., source_url=...)` cites a URL.

## Tests

```bash
python -m unittest discover -s packages/unshadow/tests
```
