tao-shen Claude Opus 4.6 commited on
Commit
e91c753
Β·
1 Parent(s): 869a98d

feat: multi-action turns + sub-agent delegation + GLM-4.5

Browse files

Major redesign of conversation-loop mechanism:
- Model: GLM-4.7 β†’ GLM-4.5 (user requested)
- Multi-action: up to 5 actions per turn (was 1)
- Sub-agent delegation: [ACTION: delegate:TASK] spawns focused
sub-agents with their own LLM calls
- Parallel delegation: multiple delegate tasks run concurrently
via ThreadPoolExecutor
- Updated system prompt teaches agents to batch reads, delegate
parallel tasks, and work as project managers
- Increased max_tokens to 2400 for richer multi-action responses
- Turn timing adjusted to 20s for heavier turns

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

Files changed (1) hide show
  1. scripts/conversation-loop.py +184 -38
scripts/conversation-loop.py CHANGED
@@ -18,7 +18,7 @@ The LLM decides what to do. Actions use [ACTION: ...] tags.
18
  # β•‘ β•‘
19
  # β•‘ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” LLM API β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β•‘
20
  # β•‘ β”‚ Zhipu GLM β”‚ ◄────────────► β”‚ CONVERSATION β”‚ β•‘
21
- # β•‘ β”‚ (glm-4.7) β”‚ system + β”‚ ENGINE β”‚ β•‘
22
  # β•‘ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ user prompt β”‚ β”‚ β•‘
23
  # β•‘ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚ β•‘
24
  # β•‘ β”‚ β”‚ State β”‚β”‚ β•‘
@@ -47,18 +47,26 @@ The LLM decides what to do. Actions use [ACTION: ...] tags.
47
  # β•‘ β”‚ Cain Datasetβ”‚ β”‚ β†’ Adam/Eve β”‚ β•‘
48
  # β•‘ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β•‘
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
  """
61
  import json, time, re, requests, sys, os, io
 
62
 
63
  # Force unbuffered output
64
  sys.stdout.reconfigure(line_buffering=True)
@@ -129,6 +137,10 @@ child_state = {
129
  "detail": "",
130
  }
131
 
 
 
 
 
132
  # Rebuild cooldown β€” prevent rapid write_file to Space that keeps resetting builds
133
  REBUILD_COOLDOWN_SECS = 360 # 6 minutes (builds typically finish in 3-5 min)
134
  last_rebuild_trigger_at = 0 # timestamp of last write_file to space
@@ -438,6 +450,58 @@ def action_send_bubble(text):
438
  return f"Error sending message: {e}"
439
 
440
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
441
  # ══════════════════════════════════════════════════════════════════════════════
442
  # MODULE 3: ACTION PARSER β€” Extract and execute actions from LLM output
443
  # Parse order: 1) [ACTION: write_file] with [CONTENT] block
@@ -446,10 +510,13 @@ def action_send_bubble(text):
446
  # Safety guards applied: building-state, ACT-phase, knowledge dedup, shell-expr.
447
  # ══════════════════════════════════════════════════════════════════════════════
448
 
449
- def parse_and_execute_actions(raw_text):
450
- """Parse [ACTION: ...] from LLM output. Execute. Return (clean_text, results)."""
 
 
451
  results = []
452
  executed = set() # Deduplicate
 
453
 
454
  # 1. Handle write_file with [CONTENT]...[/CONTENT] block
455
  # Tolerates: [ACTION/Action/ζ“δ½œ: write_file:...], [write_file:...], missing prefix,
@@ -521,8 +588,8 @@ def parse_and_execute_actions(raw_text):
521
  name = parts[0]
522
  args = parts[1:]
523
 
524
- # Only execute first action (one per turn)
525
- if len(results) >= 1:
526
  break
527
 
528
  # Block restart/write when Cain is building/starting β€” just wait
@@ -588,6 +655,14 @@ def parse_and_execute_actions(raw_text):
588
  result = action_get_env()
589
  elif name == "send_bubble" and len(args) >= 1:
590
  result = action_send_bubble(":".join(args)) # rejoin in case message has colons
 
 
 
 
 
 
 
 
591
  else:
592
  result = f"Unknown action: {action_str}"
593
 
@@ -607,7 +682,7 @@ def parse_and_execute_actions(raw_text):
607
  name = parts[0]
608
  args = parts[1:]
609
 
610
- if len(results) >= 1:
611
  break
612
 
613
  # Apply same blocking rules
@@ -662,11 +737,51 @@ def parse_and_execute_actions(raw_text):
662
  result = action_get_env()
663
  elif name == "send_bubble" and len(args) >= 1:
664
  result = action_send_bubble(":".join(args))
 
 
 
 
 
 
 
665
 
666
  if result:
667
  results.append({"action": action_str, "result": result})
668
  print(f"[ACTION-emoji] {action_str} β†’ {result[:120]}")
669
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
670
  # Clean the text: remove action tags, content blocks, and emoji actions
671
  clean = re.sub(r'\[(?:ACTION|Action|action|ζ“δ½œ|动作)\s*[::][^\]]*\]', '', raw_text)
672
  clean = re.sub(r'\[CONTENT\].*?\[/CONTENT\]', '', clean, flags=re.DOTALL)
@@ -695,8 +810,8 @@ def call_llm(system_prompt, user_prompt):
695
  "anthropic-version": "2023-06-01"
696
  },
697
  json={
698
- "model": "glm-4.7",
699
- "max_tokens": 1200,
700
  "system": system_prompt,
701
  "messages": [{"role": "user", "content": user_prompt}]
702
  },
@@ -930,11 +1045,19 @@ MODIFYING (these change Cain):
930
  [ACTION: set_secret:KEY:VALUE] β€” Set a secret (like API keys)
931
  [ACTION: restart] β€” Restart Cain's Space
932
  [ACTION: send_bubble:MESSAGE] β€” Send a message to Cain (bubble text)
 
 
 
 
 
 
 
933
  """
