1010
1111semantic-score (+ l1 ablation):
1212 Create (suspended) → llm_wait locally (heartbeat, no slot) →
13- tool_loop: Resume (knock/queue) → run → keep slot (no proactive Suspend);
14- unlocked llm_wait may be stolen by outside knockers via CP scores → …
13+ tool_loop: Resume (knock/queue) → run → keep slot (never self-Suspend);
14+ unlocked llm_wait may be stolen by outside knockers via CP scores →
15+ only eviction archives; final cleanup deletes leftovers
16+
1517
1618
1719Compares policies via /metrics deltas (mid_tool_suspend, starvation wait, resume_wait, …)
@@ -770,9 +772,9 @@ def drive_phases_semantic(
770772 """semantic-score path: llm_wait needs no slot until a Worker is held;
771773 tool_loop knocks via Resume.
772774
773- After tool we do **not** Suspend. We stay on the Worker into later spans
774- (typically unlocked llm_wait) so outside knockers + CP keepScore can steal.
775- Session -end Suspend is only cleanup if still holding .
775+ Never self- Suspend: after tool we keep the Worker; unlocked llm_wait is
776+ how outside knockers steal (single archive on eviction). Leftovers are
777+ removed by policy -end cleanup_sandboxes .
776778 """
777779 if not spans :
778780 spans = [{"phase" : "idle" , "lock" : False , "t_start_ms" : 0 , "t_end_ms" : 500 }]
@@ -801,10 +803,9 @@ def drive_phases_semantic(
801803 )
802804 if dur > 0 :
803805 time .sleep (dur )
804- # Keep the slot; no proactive Suspend — waiters may steal on llm_wait.
805806 else :
806- # llm_wait / idle: if never took a slot, stay off-Worker.
807- # If still holding after tool, unlock + heartbeat; knockers may steal .
807+ # llm_wait / idle: heartbeat only. If holding a slot, stay unlocked
808+ # so knockers can preempt — do not Suspend ourselves .
808809 client .post_semantic (
809810 sandbox_id ,
810811 {
@@ -820,8 +821,18 @@ def drive_phases_semantic(
820821 st = _sandbox_state (client , sandbox_id )
821822 if st != "running" :
822823 on_worker = False
824+ # End of spans: still no Suspend. Leave unlocked so others can steal, or
825+ # stay running until run_policy cleanup deletes the sandbox.
823826 if on_worker :
824- _try_suspend (client , sandbox_id , sandbox_locks , locks_mu )
827+ client .post_semantic (
828+ sandbox_id ,
829+ {
830+ "version" : "v1" ,
831+ "phase" : "idle" ,
832+ "lock" : False ,
833+ "workflowID" : workflow_id ,
834+ },
835+ )
825836 return resume_sec
826837
827838
@@ -900,6 +911,8 @@ def run_one_session(
900911 l3 ,
901912 )
902913 job .resumed = True
914+ # Eviction may have Suspended us; never self-Suspend.
915+ job .suspended = _sandbox_state (client , job .sandbox_id ) == "suspended"
903916 else :
904917 log (f"[session { index + 1 } /{ total } ] resume { job .sandbox_id [:8 ]} …" )
905918 job .resume_sec = l3 .resume_and_observe (client , job .sandbox_id )
@@ -914,13 +927,17 @@ def run_one_session(
914927 sandbox_locks ,
915928 locks_mu ,
916929 )
917- job .suspended = True
930+ job .suspended = True
918931 log (f"[session { index + 1 } /{ total } ] done { sid_label } " )
919932 except Exception as e : # noqa: BLE001 — per-session errors collected in report
920933 job .error = f"{ sid_label } : { e } "
921934 log (f"[session] FAIL { job .error } " )
922- if job .sandbox_id :
923- _try_suspend (client , job .sandbox_id , sandbox_locks , locks_mu )
935+ # Baseline may still try a best-effort Suspend; semantic never self-Suspends.
936+ if job .sandbox_id and not semantic_slots :
937+ try :
938+ _try_suspend (client , job .sandbox_id , sandbox_locks , locks_mu )
939+ except RuntimeError :
940+ pass
924941 finally :
925942 if held :
926943 inflight .release ()
0 commit comments