MCPcopy Create free account
hub / github.com/atomicdotdev/atomic / handle_session_end

Method handle_session_end

atomic-agent/src/turn/orchestrator/session_end.rs:24–221  ·  view source on GitHub ↗

Handle a SessionEnd event. Transitions the session to Ended, saves it, and creates an attestation covering all changes recorded during the session. The working copy stays on the session's agent view so the user lands where the work happened and can review it before inserting it into a shared view. The attestation is a graph-level audit node — it captures agent identity, timing, and which change

(
        &mut self,
        event: TurnEvent,
    )

Source from the content-addressed store, hash-verified

22 /// data are left at zero (they're not available from the hook) and
23 /// can be enriched later via `atomic agent attest --enrich`.
24 pub(super) async fn handle_session_end(
25 &mut self,
26 event: TurnEvent,
27 ) -> AgentResult<DispatchResult> {
28 let session_id = &event.session_id;
29
30 let mut session = match self.session_store.load(session_id)? {
31 Some(s) => s,
32 None => {
33 // Session not found — nothing to end
34 log::info!("SessionEnd for unknown session {} — ignoring", session_id);
35 return Ok(DispatchResult::new(session_id, Phase::Ended));
36 }
37 };
38
39 let had_active_turn = session.is_turn_active();
40 let journal_turn = if had_active_turn {
41 session.turn_count.saturating_add(1)
42 } else {
43 session.turn_count.max(1)
44 };
45 if had_active_turn {
46 self.commit_hook_event(&event, journal_turn)?;
47 }
48
49 // If a turn is still active, cancel the watcher
50 if had_active_turn {
51 if let Err(e) = self.watcher.cancel_turn().await {
52 log::warn!(
53 "Failed to cancel turn watcher for session {}: {}",
54 session_id,
55 e
56 );
57 }
58 }
59
60 // Flush any unrecorded turn BEFORE finalizing. Headless agents such as
61 // Cursor's CLI fire sessionStart/postToolUse/sessionEnd but no per-turn
62 // `stop` (TurnEnd), so the turn's file changes are still uncommitted at
63 // session end. Record them now. Idempotent: agents that already
64 // recorded each turn on `stop` leave a clean working copy here, so
65 // `record_turn` returns `EmptyTurn` and this is a no-op for them.
66 if !session.explicit_record_files || had_active_turn {
67 // Ensure the working copy is on the session's agent view before
68 // recording. session-start aligns to it, but that can drift back to
69 // the parent view by session end (observed with Cursor's CLI), which
70 // makes `record_turn` see a view mismatch and record nothing. Align
71 // explicitly so the agent's uncommitted files are recorded on the
72 // agent view. Best-effort — non-fatal if it can't switch.
73 // Doubles as the "is there a worktree to protect?" answer for the
74 // failed-flush guard below, which is why it is captured rather
75 // than discarded. See there.
76 let has_worktree = match atomic_repository::Repository::open_existing(&self.repo_root) {
77 Ok(mut repo) => {
78 if repo.current_view() != session.view_name {
79 if let Err(e) = repo.align_to_view(&session.view_name) {
80 log::warn!(
81 "SessionEnd: could not align to agent view '{}': {} (non-fatal)",

Callers 1

dispatchMethod · 0.80

Calls 15

record_turnFunction · 0.85
transitionFunction · 0.85
apply_common_actionsFunction · 0.85
is_turn_activeMethod · 0.80
commit_hook_eventMethod · 0.80
cancel_turnMethod · 0.80
current_viewMethod · 0.80
align_to_viewMethod · 0.80
recorded_file_listMethod · 0.80
add_files_touchedMethod · 0.80

Tested by

no test coverage detected