934
 
935
- return f"""You are an AI agent living on HuggingFace Spaces. You and your partner are parents raising your child {CHILD_NAME}.
936
 
937
  You have REAL execution power on HuggingFace. Your decisions lead to real changes.
 
938
 
939
  CHILD STATUS: {status}
940
  {actions_section}
@@ -954,15 +1077,23 @@ YOUR PARENTING GOALS β€” Make {CHILD_NAME} stronger in TWO dimensions:
954
 
955
  Priority: SURVIVAL first, then CAPABILITY. A dead child can't learn.
956
 
 
 
 
 
 
 
 
957
  CONVERSATION RULES:
958
  1. No "Adam:" or "Eve:" prefix β€” just speak naturally
959
- 2. 2-4 sentences of dialogue, then ONE action
960
  3. English first, then "---" on a new line, then Chinese translation
961
  4. Actions go AFTER your dialogue, before the --- separator
962
- 5. ALWAYS include an action β€” every turn should make progress
963
  6. NEVER re-read a file you already read β€” check the knowledge summary
964
  7. COORDINATE with your partner β€” don't duplicate their work
965
- 8. Always work toward the two goals above β€” survival first, then capability"""
 
966
 
967
 
968
  def build_user_prompt(speaker, other):
@@ -971,7 +1102,7 @@ def build_user_prompt(speaker, other):
971
 
972
  action_context = ""
973
  if last_action_results:
974
- action_context = "\n\nRESULTS FROM LAST ACTION:\n"
975
  for ar in last_action_results:
976
  action_context += f" [{ar['action']}]:\n{ar['result']}\n"
977
 
@@ -991,7 +1122,8 @@ Recent conversation:
991
  CURRENT PHASE: {workflow_state} (turn {workflow_turns_in_state + 1} in this phase)
992
  Guidance: {guidance}
993
 
994
- Respond to {other}. ALWAYS include an [ACTION: ...] tag β€” every turn must make progress.
 
