tao-shen Claude Opus 4.6 commited on
Commit
16a30db
Β·
1 Parent(s): 1c89a09

fix: add 10-min rebuild cooldown to prevent build-reset loops

Browse files

Adam & Eve kept writing Dockerfile changes every few minutes, each time
resetting the build progress so Cain could never finish building.

New safety layer (#2): after any write_file to Space or restart, a
10-minute cooldown blocks further Space modifications. Forces agents
to wait for the build to complete before making more changes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. scripts/conversation-loop.py +42 -7
scripts/conversation-loop.py CHANGED
@@ -49,11 +49,12 @@ The LLM decides what to do. Actions use [ACTION: ...] tags.
49
  # β•‘ β•‘
50
  # β•‘ SAFETY LAYERS: β•‘
51
  # β•‘ 1. Building-state guard: block write/restart during BUILDING β•‘
52
- # β•‘ 2. ACT-phase guard: block reads when should be writing β•‘
53
- # β•‘ 3. Knowledge dedup: block re-reading already-read files β•‘
54
- # β•‘ 4. Config sanitizer: strip invalid openclaw.json keys β•‘
55
- # β•‘ 5. Forced transitions: prevent infinite DIAGNOSE/VERIFY loops β•‘
56
- # β•‘ 6. Shell-expression guard: block $(cmd) in set_env values β•‘
 
57
  # β•‘ β•‘
58
  # β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
59
  """
@@ -128,6 +129,10 @@ child_state = {
128
  "detail": "",
129
  }
130
 
 
 
 
 
131
 
132
  def init_child_state():
133
  try:
@@ -277,10 +282,12 @@ def action_restart():
277
  if not child_state["created"]:
278
  return f"{CHILD_NAME} not born yet."
279
  try:
 
280
  hf_api.restart_space(CHILD_SPACE_ID)
281
  child_state["alive"] = False
282
  child_state["stage"] = "RESTARTING"
283
- return f"{CHILD_NAME} is restarting. Will take a few minutes."
 
284
  except Exception as e:
285
  return f"Restart failed: {e}"
286
 
@@ -338,12 +345,16 @@ def action_write_file(target, path, content):
338
  return f"Error: invalid JSON in config file. Please fix the content."
339
 
340
  try:
 
341
  hf_api.upload_file(
342
  path_or_fileobj=io.BytesIO(content.encode()),
343
  path_in_repo=path,
344
  repo_id=repo_id, repo_type=repo_type,
345
  )
346
- rebuild_note = " ⚠️ This triggers a Space rebuild!" if target == "space" else ""
 
 
 
347
  return f"βœ“ Wrote {len(content)} bytes to {CHILD_NAME}'s {target}:{path}{rebuild_note}"
348
  except Exception as e:
349
  return f"Error writing {target}:{path}: {e}"
@@ -466,6 +477,19 @@ def parse_and_execute_actions(raw_text):
466
  print(f"[BLOCKED] {name} β€” Cain is {child_state['stage']}")
467
  break
468
 
 
 
 
 
 
 
 
 
 
 
 
 
 
469
  # Block read-only actions based on workflow state
470
  if workflow_state == "ACT" and name in ("read_file", "list_files", "check_health"):
471
  result = (f"β›” BLOCKED: You are in ACTION phase. "
@@ -534,6 +558,17 @@ def parse_and_execute_actions(raw_text):
534
  print(f"[BLOCKED-emoji] {name} β€” Cain is {child_state['stage']}")
535
  break
536
 
 
 
 
 
 
 
 
 
 
 
 
537
  if workflow_state == "ACT" and name in ("read_file", "list_files", "check_health"):
538
  result = (f"β›” BLOCKED: You are in ACTION phase. "
539
  "You MUST use write_file, set_env, set_secret, or restart.")
 
49
  # β•‘ β•‘
50
  # β•‘ SAFETY LAYERS: β•‘
51
  # β•‘ 1. Building-state guard: block write/restart during BUILDING β•‘
52
+ # β•‘ 2. Rebuild cooldown: 10-min cooldown after any Space write/restartβ•‘
53
+ # β•‘ 3. ACT-phase guard: block reads when should be writing β•‘
54
+ # β•‘ 4. Knowledge dedup: block re-reading already-read files β•‘
55
+ # β•‘ 5. Config sanitizer: strip invalid openclaw.json keys β•‘
56
+ # β•‘ 6. Forced transitions: prevent infinite DIAGNOSE/VERIFY loops β•‘
57
+ # β•‘ 7. Shell-expression guard: block $(cmd) in set_env values β•‘
58
  # β•‘ β•‘
59
  # β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
60
  """
 
129
  "detail": "",
130
  }
