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–199  ·  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 // If a turn is still active, cancel the watcher
40 if session.is_turn_active() {
41 if let Err(e) = self.watcher.cancel_turn().await {
42 log::warn!(
43 "Failed to cancel turn watcher for session {}: {}",
44 session_id,
45 e
46 );
47 }
48 }
49
50 // Flush any unrecorded turn BEFORE finalizing. Headless agents such as
51 // Cursor's CLI fire sessionStart/postToolUse/sessionEnd but no per-turn
52 // `stop` (TurnEnd), so the turn's file changes are still uncommitted at
53 // session end. Record them now. Idempotent: agents that already
54 // recorded each turn on `stop` leave a clean working copy here, so
55 // `record_turn` returns `EmptyTurn` and this is a no-op for them.
56 {
57 // Ensure the working copy is on the session's agent view before
58 // recording. session-start aligns to it, but that can drift back to
59 // the parent view by session end (observed with Cursor's CLI), which
60 // makes `record_turn` see a view mismatch and record nothing. Align
61 // explicitly so the agent's uncommitted files are recorded on the
62 // agent view. Best-effort — non-fatal if it can't switch.
63 // Doubles as the "is there a worktree to protect?" answer for the
64 // failed-flush guard below, which is why it is captured rather
65 // than discarded. See there.
66 let has_worktree = match atomic_repository::Repository::open_existing(&self.repo_root) {
67 Ok(mut repo) => {
68 if repo.current_view() != session.view_name {
69 if let Err(e) = repo.align_to_view(&session.view_name) {
70 log::warn!(
71 "SessionEnd: could not align to agent view '{}': {} (non-fatal)",
72 session.view_name,
73 e,
74 );
75 } else {
76 log::info!(
77 "SessionEnd: aligned working copy to agent view '{}' before flush",
78 session.view_name,
79 );
80 }
81 }

Callers 1

dispatchMethod · 0.80

Calls 15

record_turnFunction · 0.85
transitionFunction · 0.85
apply_common_actionsFunction · 0.85
is_turn_activeMethod · 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
clear_current_promptMethod · 0.80

Tested by

no test coverage detected