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

Method handle_session_end

atomic-agent/src/turn/orchestrator/session_end.rs:20–186  ·  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 attestation is a graph-level audit node — it captures agent identity, timing, and which changes were recorded. Cost and token data are left at zero (they're not available from the hook) and can be enriched later via `atomic agent attest --enrich`

(
        &mut self,
        event: TurnEvent,
    )

Source from the content-addressed store, hash-verified

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

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