Metadata-Version: 2.5
Name: lyzr-memory-client
Version: 0.2.0
Summary: Typed sync/async Python client for the Lyzr Memory service (PepGenX and Lyzr-hosted deployments)
Project-URL: Repository, https://github.com/NeuralgoLyzr/lyzr-memory-client
Project-URL: Changelog, https://github.com/NeuralgoLyzr/lyzr-memory-client/blob/main/CHANGELOG.md
Project-URL: Issues, https://github.com/NeuralgoLyzr/lyzr-memory-client/issues
Author-email: Lyzr AI <contact@lyzr.ai>
License-Expression: MIT
License-File: LICENSE
Keywords: agents,client,lyzr,memory,pepgenx,sdk
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
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 :: Python Modules
Classifier: Typing :: Typed
Requires-Python: >=3.10
Requires-Dist: httpx<1,>=0.27
Requires-Dist: pydantic<3,>=2.7
Description-Content-Type: text/markdown

# lyzr-memory-client

Typed sync/async Python client for the Lyzr Memory `/v1` API. Use it from PepGenX (JWT-only: an Okta bearer token and nothing else) and from Lyzr-hosted deployments (`x-api-key`). Built on httpx and pydantic.

[![PyPI](https://img.shields.io/pypi/v/lyzr-memory-client)](https://pypi.org/project/lyzr-memory-client/)
[![CI](https://img.shields.io/github/actions/workflow/status/NeuralgoLyzr/lyzr-memory-client/ci.yml?branch=main)](https://github.com/NeuralgoLyzr/lyzr-memory-client/actions)

## Install

```bash
pip install lyzr-memory-client
# or
uv add lyzr-memory-client
```

Requires Python 3.10+. Runtime dependencies: `httpx` and `pydantic` only.

## PepGenX quick start

JWT-only posture: the SDK mints an Okta client-credentials token and sends `Authorization: Bearer <jwt>` and nothing else — no API key, no `team_id` / `project_id` / `user_id` headers. The server derives tenancy from the token, so pass instance defaults such as `project_id=` / `owner_id=` yourself if your calls should inherit them.

```python
import os

from lyzr_memory_client import MemoryClient

client = MemoryClient.for_pepgenx(
    os.environ["LYZR_MEMORY_BASE_URL"],  # APIM base + memory route from onboarding
    client_id=os.environ["OIDC_CLIENT_ID"],
    client_secret=os.environ["OIDC_CLIENT_SECRET"],
    issuer=os.environ["OIDC_ISSUER"],  # Okta: token URL is {issuer}/v1/token
    scope="pepgenx2.0",
    project_id=os.environ.get("PEPGENX_PROJECT_ID"),  # optional instance default
)
```

`for_pepgenx` takes exactly one credential source: `client_id` + `client_secret` (with `issuer` or `token_url`), a caller-owned `token_provider`, or a static `token=`. A provider it mints is closed by `client.close()`. Anything else raises `ConfigurationError`.

```python
me = client.whoami()
print(me.subject, me.org_id, me.roles)

client.add(
    [
        {"role": "user", "content": "I prefer short answers."},
        {"role": "assistant", "content": "Understood."},
    ],
    session_id="ses_demo",
    sync_extraction=True,
)

hits = client.search("preferred answer length")
ctx = client.get_context([{"role": "user", "content": "Remind me of my preference."}])
client.clear_session(session_id="ses_demo")
print(hits.count, ctx.context_string)
```

### Admin onboarding

Import a PepGenX onboarding key and register the service principal before memory calls succeed under `MEMORY_AGENT_REGISTRY_ENFORCE`:

```python
# Use a bootstrap/admin client that can mint keys and registrations.
created = client.admin.api_keys.create(
    "my-agent-key",
    org_id=os.environ["PEPGENX_TEAM_ID"],
    project_id=os.environ["PEPGENX_PROJECT_ID"],
    secret=os.environ["PEPGENX_ONBOARDING_SECRET"],  # their onboarding key value
)

client.admin.agent_registrations.upsert(
    client_id=os.environ["PEPGENX_OIDC_CLIENT_ID"],
    asset_id="ast_my_agent",
    team_id=os.environ["PEPGENX_TEAM_ID"],
    project_id=os.environ["PEPGENX_PROJECT_ID"],
)
```

Without a registration (or with `enabled=false`), memory reads and writes return **403** `memory_not_enabled`. See [docs/AUTH.md](docs/AUTH.md) and [docs/PEPGENX.md](docs/PEPGENX.md).

The legacy composite header contract (API key plus `team_id` / `project_id` / `user_id`) is compatibility only, reached by passing `auth=PepGenXAuth(...)` to the constructor; it is documented in [docs/AUTH.md](docs/AUTH.md) and [docs/PEPGENX.md](docs/PEPGENX.md).

## Lyzr-hosted quick start

Documented hosted base URL: `https://memory.studio.lyzr.ai`. Always pass `base_url=` or set `LYZR_MEMORY_BASE_URL`; there is deliberately no compiled default hostname.

```python
import os

from lyzr_memory_client import MemoryClient

client = MemoryClient.for_lyzr(
    base_url=os.environ.get("LYZR_MEMORY_BASE_URL", "https://memory.studio.lyzr.ai"),
    api_key=os.environ["LYZR_API_KEY"],
)
```

## Environment variables

| Variable | Purpose |
| --- | --- |
| `LYZR_MEMORY_BASE_URL` | Service base URL (required unless `base_url=` is passed) |
| `LYZR_MEMORY_TOKEN` | Bearer token for plain `BearerAuth` resolution |
| `LYZR_API_KEY` | Lyzr-hosted `x-api-key` |
| `OIDC_TOKEN_URL` / `OIDC_ISSUER` | Token endpoint, or Okta issuer (`issuer` → `{issuer}/v1/token`) |
| `OIDC_CLIENT_ID` | OAuth client id |
| `OIDC_CLIENT_SECRET` | OAuth client secret |
| `OIDC_SCOPE` | Optional space-delimited scopes |
| `OIDC_AUDIENCE` | Optional audience claim for the token request |

`ClientCredentials.from_env()` reads the `OIDC_*` variables above; pass `prefix=` to use a different naming scheme.

With `auth=None`, `MemoryClient(...)` resolves auth from the environment in this order: `OIDC_CLIENT_ID` + `OIDC_CLIENT_SECRET` (with `OIDC_ISSUER` or `OIDC_TOKEN_URL`) → a minting `BearerAuth`, then `PEPGENX_API_KEY`, then `LYZR_API_KEY`, then `LYZR_MEMORY_TOKEN`. A provider built from `OIDC_*` is owned by the client and closed with it.

## Async

```python
from lyzr_memory_client import AsyncMemoryClient

async with AsyncMemoryClient.for_lyzr(base_url=..., api_key=...) as client:
    me = await client.whoami()
```

`AsyncMemoryClient` mirrors `MemoryClient`; call `await client.aclose()` if you are not using the context manager.

## Resources and top-level helpers

Namespaces: `memories`, `summaries`, `conversations`, `providers`, `auth`, `admin.api_keys`, `admin.agent_registrations`, `health`.

Convenience methods on the client apply instance defaults (`owner_id`, `agent_id`, `session_id`, `project_id`, `provider_type`): `add`, `search`, `get_context`, `list`, `list_all`, `messages`, `clear_session`, `get`, `update`, `delete`, `graph_query`, `whoami`, `health`, `ready`. Use `with_defaults(...)` to get a new client sharing the same transport.

## Retries and timeouts

Defaults: **30 s** total timeout, **5 s** connect; **3** attempts with exponential backoff **1 s → 30 s** (jitter); `Retry-After` is honoured when present.

`POST /v1/memories` is not idempotent server-side. The SDK retries it only on connection-phase failures and HTTP **429** / **503**, never after a read timeout. Full retry rules: [docs/ERRORS.md](docs/ERRORS.md).

## Eventual consistency

Fact extraction runs asynchronously unless you pass `sync_extraction=True` on `add`. Search and list may lag briefly after a fire-and-forget add.

## Tenancy rules

- Body/query `project_id` must match the authenticated project when the principal carries one; mismatch → **400** `tenant_key_conflict`.
- Service principals (client-credentials tokens) are bound to their Okta subject as `agent_id`; they cannot choose a different `agent_id` or widen via `agent_ids`.

## Error handling

```python
from lyzr_memory_client import PermissionDeniedError

try:
    client.update(memory_id, content="…")
except PermissionDeniedError as e:
    print(e.code, e.request_id, e.detail)
```

See [docs/ERRORS.md](docs/ERRORS.md) for the full hierarchy and `.code` vocabulary.

## Transaction ids

Pass `transaction_id=` on any resource call. The SDK sends it as the `transaction_id` header; the service echoes the correlation id in `X-Request-Id` (available as `APIError.request_id`).

## Security

- Credentials are wrapped in `Secret` and never appear in logs or `repr`.
- TLS verification is on by default (`verify=True`).
- Corporate CA: `MemoryClient(..., verify="/path/to/ca.pem")`.
- HTTP proxies follow httpx `trust_env` (standard `HTTP_PROXY` / `HTTPS_PROXY` / `NO_PROXY`).

## Compatibility

SDK **0.1.x** targets Lyzr Memory API **v1** as frozen in [`spec/SOURCE`](spec/SOURCE) (OpenAPI pin: `spec/openapi.json`).

## Development

| Target | Purpose |
| --- | --- |
| `make sync` | `uv sync --locked` |
| `make lint` / `make fmt` | Ruff check / format |
| `make type` | mypy `--strict` |
| `make test` | pytest with ≥90% coverage |
| `make test-e2e` | Live e2e (`-m e2e`) |
| `make security` | bandit + pip-audit |
| `make build` | Wheel/sdist + twine check |
| `make gen-models` | Regenerate models (`scripts/gen_models.sh`) |
| `make openapi-check` | Diff against live export (`scripts/export_openapi_memory.py`) when `MEMORY_REPO` is set |
| `make sync-core` | Vendor `_core` from/to the sibling RAG client |
| `make all` | lint + type + test + security + build |

## License

MIT. See [LICENSE](LICENSE).
