Parent
#27
What to build
Make the handler dispatch path async and support both sync and async handler functions.
Dispatch: KnowledgeInteractionContext.dispatch() becomes async def. It awaits the now-async resolve_dependencies() call. For the handler invocation itself, detect whether the handler is a coroutine function via asyncio.iscoroutinefunction(): async handlers are awaited directly; sync handlers are run via asyncio.to_thread() so they do not block the event loop.
Handler type: Update the Handler type alias to accept both sync and async callables. Both should pass type checking.
Decorator wrapper: The wrapper function created inside _register_ki_decorator must detect and preserve the async-ness of the original handler. If the user decorates an async def, the wrapper should also be async def (or at minimum, the dispatch path must correctly detect the original function as async).
No changes to decorators themselves: @kb.answer_ki() and @kb.react_ki() remain synchronous — they only store the handler in the registry.
Acceptance criteria
Blocked by
Parent
#27
What to build
Make the handler dispatch path async and support both sync and async handler functions.
Dispatch:
KnowledgeInteractionContext.dispatch()becomesasync def. It awaits the now-asyncresolve_dependencies()call. For the handler invocation itself, detect whether the handler is a coroutine function viaasyncio.iscoroutinefunction(): async handlers are awaited directly; sync handlers are run viaasyncio.to_thread()so they do not block the event loop.Handler type: Update the
Handlertype alias to accept both sync and async callables. Both should pass type checking.Decorator wrapper: The wrapper function created inside
_register_ki_decoratormust detect and preserve the async-ness of the original handler. If the user decorates anasync def, the wrapper should also beasync def(or at minimum, the dispatch path must correctly detect the original function as async).No changes to decorators themselves:
@kb.answer_ki()and@kb.react_ki()remain synchronous — they only store the handler in the registry.Acceptance criteria
KnowledgeInteractionContext.dispatch()isasync defasync def) are awaited directly during dispatchdef) are run viaasyncio.to_thread()during dispatchHandlertype alias accepts both sync and async callablesanswer_ki,react_ki) remain synchronous (registration only)uv run ruff check .passesBlocked by