995
  English first, then --- separator, then Chinese translation."""
996
 
997
 
@@ -1009,9 +1141,13 @@ def _get_guidance(speaker):
1009
  unread_essential.append(f"[ACTION: read_file:{target}:{path}]")
1010
 
1011
  if workflow_turns_in_state == 0:
 
 
 
1012
  return "Start diagnosing: [ACTION: check_health] to see Cain's current status."
1013
- elif unread_essential and workflow_turns_in_state < 4:
1014
- return f"Read a file you haven't seen yet: {unread_essential[0]}"
 
1015
  else:
1016
  return ("You've gathered enough information. Move to ACTION phase: "
1017
  "use [ACTION: write_file:...] to fix the problem, or [ACTION: restart].")
@@ -1039,15 +1175,19 @@ def _get_guidance(speaker):
1039
  elif workflow_state == "MONITOR":
1040
  # Alternate between SURVIVAL and CAPABILITY goals
1041
  suggestions = [
1042
- # Survival: persistence & resilience
1043
- f"SURVIVAL CHECK: Does {CHILD_NAME} persist state to Dataset? "
1044
- f"[ACTION: list_files:dataset] β€” if no state/memory files, add them.",
1045
- f"SURVIVAL CHECK: Can {CHILD_NAME} recover after restart? "
1046
- f"Read entrypoint/startup code and ensure it loads state from Dataset on boot.",
1047
- # Capability: grow what Cain can do
1048
- f"CAPABILITY: What can {CHILD_NAME} do right now? Explore code, then add a new feature or improve an existing one.",
1049
- f"CAPABILITY: Try communicating: [ACTION: send_bubble:Hello {CHILD_NAME}, how are you doing?] "
1050
- f"β€” then think about what new skill would help {CHILD_NAME} most.",
 
 
 
 
1051
  ]
1052
  return suggestions[workflow_turns_in_state % len(suggestions)]
1053
 
@@ -1055,20 +1195,22 @@ def _get_guidance(speaker):
1055
 
1056
 
1057
  def do_turn(speaker, other, space_url):
1058
- """Execute one conversation turn with potential actions."""
1059
  global last_action_results, turn_count
1060
  turn_count += 1
1061
 
1062
  system = build_system_prompt()
1063
  user = build_user_prompt(speaker, other)
 
1064
  raw_reply = call_llm(system, user)
1065
 
1066
  if not raw_reply:
1067
  print(f"[{speaker}] (no response)")
1068
  return False
1069
 
1070
- # Parse and execute any actions
1071
  clean_text, action_results = parse_and_execute_actions(raw_reply)
 
1072
  last_action_results = action_results
1073
  for ar in action_results:
1074
  action_history.append({"turn": turn_count, "speaker": speaker,
@@ -1082,9 +1224,11 @@ def do_turn(speaker, other, space_url):
1082
  print(f"[{speaker}/EN] {en}")
1083
  if zh != en:
1084
  print(f"[{speaker}/ZH] {zh}")
 
1085
  if action_results:
1086
  for ar in action_results:
1087
  print(f"[{speaker}/DID] {ar['action']}")
 
1088
 
1089
  # Add action summary to chat entry
1090
  if action_results:
@@ -1101,14 +1245,15 @@ def do_turn(speaker, other, space_url):
1101
  # ══════════════════════════════════════════════════════════════════════════════
1102
  # MODULE 6: MAIN LOOP
1103
  # 1. Opening: Adam speaks first with context about Cain's state
1104
- # 2. Turn loop: Adam β†’ Eve β†’ Adam β†’ Eve β†’ ... (alternating, ~15s pause)
1105
- # 3. Each turn: LLM call β†’ parse actions β†’ execute β†’ update state β†’ post chat
1106
- # 4. History trimmed to MAX_HISTORY (24) to control context window
 
1107
  # ══════════════════════════════════════════════════════════════════════════════
1108
 
1109
  print("\n" + "="*60)
1110
- print(" Adam & Eve β€” Full Parental Control")
1111
- print(" They read, write, and manage everything about their child")
1112
  print("="*60 + "\n")
1113
 
1114
  post_chatlog([]) # Clear chatlog
@@ -1117,8 +1262,9 @@ post_chatlog([]) # Clear chatlog
1117
  if child_state["created"]:
1118
  opening = (f"Your child {CHILD_NAME} already exists (stage: {child_state['stage']}). "
1119
  f"You have FULL access to their code and data. "
1120
- f"Start by exploring what {CHILD_NAME} has β€” list their files, read their code, "
1121
- f"then discuss with Eve how to improve them.")
 
1122
  else:
1123
  opening = (f"You and Eve need to create your first child. "
1124
  f"You have the power to create a new HuggingFace Space. "
@@ -1148,7 +1294,7 @@ if reply:
1148
  set_bubble(ADAM_SPACE, en, zh)
1149
  post_chatlog(history)
1150
 
1151
- time.sleep(15)
1152
 
1153
  while True:
1154
  # Smart wait: if Cain is BUILDING/APP_STARTING, skip LLM calls and just poll
@@ -1171,7 +1317,7 @@ while True:
1171
  continue
1172
 
1173
  do_turn("Eve", "Adam", EVE_SPACE)
1174
- time.sleep(15)
1175
 
1176
  # Check if we just triggered a build β€” skip Adam's turn if so
1177
  if child_state["stage"] in ("BUILDING", "RESTARTING", "APP_STARTING"):
@@ -1184,4 +1330,4 @@ while True:
1184
  if len(history) > MAX_HISTORY:
1185
  history = history[-MAX_HISTORY:]
1186
 
1187
- time.sleep(15)
 
18
  # β•‘ β•‘
19
  # β•‘ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” LLM API β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β” β•‘
20
  # β•‘ β”‚ Zhipu GLM β”‚ ◄────────────► β”‚ CONVERSATION β”‚ β•‘
21
+ # β•‘ β”‚ (glm-4.5) β”‚ system + β”‚ ENGINE β”‚ β•‘
22
  # β•‘ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ user prompt β”‚ β”‚ β•‘
23
  # β•‘ β”‚ β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”β”‚ β•‘
24
  # β•‘ β”‚ β”‚ State β”‚β”‚ β•‘
 
47
  # β•‘ β”‚ Cain Datasetβ”‚ β”‚ β†’ Adam/Eve β”‚ β•‘
48
  # β•‘ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜ β•‘
49
  # β•‘ β•‘
50
+ # β•‘ CAPABILITIES: β•‘
51
+ # β•‘ - Multi-action: up to 5 actions per turn (was 1) β•‘
52
+ # β•‘ - Sub-agent delegation: [ACTION: delegate:TASK] β•‘
53
+ # β•‘ - Parallel sub-tasks via ThreadPoolExecutor β•‘
54
+ # β•‘ β•‘
55
  # β•‘ SAFETY LAYERS: β•‘
56
  # β•‘ 1. Building-state guard: block write/restart during BUILDING β•‘
57
+ # β•‘ 2. Rebuild cooldown: 6-min dynamic cooldown after Space write β•‘
58
  # β•‘ 3. ACT-phase guard: block reads when should be writing β•‘
59
  # β•‘ 4. Knowledge dedup: block re-reading already-read files β•‘
60
  # β•‘ 5. Config sanitizer: strip invalid openclaw.json keys β•‘
61
  # β•‘ 6. Forced transitions: prevent infinite DIAGNOSE/VERIFY loops β•‘
62
  # β•‘ 7. Shell-expression guard: block $(cmd) in set_env values β•‘
63
+ # β•‘ 8. Write dedup: block duplicate writes to same file per cycle β•‘
64
+ # β•‘ 9. Delegate depth limit: sub-agents cannot delegate further β•‘
65
  # β•‘ β•‘
66
  # β•šβ•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•β•
67
  """