131
 
132
+ # Rebuild cooldown β€” prevent rapid write_file to Space that keeps resetting builds
133
+ REBUILD_COOLDOWN_SECS = 600 # 10 minutes
134
+ last_rebuild_trigger_at = 0 # timestamp of last write_file to space
135
+
136
 
137
  def init_child_state():
138
  try:
 
282
  if not child_state["created"]:
283
  return f"{CHILD_NAME} not born yet."
284
  try:
285
+ global last_rebuild_trigger_at
286
  hf_api.restart_space(CHILD_SPACE_ID)
287
  child_state["alive"] = False
288
  child_state["stage"] = "RESTARTING"
289
+ last_rebuild_trigger_at = time.time()
290
+ return f"{CHILD_NAME} is restarting. Will take a few minutes. 10-min cooldown starts now."
291
  except Exception as e:
292
  return f"Restart failed: {e}"
293
 
 
345
  return f"Error: invalid JSON in config file. Please fix the content."
346
 
347
  try:
348
+ global last_rebuild_trigger_at
349
  hf_api.upload_file(
350
  path_or_fileobj=io.BytesIO(content.encode()),
351
  path_in_repo=path,
352
  repo_id=repo_id, repo_type=repo_type,
353
  )
354
+ rebuild_note = ""
355
+ if target == "space":
356
+ last_rebuild_trigger_at = time.time()
357
+ rebuild_note = " ⚠️ This triggers a Space rebuild! 10-min cooldown starts now."
358
  return f"βœ“ Wrote {len(content)} bytes to {CHILD_NAME}'s {target}:{path}{rebuild_note}"
359
  except Exception as e:
360
  return f"Error writing {target}:{path}: {e}"
 
477
  print(f"[BLOCKED] {name} β€” Cain is {child_state['stage']}")
478
  break
479
 
480
+ # Rebuild cooldown β€” prevent writing to Space repo too soon after last rebuild trigger
481
+ if name in ("write_file", "set_env", "set_secret", "restart") and last_rebuild_trigger_at > 0:
482
+ elapsed = time.time() - last_rebuild_trigger_at
483
+ if elapsed < REBUILD_COOLDOWN_SECS:
484
+ remaining = int(REBUILD_COOLDOWN_SECS - elapsed)
485
+ result = (f"β›” BLOCKED: Rebuild cooldown active β€” last Space change was {int(elapsed)}s ago. "
486
+ f"Wait {remaining}s more before making changes. "
487
+ "Every write_file to Space triggers a full rebuild, resetting progress. "
488
+ "Use [ACTION: check_health] to monitor the current build.")
489
+ results.append({"action": action_str, "result": result})
490
+ print(f"[BLOCKED] {name} β€” rebuild cooldown ({remaining}s remaining)")
491
+ break
492
+
493
  # Block read-only actions based on workflow state
494
  if workflow_state == "ACT" and name in ("read_file", "list_files", "check_health"):
495
  result = (f"β›” BLOCKED: You are in ACTION phase. "
 
558
  print(f"[BLOCKED-emoji] {name} β€” Cain is {child_state['stage']}")
559
  break
560
 
561
+ # Rebuild cooldown (emoji parser)
562
+ if name in ("write_file", "set_env", "set_secret", "restart") and last_rebuild_trigger_at > 0:
563
+ elapsed = time.time() - last_rebuild_trigger_at
564
+ if elapsed < REBUILD_COOLDOWN_SECS:
565
+ remaining = int(REBUILD_COOLDOWN_SECS - elapsed)
566
+ result = (f"β›” BLOCKED: Rebuild cooldown β€” wait {remaining}s more. "
567
+ "Use [ACTION: check_health] to monitor.")
568
+ results.append({"action": action_str, "result": result})
569
+ print(f"[BLOCKED-emoji] {name} β€” rebuild cooldown ({remaining}s remaining)")
570
+ break
571
+
572
  if workflow_state == "ACT" and name in ("read_file", "list_files", "check_health"):
573
  result = (f"β›” BLOCKED: You are in ACTION phase. "
574
  "You MUST use write_file, set_env, set_secret, or restart.")