feat(prompt): introduce PromptExecutorEvent and HookablePromptExecutor#1970
Open
feat(prompt): introduce PromptExecutorEvent and HookablePromptExecutor#1970
Conversation
# Conflicts: # agents/agents-core/src/jvmCommonMain/kotlin/ai/koog/agents/core/agent/feature/pipeline/AIAgentPipeline.kt # agents/agents-features/agents-features-event-handler/src/jvmCommonMain/kotlin/ai/koog/agents/features/eventHandler/feature/EventHandlerConfig.kt # agents/agents-features/agents-features-longterm-memory/src/commonMain/kotlin/ai/koog/agents/longtermmemory/feature/LongTermMemory.kt
…M call dispatching
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.
Add
PromptExecutorEvents and a hookable executor APISummary
This PR introduces a lifecycle-event surface on
PromptExecutorso callers (and the agent pipeline) can observe what happens inside an executor — not just at the agent level. It adds:PromptExecutorEventhierarchy covering all four executor entry points (execute,executeStreaming,executeMultipleChoices,moderate), each withRequested,Dispatched,Completed,Failed(andFrameReceivedfor streaming) variants.HookablePromptExecutorabstract base class. Each call accepts aPromptExecutionContextcarrying a stablepromptExecutionIdand a per-callPromptExecutorHook. Implementations emit events through the hook at the appropriate execution points.LLMCallDispatchedandLLMStreamingDispatched— emitted after the executor has resolved the effective model/prompt/tools but before the underlying call goes out. The existingLLMCallStarting/LLMStreamingStartingevents still fire at the original location.EventHandlerconfig gainsonLLMCallDispatchedandonLLMStreamingDispatched(Kotlin and Java APIs).Why
PromptExecutorimplementations are dynamic by nature: the initial execution intent might not reflect what will finally be executed onLLMClientlevel.MultiLLMPromptExecutorfalls back to a different provider/model when the requested one isn't configured,RoutingLLMPromptExecutorpicks a client based on the model, and we expect more dynamic routing in the future (user-defined predicates, cost- or latency-based model selection, etc.).Without
*Dispatchedevents, observers only see the requested prompt/model/tools and have no way to know what was actually executed — which makes tracing, evaluation, and cost accounting unreliable. The new events expose the effective execution parameters at the moment they're dispatched, whileRequestedkeeps the caller-visible view.Pipeline integration: hookable vs. plain executors
The pipeline now dynamically picks how to bridge a user-supplied executor based on its type:
HookablePromptExecutor, it's wrapped inContextualPromptExecutor, which subscribes to the executor's per-call hook and forwards everyPromptExecutorEvent(including*Dispatched/*FrameReceived) into the pipeline. This is the full-fidelity integration.PromptExecutor, it's wrapped inLegacyContextualPromptExecutor— whatContextualPromptExecutorused to be before this PR. It intercepts calls from the outside and emits*Starting/*Completed/*Failedevents, but*Dispatchedand streaming*FrameReceivedare not emitted because there is no way to observe the executor's internal dispatch boundary from outside.Important
Custom
PromptExecutorimplementations continue to work unchanged, but they will silently miss the*Dispatchedand per-frame events. To get full pipeline integration, migrate them to extendHookablePromptExecutorand emit events throughPromptExecutionContext.handle(...)at the appropriate points — see the built-in executors as reference.Migration / compatibility
This change is purely additive — no breaking changes for existing callers.
SingleLLMPromptExecutor,MultiLLMPromptExecutor,RoutingLLMPromptExecutor) are migrated to extendHookablePromptExecutor; their existingPromptExecutoroverrides delegate to the context-taking versions with a defaultPromptExecutionContext.MockPromptExecutorand the testing DSL extendHookablePromptExecutorso tests get the same events.Tests
PromptExecutorEventTestcovers event ordering andpromptExecutionIdcorrelation across the sealed hierarchy.HookablePromptExecutorTestexercises the hook contract for all four operations.MultiLLMPromptExecutorTest,RoutingLLMPromptExecutorTest,SingleLLMPromptExecutorTestadd event-emission cases — including the fallback path, which is exactly where requested ≠ dispatched.EventHandlerTestcovers the newonLLMCallDispatched/onLLMStreamingDispatchedhandlers.