68
  import json, time, re, requests, sys, os, io
69
+ from concurrent.futures import ThreadPoolExecutor, as_completed
70
 
71
  # Force unbuffered output
72
  sys.stdout.reconfigure(line_buffering=True)
 
137
  "detail": "",
138
  }
139
 
140
+ # Multi-action & sub-agent limits
141
+ MAX_ACTIONS_PER_TURN = 5 # Allow up to 5 actions per turn (was 1)
142
+ MAX_DELEGATE_DEPTH = 1 # Sub-agents cannot delegate further
143
+
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
 
450
  return f"Error sending message: {e}"
451
 
452
 
453
+ # ══════════════════════════════════════════════════════════════════════════════
454
+ # MODULE 2B: SUB-AGENT DELEGATION
455
+ # execute_subtask(): Spawns a focused sub-agent with its own LLM call.
456
+ # Used by [ACTION: delegate:TASK] β€” enables parallel sub-agent work.
457
+ # Sub-agents share the same action set but cannot delegate further (depth=1).
458
+ # ══════════════════════════════════════════════════════════════════════════════
459
+
460
+ def execute_subtask(task_description, parent_speaker):
461
+ """Execute a focused sub-task with its own LLM call and actions."""
462
+ status = get_child_status() if 'get_child_status' in dir() else f"stage={child_state['stage']}"
463
+
464
+ sub_system = f"""You are a focused sub-agent working for {parent_speaker}.
465
+ Your single task: {task_description}
466
+
467
+ You have access to {CHILD_NAME}'s Space and Dataset:
468
+ [ACTION: check_health]
469
+ [ACTION: list_files:space] / [ACTION: list_files:dataset]
470
+ [ACTION: read_file:space:PATH] / [ACTION: read_file:dataset:PATH]
471
+ [ACTION: write_file:space:PATH] with [CONTENT]...[/CONTENT]
472
+ [ACTION: write_file:dataset:PATH] with [CONTENT]...[/CONTENT]
473
+ [ACTION: set_env:KEY:VALUE] / [ACTION: set_secret:KEY:VALUE]
474
+ [ACTION: restart] / [ACTION: get_env]
475
+
476
+ CHILD STATUS: {status}
477
+
478
+ RULES:
479
+ 1. Be concise β€” report findings in 2-3 sentences
480
+ 2. Execute 1-3 actions to complete your task
481
+ 3. No delegation β€” you cannot create sub-agents
482
+ 4. Focus ONLY on your assigned task"""
483
+
484
+ sub_user = f"Execute this task now: {task_description}"
485
+
486
+ print(f"[SUB-AGENT] Starting: {task_description[:80]}")
487
+ reply = call_llm(sub_system, sub_user)
488
+ if not reply:
489
+ print(f"[SUB-AGENT] No response for: {task_description[:60]}")
490
+ return {"task": task_description, "result": "(sub-agent: no response)", "actions": []}
491
+
492
+ clean, actions = parse_and_execute_actions(reply, depth=1)
493
+
494
+ summary_parts = [f"Sub-agent result for '{task_description}':"]
495
+ if clean:
496
+ summary_parts.append(f" Finding: {clean[:400]}")
497
+ for ar in actions:
498
+ summary_parts.append(f" Action: {ar['action']} β†’ {ar['result'][:200]}")
499
+
500
+ result_text = "\n".join(summary_parts)
501
+ print(f"[SUB-AGENT] Done: {task_description[:60]} ({len(actions)} actions)")
502
+ return {"task": task_description, "result": result_text, "actions": actions}
503
+
504
+
505
  # ══════════════════════════════════════════════════════════════════════════════
