Skip to content

fix(session): emit session:start once per session, not per execute() — v1.6.0 - #84

Merged
Brian Krabach (bkrabach) merged 1 commit into
mainfrom
fix/session-start-per-session-not-per-execute
May 18, 2026
Merged

fix(session): emit session:start once per session, not per execute() — v1.6.0#84
Brian Krabach (bkrabach) merged 1 commit into
mainfrom
fix/session-start-per-session-not-per-execute

Conversation

@bkrabach

Copy link
Copy Markdown
Collaborator

Root Cause

Rust port on 2026-02-14 (commit d2826b4) added session:start emission inside Session::execute(). Every interactive prompt re-fired the event. Hook authors (hooks-routing, hook-context-intelligence, others) registered handlers expecting once-per-session semantics — those handlers were re-running expensive work (HTTP calls to list_models, provider enumeration, agent iteration) on every turn, contributing ~7–13s wall-clock cost per interactive prompt.

Evidence

  • Sessions 11622fa7 (v1.1.0), 913b256a (v1.5.3) both showed 3× session:start in 3-turn interactive sessions
  • Pre-Rust Python amplifier_core/session.py from commit f6d8ed6 (2025-10-08) had NO hooks.emit in execute() — confirming this was a regression introduced during the Rust port
  • Commit d2826b4 message explicitly stated lifecycle events would be in the kernel, but the port embedded them in execute() rather than initialize(), breaking the once-per-session invariant

Fix

Added lifecycle_event_emitted: AtomicBool field on Session; new claim_lifecycle_event() method atomically returns true once per session lifetime. All three emission sites (Rust kernel execute(), PyO3 binding, pure-Python session) guarded by this claim.

Tests

  • 2 new Rust tests (session_start_emitted_once_across_multiple_execute_calls, session_resume_emitted_once_across_multiple_execute_calls)
  • 2 existing Rust tests strengthened from "at least 1" to "exactly 1"
  • 3 new Python tests in test_session_start_dedup.py::TestSessionStartOncePerSession (will turn green after build/install)
  • Full Rust test suite: 469 tests pass, 0 failures

Behavior Change

session:start and session:resume now fire exactly once per session lifetime, not per execute() call. This restores the pre-Rust contract. session:end continues to fire once at cleanup (unchanged). execute() becomes pure orchestrator delegation.

Performance Impact

Removes ~7–13 seconds of per-turn wall-clock cost in interactive sessions where bundle includes routing-matrix or context-intelligence hooks.

Restores pre-Rust contract: session lifecycle events fire at session
initialization, not on every Session::execute() call.

The Rust port (d2826b4, 2026-02-14) introduced session:start emission
inside execute(), causing every interactive prompt to re-fire the event.
Hook authors (hooks-routing, hook-context-intelligence, others) had
registered handlers expecting once-per-session semantics — those
handlers were re-running expensive work (HTTP calls to list_models,
provider enumeration) on every turn, contributing ~7-13s wall-clock
cost per interactive prompt.

Fix: add lifecycle_event_emitted AtomicBool guard to Session struct.
claim_lifecycle_event() returns true exactly once per session lifetime;
all three emission sites are guarded by this claim.

  - crates/amplifier-core/src/session.rs: add lifecycle_event_emitted
    field + claim_lifecycle_event() method; guard emit in execute().
  - bindings/python/src/session.rs: guard emit in PySession::execute()
    via claim_lifecycle_event() on the inner Session.
  - python/amplifier_core/session.py: guard emit in AmplifierSession
    execute() via _lifecycle_event_emitted bool flag.

- session:start fires once when fresh session first executes
- session:resume fires once when resumed session first executes
- session:end continues to fire once at cleanup (unchanged)
- execute() becomes pure orchestrator delegation after first turn

Tests:
  - NEW: session_start_emitted_once_across_multiple_execute_calls
    (3 execute() calls → exactly 1 session:start; was failing with 3)
  - NEW: session_resume_emitted_once_across_multiple_execute_calls
  - UPDATED: execute_emits_session_start_event now calls execute() twice
    and asserts exactly 1 event (not just "at least 1")
  - UPDATED: execute_emits_session_resume_for_resumed_session same
  - NEW Python: TestSessionStartOncePerSession — 3 multi-turn tests
    documenting once-per-session contract for the Python binding

Rust test suite: 469 tests, 0 failures.
Python multi-turn tests are RED against installed v1.2.6 (expected —
they verify the contract this fix delivers; GREEN after build/install).

Version bumped 1.5.3 → 1.6.0.

Co-authored-by: Amplifier <noreply@anthropic.com>
@bkrabach
Brian Krabach (bkrabach) deleted the fix/session-start-per-session-not-per-execute branch May 18, 2026 15:53
Brian Krabach (bkrabach) added a commit that referenced this pull request May 19, 2026
Two assert_eq! macros in mod tests had multi-line arg formatting that
rustfmt --check rejected. PR #84 landed despite the failing format check
because admin-merge bypassed CI. This restores green Rust Core CI.

Both diffs collapse:
    assert_eq!(
-       start_count,
-       1,
+       start_count, 1,
        "…"
    );

No functional change.

Co-authored-by: Amplifier <amplifier@microsoft.com>
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.

1 participant