Spaces:
Runtime error
Runtime error
fix: add 10-min rebuild cooldown to prevent build-reset loops
Browse filesAdam & 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>
- 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.
|
| 53 |
-
# β 3.
|
| 54 |
-
# β 4.
|
| 55 |
-
# β 5.
|
| 56 |
-
# β 6.
|
|
|
|
| 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 |
-
|
|
|
|
| 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 = "
|
|
|
|
|
|
|
|
|
|
| 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.")
|