Skip to content

fix(python-sdk): recall memories in sync LangChain calls inside a running loop - #653

Merged
HoangDucBach merged 1 commit into
devfrom
fix/langchain-sync-recall-skip
Aug 18, 2026
Merged

fix(python-sdk): recall memories in sync LangChain calls inside a running loop#653
HoangDucBach merged 1 commit into
devfrom
fix/langchain-sync-recall-skip

Conversation

@HoangDucBach

@HoangDucBach HoangDucBach commented Aug 15, 2026

Copy link
Copy Markdown
Collaborator

Ticket

Fixes #607

Stacked on #606: _run_memwal() here calls _with_fresh_http_client(), which that PR introduces. Base is fix/sync-http-client-lifecycle so the diff below stays scoped to this change alone. Merge #606 first; GitHub then retargets this to dev automatically.

What changed?

  • patched_generate() now injects through the module's existing _run_blocking() helper, which runs the coroutine on a worker thread with its own loop.
  • Added a local _run_memwal() mirroring the sync OpenAI wrapper, so both share one idiom for running memory work from sync code.

Why is this needed?

The sync _generate() wrapper appended the untouched message list whenever an event loop was already running — the normal case in notebooks and async application hosts — and neither raised, warned, nor documented the skip. The LLM call still succeeded, so callers got an ordinary answer with no memory context and no signal that Walrus Memory had been bypassed.

_run_blocking() already solves this for the sync OpenAI wrapper; this brings LangChain to the same behaviour rather than inventing a new mechanism.

Scope

Memory injection on the sync LangChain path (_generate). patched_agenerate already awaits injection on the caller's loop and is untouched.

Out of scope

httpx client lifecycle leak (#606), the PR this is based on.

How was this tested?

  • Unit tests
  • Integration tests
  • End-to-end tests
  • Manual testing
  • Not applicable

2 new tests in TestWithMemWalLangChain: the plain sync path, and the regression case inside a running loop — an async test body is a running loop, the exact condition that disabled injection. Verified to fail against the pre-fix wrapper. Suite: 121 passed, ruff clean.

How can the reviewer verify it?

cd packages/python-sdk-memwal
python3 -m pytest tests/ -q -m "not integration"        # 121 passed
python3 -m ruff check memwal/ tests/test_middleware.py  # clean

To watch the test catch the bug, restore the old branch in patched_generate() (if loop is not None and loop.is_running(): enriched.append(msg_list)) and rerun with -k sync_generate → the running-loop test fails, the plain sync one still passes.

Risks and dependencies

Author checklist

  • This pull request maps to one ticket and one logical outcome.
  • I reviewed the complete diff myself.
  • I removed unrelated, debug, and temporary changes.
  • I ran the relevant tests.
  • CI is green.
  • The branch is up to date with its target branch.
  • I added or updated tests where appropriate.
  • I documented any important risk, dependency, rollout, or follow-up.
  • I provided clear verification steps.
  • The pull request is ready for review and is no longer a Draft.

@HoangDucBach
HoangDucBach force-pushed the fix/langchain-sync-recall-skip branch from f144009 to 2327dab Compare August 15, 2026 02:39
@HoangDucBach
HoangDucBach changed the base branch from fix/sync-http-client-lifecycle to dev August 15, 2026 10:32
@HoangDucBach
HoangDucBach changed the base branch from dev to fix/sync-http-client-lifecycle August 17, 2026 02:25

@ducnmm ducnmm left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review

Verdict: ✅ Approve

Clean fix for a real, sneaky bug: patched_generate silently skipped recall inside running loops, making Walrus Memory appear connected while never actually injecting context.

  • Reuses _run_blocking + _with_fresh_http_client instead of inventing a new mechanism — right call
  • _run_memwal local function keeps the pattern compact
  • Tests cover both plain sync path and the running-loop regression case
  • Callers will now see recall latency they were silently skipping before — documented in Risks ✅
  • No blocking issues. Ready to merge after #652.

@HoangDucBach
HoangDucBach force-pushed the fix/langchain-sync-recall-skip branch from 2327dab to 43391b8 Compare August 18, 2026 08:35
Base automatically changed from fix/sync-http-client-lifecycle to dev August 18, 2026 08:38
…ning loop

with_memwal_langchain() advertises recall-before-call, but the sync _generate()
wrapper appended the untouched message list whenever an event loop was already
running — the normal case in notebooks and async application hosts. The LLM
call still succeeded, so callers got a plain answer with no memory context and
no warning: Walrus Memory looked connected while recall never ran.

Route injection through the module's existing _run_blocking() helper, which
moves the coroutine onto a worker thread, so the sync LangChain wrapper now
behaves like the sync OpenAI one instead of silently degrading.

Fixes #607
@HoangDucBach
HoangDucBach force-pushed the fix/langchain-sync-recall-skip branch from 43391b8 to 8181429 Compare August 18, 2026 08:38
@HoangDucBach
HoangDucBach merged commit a28d5bc into dev Aug 18, 2026
16 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Python SDK/Bug] LangChain sync wrapper skips memory recall inside running event loops

2 participants