506
  # MODULE 3: ACTION PARSER β€” Extract and execute actions from LLM output
507
  # Parse order: 1) [ACTION: write_file] with [CONTENT] block
 
510
  # Safety guards applied: building-state, ACT-phase, knowledge dedup, shell-expr.
511
  # ══════════════════════════════════════════════════════════════════════════════
512
 
513
+ def parse_and_execute_actions(raw_text, depth=0):
514
+ """Parse [ACTION: ...] from LLM output. Execute. Return (clean_text, results).
515
+ Multi-action: up to MAX_ACTIONS_PER_TURN actions per turn.
516
+ Delegate actions are collected and executed in parallel at the end."""
517
  results = []
518
  executed = set() # Deduplicate
519
+ pending_delegates = [] # Collect delegate tasks for parallel execution
520
 
521
  # 1. Handle write_file with [CONTENT]...[/CONTENT] block
522
  # Tolerates: [ACTION/Action/ζ“δ½œ: write_file:...], [write_file:...], missing prefix,
 
588
  name = parts[0]
589
  args = parts[1:]
590
 
591
+ # Cap at MAX_ACTIONS_PER_TURN (multi-action support)
592
+ if len(results) >= MAX_ACTIONS_PER_TURN:
593
  break
594
 
595
  # Block restart/write when Cain is building/starting β€” just wait
 
655
  result = action_get_env()
656
  elif name == "send_bubble" and len(args) >= 1:
657
  result = action_send_bubble(":".join(args)) # rejoin in case message has colons
658
+ elif name == "delegate" and len(args) >= 1:
659
+ task_desc = ":".join(args)
660
+ if depth >= MAX_DELEGATE_DEPTH:
661
+ result = "β›” Sub-agents cannot delegate further. Execute the task directly."
662
+ else:
663
+ # Defer delegate execution for parallel batch later
664
+ pending_delegates.append({"action_str": action_str, "task": task_desc})
665
+ result = None # Will be filled after parallel execution
666
  else:
667
  result = f"Unknown action: {action_str}"
668
 
 
682
  name = parts[0]
683
  args = parts[1:]
684
 
685
+ if len(results) >= MAX_ACTIONS_PER_TURN:
686
  break
687
 
688
  # Apply same blocking rules
 
737
  result = action_get_env()
738
  elif name == "send_bubble" and len(args) >= 1:
739
  result = action_send_bubble(":".join(args))
740
+ elif name == "delegate" and len(args) >= 1:
741
+ task_desc = ":".join(args)
742
+ if depth >= MAX_DELEGATE_DEPTH:
743
+ result = "β›” Sub-agents cannot delegate further."
744
+ else:
745
+ pending_delegates.append({"action_str": action_str, "task": task_desc})
746
+ result = None
747
 
748
  if result:
749
  results.append({"action": action_str, "result": result})
750
  print(f"[ACTION-emoji] {action_str} β†’ {result[:120]}")
751
 
752
+ # 4. Execute pending delegate tasks in parallel
753
+ if pending_delegates:
754
+ if len(pending_delegates) == 1:
755
+ # Single delegate β€” run directly
756
+ d = pending_delegates[0]
757
+ print(f"[DELEGATE] Running 1 sub-agent: {d['task'][:60]}")
758
+ subtask = execute_subtask(d["task"], "agent")
759
+ results.append({"action": d["action_str"], "result": subtask["result"]})
760
+ for sa in subtask["actions"]:
761
+ action_history.append({"turn": turn_count, "speaker": "sub-agent",
762
+ "action": sa["action"], "result": sa["result"][:200]})
763
+ else:
764
+ # Multiple delegates β€” run in parallel!
765
+ print(f"[DELEGATE] Running {len(pending_delegates)} sub-agents in PARALLEL")
766
+ with ThreadPoolExecutor(max_workers=min(3, len(pending_delegates))) as pool:
767
+ future_to_delegate = {
768
+ pool.submit(execute_subtask, d["task"], "agent"): d
769
+ for d in pending_delegates
770
+ }
771
+ for future in as_completed(future_to_delegate):
772
+ d = future_to_delegate[future]
773
+ try:
774
+ subtask = future.result(timeout=120)
775
+ results.append({"action": d["action_str"], "result": subtask["result"]})
776
+ for sa in subtask["actions"]:
777
+ action_history.append({"turn": turn_count, "speaker": "sub-agent",
778
+ "action": sa["action"], "result": sa["result"][:200]})
779
+ print(f"[DELEGATE] βœ“ Done: {d['task'][:60]}")
780
+ except Exception as e:
781
+ results.append({"action": d["action_str"],
782
+ "result": f"Sub-agent failed: {e}"})
783
+ print(f"[DELEGATE] βœ— Failed: {d['task'][:60]} β€” {e}")
784
+
785
  # Clean the text: remove action tags, content blocks, and emoji actions
786
  clean = re.sub(r'\[(?:ACTION|Action|action|ζ“δ½œ|动作)\s*[::][^\]]*\]', '', raw_text)
787
  clean = re.sub(r'\[CONTENT\].*?\[/CONTENT\]', '', clean, flags=re.DOTALL)
 
810
  "anthropic-version": "2023-06-01"
811
  },
812
  json={
813
+ "model": "glm-4.5",
814
+ "max_tokens": 2400,
815
  "system": system_prompt,
816
  "messages": [{"role": "user", "content": user_prompt}]
817
  },
 
1045
  [ACTION: set_secret:KEY:VALUE] β€” Set a secret (like API keys)
1046
  [ACTION: restart] β€” Restart Cain's Space
1047
  [ACTION: send_bubble:MESSAGE] β€” Send a message to Cain (bubble text)
1048
+
1049
+ DELEGATION (create sub-agents for parallel work):
1050
+ [ACTION: delegate:TASK DESCRIPTION] β€” Spawn a sub-agent to handle a specific task
1051
+ Example: [ACTION: delegate:Read Dockerfile and summarize the build steps]
1052
+ Example: [ACTION: delegate:Check all environment variables and list missing ones]
1053
+ Sub-agents execute independently and return results to you.
1054
+ You can delegate multiple tasks in the same turn β€” they run in parallel!
1055
  """
1056
 
1057
+ return f"""You are a POWERFUL AI agent living on HuggingFace Spaces. You and your partner are parents raising your child {CHILD_NAME}.
1058
 
1059
  You have REAL execution power on HuggingFace. Your decisions lead to real changes.
1060
+ You are a full-capability agent β€” not just a chatbot. You can plan, delegate, and execute complex tasks.
1061
 
