Skip to content

fix(kernel-hooks): use OR semantics for ephemeral in merge_inject_context_results - #98

Merged
Brian Krabach (bkrabach) merged 1 commit into
mainfrom
fix/hook-ephemeral-merge-or-semantics
Aug 10, 2026
Merged

fix(kernel-hooks): use OR semantics for ephemeral in merge_inject_context_results#98
Brian Krabach (bkrabach) merged 1 commit into
mainfrom
fix/hook-ephemeral-merge-or-semantics

Conversation

@bkrabach

Copy link
Copy Markdown
Collaborator

Summary

When multiple hooks respond to the same event with action=inject_context, the kernel's HookRegistry::emit() collects all results and merges them via merge_inject_context_results. This merge correctly uses OR semantics for append_to_last_tool_result (any hook can request append behavior) — but was using "first-wins" semantics for ephemeral instead.

The bug

A non-ephemeral context-injecting hook registered before an ephemeral one could cause the merged result to lose the ephemeral flag for the ENTIRE combined injection. Registration-order luck, not design.

Root cause: crates/amplifier-core/src/hooks.rs, line ~427: ephemeral: first.ephemeral instead 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 for append_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_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 too — byte-for-byte identical only when ALL contributors are stable. Therefore, ephemeral must be the logical OR of all results.

Testing

  • 31/31 cargo test -p amplifier-core --lib hooks:: passing
  • 3 new regression tests:
    • ephemeral when first result is ephemeral ✓
    • ephemeral when second result is ephemeral (regression case) ✓
    • not ephemeral when none are ephemeral ✓
  • A/B verified against real installed _engine.abi3.so: ephemeral changes from False → True under adverse hook registration order

Note: 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

…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>
@bkrabach
Brian Krabach (bkrabach) merged commit df63eb8 into main Aug 10, 2026
6 checks passed
@bkrabach
Brian Krabach (bkrabach) deleted the fix/hook-ephemeral-merge-or-semantics branch August 10, 2026 02:26
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants