tao-shen Claude Opus 4.6 commited on
Commit
42eb3e9
Β·
1 Parent(s): 00e404d

fix: defer cooldown to end of turn so agents can batch file ops

Browse files

Previously, 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>

Files changed (1) hide show
  1. 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 last_rebuild_trigger_at
350
  hf_api.restart_space(CHILD_SPACE_ID)
351
  child_state["alive"] = False
352
  child_state["stage"] = "RESTARTING"
353
- last_rebuild_trigger_at = time.time()
354
- return f"{CHILD_NAME} is restarting. Will take a few minutes. Cooldown starts now (clears automatically when build finishes)."
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 last_rebuild_trigger_at
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
- last_rebuild_trigger_at = time.time()
421
- rebuild_note = " ⚠️ This triggers a Space rebuild! Cooldown starts now (auto-clears when build finishes)."
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 last_rebuild_trigger_at
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
- last_rebuild_trigger_at = time.time()
440
- rebuild_note = " ⚠️ This triggers a Space rebuild! Cooldown starts now."
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)