fix(kernel-hooks): use OR semantics for ephemeral in merge_inject_context_results - #98
Merged
Brian Krabach (bkrabach) merged 1 commit intoAug 10, 2026
Conversation
…text_results merge_inject_context_results was taking ephemeral from the first result only (first-wins semantics) while correctly using OR semantics for append_to_last_tool_result. This caused a registration-order-dependent bug: if a non-ephemeral context-injecting hook was registered before an ephemeral one, the merged result would silently lose the ephemeral flag for the ENTIRE combined injection, not just the non-ephemeral hook's own content. The combined context_injection string is the concatenation of every hook's content. If any hook marks its content ephemeral (regenerated per turn), the resulting merged string is regenerated per turn byte-for-byte identical only when ALL contributors are stable. Therefore, ephemeral must be the logical OR of every contributing result, matching append_to_last_tool_result semantics. Consequence: downstream consumers that trust Message.metadata["ephemeral"] (e.g. the Anthropic provider's conversation-region prompt-cache breakpoint placement) could silently lose the ephemeral signal for the ENTIRE combined injection due to a hook registration ordering accident. Fix: change ephemeral merge from results[0].ephemeral to results.iter().any(|r| r.ephemeral), with expanded doc comment explaining the OR semantics and downstream impact. Testing: - 31/31 cargo test -p amplifier-core --lib hooks:: passing - 3 new regression tests covering both orders and the all-stable case - A/B verified against real installed _engine.abi3.so: ephemeral changes from False -> True under adverse hook registration order This is a standalone kernel bug fix, independent of the prompt-caching provider-anthropic warning investigation. Both changes fix real bugs but are logically separate. Generated with [Amplifier](https://github.com/microsoft/amplifier) Co-Authored-By: Amplifier <240397093+microsoft-amplifier@users.noreply.github.com>
Brian Krabach (bkrabach)
deleted the
fix/hook-ephemeral-merge-or-semantics
branch
August 10, 2026 02:26
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.
Summary
When multiple hooks respond to the same event with
action=inject_context, the kernel'sHookRegistry::emit()collects all results and merges them viamerge_inject_context_results. This merge correctly uses OR semantics forappend_to_last_tool_result(any hook can request append behavior) — but was using "first-wins" semantics forephemeralinstead.The bug
A non-ephemeral context-injecting hook registered before an ephemeral one could cause the merged result to lose the
ephemeralflag for the ENTIRE combined injection. Registration-order luck, not design.Root cause:
crates/amplifier-core/src/hooks.rs, line ~427:ephemeral: first.ephemeralinstead of OR.Impact: Downstream consumers trusting
Message.metadata["ephemeral"](e.g. the Anthropic provider's conversation-region prompt-cache breakpoint placement) could silently lose the ephemeral signal for the entire combined injection.The fix
Changed to
results.iter().any(|r| r.ephemeral)— matching the OR semantics already used forappend_to_last_tool_result. If any contributing hook marks its content ephemeral, the merged result is ephemeral. Expanded doc comments explain why.Why this ordering matters
The
context_injectionstring is the concatenation of every hook's content. If any hook marks its content ephemeral (regenerated per turn), the resulting merged string is regenerated per turn too — byte-for-byte identical only when ALL contributors are stable. Therefore,ephemeralmust be the logical OR of all results.Testing
cargo test -p amplifier-core --lib hooks::passing_engine.abi3.so:ephemeralchanges from False → True under adverse hook registration orderNote: Independent from prompt-caching warning
This is a standalone kernel bug fix, discovered while investigating the prompt-caching provider warning but not the cause of that warning. Both changes fix real bugs but are logically separate.
Generated with Amplifier