66Uses dataset arrivals + phase_spans + task_profile only.
77
88Baseline policies (random / resource-evict):
9- Create → Resume once → hold Worker through all phases → Suspend
9+ Create → Resume once → hold Worker through all phases (never self-Suspend);
10+ unlocked peers may be stolen by knockers; leftovers cleaned up at policy end.
1011
1112semantic-score (+ l1 ablation):
1213 Create (suspended) → llm_wait locally (heartbeat, no slot) →
3031Ablation aliases: `semantic-score-l1` → PRIOR_MIX=0; `semantic-score` → PRIOR_MIX=0.3.
3132
3233Concurrency: max_inflight defaults to workers+1 (slot contention). Concurrent
33- Resume knockers are allowed so semantic-score can rank waiters; Suspend is
34- serialized per sandbox id only (avoid double-checkpoint of the same id) .
34+ Resume knockers are allowed so semantic-score can rank waiters. No client
35+ self-Suspend; only CP eviction archives running sandboxes .
3536"""
3637
3738from __future__ import annotations
@@ -692,48 +693,19 @@ def _phase_needs_worker(phase: str, lock: bool) -> bool:
692693 return lock or phase == "tool_loop"
693694
694695
695- def _try_suspend (
696- client : CPClient ,
697- sandbox_id : str ,
698- sandbox_locks : dict [str , threading .Lock ],
699- locks_mu : threading .Lock ,
700- ) -> bool :
701- """Suspend if running. Returns False if already off-worker / not running."""
702- with _sandbox_lock (sandbox_locks , locks_mu , sandbox_id ):
703- try :
704- client .suspend (sandbox_id )
705- return True
706- except RuntimeError as e :
707- msg = str (e ).lower ()
708- if "suspended" in msg or "want running" in msg or "want suspended" in msg :
709- return False
710- raise
711-
712-
713- def _sandbox_lock (
714- sandbox_locks : dict [str , threading .Lock ],
715- locks_mu : threading .Lock ,
716- sandbox_id : str ,
717- ) -> threading .Lock :
718- with locks_mu :
719- lk = sandbox_locks .get (sandbox_id )
720- if lk is None :
721- lk = threading .Lock ()
722- sandbox_locks [sandbox_id ] = lk
723- return lk
724-
725-
726696def drive_phases_hold (
727697 client : CPClient ,
728698 sandbox_id : str ,
729699 spans : list [dict [str , Any ]],
730700 speed : float ,
731701 workflow_id : str ,
732702 min_lock_sec : float ,
733- sandbox_locks : dict [str , threading .Lock ],
734- locks_mu : threading .Lock ,
735703) -> None :
736- """Baseline: already Running; hold the Worker through all spans, then Suspend."""
704+ """Baseline: already Running; hold the Worker through all spans.
705+
706+ Never self-Suspend: leave unlocked idle so knockers can steal; leftovers
707+ are removed by policy-end cleanup_sandboxes.
708+ """
737709 if not spans :
738710 spans = [{"phase" : "idle" , "lock" : False , "t_start_ms" : 0 , "t_end_ms" : 500 }]
739711 for span in spans :
@@ -755,7 +727,15 @@ def drive_phases_hold(
755727 )
756728 if dur > 0 :
757729 time .sleep (dur )
758- _try_suspend (client , sandbox_id , sandbox_locks , locks_mu )
730+ client .post_semantic (
731+ sandbox_id ,
732+ {
733+ "version" : "v1" ,
734+ "phase" : "idle" ,
735+ "lock" : False ,
736+ "workflowID" : workflow_id ,
737+ },
738+ )
759739
760740
761741def drive_phases_semantic (
@@ -765,8 +745,6 @@ def drive_phases_semantic(
765745 speed : float ,
766746 workflow_id : str ,
767747 min_lock_sec : float ,
768- sandbox_locks : dict [str , threading .Lock ],
769- locks_mu : threading .Lock ,
770748 l3 : L3Tracker ,
771749) -> float :
772750 """semantic-score path: llm_wait needs no slot until a Worker is held;
@@ -854,8 +832,6 @@ def run_one_session(
854832 index : int ,
855833 total : int ,
856834 inflight : threading .Semaphore ,
857- sandbox_locks : dict [str , threading .Lock ],
858- locks_mu : threading .Lock ,
859835 l3 : L3Tracker ,
860836 semantic_slots : bool ,
861837) -> SessionJob :
@@ -906,8 +882,6 @@ def run_one_session(
906882 speed ,
907883 sid_label ,
908884 min_lock_sec ,
909- sandbox_locks ,
910- locks_mu ,
911885 l3 ,
912886 )
913887 job .resumed = True
@@ -924,20 +898,13 @@ def run_one_session(
924898 speed ,
925899 sid_label ,
926900 min_lock_sec ,
927- sandbox_locks ,
928- locks_mu ,
929901 )
930- job .suspended = True
902+ # Eviction may have Suspended us; never self-Suspend.
903+ job .suspended = _sandbox_state (client , job .sandbox_id ) == "suspended"
931904 log (f"[session { index + 1 } /{ total } ] done { sid_label } " )
932905 except Exception as e : # noqa: BLE001 — per-session errors collected in report
933906 job .error = f"{ sid_label } : { e } "
934907 log (f"[session] FAIL { job .error } " )
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
941908 finally :
942909 if held :
943910 inflight .release ()
@@ -977,8 +944,6 @@ def run_policy(
977944 if max_inflight <= 0 :
978945 max_inflight = max (2 , worker_count + 1 )
979946 inflight = threading .Semaphore (max_inflight )
980- sandbox_locks : dict [str , threading .Lock ] = {}
981- locks_mu = threading .Lock ()
982947 # Pool large enough that arrivals can block on the semaphore without starving.
983948 pool_n = min (total , max (max_inflight * 2 , 8 ))
984949 mode = "semantic tool-only slots" if semantic_slots else "hold-all-phases"
@@ -1008,8 +973,6 @@ def run_policy(
1008973 i ,
1009974 total ,
1010975 inflight ,
1011- sandbox_locks ,
1012- locks_mu ,
1013976 l3 ,
1014977 semantic_slots ,
1015978 )
@@ -1154,7 +1117,7 @@ def write_compare(results: list[PolicyResult], path: Path) -> None:
11541117 "- `evict_llm_wait`: preferred interrupt window for semantic-score." ,
11551118 "- `mid_tool_rate = mid_tool / suspend` (0 if no suspends)." ,
11561119 "- `vic_hard` / `vic_easy` / `hard_rate`: eviction victims attributed via "
1157- "running→suspended peers at Resume time (excludes voluntary end Suspend)." ,
1120+ "running→suspended peers at Resume time (eviction only; no client self- Suspend)." ,
11581121 "- `hard_rate = vic_hard / (hard+mid+easy+inactive)`; lower favors semantic-score L3." ,
11591122 "- `resume_hard_s` / `resume_easy_s`: mean client Resume RTT for that cohort "
11601123 "(proxy for wait+restore; not OTel resume_wait)." ,
0 commit comments