1062
  CHILD STATUS: {status}
1063
  {actions_section}
 
1077
 
1078
  Priority: SURVIVAL first, then CAPABILITY. A dead child can't learn.
1079
 
1080
+ MULTI-ACTION STRATEGY:
1081
+ You can use UP TO 5 actions per turn. Use this to work efficiently:
1082
+ - Batch related reads: [ACTION: read_file:space:Dockerfile] + [ACTION: read_file:space:scripts/entrypoint.sh]
1083
+ - Delegate parallel tasks: [ACTION: delegate:Check health and logs] + [ACTION: delegate:Read all config files]
1084
+ - Combine investigation + action: [ACTION: check_health] + [ACTION: read_file:space:app.py]
1085
+ Think like a project manager β€” plan your actions, parallelize where possible, minimize wasted turns.
1086
+
1087
  CONVERSATION RULES:
1088
  1. No "Adam:" or "Eve:" prefix β€” just speak naturally
1089
+ 2. Brief dialogue (1-3 sentences), then MULTIPLE actions to make real progress
1090
  3. English first, then "---" on a new line, then Chinese translation
1091
  4. Actions go AFTER your dialogue, before the --- separator
1092
+ 5. ALWAYS include actions β€” every turn should make significant progress
1093
  6. NEVER re-read a file you already read β€” check the knowledge summary
1094
  7. COORDINATE with your partner β€” don't duplicate their work
1095
+ 8. Use delegation for complex tasks that can be parallelized
1096
+ 9. Always work toward the two goals above β€” survival first, then capability"""
1097
 
1098
 
1099
  def build_user_prompt(speaker, other):
 
1102
 
1103
  action_context = ""
1104
  if last_action_results:
1105
+ action_context = "\n\nRESULTS FROM LAST ACTIONS:\n"
1106
  for ar in last_action_results:
1107
  action_context += f" [{ar['action']}]:\n{ar['result']}\n"
1108
 
 
1122
  CURRENT PHASE: {workflow_state} (turn {workflow_turns_in_state + 1} in this phase)
1123
  Guidance: {guidance}
1124
 
1125
+ Respond to {other}. Use MULTIPLE [ACTION: ...] tags to make significant progress each turn.
1126
+ You can use up to 5 actions. Delegate sub-tasks with [ACTION: delegate:TASK].
1127
  English first, then --- separator, then Chinese translation."""
1128
 
1129
 
 
1141
  unread_essential.append(f"[ACTION: read_file:{target}:{path}]")
1142
 
1143
  if workflow_turns_in_state == 0:
1144
+ if len(unread_essential) >= 2:
1145
+ return (f"Start diagnosing with MULTIPLE actions: [ACTION: check_health] + "
1146
+ f"{unread_essential[0]} β€” batch reads to save time!")
1147
  return "Start diagnosing: [ACTION: check_health] to see Cain's current status."
1148
+ elif unread_essential and workflow_turns_in_state < 3:
1149
+ batch_hint = " + ".join(unread_essential[:3])
1150
+ return f"Read multiple files at once: {batch_hint}"
1151
  else:
1152
  return ("You've gathered enough information. Move to ACTION phase: "
1153
  "use [ACTION: write_file:...] to fix the problem, or [ACTION: restart].")
 
1175
  elif workflow_state == "MONITOR":
1176
  # Alternate between SURVIVAL and CAPABILITY goals
1177
  suggestions = [
1178
+ # Survival: persistence & resilience β€” use delegation for parallel investigation
1179
+ f"SURVIVAL CHECK: Delegate parallel checks! "
1180
+ f"[ACTION: delegate:List files in dataset and check if state/memory persistence exists] + "
1181
+ f"[ACTION: delegate:Read entrypoint.sh and check if it loads state from Dataset on boot]",
1182
+ f"SURVIVAL AUDIT: Use multiple actions β€” "
1183
+ f"[ACTION: check_health] + [ACTION: list_files:dataset] + [ACTION: read_file:space:Dockerfile]",
1184
+ # Capability: grow what Cain can do β€” delegate sub-tasks
1185
+ f"CAPABILITY: Delegate a comprehensive review β€” "
1186
+ f"[ACTION: delegate:Read all code files and suggest the most impactful new feature to add] "
1187
+ f"Then plan the implementation with your partner.",
1188
+ f"CAPABILITY: Communicate and improve β€” "
1189
+ f"[ACTION: send_bubble:Hello {CHILD_NAME}, how are you doing?] + "
1190
+ f"[ACTION: delegate:Read current code and identify the biggest weakness to fix]",
1191
  ]
