remember 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,6 +32,9 @@ fe/*.tsbuildinfo
32
32
  fe/.pages-deploy.digest
33
33
  fe/.agent-browser-shots/
34
34
  fe/.pen-exports/
35
+ fe/playwright-report/
36
+ fe/test-results/
37
+ .e2e-runtime/
35
38
 
36
39
  # Observability host — secret-bearing / generated deploy artifacts (never commit)
37
40
  infra/obs/.env
@@ -47,6 +50,8 @@ infra/obs/certs/
47
50
  *.env
48
51
  !*.env.example
49
52
  !infra/obs/.env.example
53
+ # Reviewed non-secret image/address inventory; credentials are separate files.
54
+ !infra/storage-cell/runtime.env
50
55
  *.rendered.yaml
51
56
  Caddyfile.rendered
52
57
  **/umcobs0.conf
@@ -66,3 +71,8 @@ fe_admin/storybook-static/
66
71
  # Built client distributions (published via the release workflow, never committed).
67
72
  client/dist/
68
73
  client/.venv/
74
+
75
+ # RememberFS local native build products (source and lockfiles remain tracked).
76
+ rememberfs/target/
77
+ experiments/rememberfs/rust-spike/target/
78
+ experiments/rememberfs/go-spike/rememberfs-go-spike
@@ -0,0 +1,106 @@
1
+ Metadata-Version: 2.5
2
+ Name: remember
3
+ Version: 0.3.0
4
+ Summary: Official Python client for the remember.dev managed memory service.
5
+ Project-URL: Homepage, https://remember.dev
6
+ Project-URL: Documentation, https://remember.dev/docs
7
+ Project-URL: Source, https://github.com/writeitai/ultimate-memory-cloud
8
+ Author-email: "WriteIt.ai s.r.o." <info@writeit.ai>
9
+ License: Apache-2.0
10
+ Keywords: agents,memory,remember.dev,rememberstack
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Typing :: Typed
17
+ Requires-Python: >=3.12
18
+ Requires-Dist: httpx>=0.27
19
+ Requires-Dist: rememberstack>=0.8.1
20
+ Description-Content-Type: text/markdown
21
+
22
+ # remember
23
+
24
+ Official Python SDK for the [remember.dev](https://remember.dev) managed memory service.
25
+
26
+ This distribution provides:
27
+ - **`remember.Client`** (D65): The primary ergonomic client for memory operations. Connects directly to your dedicated tenant deployment ingress with a single deployment API token (`umc_dp_…`).
28
+ - **`remember.CloudClient`** (D53): The control-plane client for checking deployment readiness, credit balances, and spend gates using a control-plane token (`umc_cp_…`).
29
+ - **`remember-status`**: CLI tool for quick control-plane status checks.
30
+
31
+ ## Install
32
+
33
+ ```console
34
+ pip install remember
35
+ ```
36
+
37
+ This automatically installs `rememberstack` (which provides the `remember` CLI tool on `PATH`).
38
+
39
+ ## Quickstart: Memory Operations (`Client`)
40
+
41
+ Mint an **API Token** (`umc_dp_…`) from your dashboard at **Settings → API Tokens**, then initialize `Client`:
42
+
43
+ ```python
44
+ import remember
45
+
46
+ # Client accepts a bare secret (umc_dp_...) or a full Bearer string,
47
+ # and converts string file paths automatically:
48
+ with remember.Client(
49
+ api_key="umc_dp_…",
50
+ base_url="https://<deployment-id>.dp.remember.dev",
51
+ ) as memory:
52
+ landed = memory.ingest("notes/project.md")
53
+ print("Ingested version:", landed.version_id)
54
+
55
+ # Search claims or chunks
56
+ claims = memory.search_claims(query="architecture decisions")
57
+ ```
58
+
59
+ ### Environment Variables
60
+
61
+ For zero-config agent setups, set `REMEMBER_API_KEY` and `REMEMBER_API_URL`:
62
+
63
+ ```console
64
+ export REMEMBER_API_URL="https://<deployment-id>.dp.remember.dev"
65
+ export REMEMBER_API_KEY="umc_dp_…"
66
+ ```
67
+
68
+ ```python
69
+ import remember
70
+
71
+ with remember.Client.from_env() as memory:
72
+ landed = memory.ingest("notes/project.md")
73
+ ```
74
+
75
+ `Client` also automatically falls back to `REMEMBERSTACK_API_AUTHORIZATION` and `REMEMBERSTACK_API_URL` for seamless compatibility.
76
+
77
+ ## Quickstart: Status & Balances (`CloudClient`)
78
+
79
+ To check billing balance and deployment readiness in CI or monitoring bots, use `CloudClient` with an organisation-scoped control-plane token (`umc_cp_…`).
80
+
81
+ Control tokens are read-only, expiring status credentials (D53/D65). Because they are an API-only operational surface, mint one via the control-plane endpoint while authenticated with an owner session:
82
+ - **Endpoint**: `POST /app/api/v1/orgs/{org_id}/control-tokens` (or `POST /v1/orgs/{org_id}/control-tokens` against the direct control-plane URL).
83
+ - **Body**: `{"label": "ci-monitor", "expires_in_days": 90}` (accepts `label` and optional `expires_in_days` up to 90).
84
+
85
+ ```console
86
+ export REMEMBER_CLOUD_TOKEN='umc_cp_…'
87
+ export REMEMBER_CLOUD_ORG='your-organisation-id'
88
+ ```
89
+
90
+ ```python
91
+ from remember import CloudClient
92
+
93
+ with CloudClient.from_env() as cloud:
94
+ if cloud.is_ready():
95
+ print("Balance:", cloud.billing_status().balance)
96
+ ```
97
+
98
+ You can also run the status CLI:
99
+
100
+ ```console
101
+ $ remember-status
102
+ deployment active bb81063d-6b7b-41d0-a1df-dc57d4f1fe87
103
+ endpoint live bb81063d-6b7b-41d0-a1df-dc57d4f1fe87.dp.remember.dev
104
+ billing active balance 42.10
105
+ spend allow
106
+ ```
@@ -0,0 +1,85 @@
1
+ # remember
2
+
3
+ Official Python SDK for the [remember.dev](https://remember.dev) managed memory service.
4
+
5
+ This distribution provides:
6
+ - **`remember.Client`** (D65): The primary ergonomic client for memory operations. Connects directly to your dedicated tenant deployment ingress with a single deployment API token (`umc_dp_…`).
7
+ - **`remember.CloudClient`** (D53): The control-plane client for checking deployment readiness, credit balances, and spend gates using a control-plane token (`umc_cp_…`).
8
+ - **`remember-status`**: CLI tool for quick control-plane status checks.
9
+
10
+ ## Install
11
+
12
+ ```console
13
+ pip install remember
14
+ ```
15
+
16
+ This automatically installs `rememberstack` (which provides the `remember` CLI tool on `PATH`).
17
+
18
+ ## Quickstart: Memory Operations (`Client`)
19
+
20
+ Mint an **API Token** (`umc_dp_…`) from your dashboard at **Settings → API Tokens**, then initialize `Client`:
21
+
22
+ ```python
23
+ import remember
24
+
25
+ # Client accepts a bare secret (umc_dp_...) or a full Bearer string,
26
+ # and converts string file paths automatically:
27
+ with remember.Client(
28
+ api_key="umc_dp_…",
29
+ base_url="https://<deployment-id>.dp.remember.dev",
30
+ ) as memory:
31
+ landed = memory.ingest("notes/project.md")
32
+ print("Ingested version:", landed.version_id)
33
+
34
+ # Search claims or chunks
35
+ claims = memory.search_claims(query="architecture decisions")
36
+ ```
37
+
38
+ ### Environment Variables
39
+
40
+ For zero-config agent setups, set `REMEMBER_API_KEY` and `REMEMBER_API_URL`:
41
+
42
+ ```console
43
+ export REMEMBER_API_URL="https://<deployment-id>.dp.remember.dev"
44
+ export REMEMBER_API_KEY="umc_dp_…"
45
+ ```
46
+
47
+ ```python
48
+ import remember
49
+
50
+ with remember.Client.from_env() as memory:
51
+ landed = memory.ingest("notes/project.md")
52
+ ```
53
+
54
+ `Client` also automatically falls back to `REMEMBERSTACK_API_AUTHORIZATION` and `REMEMBERSTACK_API_URL` for seamless compatibility.
55
+
56
+ ## Quickstart: Status & Balances (`CloudClient`)
57
+
58
+ To check billing balance and deployment readiness in CI or monitoring bots, use `CloudClient` with an organisation-scoped control-plane token (`umc_cp_…`).
59
+
60
+ Control tokens are read-only, expiring status credentials (D53/D65). Because they are an API-only operational surface, mint one via the control-plane endpoint while authenticated with an owner session:
61
+ - **Endpoint**: `POST /app/api/v1/orgs/{org_id}/control-tokens` (or `POST /v1/orgs/{org_id}/control-tokens` against the direct control-plane URL).
62
+ - **Body**: `{"label": "ci-monitor", "expires_in_days": 90}` (accepts `label` and optional `expires_in_days` up to 90).
63
+
64
+ ```console
65
+ export REMEMBER_CLOUD_TOKEN='umc_cp_…'
66
+ export REMEMBER_CLOUD_ORG='your-organisation-id'
67
+ ```
68
+
69
+ ```python
70
+ from remember import CloudClient
71
+
72
+ with CloudClient.from_env() as cloud:
73
+ if cloud.is_ready():
74
+ print("Balance:", cloud.billing_status().balance)
75
+ ```
76
+
77
+ You can also run the status CLI:
78
+
79
+ ```console
80
+ $ remember-status
81
+ deployment active bb81063d-6b7b-41d0-a1df-dc57d4f1fe87
82
+ endpoint live bb81063d-6b7b-41d0-a1df-dc57d4f1fe87.dp.remember.dev
83
+ billing active balance 42.10
84
+ spend allow
85
+ ```
@@ -4,8 +4,8 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "remember"
7
- version = "0.2.0"
8
- description = "Control-plane client for the remember.dev managed memory service."
7
+ version = "0.3.0"
8
+ description = "Official Python client for the remember.dev managed memory service."
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.12"
11
11
  license = { text = "Apache-2.0" }
@@ -20,9 +20,9 @@ classifiers = [
20
20
  "Typing :: Typed",
21
21
  ]
22
22
 
23
- # D43 rule 1: the cloud distribution may depend on the memory client; never the
24
- # reverse. The memory verbs stay in rememberstack — this package adds only the
25
- # control-plane questions that engine has no business answering.
23
+ # D43 rule 1 & D65: the cloud distribution depends on rememberstack for the core
24
+ # memory client and engine wire models. This package exposes the ergonomic Client
25
+ # facade for data-plane memory operations and CloudClient for control-plane queries.
26
26
  dependencies = [
27
27
  "httpx>=0.27",
28
28
  "rememberstack>=0.8.1",
@@ -0,0 +1,77 @@
1
+ """Python client library for the remember.dev managed memory service (D65).
2
+
3
+ PyPI project **`remember`**, import name **`remember`**.
4
+
5
+ What this package provides
6
+ --------------------------
7
+
8
+ 1. **`Client`** (D65):
9
+ The primary developer memory client. Accepts a single deployment API token
10
+ (``umc_dp_…``) or bare secret, connects directly to tenant deployment ingress,
11
+ and provides ergonomic access to all memory operations (ingest, search,
12
+ pipeline readiness, and assured operations).
13
+
14
+ import remember
15
+
16
+ with remember.Client(
17
+ api_key="umc_dp_…",
18
+ base_url="https://<deployment-id>.dp.remember.dev",
19
+ ) as memory:
20
+ landed = memory.ingest("guide.pdf")
21
+
22
+ Or zero-config from environment variables (``REMEMBER_API_KEY`` and ``REMEMBER_API_URL``):
23
+
24
+ memory = remember.Client.from_env()
25
+
26
+ 2. **`CloudClient`** (D53):
27
+ The control-plane client. Uses an organisation-scoped control-plane token
28
+ (``umc_cp_…``) to inspect deployment readiness, credit balance, and spend gates.
29
+
30
+ from remember import CloudClient
31
+
32
+ with CloudClient.from_env() as cloud:
33
+ status = cloud.billing_status()
34
+ print(status.state, status.balance)
35
+
36
+ 3. **Core Re-exports**:
37
+ Typed models and errors (:class:`Envelope`, :class:`ContextBundleV1`,
38
+ :class:`IngestedVersion`, :class:`ReadinessRequirements`, :class:`MemoryApiError`)
39
+ are re-exported directly from ``remember`` for convenient typing.
40
+ """
41
+
42
+ from rememberstack.client import MemoryApiError
43
+ from rememberstack.client import ReadinessRequirements
44
+ from rememberstack.model import ContextBundleV1
45
+ from rememberstack.model import Envelope
46
+ from rememberstack.model import IngestedVersion
47
+
48
+ from remember.client import Client
49
+ from remember.client import CloudClient
50
+ from remember.errors import CloudError
51
+ from remember.errors import NotPermitted
52
+ from remember.errors import RateLimited
53
+ from remember.errors import Unauthenticated
54
+ from remember.models import BillingStatus
55
+ from remember.models import Deployment
56
+ from remember.models import LedgerEntry
57
+ from remember.models import SpendGate
58
+
59
+ __all__ = [
60
+ "BillingStatus",
61
+ "Client",
62
+ "CloudClient",
63
+ "CloudError",
64
+ "ContextBundleV1",
65
+ "Deployment",
66
+ "Envelope",
67
+ "IngestedVersion",
68
+ "LedgerEntry",
69
+ "MemoryApiError",
70
+ "NotPermitted",
71
+ "RateLimited",
72
+ "ReadinessRequirements",
73
+ "SpendGate",
74
+ "Unauthenticated",
75
+ ]
76
+
77
+ __version__ = "0.3.0"
@@ -1,8 +1,9 @@
1
- """The control-plane client.
1
+ """The remember.dev clients.
2
2
 
3
- One class, a handful of read methods, and no memory verbs. Everything it can do
4
- is what D53's ``status:read`` profile permits — which is deliberate: a credential
5
- that could do more would be a credential worth stealing.
3
+ - :class:`Client`: Ergonomic memory client connecting directly to tenant deployment
4
+ ingress for memory storage and retrieval (D65).
5
+ - :class:`CloudClient`: Control-plane client for organisation status, deployment
6
+ inspection, and billing balances (D53).
6
7
  """
7
8
 
8
9
  from __future__ import annotations
@@ -10,11 +11,18 @@ from __future__ import annotations
10
11
  from collections.abc import Iterator
11
12
  from collections.abc import Mapping
12
13
  from contextlib import contextmanager
14
+ from datetime import datetime
13
15
  import os
16
+ from pathlib import Path
14
17
  from types import TracebackType
15
18
  from typing import Any
19
+ from typing import Literal
20
+ from typing import Self
16
21
 
17
22
  import httpx
23
+ from rememberstack.client import ClientSettings
24
+ from rememberstack.client import MemoryClient
25
+ from rememberstack.model.documents import IngestedVersion
18
26
 
19
27
  from remember.errors import CloudError
20
28
  from remember.errors import NotPermitted
@@ -34,6 +42,156 @@ TOKEN_ENV = "REMEMBER_CLOUD_TOKEN"
34
42
  ORG_ENV = "REMEMBER_CLOUD_ORG"
35
43
  BASE_URL_ENV = "REMEMBER_CLOUD_URL"
36
44
 
45
+ #: Environment variables for the unified data-plane client (D65).
46
+ API_KEY_ENV = "REMEMBER_API_KEY"
47
+ API_URL_ENV = "REMEMBER_API_URL"
48
+ REMEMBERSTACK_AUTH_ENV = "REMEMBERSTACK_API_AUTHORIZATION"
49
+ REMEMBERSTACK_URL_ENV = "REMEMBERSTACK_API_URL"
50
+
51
+
52
+ def _format_bearer(token: str) -> str:
53
+ """Ensure a token string has the standard Bearer header prefix."""
54
+ if not token or not token.strip():
55
+ raise ValueError("API key or authorization token cannot be empty")
56
+ if "\r" in token or "\n" in token:
57
+ raise ValueError("Authorization token must not contain newline characters")
58
+ cleaned = token.strip()
59
+ if cleaned.lower() == "bearer":
60
+ raise ValueError("Bearer token value cannot be empty")
61
+ if cleaned.lower().startswith("bearer "):
62
+ rest = cleaned[7:].strip()
63
+ if not rest:
64
+ raise ValueError("Bearer token value cannot be empty")
65
+ return f"Bearer {rest}"
66
+ return f"Bearer {cleaned}"
67
+
68
+
69
+ class Client(MemoryClient):
70
+ """Ergonomic data-plane memory client for remember.dev (D65).
71
+
72
+ Subclasses :class:`rememberstack.client.MemoryClient`, providing:
73
+ - ``api_key`` parameter accepting bare secrets (``umc_dp_...``) or full
74
+ ``Bearer`` headers.
75
+ - Path string support in :meth:`ingest` (accepts ``str``, ``Path``, or ``bytes``).
76
+ - Environment configuration from ``REMEMBER_API_KEY`` and ``REMEMBER_API_URL``
77
+ with fallbacks to ``REMEMBERSTACK_API_AUTHORIZATION`` and ``REMEMBERSTACK_API_URL``.
78
+ - Direct connection to deployment ingress (queries are never proxied through
79
+ the control plane).
80
+ """
81
+
82
+ def __init__(
83
+ self,
84
+ *,
85
+ api_key: str | None = None,
86
+ base_url: str | None = None,
87
+ authorization: str | None = None,
88
+ client: httpx.Client | None = None,
89
+ timeout: float | None = None,
90
+ settings: ClientSettings | None = None,
91
+ ) -> None:
92
+ if client is not None and any(
93
+ value is not None
94
+ for value in (api_key, base_url, authorization, timeout, settings)
95
+ ):
96
+ raise ValueError(
97
+ "an injected client cannot be combined with client settings"
98
+ )
99
+ if client is not None:
100
+ super().__init__(client=client)
101
+ return
102
+
103
+ resolved_authorization: str | None = None
104
+ if api_key is not None:
105
+ resolved_authorization = _format_bearer(api_key)
106
+ elif authorization is not None:
107
+ resolved_authorization = authorization
108
+ elif settings is not None and settings.api_authorization is not None:
109
+ resolved_authorization = settings.api_authorization.get_secret_value()
110
+ elif os.getenv(API_KEY_ENV):
111
+ resolved_authorization = _format_bearer(os.environ[API_KEY_ENV])
112
+ elif os.getenv(REMEMBERSTACK_AUTH_ENV):
113
+ resolved_authorization = os.environ[REMEMBERSTACK_AUTH_ENV]
114
+
115
+ resolved_base_url: str | None = None
116
+ if base_url is not None:
117
+ resolved_base_url = base_url
118
+ elif settings is not None and settings.api_url:
119
+ resolved_base_url = settings.api_url
120
+ elif os.getenv(API_URL_ENV):
121
+ resolved_base_url = os.environ[API_URL_ENV]
122
+ elif os.getenv(REMEMBERSTACK_URL_ENV):
123
+ resolved_base_url = os.environ[REMEMBERSTACK_URL_ENV]
124
+
125
+ super().__init__(
126
+ base_url=resolved_base_url,
127
+ authorization=resolved_authorization,
128
+ timeout=timeout,
129
+ settings=settings,
130
+ )
131
+
132
+ @classmethod
133
+ def from_env(cls, **overrides: Any) -> Self:
134
+ """Build from environment variables with keyword argument overrides."""
135
+ return cls(**overrides)
136
+
137
+ def ingest(
138
+ self,
139
+ source: str | Path | bytes,
140
+ *,
141
+ filename: str | None = None,
142
+ mime: str | None = None,
143
+ title: str | None = None,
144
+ source_kind: str | None = None,
145
+ source_ref: str | None = None,
146
+ source_modified_at: datetime | None = None,
147
+ versioning_mode: Literal["snapshot", "living"] = "snapshot",
148
+ source_version_ref: str | None = None,
149
+ ) -> IngestedVersion:
150
+ """Ingest a document from a file path, string path, or raw bytes."""
151
+ resolved_source = Path(source) if isinstance(source, str) else source
152
+ return super().ingest(
153
+ resolved_source,
154
+ filename=filename,
155
+ mime=mime,
156
+ title=title,
157
+ source_kind=source_kind,
158
+ source_ref=source_ref,
159
+ source_modified_at=source_modified_at,
160
+ versioning_mode=versioning_mode,
161
+ source_version_ref=source_version_ref,
162
+ )
163
+
164
+ def ingest_file(
165
+ self,
166
+ file_path: str | Path,
167
+ *,
168
+ filename: str | None = None,
169
+ mime: str | None = None,
170
+ title: str | None = None,
171
+ source_kind: str | None = None,
172
+ source_ref: str | None = None,
173
+ source_modified_at: datetime | None = None,
174
+ versioning_mode: Literal["snapshot", "living"] = "snapshot",
175
+ source_version_ref: str | None = None,
176
+ ) -> IngestedVersion:
177
+ """Alias for :meth:`ingest` accepting a string file path or :class:`pathlib.Path`."""
178
+ return self.ingest(
179
+ file_path,
180
+ filename=filename,
181
+ mime=mime,
182
+ title=title,
183
+ source_kind=source_kind,
184
+ source_ref=source_ref,
185
+ source_modified_at=source_modified_at,
186
+ versioning_mode=versioning_mode,
187
+ source_version_ref=source_version_ref,
188
+ )
189
+
190
+ def __enter__(self) -> Self:
191
+ """Support ``with Client(...) as memory:`` returning Self."""
192
+ super().__enter__()
193
+ return self
194
+
37
195
 
38
196
  class CloudClient:
39
197
  """Ask the control plane what it knows about one organisation.
@@ -67,7 +225,7 @@ class CloudClient:
67
225
  )
68
226
 
69
227
  @classmethod
70
- def from_env(cls, **overrides: Any) -> "CloudClient":
228
+ def from_env(cls, **overrides: Any) -> Self:
71
229
  """Build from ``REMEMBER_CLOUD_TOKEN`` / ``_ORG`` / ``_URL``.
72
230
 
73
231
  The usual shape for an agent: credentials in the environment, nothing in
@@ -168,7 +326,7 @@ class CloudClient:
168
326
  """Release the underlying connection pool."""
169
327
  self._http.close()
170
328
 
171
- def __enter__(self) -> "CloudClient":
329
+ def __enter__(self) -> Self:
172
330
  """Support ``with CloudClient(...) as cloud:``."""
173
331
  return self
174
332
 
@@ -0,0 +1,195 @@
1
+ """Tests for remember.Client facade (D65)."""
2
+
3
+ from __future__ import annotations
4
+
5
+ from pathlib import Path
6
+
7
+ import httpx
8
+ from pydantic import SecretStr
9
+ import pytest
10
+ import remember
11
+ from remember import Client
12
+ from remember.client import _format_bearer
13
+ from rememberstack.client import ClientSettings
14
+
15
+
16
+ def test_format_bearer() -> None:
17
+ assert _format_bearer("umc_dp_secret") == "Bearer umc_dp_secret"
18
+ assert _format_bearer("Bearer umc_dp_secret") == "Bearer umc_dp_secret"
19
+ assert _format_bearer("bearer umc_dp_secret") == "Bearer umc_dp_secret"
20
+ assert _format_bearer(" Bearer umc_dp_secret ") == "Bearer umc_dp_secret"
21
+
22
+
23
+ def test_format_bearer_validation() -> None:
24
+ with pytest.raises(ValueError, match="cannot be empty"):
25
+ _format_bearer("")
26
+ with pytest.raises(ValueError, match="cannot be empty"):
27
+ _format_bearer(" ")
28
+ with pytest.raises(ValueError, match="cannot be empty"):
29
+ _format_bearer("Bearer")
30
+ with pytest.raises(ValueError, match="cannot be empty"):
31
+ _format_bearer("Bearer ")
32
+ with pytest.raises(ValueError, match="newline"):
33
+ _format_bearer("umc_dp_key\r\n")
34
+ with pytest.raises(ValueError, match="newline"):
35
+ _format_bearer("Bearer secret\n")
36
+
37
+
38
+ def test_client_bare_api_key_formats_bearer() -> None:
39
+ client = Client(api_key="umc_dp_12345", base_url="https://dp.test")
40
+ auth_header = client._client.headers.get("Authorization")
41
+ assert auth_header == "Bearer umc_dp_12345"
42
+ assert str(client._client.base_url) == "https://dp.test"
43
+
44
+
45
+ def test_client_api_key_with_bearer_prefix() -> None:
46
+ client = Client(api_key="Bearer umc_dp_12345", base_url="https://dp.test")
47
+ auth_header = client._client.headers.get("Authorization")
48
+ assert auth_header == "Bearer umc_dp_12345"
49
+
50
+
51
+ def test_client_api_key_takes_precedence_over_authorization() -> None:
52
+ client = Client(
53
+ api_key="umc_dp_primary",
54
+ authorization="Bearer umc_dp_fallback",
55
+ base_url="https://dp.test",
56
+ )
57
+ assert client._client.headers.get("Authorization") == "Bearer umc_dp_primary"
58
+
59
+
60
+ def test_client_env_var_resolution(monkeypatch: pytest.MonkeyPatch) -> None:
61
+ monkeypatch.setenv("REMEMBER_API_KEY", "umc_dp_from_env")
62
+ monkeypatch.setenv("REMEMBER_API_URL", "https://env.dp.remember.dev")
63
+ # Fallbacks that must be overridden
64
+ monkeypatch.setenv("REMEMBERSTACK_API_AUTHORIZATION", "Bearer rs_auth")
65
+ monkeypatch.setenv("REMEMBERSTACK_API_URL", "https://rs.dp.remember.dev")
66
+
67
+ client = Client.from_env()
68
+ assert client._client.headers.get("Authorization") == "Bearer umc_dp_from_env"
69
+ assert str(client._client.base_url) == "https://env.dp.remember.dev"
70
+
71
+
72
+ def test_client_falls_back_to_rememberstack_env(
73
+ monkeypatch: pytest.MonkeyPatch,
74
+ ) -> None:
75
+ monkeypatch.delenv("REMEMBER_API_KEY", raising=False)
76
+ monkeypatch.delenv("REMEMBER_API_URL", raising=False)
77
+ monkeypatch.setenv("REMEMBERSTACK_API_AUTHORIZATION", "Bearer rs_auth")
78
+ monkeypatch.setenv("REMEMBERSTACK_API_URL", "https://rs.dp.remember.dev")
79
+
80
+ client = Client.from_env()
81
+ assert client._client.headers.get("Authorization") == "Bearer rs_auth"
82
+ assert str(client._client.base_url) == "https://rs.dp.remember.dev"
83
+
84
+
85
+ def test_client_explicit_settings_overrides_env(
86
+ monkeypatch: pytest.MonkeyPatch,
87
+ ) -> None:
88
+ monkeypatch.setenv("REMEMBER_API_KEY", "umc_dp_ambient")
89
+ monkeypatch.setenv("REMEMBER_API_URL", "https://ambient.dp.remember.dev")
90
+
91
+ settings = ClientSettings(
92
+ api_url="https://settings.dp.remember.dev",
93
+ api_authorization=SecretStr("Bearer rs_settings_auth"),
94
+ )
95
+ client = Client(settings=settings)
96
+ assert client._client.headers.get("Authorization") == "Bearer rs_settings_auth"
97
+ assert str(client._client.base_url) == "https://settings.dp.remember.dev"
98
+
99
+
100
+ def test_client_ignores_cloud_token_and_org(monkeypatch: pytest.MonkeyPatch) -> None:
101
+ monkeypatch.delenv("REMEMBER_API_KEY", raising=False)
102
+ monkeypatch.delenv("REMEMBER_API_URL", raising=False)
103
+ monkeypatch.delenv("REMEMBERSTACK_API_AUTHORIZATION", raising=False)
104
+ monkeypatch.delenv("REMEMBERSTACK_API_URL", raising=False)
105
+ monkeypatch.setenv("REMEMBER_CLOUD_TOKEN", "umc_cp_secret")
106
+ monkeypatch.setenv("REMEMBER_CLOUD_ORG", "some-org-id")
107
+
108
+ # Client must NOT pick up control-plane tokens
109
+ client = Client.from_env()
110
+ assert client._client.headers.get("Authorization") is None
111
+
112
+
113
+ def test_client_injected_client_refuses_settings() -> None:
114
+ mock_http = httpx.Client()
115
+ with pytest.raises(ValueError, match="injected client cannot be combined"):
116
+ Client(client=mock_http, api_key="umc_dp_test")
117
+
118
+
119
+ def test_client_ingest_string_path(tmp_path: Path) -> None:
120
+ test_file = tmp_path / "sample.txt"
121
+ test_file.write_text("hello memory")
122
+
123
+ seen_request: dict[str, str] = {}
124
+
125
+ def handler(request: httpx.Request) -> httpx.Response:
126
+ seen_request["path"] = request.url.path
127
+ seen_request["filename"] = request.url.params.get("filename", "")
128
+ seen_request["mime"] = request.url.params.get("mime", "")
129
+ seen_request["content"] = request.content.decode()
130
+ return httpx.Response(
131
+ 200,
132
+ json={
133
+ "deployment_id": "00000000-0000-0000-0000-000000000000",
134
+ "doc_id": "11111111-1111-1111-1111-111111111111",
135
+ "version_id": "22222222-2222-2222-2222-222222222222",
136
+ "content_hash": "sha256:abcd",
137
+ "created": True,
138
+ },
139
+ )
140
+
141
+ mock_client = httpx.Client(
142
+ base_url="https://dp.test", transport=httpx.MockTransport(handler)
143
+ )
144
+ with Client(client=mock_client) as memory:
145
+ landed = memory.ingest(str(test_file))
146
+
147
+ assert seen_request["path"] == "/ingest"
148
+ assert seen_request["filename"] == "sample.txt"
149
+ assert seen_request["mime"] == "text/plain"
150
+ assert seen_request["content"] == "hello memory"
151
+ assert str(landed.version_id) == "22222222-2222-2222-2222-222222222222"
152
+ assert landed.created is True
153
+
154
+
155
+ def test_client_ingest_file_alias(tmp_path: Path) -> None:
156
+ test_file = tmp_path / "doc.md"
157
+ test_file.write_text("# Doc\n\nContent here.")
158
+
159
+ def handler(request: httpx.Request) -> httpx.Response:
160
+ return httpx.Response(
161
+ 200,
162
+ json={
163
+ "deployment_id": "00000000-0000-0000-0000-000000000000",
164
+ "doc_id": "11111111-1111-1111-1111-111111111111",
165
+ "version_id": "22222222-2222-2222-2222-222222222222",
166
+ "content_hash": "sha256:abcd",
167
+ "created": True,
168
+ },
169
+ )
170
+
171
+ mock_client = httpx.Client(
172
+ base_url="https://dp.test", transport=httpx.MockTransport(handler)
173
+ )
174
+ with Client(client=mock_client) as memory:
175
+ # String path
176
+ landed_str = memory.ingest_file(str(test_file))
177
+ assert landed_str.created is True
178
+
179
+ # Path object
180
+ landed_path = memory.ingest_file(test_file)
181
+ assert landed_path.created is True
182
+
183
+
184
+ def test_top_level_exports() -> None:
185
+ assert hasattr(remember, "Client")
186
+ assert hasattr(remember, "CloudClient")
187
+ assert hasattr(remember, "Envelope")
188
+ assert hasattr(remember, "ContextBundleV1")
189
+ assert hasattr(remember, "IngestedVersion")
190
+ assert hasattr(remember, "MemoryApiError")
191
+ assert hasattr(remember, "ReadinessRequirements")
192
+ assert hasattr(remember, "BillingStatus")
193
+ assert hasattr(remember, "Deployment")
194
+ assert hasattr(remember, "LedgerEntry")
195
+ assert hasattr(remember, "SpendGate")
@@ -496,7 +496,7 @@ wheels = [
496
496
 
497
497
  [[package]]
498
498
  name = "remember"
499
- version = "0.2.0"
499
+ version = "0.3.0"
500
500
  source = { editable = "." }
501
501
  dependencies = [
502
502
  { name = "httpx" },
remember-0.2.0/PKG-INFO DELETED
@@ -1,78 +0,0 @@
1
- Metadata-Version: 2.5
2
- Name: remember
3
- Version: 0.2.0
4
- Summary: Control-plane client for the remember.dev managed memory service.
5
- Project-URL: Homepage, https://remember.dev
6
- Project-URL: Documentation, https://remember.dev/docs
7
- Project-URL: Source, https://github.com/writeitai/ultimate-memory-cloud
8
- Author-email: "WriteIt.ai s.r.o." <info@writeit.ai>
9
- License: Apache-2.0
10
- Keywords: agents,memory,remember.dev,rememberstack
11
- Classifier: Development Status :: 3 - Alpha
12
- Classifier: Intended Audience :: Developers
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.12
15
- Classifier: Programming Language :: Python :: 3.13
16
- Classifier: Typing :: Typed
17
- Requires-Python: >=3.12
18
- Requires-Dist: httpx>=0.27
19
- Requires-Dist: rememberstack>=0.8.1
20
- Description-Content-Type: text/markdown
21
-
22
- # remember
23
-
24
- Control-plane client for the [remember.dev](https://remember.dev) managed memory service.
25
-
26
- `rememberstack` answers *memory* questions — ingest a document, search claims, run an assured
27
- operation. This package answers the questions an operator of that memory also has:
28
-
29
- - is my deployment ready?
30
- - what is my balance, and what did that ingest cost?
31
- - has spend safety parked my work?
32
-
33
- ```python
34
- from remember import CloudClient
35
-
36
- with CloudClient.from_env() as cloud:
37
- if cloud.is_ready():
38
- print(cloud.billing_status().balance)
39
- ```
40
-
41
- ```console
42
- $ remember-status
43
- deployment active bb81063d-6b7b-41d0-a1df-dc57d4f1fe87
44
- endpoint live bb81063d-6b7b-41d0-a1df-dc57d4f1fe87.dp.remember.dev
45
- billing active balance 42.10
46
- spend allow
47
- ```
48
-
49
- ## Install
50
-
51
- ```console
52
- pip install remember
53
- ```
54
-
55
- This installs `rememberstack` too. The `remember` **command** comes from that package and is
56
- unaffected by this one; this distribution adds only `remember-status`.
57
-
58
- ## Credentials
59
-
60
- This client authenticates with a **control-plane token** (`umc_cp_…`) — the D53 credential kind.
61
- It is not the same thing as a deployment API token (`umc_dp_…`): that one authenticates *memory*
62
- calls at the deployment's ingress and will be rejected here.
63
-
64
- Mint one with `POST /v1/orgs/<org>/control-tokens` while signed in to the app. There is no button
65
- for this yet — the API landed before the interface did — so today it is a call, not a click:
66
-
67
- ```console
68
- export REMEMBER_CLOUD_TOKEN='umc_cp_…'
69
- export REMEMBER_CLOUD_ORG='your-organisation-id'
70
- ```
71
-
72
- The secret is shown once. The credential is organisation-scoped, **read-only**, expires (90 days by
73
- default), and can be surrendered by its holder at any time.
74
-
75
- ## What it will not do
76
-
77
- No memory verbs, no second ingest or search contract. To *use* the memory, use `rememberstack`
78
- pointed at your deployment; this package tells you what that memory costs and whether it is ready.
remember-0.2.0/README.md DELETED
@@ -1,57 +0,0 @@
1
- # remember
2
-
3
- Control-plane client for the [remember.dev](https://remember.dev) managed memory service.
4
-
5
- `rememberstack` answers *memory* questions — ingest a document, search claims, run an assured
6
- operation. This package answers the questions an operator of that memory also has:
7
-
8
- - is my deployment ready?
9
- - what is my balance, and what did that ingest cost?
10
- - has spend safety parked my work?
11
-
12
- ```python
13
- from remember import CloudClient
14
-
15
- with CloudClient.from_env() as cloud:
16
- if cloud.is_ready():
17
- print(cloud.billing_status().balance)
18
- ```
19
-
20
- ```console
21
- $ remember-status
22
- deployment active bb81063d-6b7b-41d0-a1df-dc57d4f1fe87
23
- endpoint live bb81063d-6b7b-41d0-a1df-dc57d4f1fe87.dp.remember.dev
24
- billing active balance 42.10
25
- spend allow
26
- ```
27
-
28
- ## Install
29
-
30
- ```console
31
- pip install remember
32
- ```
33
-
34
- This installs `rememberstack` too. The `remember` **command** comes from that package and is
35
- unaffected by this one; this distribution adds only `remember-status`.
36
-
37
- ## Credentials
38
-
39
- This client authenticates with a **control-plane token** (`umc_cp_…`) — the D53 credential kind.
40
- It is not the same thing as a deployment API token (`umc_dp_…`): that one authenticates *memory*
41
- calls at the deployment's ingress and will be rejected here.
42
-
43
- Mint one with `POST /v1/orgs/<org>/control-tokens` while signed in to the app. There is no button
44
- for this yet — the API landed before the interface did — so today it is a call, not a click:
45
-
46
- ```console
47
- export REMEMBER_CLOUD_TOKEN='umc_cp_…'
48
- export REMEMBER_CLOUD_ORG='your-organisation-id'
49
- ```
50
-
51
- The secret is shown once. The credential is organisation-scoped, **read-only**, expires (90 days by
52
- default), and can be surrendered by its holder at any time.
53
-
54
- ## What it will not do
55
-
56
- No memory verbs, no second ingest or search contract. To *use* the memory, use `rememberstack`
57
- pointed at your deployment; this package tells you what that memory costs and whether it is ready.
@@ -1,74 +0,0 @@
1
- """Control-plane client for the remember.dev managed service.
2
-
3
- PyPI project **`remember`**, import name **`remember`** (D43 left the import name
4
- open and required the first publish to document it; this is that record). The
5
- distribution installs **no** ``remember`` console script — that entry point
6
- belongs to ``rememberstack`` alone, and two distributions competing for one name
7
- on ``PATH`` is the collision D43 rule 3 forbids.
8
-
9
- What this package is for
10
- ------------------------
11
-
12
- ``rememberstack`` answers *memory* questions: ingest a document, search claims,
13
- run an assured operation. It cannot answer the questions an operator of that
14
- memory also has —
15
-
16
- * is my deployment ready?
17
- * what is my balance, and what did that ingest cost?
18
- * has spend safety parked my work?
19
-
20
- Those live on the control plane, which until D53 accepted only a browser session
21
- cookie. This client speaks to it with a **control-plane token** (``umc_cp_…``):
22
- organisation-scoped, read-only, bounded lifetime, revocable by its bearer.
23
-
24
- from remember import CloudClient
25
-
26
- with CloudClient.from_env() as cloud:
27
- status = cloud.billing_status()
28
- print(status.state, status.balance)
29
-
30
- Getting a credential
31
- --------------------
32
-
33
- A control-plane token is not a deployment API token: `umc_dp_…` authenticates
34
- *memory* calls at a deployment's ingress and is rejected here. Mint one with
35
- `POST /v1/orgs/<org>/control-tokens` while signed in to the app — there is no
36
- button for it yet, the API landed before the interface did. Then:
37
-
38
- export REMEMBER_CLOUD_TOKEN='umc_cp_…'
39
- export REMEMBER_CLOUD_ORG='your-organisation-id'
40
-
41
- The secret is shown once. `remember login` does not yet mint this kind — that
42
- needs an amendment to the device grant (D40), tracked in D53 §6.
43
-
44
- What it deliberately does not do
45
- --------------------------------
46
-
47
- No memory verbs. No second ingest, search, or envelope contract (D35). If you
48
- want to *use* the memory, use ``rememberstack``; this package tells you what the
49
- memory costs and whether it is ready.
50
- """
51
-
52
- from remember.client import CloudClient
53
- from remember.errors import CloudError
54
- from remember.errors import NotPermitted
55
- from remember.errors import RateLimited
56
- from remember.errors import Unauthenticated
57
- from remember.models import BillingStatus
58
- from remember.models import Deployment
59
- from remember.models import LedgerEntry
60
- from remember.models import SpendGate
61
-
62
- __all__ = [
63
- "BillingStatus",
64
- "CloudClient",
65
- "CloudError",
66
- "Deployment",
67
- "LedgerEntry",
68
- "NotPermitted",
69
- "RateLimited",
70
- "SpendGate",
71
- "Unauthenticated",
72
- ]
73
-
74
- __version__ = "0.2.0"
File without changes
File without changes
File without changes
File without changes