You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: CHANGELOG.md
+21Lines changed: 21 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -6,6 +6,27 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
6
7
7
## [Unreleased]
8
8
9
+
## [1.1.0] - 2026-06-09
10
+
11
+
### Added
12
+
- `atomicmemory.contract.v1`: a wire codec for the v1 provider contract's deliberately mixed-case encoding (`Memory.createdAt`/`updatedAt` and `SearchResult.rankingScore` are camelCase on the wire; `version_id`, `observed_at`, and retrieval-receipt fields are snake_case). Encode/decode helpers cover `Memory`, `Provenance`, `SearchResult`, `SearchResultPage`, `SearchRequest`, and ingest payloads (`IngestInput`, `IngestResult`). Dates follow the contract's ISO-8601 UTC millisecond `Z` form (`_to_iso_z`, equivalent to TS `toISOString()`). Naive datetimes in encode paths are assumed UTC. `encode_ingest_input` fails closed on the Python-ahead `content_class` field (no place in the v1 `additionalProperties: false` schemas; TS contract alignment is a recorded follow-up). Explicit-null `version_id` in `SearchResult` normalizes to absent on re-encode, matching the TS optional declaration. `encode_search_request` uses `by_alias=True` so Python-keyword-safe combinator field names (`and_`/`or_`/`not_`) emit their wire aliases; a recursive `_jsonify` walk converts any `datetime` operands in filter trees to the toISOString form. In-process models and provider mappers are unchanged.
13
+
- Vendored the TS SDK's versioned v1 wire contract (JSON Schemas, cross-provider conformance corpus, and CONTRACT.md) under `contract/`, with explicit provenance in `contract/VENDORED.json` and a documented refresh script (`scripts/refresh_contract.py`, never run in CI). A pytest conformance harness proves corpus fixtures decode into the Python models (directly for snake-on-wire types, through the codec for the mixed-case search response) and that SDK emissions validate against the vendored draft-2020-12 schemas, with the TS suite's negative cases mirrored against both schemas and Pydantic. The `capabilities-descriptor` case is schema-only (no Python model in this release — recorded follow-up).
14
+
-`atomicmemory.contract` re-exports `v1` as a specialty import surface; deliberately not re-exported from the package root to keep the root namespace focused on the core provider API.
15
+
-`AsyncProviderFactory` now accepts factories that return an `Awaitable[AsyncProviderRegistration]`, enabling lazy or async provider construction during `AsyncMemoryService.initialize()`.
16
+
-`MemoryService.initialize()` and `AsyncMemoryService.initialize()` raise `ConfigError` when the configured default provider has no registered factory, making a misconfigured default an immediate, explicit error rather than a silent no-op.
17
+
18
+
### Changed
19
+
-`content_class` is now accepted on **every** ingest mode (`text`, `messages`, and `verbatim`), not just `verbatim`, and is forwarded to core for all modes. Extraction-based ingests (`text`/`messages`) can now satisfy a core running the default `RAW_CONTENT_POLICY=reject`. Still never defaulted — omitting it leaves the field off the wire and a reject-policy core fails closed.
20
+
- Both clients' `initialize()` is now concurrency-safe and idempotent: concurrent callers share a single initialization run (the first caller's registry wins), and the completed outcome — success or failure — is captured in loop-independent state for `AsyncMemoryClient`.
21
+
- A failed `initialize()` is sticky: retrying re-raises the original error from any caller; resolve the cause and construct a new client rather than retrying on the same instance.
22
+
-`AsyncMemoryClient.initialize()` shields each waiter from cancellation so that one waiter's timeout or cancellation never cancels the shared run for other concurrent callers.
23
+
-`AsyncMemoryClient.close()` during a pending initialization cancels the shared run; staged providers are torn down by the service's atomic-initialize cleanup, any concurrent `initialize()` waiter receives `CancelledError`, and the client ends in the not-initialized state without recording a sticky error.
24
+
- Both `MemoryService` and `AsyncMemoryService` stage provider registrations atomically: factories and provider `initialize()` calls run against a local staging area, and the maps are replaced only after every provider succeeds; on any failure, already-staged providers are torn down best-effort before the original error re-raises.
25
+
-`MemoryService.close()` and `AsyncMemoryService.close()` are best-effort: every provider gets a chance to close regardless of earlier failures, maps are cleared in a `finally` block, and the first failure is re-raised after all providers have been given the chance to close.
26
+
27
+
### Fixed
28
+
-`atomicmemory.__version__` reported `1.0.0` while package metadata said `1.0.1`; all version sources now agree at `1.1.0`, guarded by a regression test that will fail if they drift again.
Copy file name to clipboardExpand all lines: README.md
+47-3Lines changed: 47 additions & 3 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -32,13 +32,13 @@ pip install 'atomicmemory[embeddings]' # + sentence-transformers for local
32
32
33
33
## Quick start
34
34
35
-
Prerequisite: start `atomicmemory-core` first. Follow the [Core Quickstart](https://docs.atomicstrata.ai/quickstart) if you do not already have a backend at `http://localhost:3050`.
35
+
Prerequisite: start `atomicmemory-core` first. Follow the [Core Quickstart](https://docs.atomicstrata.ai/quickstart) if you do not already have a backend at `http://localhost:17350`.
36
36
37
37
```python
38
38
from atomicmemory import AtomicMemoryClient
39
39
40
40
with AtomicMemoryClient({
41
-
"apiUrl": "http://localhost:3050",
41
+
"apiUrl": "http://localhost:17350",
42
42
"apiKey": "server-api-key",
43
43
"userId": "demo",
44
44
}) as client:
@@ -72,7 +72,7 @@ from atomicmemory import AsyncAtomicMemoryClient
72
72
73
73
asyncdefmain() -> None:
74
74
asyncwith AsyncAtomicMemoryClient({
75
-
"apiUrl": "http://localhost:3050",
75
+
"apiUrl": "http://localhost:17350",
76
76
"apiKey": "server-api-key",
77
77
"userId": "demo",
78
78
}) as client:
@@ -131,6 +131,50 @@ The `client.storage` namespace mirrors the TypeScript SDK's direct storage API:
131
131
132
132
Every storage request sends `Authorization: Bearer <apiKey>` and `X-AtomicMemory-User-Id`. The SDK never sends the legacy `?user_id=` URL parameter.
133
133
134
+
## v1 wire contract
135
+
136
+
`atomicmemory.contract.v1` is the wire codec for the v1 provider-contract encoding. The wire form is deliberately mixed-case — `Memory.createdAt`/`updatedAt` and `SearchResult.rankingScore` are camelCase; `version_id`, `observed_at`, and retrieval-receipt fields are snake_case — as pinned by the vendored `contract/CONTRACT.md`. This module is the only place that mapping lives; in-process models and provider mappers are unchanged.
137
+
138
+
```python
139
+
from atomicmemory.contract import v1
140
+
141
+
# decode a wire search response (e.g. from a cross-SDK provider call)
# re-encode to the exact v1 wire form (millisecond-precision UTC datetimes)
171
+
wire_out = v1.encode_search_result_page(page)
172
+
```
173
+
174
+
Two behaviors to know: naive datetimes passed to encode functions are assumed UTC (bare `astimezone()` would shift by the host's UTC offset); `encode_ingest_input` rejects models carrying `content_class` with a clear error because the v1 schemas have `additionalProperties: false` and no such field — this is a Python-ahead field pending TS contract alignment.
175
+
176
+
This is NOT the AtomicMemory core HTTP API. That boundary stays in the provider mappers. The import path is `atomicmemory.contract` — deliberately not re-exported from the package root to keep the root namespace focused on the core provider API.
0 commit comments