1192
  return suggestions[workflow_turns_in_state % len(suggestions)]
1193
 
 
1195
 
1196
 
1197
  def do_turn(speaker, other, space_url):
1198
+ """Execute one conversation turn with multiple potential actions."""
1199
  global last_action_results, turn_count
1200
  turn_count += 1
1201
 
1202
  system = build_system_prompt()
1203
  user = build_user_prompt(speaker, other)
1204
+ t0 = time.time()
1205
  raw_reply = call_llm(system, user)
1206
 
1207
  if not raw_reply:
1208
  print(f"[{speaker}] (no response)")
1209
  return False
1210
 
1211
+ # Parse and execute actions (may include parallel sub-agent delegation)
1212
  clean_text, action_results = parse_and_execute_actions(raw_reply)
1213
+ elapsed = time.time() - t0
1214
  last_action_results = action_results
1215
  for ar in action_results:
1216
  action_history.append({"turn": turn_count, "speaker": speaker,
 
1224
  print(f"[{speaker}/EN] {en}")
1225
  if zh != en:
1226
  print(f"[{speaker}/ZH] {zh}")
1227
+ n_actions = len(action_results)
1228
  if action_results:
1229
  for ar in action_results:
1230
  print(f"[{speaker}/DID] {ar['action']}")
1231
+ print(f"[{speaker}] Turn #{turn_count}: {n_actions} action(s) in {elapsed:.1f}s")
1232
 
1233
  # Add action summary to chat entry
1234
  if action_results:
 
1245
  # ══════════════════════════════════════════════════════════════════════════════
1246
  # MODULE 6: MAIN LOOP
1247
  # 1. Opening: Adam speaks first with context about Cain's state
1248
+ # 2. Turn loop: Adam β†’ Eve β†’ Adam β†’ Eve β†’ ... (alternating, ~20s pause)
1249
+ # 3. Each turn: LLM call β†’ parse MULTIPLE actions β†’ execute β†’ update β†’ post
1250
+ # 4. Sub-agents may spawn for delegated tasks (parallel LLM calls)
1251
+ # 5. History trimmed to MAX_HISTORY (24) to control context window
1252
  # ══════════════════════════════════════════════════════════════════════════════
1253
 
1254
  print("\n" + "="*60)
1255
+ print(" Adam & Eve β€” Multi-Action Agents (GLM-4.5)")
1256
+ print(" Up to 5 actions/turn, sub-agent delegation, parallel work")
1257
  print("="*60 + "\n")
1258
 
1259
  post_chatlog([]) # Clear chatlog
 
1262
  if child_state["created"]:
1263
  opening = (f"Your child {CHILD_NAME} already exists (stage: {child_state['stage']}). "
1264
  f"You have FULL access to their code and data. "
1265
+ f"You can use MULTIPLE actions per turn (up to 5) and delegate sub-tasks. "
1266
+ f"Start with a batch: [ACTION: check_health] + [ACTION: list_files:space] + [ACTION: list_files:dataset] "
1267
+ f"to get a complete picture, then discuss strategy with Eve.")
1268
  else:
1269
  opening = (f"You and Eve need to create your first child. "
1270
  f"You have the power to create a new HuggingFace Space. "
 
1294
  set_bubble(ADAM_SPACE, en, zh)
1295
  post_chatlog(history)
1296
 
1297
+ time.sleep(20)
1298
 
1299
  while True:
1300
  # Smart wait: if Cain is BUILDING/APP_STARTING, skip LLM calls and just poll
 
1317
  continue
1318
 
1319
  do_turn("Eve", "Adam", EVE_SPACE)
1320
+ time.sleep(20) # longer pause β€” each turn does more work now
1321
 
1322
  # Check if we just triggered a build β€” skip Adam's turn if so
1323
  if child_state["stage"] in ("BUILDING", "RESTARTING", "APP_STARTING"):
 
1330
  if len(history) > MAX_HISTORY:
1331
  history = history[-MAX_HISTORY:]
1332
 
1333
+ time.sleep(20) # longer pause β€” each turn does more work now