From c9ff37dcbfa0605820e79bb88bce82957233bd7c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nicol=C3=B2=20Boschi?= Date: Thu, 26 Mar 2026 15:21:43 +0100 Subject: [PATCH] fix(python-client): async=true silently ignored on retain (#709) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * docs(claude-code): tidy configuration reference and sync README Add missing settings (retainMode, retainToolCalls, retainTags, retainMetadata, embedPackagePath, llmApiKeyEnv, agentName, and several recall options) that existed in code but not in docs. Restructure config tables with prose introductions, clearer descriptions, and consistent layout across both files. * refactor(claude-code): remove recallTopK setting Unused client-side cap — Hindsight server already controls result count via recallBudget and recallMaxTokens. * fix(python-client): async=true was silently ignored on retain calls The hand-written client wrapper passed `async_=retain_async` to RetainRequest, but the generated Pydantic model uses `var_async` as the Python field name (with `alias="async"`). The `async_` kwarg didn't match either the field name or the alias, so Pydantic silently ignored it — every retain call ran synchronously regardless of the flag. This has been broken since the client was first introduced (6073ac4f), not a regression. Also adds unit tests that verify the async field serializes correctly in the request JSON, preventing future regressions. --- .../hindsight_client/hindsight_client.py | 4 +- .../python/tests/test_retain_request_async.py | 42 +++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) create mode 100644 hindsight-clients/python/tests/test_retain_request_async.py diff --git a/hindsight-clients/python/hindsight_client/hindsight_client.py b/hindsight-clients/python/hindsight_client/hindsight_client.py index b4e4441f..c88768a9 100644 --- a/hindsight-clients/python/hindsight_client/hindsight_client.py +++ b/hindsight-clients/python/hindsight_client/hindsight_client.py @@ -204,7 +204,7 @@ class Hindsight: request_obj = retain_request.RetainRequest( items=memory_items, - async_=retain_async, + var_async=retain_async, document_tags=document_tags, ) @@ -618,7 +618,7 @@ class Hindsight: request_obj = retain_request.RetainRequest( items=memory_items, - async_=retain_async, + var_async=retain_async, document_tags=document_tags, ) diff --git a/hindsight-clients/python/tests/test_retain_request_async.py b/hindsight-clients/python/tests/test_retain_request_async.py new file mode 100644 index 00000000..afe53c38 --- /dev/null +++ b/hindsight-clients/python/tests/test_retain_request_async.py @@ -0,0 +1,42 @@ +""" +Test that RetainRequest correctly serializes the async field. + +Regression test for a bug where the client passed async_=True (invalid kwarg) +instead of var_async=True, causing async mode to be silently ignored. +""" + +from hindsight_client_api.models.memory_item import MemoryItem +from hindsight_client_api.models.retain_request import RetainRequest + + +def _make_item(): + return MemoryItem(content="test content") + + +def test_retain_request_async_true_serialized(): + """var_async=True must appear as 'async': True in the serialized dict.""" + req = RetainRequest(items=[_make_item()], var_async=True) + d = req.to_dict() + assert d["async"] is True + + +def test_retain_request_async_false_serialized(): + """var_async=False (default) must appear as 'async': False.""" + req = RetainRequest(items=[_make_item()], var_async=False) + d = req.to_dict() + assert d["async"] is False + + +def test_retain_request_default_is_sync(): + """Omitting var_async should default to synchronous (async=False).""" + req = RetainRequest(items=[_make_item()]) + d = req.to_dict() + assert d["async"] is False + + +def test_retain_request_async_json_roundtrip(): + """async=True must survive a JSON serialization roundtrip.""" + req = RetainRequest(items=[_make_item()], var_async=True) + json_str = req.to_json() + restored = RetainRequest.from_json(json_str) + assert restored.var_async is True