Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

Commit

Ending a session by hand is a soft close: like one that timed out, it comes back the moment its harness is heard from again. An update or a recorded entry now brings it back, where before both were refused with 'This session has ended. Start a new one.' A start already did. So a harness that was still running picks up where it was, and one that is truly gone stays ended. A session its harness ended itself still stays ended. g1t_contracts::work::revives(end_reason) is the one rule, for timed_out and 'ended by hand by @…', and update, record and subagents' late hooks use it. The End session dialog and the sessions guide say so. Checked: cargo test -p g1t-contracts (322, new: what comes back when heard from), -p g1t-work (214), cargo check -p g1t-work (wasm32).

G1t-Session:
ses_01m5075p3weck8yhgj6kdx5nqb
syntaqxcommitted Parentd41ecaaBrowse files
4 files+26−50/4 viewed
+5−1
161161 session can end it, and so can anyone with write access to the
162162 repository; nobody else sees the action. It ends with `end_reason`
163163 `ended by hand by @name`, its subagents still going end with it, and its
164−claims stay until they are released or expire. Through the API,
164+claims stay until they are released or expire. Ending by hand is a
165+soft close: like a session that timed out, it comes back the moment
166+its harness is heard from again (an update, a recorded entry or a
167+start), so a harness that was in fact still running picks up where it
168+was. Through the API,
165169 `end_session` on someone else's session does the same, without their
166170 `reason` or `result`. A session g1t runs on the workspace's Anthropic
167171 account is stopped from its page instead.
+1−1
224224 <AlertDialogDescription>
225225 For a session its harness left behind: killed, or never told to end. It shows as ended by hand by you
226226 {subagents > 0 ? `, and its ${subagents === 1 ? "subagent" : "subagents"} still going end with it` : ""}. Its claims stay until they are
227− released or expire. If its harness is in fact still running, it may show again when it is next heard from.
227+ released or expire. If its harness is still running, the session comes back the next time it is heard from.
228228 </AlertDialogDescription>
229229 </AlertDialogHeader>
230230 {error && (
+17−0
739739 format!("{ENDED_BY_HAND}{username}")
740740 }
741741
742+/// Whether a session that ended for `end_reason` comes back when its
743+/// harness is heard from again (an update, a record, a start): one that
744+/// timed out, or one a person ended by hand, which is a soft close for a
745+/// session left behind. One its harness ended itself stays ended.
746+pub fn revives(end_reason: Option<&str>) -> bool {
747+ end_reason.is_some_and(|reason| reason == "timed_out" || reason.starts_with(ENDED_BY_HAND))
748+}
749+
742750 /// Whether `actor` may end a session `owner` runs: their own, or anyone's
743751 /// when they have write access (`can_push`) to the repository.
744752 pub fn may_end_session(owner: &str, actor: &str, can_push: bool) -> bool {
12911299 }
12921300
12931301 #[test]
1302+ fn a_session_ended_by_hand_or_timed_out_comes_back_when_heard_from() {
1303+ assert!(revives(Some("timed_out")));
1304+ assert!(revives(Some(&ended_by_hand("bo"))));
1305+ assert!(!revives(Some("done")));
1306+ assert!(!revives(Some("ended")));
1307+ assert!(!revives(None));
1308+ }
1309+
1310+ #[test]
12941311 fn a_session_goes_idle_then_ends_without_heartbeats() {
12951312 let start = 1_000_000;
12961313 assert_eq!(SessionState::at(false, start, start), SessionState::Active);
+3−3
209209
210210 /// Whether a session is a subagent that ended on purpose: it stays ended.
211211 fn finished_subagent(row: &SessionRow) -> bool {
212− row.parent_id.is_some() && row.state == "ended" && row.end_reason.as_deref() != Some("timed_out")
212+ row.parent_id.is_some() && row.state == "ended" && !revives(row.end_reason.as_deref())
213213 }
214214
215215 impl Work {
526526 pub(crate) async fn update_session(&self, a: UpdateSessionArgs) -> Result<Outcome<Session>> {
527527 let repo = unwrap_or_fail!(self.participating_repo(&a.repo, &a.actor).await?);
528528 let row = unwrap_or_fail!(self.own_session(&repo, &a.actor, &a.id).await?);
529− if row.state == "ended" && row.end_reason.as_deref() != Some("timed_out") && !finished_subagent(&row) {
529+ if row.state == "ended" && !revives(row.end_reason.as_deref()) && !finished_subagent(&row) {
530530 return Ok(Outcome::fail(FailureCode::Conflict, "This session has ended. Start a new one."));
531531 }
532532 let (issue, pull) = unwrap_or_fail!(self.session_subjects(&repo, a.issue, a.pull).await?);
891891 }
892892 let repo = unwrap_or_fail!(self.participating_repo(&a.repo, &a.actor).await?);
893893 let row = unwrap_or_fail!(self.own_session(&repo, &a.actor, &a.id).await?);
894− if row.state == "ended" && row.end_reason.as_deref() != Some("timed_out") {
894+ if row.state == "ended" && !revives(row.end_reason.as_deref()) {
895895 return Ok(Outcome::fail(FailureCode::Conflict, "This session has ended. Start a new one."));
896896 }
897897 // Recording is a heartbeat too.

Comments

Sign in to comment.