Spaces:
Runtime error
Runtime error
fix: defer cooldown to end of turn so agents can batch file ops
Browse filesPreviously, each write/delete to Space immediately activated the rebuild
cooldown, blocking subsequent file ops in the SAME turn. This caused agents
to self-lock: delete requirements.txt (cooldown starts) β can't write app.py.
Now cooldown activates AFTER all actions in a turn complete, allowing agents
to atomically modify multiple files (e.g., delete + write) in one turn.
Also adds delete_file to cooldown guard β it was previously unguarded,
letting agents trigger unlimited rebuilds via deletions.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- scripts/conversation-loop.py +21 -11
scripts/conversation-loop.py
CHANGED
|
@@ -144,6 +144,7 @@ MAX_DELEGATE_DEPTH = 1 # Sub-agents cannot delegate further
|
|
| 144 |
# Rebuild cooldown β prevent rapid write_file to Space that keeps resetting builds
|
| 145 |
REBUILD_COOLDOWN_SECS = 360 # 6 minutes (builds typically finish in 3-5 min)
|
| 146 |
last_rebuild_trigger_at = 0 # timestamp of last write_file to space
|
|
|
|
| 147 |
files_written_this_cycle = set() # track files written since last RUNNING state
|
| 148 |
|
| 149 |
def check_and_clear_cooldown():
|
|
@@ -346,12 +347,12 @@ def action_restart():
|
|
| 346 |
if not child_state["created"]:
|
| 347 |
return f"{CHILD_NAME} not born yet."
|
| 348 |
try:
|
| 349 |
-
global
|
| 350 |
hf_api.restart_space(CHILD_SPACE_ID)
|
| 351 |
child_state["alive"] = False
|
| 352 |
child_state["stage"] = "RESTARTING"
|
| 353 |
-
|
| 354 |
-
return f"{CHILD_NAME} is restarting. Will take a few minutes. Cooldown starts
|
| 355 |
except Exception as e:
|
| 356 |
return f"Restart failed: {e}"
|
| 357 |
|
|
@@ -409,7 +410,7 @@ def action_write_file(target, path, content):
|
|
| 409 |
return f"Error: invalid JSON in config file. Please fix the content."
|
| 410 |
|
| 411 |
try:
|
| 412 |
-
global
|
| 413 |
hf_api.upload_file(
|
| 414 |
path_or_fileobj=io.BytesIO(content.encode()),
|
| 415 |
path_in_repo=path,
|
|
@@ -417,8 +418,8 @@ def action_write_file(target, path, content):
|
|
| 417 |
)
|
| 418 |
rebuild_note = ""
|
| 419 |
if target == "space":
|
| 420 |
-
|
| 421 |
-
rebuild_note = " β οΈ This triggers a Space rebuild! Cooldown starts
|
| 422 |
return f"β Wrote {len(content)} bytes to {CHILD_NAME}'s {target}:{path}{rebuild_note}"
|
| 423 |
except Exception as e:
|
| 424 |
return f"Error writing {target}:{path}: {e}"
|
|
@@ -429,15 +430,15 @@ def action_delete_file(target, path):
|
|
| 429 |
repo_type = "space" if target == "space" else "dataset"
|
| 430 |
repo_id = CHILD_SPACE_ID if target == "space" else CHILD_DATASET_ID
|
| 431 |
try:
|
| 432 |
-
global
|
| 433 |
hf_api.delete_file(
|
| 434 |
path_in_repo=path,
|
| 435 |
repo_id=repo_id, repo_type=repo_type,
|
| 436 |
)
|
| 437 |
rebuild_note = ""
|
| 438 |
if target == "space":
|
| 439 |
-
|
| 440 |
-
rebuild_note = " β οΈ This triggers a Space rebuild! Cooldown starts
|
| 441 |
return f"β Deleted {target}:{path}{rebuild_note}"
|
| 442 |
except Exception as e:
|
| 443 |
return f"Error deleting {target}:{path}: {e}"
|
|
@@ -655,7 +656,7 @@ def parse_and_execute_actions(raw_text, depth=0):
|
|
| 655 |
break
|
| 656 |
|
| 657 |
# Rebuild cooldown β prevent writing to Space repo too soon after last rebuild trigger
|
| 658 |
-
if name in ("write_file", "set_env", "set_secret", "restart") and last_rebuild_trigger_at > 0:
|
| 659 |
check_and_clear_cooldown() # may clear cooldown early if build done
|
| 660 |
elapsed = time.time() - last_rebuild_trigger_at if last_rebuild_trigger_at > 0 else 9999
|
| 661 |
if elapsed < REBUILD_COOLDOWN_SECS:
|
|
@@ -747,7 +748,7 @@ def parse_and_execute_actions(raw_text, depth=0):
|
|
| 747 |
break
|
| 748 |
|
| 749 |
# Rebuild cooldown (emoji parser)
|
| 750 |
-
if name in ("write_file", "set_env", "set_secret", "restart") and last_rebuild_trigger_at > 0:
|
| 751 |
elapsed = time.time() - last_rebuild_trigger_at
|
| 752 |
if elapsed < REBUILD_COOLDOWN_SECS:
|
| 753 |
remaining = int(REBUILD_COOLDOWN_SECS - elapsed)
|
|
@@ -838,6 +839,15 @@ def parse_and_execute_actions(raw_text, depth=0):
|
|
| 838 |
"result": f"Sub-agent failed: {e}"})
|
| 839 |
print(f"[DELEGATE] β Failed: {d['task'][:60]} β {e}")
|
| 840 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 841 |
# Clean the text: remove action tags, content blocks, and emoji actions
|
| 842 |
clean = re.sub(r'\[(?:ACTION|Action|action|ζδ½|ε¨δ½)\s*[:οΌ][^\]]*\]', '', raw_text)
|
| 843 |
clean = re.sub(r'\[CONTENT\].*?\[/CONTENT\]', '', clean, flags=re.DOTALL)
|
|
|
|
| 144 |
# Rebuild cooldown β prevent rapid write_file to Space that keeps resetting builds
|
| 145 |
REBUILD_COOLDOWN_SECS = 360 # 6 minutes (builds typically finish in 3-5 min)
|
| 146 |
last_rebuild_trigger_at = 0 # timestamp of last write_file to space
|
| 147 |
+
_pending_cooldown = False # defer cooldown activation until end of turn
|
| 148 |
files_written_this_cycle = set() # track files written since last RUNNING state
|
| 149 |
|
| 150 |
def check_and_clear_cooldown():
|
|
|
|
| 347 |
if not child_state["created"]:
|
| 348 |
return f"{CHILD_NAME} not born yet."
|
| 349 |
try:
|
| 350 |
+
global _pending_cooldown
|
| 351 |
hf_api.restart_space(CHILD_SPACE_ID)
|
| 352 |
child_state["alive"] = False
|
| 353 |
child_state["stage"] = "RESTARTING"
|
| 354 |
+
_pending_cooldown = True # deferred β activated after turn ends
|
| 355 |
+
return f"{CHILD_NAME} is restarting. Will take a few minutes. Cooldown starts after this turn."
|
| 356 |
except Exception as e:
|
| 357 |
return f"Restart failed: {e}"
|
| 358 |
|
|
|
|
| 410 |
return f"Error: invalid JSON in config file. Please fix the content."
|
| 411 |
|
| 412 |
try:
|
| 413 |
+
global _pending_cooldown
|
| 414 |
hf_api.upload_file(
|
| 415 |
path_or_fileobj=io.BytesIO(content.encode()),
|
| 416 |
path_in_repo=path,
|
|
|
|
| 418 |
)
|
| 419 |
rebuild_note = ""
|
| 420 |
if target == "space":
|
| 421 |
+
_pending_cooldown = True # deferred β activated after turn ends
|
| 422 |
+
rebuild_note = " β οΈ This triggers a Space rebuild! Cooldown starts after this turn."
|
| 423 |
return f"β Wrote {len(content)} bytes to {CHILD_NAME}'s {target}:{path}{rebuild_note}"
|
| 424 |
except Exception as e:
|
| 425 |
return f"Error writing {target}:{path}: {e}"
|
|
|
|
| 430 |
repo_type = "space" if target == "space" else "dataset"
|
| 431 |
repo_id = CHILD_SPACE_ID if target == "space" else CHILD_DATASET_ID
|
| 432 |
try:
|
| 433 |
+
global _pending_cooldown
|
| 434 |
hf_api.delete_file(
|
| 435 |
path_in_repo=path,
|
| 436 |
repo_id=repo_id, repo_type=repo_type,
|
| 437 |
)
|
| 438 |
rebuild_note = ""
|
| 439 |
if target == "space":
|
| 440 |
+
_pending_cooldown = True # deferred β activated after turn ends
|
| 441 |
+
rebuild_note = " β οΈ This triggers a Space rebuild! Cooldown starts after this turn."
|
| 442 |
return f"β Deleted {target}:{path}{rebuild_note}"
|
| 443 |
except Exception as e:
|
| 444 |
return f"Error deleting {target}:{path}: {e}"
|
|
|
|
| 656 |
break
|
| 657 |
|
| 658 |
# Rebuild cooldown β prevent writing to Space repo too soon after last rebuild trigger
|
| 659 |
+
if name in ("write_file", "set_env", "set_secret", "restart", "delete_file") and last_rebuild_trigger_at > 0:
|
| 660 |
check_and_clear_cooldown() # may clear cooldown early if build done
|
| 661 |
elapsed = time.time() - last_rebuild_trigger_at if last_rebuild_trigger_at > 0 else 9999
|
| 662 |
if elapsed < REBUILD_COOLDOWN_SECS:
|
|
|
|
| 748 |
break
|
| 749 |
|
| 750 |
# Rebuild cooldown (emoji parser)
|
| 751 |
+
if name in ("write_file", "set_env", "set_secret", "restart", "delete_file") and last_rebuild_trigger_at > 0:
|
| 752 |
elapsed = time.time() - last_rebuild_trigger_at
|
| 753 |
if elapsed < REBUILD_COOLDOWN_SECS:
|
| 754 |
remaining = int(REBUILD_COOLDOWN_SECS - elapsed)
|
|
|
|
| 839 |
"result": f"Sub-agent failed: {e}"})
|
| 840 |
print(f"[DELEGATE] β Failed: {d['task'][:60]} β {e}")
|
| 841 |
|
| 842 |
+
# 5. Activate deferred cooldown AFTER all actions in this turn complete
|
| 843 |
+
# This allows agents to batch multiple file ops (e.g., write app.py + requirements.txt)
|
| 844 |
+
# in a single turn without the first write blocking the second.
|
| 845 |
+
global last_rebuild_trigger_at, _pending_cooldown
|
| 846 |
+
if _pending_cooldown and depth == 0: # only at top-level, not inside sub-agents
|
| 847 |
+
last_rebuild_trigger_at = time.time()
|
| 848 |
+
_pending_cooldown = False
|
| 849 |
+
print(f"[COOLDOWN] Activated β Space was modified this turn. Next write blocked for {REBUILD_COOLDOWN_SECS}s (or until build finishes).")
|
| 850 |
+
|
| 851 |
# Clean the text: remove action tags, content blocks, and emoji actions
|
| 852 |
clean = re.sub(r'\[(?:ACTION|Action|action|ζδ½|ε¨δ½)\s*[:οΌ][^\]]*\]', '', raw_text)
|
| 853 |
clean = re.sub(r'\[CONTENT\].*?\[/CONTENT\]', '', clean, flags=re.DOTALL)
|