feat(async): AsyncMemory facade + 3-tier cancellation contract (M3.2 PR-A)#3
Merged
Conversation
…PR-A)
PR-A delivers the async/await-native mirror of openmem.Memory:
* AsyncMemory facade — zero-I/O constructor, lazy backend init,
async context manager, cross-loop guard, idempotent close.
* AsyncPostgresAdapter on asyncpg + pgvector (native cancel; SQL
placeholders translated %s -> $1 from shared _postgres_sql so
sync/async share one source of truth).
* AsyncPassthroughAdapter on httpx.AsyncClient (native cancel).
* AsyncThreadwrapAdapter wrapping mem0 / supermemory / letta sync
adapters via a per-instance ThreadPoolExecutor (best-effort
cancel; orphan completion logged at DEBUG).
Cancellation contract C-CAN-1..5:
- native awaiter receives CancelledError <= 50 ms
- native pool/socket released <= 500 ms post-cancel
- postgres server-side query absent from pg_stat_activity <= 1 s
- threadwrap awaiter cancels immediately; worker keeps running
- subsequent verbs on the same AsyncMemory succeed (no pool
corruption)
Packaging:
- new [async] install extra (asyncpg>=0.29, httpx>=0.27)
- `from openmem import AsyncMemory` raises a clear ImportError
containing `pip install 'openmem[async]'` when the extra is
missing (FR-026 / C-EXT-1..3); bare openmem import triggers no
async dependency resolution.
Sync compatibility:
- openmem.Memory is byte-identical (signature snapshot committed
at sdk-python/tests/async/_signatures_baseline.json; SC-008 /
FR-011)
- 365 sync tests still pass with no removals
- Bare-install packaging guard runs in a throwaway venv
Results: 419 passed, 26 skipped, coverage 87.44% (>= 85% gate).
Version bump 0.1.0 -> 0.4.0.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
PR-A of M3.2 (
005-async-fastapi). Ships an async/await-native mirror ofopenmem.Memorywith zero changes to the sync surface. PR-B (the FastAPIserver) will follow once this lands.
What's new
openmem.AsyncMemory— async facadefirst verb call (data-model AM-INV-3 / C-LIFE-1).
async with AsyncMemory(...) as mem:context manager + idempotentclose().call; awaiting a verb on a different loop raises
RuntimeErrorbefore any backend call (C-LOOP-1).
add()validatesuser_idcentrally so the threadwrap tier can'tsmuggle empty/whitespace ids past
require_user_id(C-ERR-3).Three async adapters
postgresasyncpg+ pgvector pool; SQL placeholders translated%s → $1from a new sharedopenmem.adapters._postgres_sqlmodule so sync + async use one source of truth. Lazy pool init guarded byasyncio.Lockto prevent fan-out races starvingmax_connections.passthroughhttpx.AsyncClient; identical timeout / retry / header conventions to syncpassthrough.py.mem0,supermemory,lettaThreadPoolExecutor; verbs dispatch viaexecutor.submit() + asyncio.wrap_future()so the orphan-completion DEBUG log fires after the worker thread actually finishes.Cancellation contract (
contracts/async-memory.md§3)CancelledError≤ 50 ms.postgrespg_stat_activity≤ 1 s.AsyncMemorysucceed (no pool corruption).Packaging
[async]install extra:asyncpg>=0.29,httpx>=0.27.from openmem import AsyncMemoryraises a clearImportErrorcontaining
pip install 'openmem[async]'when the extra is missing(FR-026 / C-EXT-1..3). The bare
openmemimport triggers noasync dependency resolution.
Sync compatibility (non-negotiable)
openmem.Memoryis byte-identical — every public signature, default,and behavior unchanged.
tests/async/test_memory_signatures.pycommits aJSON snapshot at
tests/async/_signatures_baseline.json; any driftfails the gate (SC-008 / FR-011).
extras contract end-to-end (
tests/async/test_packaging_extras.py).no new failures).
Test results
get)OMP_LIVE=1)Version
0.1.0→0.4.0(sdk-python/pyproject.toml+openmem.__version__).Files of interest
sdk-python/openmem/async_memory.pysdk-python/openmem/adapters/async_{postgres,passthrough,threadwrap,base}.pysdk-python/openmem/adapters/_postgres_sql.py,_validation.pysdk-python/tests/async/(10 files, 52 tests + signature snapshot)specs/005-async-fastapi/README.md,sdk-python/README.md,CHANGELOG.mdOut of scope (PR-B)
Phase 6 FastAPI server (
omp serve, T034–T051) and PR-B polish(T057–T062). Tracked in specs/005-async-fastapi/tasks.md.
Checklist
OMP_LIVE=1(6 / 6)Memorysignature snapshot committed