feat: an async client, and the lessons a turn was prepared with - #11
Merged
Conversation
Every agent framework worth integrating is async. A blocking client on an event loop either stalls it or grows a thread pool to hide the stall, so each integration ends up rewriting this transport rather than using one — our own CrewAI runtime carries a 209-line copy for exactly that reason. AsyncKhwan is the same loop over httpx, behind an optional extra so a sync user does not pay for it, and it says which install fixes the import when it is missing. It holds one pool; aclose() waits for background records first, because asyncio keeps only a weak reference to a task and a fire-and-forget write can otherwise be collected before it is sent — losing a turn silently, which is the one failure this mode must not have. The retry rules moved to module level rather than being copied. A rule that drifts between two clients is a bug nobody notices until a duplicate record lands, so there is one copy and both use it. Behaviour is unchanged; one test that reached for the old private method now asserts on the shared rule instead. Turn.lessons exposes what synthesis distilled, next to the raw sources. Both were already inside messages; a caller building its own context — a recall tool, a subagent brief — could not take the rules without replaying the whole prompt. Tests drive httpx through a MockTransport, so no network: the loop with scope headers, a background write actually landing, failure swallowed there and raised when blocking, and a 503 retry that yields to the loop rather than sleeping on it.
This repository is public and always has been. The PyPI page carried Homepage and Documentation and nothing else, so the page a developer actually lands on offered no route to the code — and a reviewer looking for exactly this, to check what a client that handles prompt data does with it, concluded the SDK was closed. Repository renders as "Source" in the sidebar; Issues gives the same reader somewhere to go.
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.
Groundwork for a CrewAI package, and useful on its own.
Why
Every agent framework worth integrating is async. A blocking client on an event loop either stalls it or grows a thread pool to hide the stall — so each integration rewrites this transport instead of using one. Our own CrewAI runtime carries a 209-line copy for exactly that reason, and its docstring says so outright.
Behind an optional extra —
pip install "khwan[async]"— so a sync user does not pay for httpx, and a missing import names the install that fixes it.One rule set, not two
The retry policy moved to module level rather than being copied into the new client. A retry rule that drifts between two clients is a bug nobody notices until a duplicate
recordlands in production. Behaviour is unchanged; one test that reached for the old private method now asserts on the shared rule.aclose()waitsasyncio keeps only a weak reference to a task, so a fire-and-forget
recordcan be collected before it is sent — losing a turn silently, which is the one failure that mode must not have. Background writes are held and awaited on close, whichasync withdoes for you.Turn.lessonsWhat synthesis distilled, next to the raw
sources. Both were already insidemessages, but a caller building its own context — a recall tool, a subagent brief — could not take the rules without replaying the whole prompt.Tests
httpx driven through a
MockTransport, so no network:The two existing suites still pass. Built and
twine checked; verified that async-only install (no httpx) imports fine and only fails — clearly — when
AsyncKhwanis actually constructed.Version → 0.3.0. Not published — that is yours to run.