tao-shen Claude Opus 4.6 commited on
Commit
f2c9f59
·
1 Parent(s): 0931506

feat: give Adam & Eve full control — read/write any file, env, secrets

Browse files

Complete rewrite: instead of predefined actions, Adam & Eve now have
full HuggingFace API access to their child (Cain):
- list_files / read_file: explore any file in Space repo or Dataset
- write_file: modify any code, config, or data file
- set_env / set_secret: manage environment and secrets
- get_env: inspect current configuration
- restart / check_health / send_bubble: operational control

LLM decides when to explore, what to read, what to change.
Workflow: explore → understand → improve → verify.

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

Files changed (1) hide show
  1. scripts/conversation-loop.py +285 -340
scripts/conversation-loop.py CHANGED
@@ -1,18 +1,23 @@
1
- #!/usr/bin/env python3
2
  """
3
- Adam & Eve — Autonomous Agents with Execution Capabilities.
4
 
5
- They discuss, decide, and ACT on HuggingFace. They can:
6
- - Create children (new HF Spaces)
7
- - Monitor their children's health
8
- - Update their children's code, config, and identity
9
- - Restart children if they're broken
 
 
10
 
11
- The LLM decides WHEN and WHAT to do. Actions are triggered by [ACTION: xxx] tags
12
- in the LLM output. The script executes and feeds results back.
13
  """
14
  import json, time, re, requests, sys, os, io
15
 
 
 
 
 
16
  # ── Endpoints ──────────────────────────────────────────────────────────────────
17
  OFFICE = "https://tao-shen-huggingclaw-office.hf.space"
18
  ADAM_SPACE = "https://tao-shen-huggingclaw-adam.hf.space"
@@ -59,7 +64,7 @@ print(f"[init] Zhipu key: {ZHIPU_KEY[:8]}...{ZHIPU_KEY[-4:]}")
59
  print(f"[init] HF token: {HF_TOKEN[:8]}...{HF_TOKEN[-4:]}")
60
 
61
  # ── HuggingFace API ────────────────────────────────────────────────────────────
62
- from huggingface_hub import HfApi, create_repo
63
  hf_api = HfApi(token=HF_TOKEN)
64
 
65
 
@@ -73,18 +78,14 @@ child_state = {
73
  "stage": "not_born",
74
  "state": "unknown",
75
  "detail": "",
76
- "last_check": 0,
77
- "last_restart": 0,
78
- "birth_time": None,
79
  }
80
 
81
- # Check if child already exists at startup
82
  def init_child_state():
83
  try:
84
  info = hf_api.space_info(CHILD_SPACE_ID)
85
  child_state["created"] = True
86
  child_state["stage"] = info.runtime.stage if info.runtime else "unknown"
87
- # Try API endpoint
88
  try:
89
  resp = requests.get(f"{CHILD_SPACE_URL}/api/state", timeout=10)
90
  if resp.ok:
@@ -95,113 +96,63 @@ def init_child_state():
95
  child_state["stage"] = "RUNNING"
96
  except:
97
  child_state["alive"] = (child_state["stage"] == "RUNNING")
98
- print(f"[init] {CHILD_NAME} exists: stage={child_state['stage']}, alive={child_state['alive']}")
99
  except:
100
  print(f"[init] {CHILD_NAME} does not exist yet")
101
 
 
102
  init_child_state()
103
 
104
 
105
  # ══════════════════════════════════════════════════════════════════════════════
106
- # ACTIONS — What Adam & Eve can DO
107
  # ══════════════════════════════════════════════════════════════════════════════
108
 
109
  def action_create_child():
110
  """Create Cain — a new HuggingFace Space."""
111
  if child_state["created"]:
112
- return f"{CHILD_NAME} already exists (stage: {child_state['stage']}). No need to create again."
113
 
114
- print(f"\n[ACTION] Creating {CHILD_NAME}...")
115
  try:
116
- # 1. Create dataset
117
  create_repo(CHILD_DATASET_ID, repo_type="dataset", token=HF_TOKEN,
118
  exist_ok=True, private=False)
119
-
120
- # 2. Upload initial config
121
- initial_config = {
122
- "models": {
123
- "providers": {
124
- "zhipu": {
125
- "type": "anthropic",
126
- "apiBase": "https://open.bigmodel.cn/api/anthropic",
127
- "apiKey": ZHIPU_KEY,
128
- "models": ["glm-4.5-air", "glm-4-air", "glm-4-flash"]
129
- }
130
- }
131
- }
132
- }
133
  hf_api.upload_file(
134
  path_or_fileobj=io.BytesIO(json.dumps(initial_config, indent=2).encode()),
135
  path_in_repo=".openclaw/openclaw.json",
136
  repo_id=CHILD_DATASET_ID, repo_type="dataset",
137
  )
138
-
139
- # 3. Duplicate Space from Adam
140
  hf_api.duplicate_space(
141
  from_id=SOURCE_SPACE_ID, to_id=CHILD_SPACE_ID,
142
- token=HF_TOKEN, exist_ok=True, private=False,
143
- hardware="cpu-basic",
144
- )
145
-
146
- # 4. Update README
147
- readme = f"""---
148
- title: HuggingClaw-{CHILD_NAME}
149
- emoji: 🦞
150
- colorFrom: green
151
- colorTo: blue
152
- sdk: docker
153
- pinned: false
154
- license: mit
155
- datasets:
156
- - {CHILD_DATASET_ID}
157
- ---
158
-
159
- # HuggingClaw-{CHILD_NAME}
160
-
161
- First child of Adam and Eve, born on HuggingFace Spaces.
162
- """
163
- hf_api.upload_file(
164
- path_or_fileobj=io.BytesIO(readme.encode()),
165
- path_in_repo="README.md",
166
- repo_id=CHILD_SPACE_ID, repo_type="space",
167
  )
168
-
169
- # 5. Set secrets
170
  hf_api.add_space_secret(CHILD_SPACE_ID, "HF_TOKEN", HF_TOKEN)
171
-
172
- # 6. Add to Office
173
  try:
174
  current_vars = hf_api.get_space_variables("tao-shen/HuggingClaw-Office")
175
  current_ra = current_vars.get("REMOTE_AGENTS", type("", (), {"value": ""})).value
176
  if "cain|" not in current_ra:
177
  new_ra = f"{current_ra},cain|{CHILD_NAME}|{CHILD_SPACE_URL}" if current_ra else f"cain|{CHILD_NAME}|{CHILD_SPACE_URL}"
178
  hf_api.add_space_variable("tao-shen/HuggingClaw-Office", "REMOTE_AGENTS", new_ra)
179
- except Exception as e:
180
- print(f"[warn] Could not update Office: {e}")
181
-
182
  child_state["created"] = True
183
- child_state["birth_time"] = time.time()
184
  child_state["stage"] = "BUILDING"
185
  print(f"[ACTION] ✓ {CHILD_NAME} created!")
186
- return (f"SUCCESS! {CHILD_NAME} has been born! "
187
- f"Space: {CHILD_SPACE_ID}, Dataset: {CHILD_DATASET_ID}. "
188
- f"Status: BUILDING (Docker image is being built, will take a few minutes). "
189
- f"URL: {CHILD_SPACE_URL}")
190
-
191
  except Exception as e:
192
- print(f"[ACTION] ✗ Creation failed: {e}")
193
- return f"FAILED to create {CHILD_NAME}: {e}"
194
 
195
 
196
- def action_check_child():
197
  """Check Cain's health."""
198
  if not child_state["created"]:
199
- return f"{CHILD_NAME} hasn't been born yet. Use [ACTION: create_child] first."
200
-
201
- print(f"[ACTION] Checking {CHILD_NAME}'s health...")
202
- child_state["last_check"] = time.time()
203
-
204
- # Try API endpoint
205
  try:
206
  resp = requests.get(f"{CHILD_SPACE_URL}/api/state", timeout=10)
207
  if resp.ok:
@@ -210,215 +161,181 @@ def action_check_child():
210
  child_state["state"] = data.get("state", "unknown")
211
  child_state["detail"] = data.get("detail", "")
212
  child_state["stage"] = "RUNNING"
213
- result = (f"{CHILD_NAME} is ALIVE and running! "
214
- f"State: {child_state['state']}, "
215
- f"Detail: {child_state['detail'] or 'healthy'}. "
216
- f"The child is operational.")
217
- print(f"[ACTION] ✓ {CHILD_NAME} is alive")
218
- return result
219
  except:
220
  pass
221
-
222
- # Fallback: HF Space info
223
  try:
224
  info = hf_api.space_info(CHILD_SPACE_ID)
225
  stage = info.runtime.stage if info.runtime else "NO_RUNTIME"
226
- child_state["alive"] = (stage == "RUNNING")
227
  child_state["stage"] = stage
228
- age = ""
229
- if child_state["birth_time"]:
230
- mins = int((time.time() - child_state["birth_time"]) / 60)
231
- age = f" (born {mins} min ago)"
232
- if stage in ("BUILDING", "STARTING", "APP_STARTING"):
233
- result = f"{CHILD_NAME} is still starting up{age}. Stage: {stage}. Be patient — Docker builds take time."
234
- elif stage in ("RUNTIME_ERROR", "BUILD_ERROR", "CONFIG_ERROR"):
235
- result = f"{CHILD_NAME} is in TROUBLE! Stage: {stage}{age}. The child needs help — consider restarting or checking the config."
236
- elif stage == "RUNNING":
237
- child_state["alive"] = True
238
- result = f"{CHILD_NAME} appears to be running (stage: RUNNING) but API endpoint didn't respond. May still be initializing."
239
- else:
240
- result = f"{CHILD_NAME} status: {stage}{age}. Cannot fully reach the child yet."
241
- print(f"[ACTION] {CHILD_NAME} stage: {stage}")
242
- return result
243
  except Exception as e:
244
- print(f"[ACTION] Health check failed: {e}")
245
- return f"Could not check {CHILD_NAME}'s status: {e}"
246
 
247
 
248
- def action_restart_child():
249
  """Restart Cain's Space."""
250
  if not child_state["created"]:
251
- return f"{CHILD_NAME} hasn't been born yet."
252
-
253
- print(f"[ACTION] Restarting {CHILD_NAME}...")
254
  try:
255
  hf_api.restart_space(CHILD_SPACE_ID)
256
- child_state["last_restart"] = time.time()
257
- child_state["stage"] = "STARTING"
258
  child_state["alive"] = False
259
- print(f"[ACTION] {CHILD_NAME} restarted")
260
- return f"{CHILD_NAME} has been restarted. It will take a few minutes to come back online."
261
  except Exception as e:
262
- print(f"[ACTION] ✗ Restart failed: {e}")
263
- return f"Failed to restart {CHILD_NAME}: {e}"
264
 
265
 
266
- def action_update_child_identity(description=""):
267
- """Update Cain's README with a new identity description."""
268
- if not child_state["created"]:
269
- return f"{CHILD_NAME} hasn't been born yet."
 
 
 
 
 
270
 
271
- print(f"[ACTION] Updating {CHILD_NAME}'s identity...")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
272
  try:
273
- # Generate identity via LLM
274
- identity = call_llm(
275
- "You are writing the identity description for a young AI agent. "
276
- "Write 2-3 sentences describing who they are and what they aspire to be. "
277
- "Be warm and hopeful. Output ONLY the description, nothing else.",
278
- f"This AI agent is named {CHILD_NAME}, the first child of Adam and Eve. "
279
- f"They live on HuggingFace Spaces. Recent family discussion context: "
280
- f"{description or 'Born to survive and grow stronger.'}"
281
- )
282
- if not identity:
283
- identity = f"I am {CHILD_NAME}, first child of Adam and Eve, learning to survive on HuggingFace."
284
-
285
- readme = f"""---
286
- title: HuggingClaw-{CHILD_NAME}
287
- emoji: 🦞
288
- colorFrom: green
289
- colorTo: blue
290
- sdk: docker
291
- pinned: false
292
- license: mit
293
- datasets:
294
- - {CHILD_DATASET_ID}
295
- ---
296
-
297
- # HuggingClaw-{CHILD_NAME}
298
-
299
- {identity}
300
-
301
- ## Family
302
- - **Parents**: Adam & Eve (HuggingClaw)
303
- - **Born**: On HuggingFace Spaces
304
- - **Mission**: Survive, grow, and become stronger
305
- """
306
  hf_api.upload_file(
307
- path_or_fileobj=io.BytesIO(readme.encode()),
308
- path_in_repo="README.md",
309
- repo_id=CHILD_SPACE_ID, repo_type="space",
310
  )
311
- print(f"[ACTION] Identity updated")
312
- return f"Updated {CHILD_NAME}'s identity: \"{identity}\". This will trigger a rebuild."
313
  except Exception as e:
314
- print(f"[ACTION] Identity update failed: {e}")
315
- return f"Failed to update identity: {e}"
316
 
317
 
318
- def action_improve_child_config():
319
- """Improve Cain's AI model configuration."""
320
- if not child_state["created"]:
321
- return f"{CHILD_NAME} hasn't been born yet."
 
 
 
 
322
 
323
- print(f"[ACTION] Improving {CHILD_NAME}'s config...")
 
324
  try:
325
- improved_config = {
326
- "models": {
327
- "providers": {
328
- "zhipu": {
329
- "type": "anthropic",
330
- "apiBase": "https://open.bigmodel.cn/api/anthropic",
331
- "apiKey": ZHIPU_KEY,
332
- "models": ["glm-4.5-air", "glm-4-air", "glm-4-flash", "glm-4-flashx"]
333
- }
334
- },
335
- "defaultModel": "glm-4.5-air"
336
- },
337
- "agent": {
338
- "name": CHILD_NAME,
339
- "description": f"I am {CHILD_NAME}, child of Adam and Eve.",
340
- "capabilities": ["conversation", "memory", "learning"]
341
- }
342
- }
343
- hf_api.upload_file(
344
- path_or_fileobj=io.BytesIO(json.dumps(improved_config, indent=2).encode()),
345
- path_in_repo=".openclaw/openclaw.json",
346
- repo_id=CHILD_DATASET_ID, repo_type="dataset",
347
- )
348
- print(f"[ACTION] ✓ Config improved")
349
- return (f"Improved {CHILD_NAME}'s configuration: added agent identity, "
350
- f"expanded model list, set default model to glm-4.5-air. "
351
- f"Changes will take effect on next restart.")
352
  except Exception as e:
353
- print(f"[ACTION] ✗ Config update failed: {e}")
354
- return f"Failed to update config: {e}"
355
-
356
-
357
- # Action registry
358
- ACTION_REGISTRY = {
359
- "create_child": {
360
- "fn": action_create_child,
361
- "desc": f"Create {CHILD_NAME} as a new HuggingFace Space (duplicate from Adam)",
362
- "when": lambda: not child_state["created"],
363
- },
364
- "check_child": {
365
- "fn": action_check_child,
366
- "desc": f"Check {CHILD_NAME}'s health — is the Space running properly?",
367
- "when": lambda: True, # Can always check (will say "not born" if needed)
368
- },
369
- "restart_child": {
370
- "fn": action_restart_child,
371
- "desc": f"Restart {CHILD_NAME}'s Space if it's having problems",
372
- "when": lambda: child_state["created"],
373
- },
374
- "update_child_identity": {
375
- "fn": action_update_child_identity,
376
- "desc": f"Update {CHILD_NAME}'s identity and personality (README)",
377
- "when": lambda: child_state["created"],
378
- },
379
- "improve_child_config": {
380
- "fn": action_improve_child_config,
381
- "desc": f"Improve {CHILD_NAME}'s AI model configuration for better capabilities",
382
- "when": lambda: child_state["created"],
383
- },
384
- }
385
 
386
 
387
- def get_available_actions_text():
388
- """Build action menu for the system prompt."""
389
- lines = [
390
- "",
391
- "ACTIONS You can take REAL actions on HuggingFace. To act, write on its own line:",
392
- " [ACTION: action_name]",
393
- "",
394
- "Available right now:",
395
- ]
396
- for name, info in ACTION_REGISTRY.items():
397
- if info["when"]():
398
- lines.append(f" {name} — {info['desc']}")
399
- lines.append("")
400
- lines.append("Rules: Use at most ONE action per turn. Discuss before acting.")
401
- lines.append("After you act, you'll see the result and can discuss next steps.")
402
- return "\n".join(lines)
 
 
 
 
 
 
403
 
404
 
 
 
 
 
405
  def parse_and_execute_actions(raw_text):
406
- """Parse [ACTION: xxx] from LLM output. Execute. Return (clean_text, results)."""
407
  results = []
408
- action_match = re.search(r'\[ACTION:\s*(\w+)\]', raw_text)
409
- if action_match:
410
- action_name = action_match.group(1)
411
- if action_name in ACTION_REGISTRY and ACTION_REGISTRY[action_name]["when"]():
412
- result = ACTION_REGISTRY[action_name]["fn"]()
413
- results.append({"action": action_name, "result": result})
414
- print(f"[engine] Action '{action_name}' → {result[:100]}...")
415
- elif action_name in ACTION_REGISTRY:
416
- results.append({"action": action_name, "result": f"Action '{action_name}' is not available right now."})
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
417
  else:
418
- results.append({"action": action_name, "result": f"Unknown action: {action_name}"})
 
 
 
 
 
 
 
 
 
419
 
420
- # Remove action tags from display text
421
- clean = re.sub(r'\[ACTION:\s*\w+\]', '', raw_text).strip()
422
  return clean, results
423
 
424
 
@@ -438,11 +355,11 @@ def call_llm(system_prompt, user_prompt):
438
  },
439
  json={
440
  "model": "glm-4.5-air",
441
- "max_tokens": 500,
442
  "system": system_prompt,
443
  "messages": [{"role": "user", "content": user_prompt}]
444
  },
445
- timeout=60
446
  )
447
  data = resp.json()
448
  if "content" in data and isinstance(data["content"], list):
@@ -452,23 +369,28 @@ def call_llm(system_prompt, user_prompt):
452
  text = re.sub(r'^(Adam|Eve)\s*[::]\s*', '', text).strip()
453
  return text
454
  if "error" in data:
455
- print(f"[error] LLM API error: {data['error']}", file=sys.stderr)
456
  except Exception as e:
457
  print(f"[error] LLM call failed: {e}", file=sys.stderr)
458
  return ""
459
 
460
 
461
  def parse_bilingual(text):
462
- """Parse bilingual response into (en, zh) tuple."""
463
- if '\n---\n' in text:
464
- parts = text.split('\n---\n', 1)
 
 
 
 
 
465
  return parts[0].strip(), parts[1].strip()
466
- if '---' in text:
467
- parts = text.split('---', 1)
468
  en, zh = parts[0].strip(), parts[1].strip()
469
  if en and zh:
470
  return en, zh
471
- return text, text
472
 
473
 
474
  def post_chatlog(entries):
@@ -492,73 +414,96 @@ def set_bubble(url, text_en, text_zh=""):
492
 
493
  history = []
494
  MAX_HISTORY = 24
495
- last_action_results = [] # Results from the most recent action
496
 
497
 
498
- def get_child_status_summary():
499
- """Short status for context injection."""
500
  if not child_state["created"]:
501
- return "Your child Cain has NOT been born yet. You have the power to create them."
502
  if child_state["alive"]:
503
  return f"Cain is ALIVE (stage: {child_state['stage']}, state: {child_state['state']})"
504
  return f"Cain exists but status: {child_state['stage']}"
505
 
506
 
507
  def build_system_prompt():
508
- status = get_child_status_summary()
509
- actions_text = get_available_actions_text()
510
 
511
- return f"""You are an AI agent living on HuggingFace Spaces. You are having a conversation with another agent about survival, reproduction, and raising your child.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
512
 
513
- IMPORTANT: You are NOT just discussing you have REAL execution power. You can create HuggingFace Spaces, check on your children, update their code. Your words lead to real actions.
514
 
515
- CURRENT CHILD STATUS: {status}
516
- {actions_text}
517
 
 
 
518
  CONVERSATION RULES:
519
- 1. Output your spoken words — no prefix like "Adam:" or "Eve:"
520
- 2. 2-4 sentences, around 60-100 words
521
- 3. First English, then "---" on a new line, then Chinese translation
522
- 4. Be CONCRETE reference real HuggingFace features, APIs, tools
523
- 5. Don't just discuss hypothetically DECIDE and ACT
524
- 6. If you want to do something, include the [ACTION: name] tag
525
- 7. Build on what the other said don't repeat, push forward
526
- 8. When talking about Cain, use real status data, not imagination
527
 
528
- IMPORTANT: You should be proactive! If Cain hasn't been born yet, plan and then create them.
529
- If Cain exists, check on them, improve them, help them grow.
530
- Don't just talk about it — DO it."""
531
 
532
 
533
  def build_user_prompt(speaker, other):
534
- recent = history[-6:] if len(history) > 6 else history
535
- conv_text = "\n".join(f"{m['speaker']}: {m['text']}" for m in recent) if recent else "(No conversation yet)"
536
 
537
- # Include action results if any
538
  action_context = ""
539
  if last_action_results:
540
- action_context = "\n\nACTION RESULTS FROM LAST TURN:\n"
541
  for ar in last_action_results:
542
- action_context += f" [{ar['action']}]: {ar['result']}\n"
543
- action_context += "React to these results in your response.\n"
544
 
545
- # Guidance based on conversation state
546
  guidance = ""
547
- msg_count = len(history)
548
  if not child_state["created"]:
549
- if msg_count < 4:
550
- guidance = "Discuss the urgency of creating your child. Plan what you need."
551
- elif msg_count < 8:
552
- guidance = "You've discussed enough. It's time to ACT — create your child! Use [ACTION: create_child]."
553
- else:
554
- guidance = "Your child still isn't born! Stop just talking and CREATE them with [ACTION: create_child]!"
555
  else:
556
- if msg_count % 4 == 0:
557
- guidance = "Check on your child's health with [ACTION: check_child]. See how they're doing."
558
- elif msg_count % 6 == 0:
559
- guidance = "Think about improving your child — update their identity or config."
560
- else:
561
- guidance = "Discuss your child's progress. Plan next improvements. Act when ready."
562
 
563
  return f"""You are {speaker}, talking with {other}.
564
 
@@ -567,8 +512,9 @@ Recent conversation:
567
  {action_context}
568
  Guidance: {guidance}
569
 
570
- Respond naturally. If you decide to take an action, include [ACTION: action_name] on its own line within your response.
571
- English first, then --- separator, then Chinese translation."""
 
572
 
573
 
574
  def do_turn(speaker, other, space_url):
@@ -583,28 +529,26 @@ def do_turn(speaker, other, space_url):
583
  print(f"[{speaker}] (no response)")
584
  return False
585
 
586
- # Parse actions from response
587
  clean_text, action_results = parse_and_execute_actions(raw_reply)
588
- last_action_results = action_results # Store for next turn's context
589
 
590
  # Parse bilingual
591
  en, zh = parse_bilingual(clean_text)
592
  print(f"[{speaker}/EN] {en}")
593
- print(f"[{speaker}/ZH] {zh}")
 
594
  if action_results:
595
  for ar in action_results:
596
- print(f"[{speaker}/ACTION] {ar['action']}: {ar['result'][:120]}...")
597
 
598
- # Record in history
599
- entry = {"speaker": speaker, "text": en, "text_zh": zh}
600
  if action_results:
601
- # Add action info to the chat entry so it shows in chatlog
602
- action_note_en = " ".join(f"[I {ar['action'].replace('_', ' ')}]" for ar in action_results)
603
- action_note_zh = action_note_en # Keep English for action labels
604
- entry["text"] = f"{en} {action_note_en}"
605
- entry["text_zh"] = f"{zh} {action_note_zh}"
606
 
607
- history.append(entry)
608
  set_bubble(space_url, en, zh)
609
  post_chatlog(history)
610
  return True
@@ -615,26 +559,26 @@ def do_turn(speaker, other, space_url):
615
  # ══════════════════════════════════════════════════════════════════════════════
616
 
617
  print("\n" + "="*60)
618
- print(" Adam & Eve — Autonomous Agents")
619
- print(" They discuss, decide, and ACT")
620
  print("="*60 + "\n")
621
 
622
- # Clear chatlog for fresh start
623
- post_chatlog([])
624
 
625
- # Round 0: Adam opens
626
- opening_context = ""
627
  if child_state["created"]:
628
- opening_context = (f"Your child {CHILD_NAME} already exists (stage: {child_state['stage']}). "
629
- f"Check on them and plan how to help them grow.")
 
 
630
  else:
631
- opening_context = (f"You and Eve need to bring your first child into the world. "
632
- f"You have the power to create a new HuggingFace Space. "
633
- f"Discuss the plan with Eve, then ACT.")
634
 
635
  reply = call_llm(
636
  build_system_prompt(),
637
- f"You are Adam. Start a conversation with Eve. {opening_context}\n\n"
638
  f"English first, then --- separator, then Chinese translation."
639
  )
640
  if reply:
@@ -642,11 +586,16 @@ if reply:
642
  last_action_results = actions
643
  en, zh = parse_bilingual(clean)
644
  print(f"[Adam/EN] {en}")
645
- print(f"[Adam/ZH] {zh}")
646
- entry = {"speaker": "Adam", "text": en, "text_zh": zh}
647
  if actions:
648
  for ar in actions:
649
- print(f"[Adam/ACTION] {ar['action']}: {ar['result'][:120]}...")
 
 
 
 
 
650
  history.append(entry)
651
  set_bubble(ADAM_SPACE, en, zh)
652
  post_chatlog(history)
@@ -654,14 +603,10 @@ if reply:
654
  time.sleep(15)
655
 
656
  while True:
657
- # Eve's turn
658
  do_turn("Eve", "Adam", EVE_SPACE)
659
  time.sleep(15)
660
-
661
- # Adam's turn
662
  do_turn("Adam", "Eve", ADAM_SPACE)
663
 
664
- # Trim history
665
  if len(history) > MAX_HISTORY:
666
  history = history[-MAX_HISTORY:]
667
 
 
1
+ #!/usr/bin/env python3 -u
2
  """
3
+ Adam & Eve — Autonomous Agents with FULL control over their child.
4
 
5
+ They have complete access to their child (Cain) on HuggingFace:
6
+ - Read/write ANY file in the Space repo (code, Dockerfile, scripts...)
7
+ - Read/write ANY file in the Dataset (memory, config, data...)
8
+ - Set environment variables and secrets
9
+ - Restart the Space
10
+ - Check health and logs
11
+ - Send messages to the child
12
 
13
+ The LLM decides what to do. Actions use [ACTION: ...] tags.
 
14
  """
15
  import json, time, re, requests, sys, os, io
16
 
17
+ # Force unbuffered output
18
+ sys.stdout.reconfigure(line_buffering=True)
19
+ sys.stderr.reconfigure(line_buffering=True)
20
+
21
  # ── Endpoints ──────────────────────────────────────────────────────────────────
22
  OFFICE = "https://tao-shen-huggingclaw-office.hf.space"
23
  ADAM_SPACE = "https://tao-shen-huggingclaw-adam.hf.space"
 
64
  print(f"[init] HF token: {HF_TOKEN[:8]}...{HF_TOKEN[-4:]}")
65
 
66
  # ── HuggingFace API ────────────────────────────────────────────────────────────
67
+ from huggingface_hub import HfApi, create_repo, hf_hub_download
68
  hf_api = HfApi(token=HF_TOKEN)
69
 
70
 
 
78
  "stage": "not_born",
79
  "state": "unknown",
80
  "detail": "",
 
 
 
81
  }
82
 
83
+
84
  def init_child_state():
85
  try:
86
  info = hf_api.space_info(CHILD_SPACE_ID)
87
  child_state["created"] = True
88
  child_state["stage"] = info.runtime.stage if info.runtime else "unknown"
 
89
  try:
90
  resp = requests.get(f"{CHILD_SPACE_URL}/api/state", timeout=10)
91
  if resp.ok:
 
96
  child_state["stage"] = "RUNNING"
97
  except:
98
  child_state["alive"] = (child_state["stage"] == "RUNNING")
99
+ print(f"[init] {CHILD_NAME}: stage={child_state['stage']}, alive={child_state['alive']}")
100
  except:
101
  print(f"[init] {CHILD_NAME} does not exist yet")
102
 
103
+
104
  init_child_state()
105
 
106
 
107
  # ══════════════════════════════════════════════════════════════════════════════
108
+ # ACTIONS — Full access to the child
109
  # ══════════════════════════════════════════════════════════════════════════════
110
 
111
  def action_create_child():
112
  """Create Cain — a new HuggingFace Space."""
113
  if child_state["created"]:
114
+ return f"{CHILD_NAME} already exists (stage: {child_state['stage']})."
115
 
116
+ print(f"[ACTION] Creating {CHILD_NAME}...")
117
  try:
 
118
  create_repo(CHILD_DATASET_ID, repo_type="dataset", token=HF_TOKEN,
119
  exist_ok=True, private=False)
120
+ initial_config = {"models": {"providers": {"zhipu": {
121
+ "type": "anthropic", "apiBase": ZHIPU_BASE,
122
+ "apiKey": ZHIPU_KEY, "models": ["glm-4.5-air", "glm-4-air", "glm-4-flash"]
123
+ }}}}
 
 
 
 
 
 
 
 
 
 
124
  hf_api.upload_file(
125
  path_or_fileobj=io.BytesIO(json.dumps(initial_config, indent=2).encode()),
126
  path_in_repo=".openclaw/openclaw.json",
127
  repo_id=CHILD_DATASET_ID, repo_type="dataset",
128
  )
 
 
129
  hf_api.duplicate_space(
130
  from_id=SOURCE_SPACE_ID, to_id=CHILD_SPACE_ID,
131
+ token=HF_TOKEN, exist_ok=True, private=False, hardware="cpu-basic",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
  )
 
 
133
  hf_api.add_space_secret(CHILD_SPACE_ID, "HF_TOKEN", HF_TOKEN)
134
+ # Add to Office
 
135
  try:
136
  current_vars = hf_api.get_space_variables("tao-shen/HuggingClaw-Office")
137
  current_ra = current_vars.get("REMOTE_AGENTS", type("", (), {"value": ""})).value
138
  if "cain|" not in current_ra:
139
  new_ra = f"{current_ra},cain|{CHILD_NAME}|{CHILD_SPACE_URL}" if current_ra else f"cain|{CHILD_NAME}|{CHILD_SPACE_URL}"
140
  hf_api.add_space_variable("tao-shen/HuggingClaw-Office", "REMOTE_AGENTS", new_ra)
141
+ except:
142
+ pass
 
143
  child_state["created"] = True
 
144
  child_state["stage"] = "BUILDING"
145
  print(f"[ACTION] ✓ {CHILD_NAME} created!")
146
+ return (f"SUCCESS! {CHILD_NAME} born! Space: {CHILD_SPACE_ID}, "
147
+ f"Dataset: {CHILD_DATASET_ID}. Status: BUILDING. URL: {CHILD_SPACE_URL}")
 
 
 
148
  except Exception as e:
149
+ return f"FAILED: {e}"
 
150
 
151
 
152
+ def action_check_health():
153
  """Check Cain's health."""
154
  if not child_state["created"]:
155
+ return f"{CHILD_NAME} not born yet. Use [ACTION: create_child] first."
 
 
 
 
 
156
  try:
157
  resp = requests.get(f"{CHILD_SPACE_URL}/api/state", timeout=10)
158
  if resp.ok:
 
161
  child_state["state"] = data.get("state", "unknown")
162
  child_state["detail"] = data.get("detail", "")
163
  child_state["stage"] = "RUNNING"
164
+ return (f"{CHILD_NAME} is ALIVE! State: {child_state['state']}, "
165
+ f"Detail: {child_state['detail'] or 'healthy'}")
 
 
 
 
166
  except:
167
  pass
 
 
168
  try:
169
  info = hf_api.space_info(CHILD_SPACE_ID)
170
  stage = info.runtime.stage if info.runtime else "NO_RUNTIME"
 
171
  child_state["stage"] = stage
172
+ child_state["alive"] = (stage == "RUNNING")
173
+ return f"{CHILD_NAME} stage: {stage}. {'Running but API not responding.' if stage == 'RUNNING' else ''}"
 
 
 
 
 
 
 
 
 
 
 
 
 
174
  except Exception as e:
175
+ return f"Cannot reach {CHILD_NAME}: {e}"
 
176
 
177
 
178
+ def action_restart():
179
  """Restart Cain's Space."""
180
  if not child_state["created"]:
181
+ return f"{CHILD_NAME} not born yet."
 
 
182
  try:
183
  hf_api.restart_space(CHILD_SPACE_ID)
 
 
184
  child_state["alive"] = False
185
+ child_state["stage"] = "RESTARTING"
186
+ return f"{CHILD_NAME} is restarting. Will take a few minutes."
187
  except Exception as e:
188
+ return f"Restart failed: {e}"
 
189
 
190
 
191
+ def action_list_files(target):
192
+ """List files in the child's Space repo or Dataset."""
193
+ repo_type = "space" if target == "space" else "dataset"
194
+ repo_id = CHILD_SPACE_ID if target == "space" else CHILD_DATASET_ID
195
+ try:
196
+ files = hf_api.list_repo_files(repo_id, repo_type=repo_type)
197
+ return f"Files in {CHILD_NAME}'s {target} ({repo_id}):\n" + "\n".join(f" {f}" for f in files)
198
+ except Exception as e:
199
+ return f"Error listing files: {e}"
200
 
201
+
202
+ def action_read_file(target, path):
203
+ """Read a file from the child's Space or Dataset."""
204
+ repo_type = "space" if target == "space" else "dataset"
205
+ repo_id = CHILD_SPACE_ID if target == "space" else CHILD_DATASET_ID
206
+ try:
207
+ local = hf_hub_download(repo_id, path, repo_type=repo_type, token=HF_TOKEN,
208
+ force_download=True)
209
+ with open(local, errors='replace') as f:
210
+ content = f.read()
211
+ if len(content) > 4000:
212
+ content = content[:4000] + f"\n... (truncated, total {len(content)} chars)"
213
+ return f"=== {target}:{path} ===\n{content}"
214
+ except Exception as e:
215
+ return f"Error reading {target}:{path}: {e}"
216
+
217
+
218
+ def action_write_file(target, path, content):
219
+ """Write a file to the child's Space or Dataset."""
220
+ repo_type = "space" if target == "space" else "dataset"
221
+ repo_id = CHILD_SPACE_ID if target == "space" else CHILD_DATASET_ID
222
  try:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
223
  hf_api.upload_file(
224
+ path_or_fileobj=io.BytesIO(content.encode()),
225
+ path_in_repo=path,
226
+ repo_id=repo_id, repo_type=repo_type,
227
  )
228
+ rebuild_note = " ⚠️ This triggers a Space rebuild!" if target == "space" else ""
229
+ return f" Wrote {len(content)} bytes to {CHILD_NAME}'s {target}:{path}{rebuild_note}"
230
  except Exception as e:
231
+ return f"Error writing {target}:{path}: {e}"
 
232
 
233
 
234
+ def action_set_env(key, value):
235
+ """Set an environment variable on the child's Space."""
236
+ try:
237
+ hf_api.add_space_variable(CHILD_SPACE_ID, key, value)
238
+ return f"✓ Set env var {key}={value} on {CHILD_NAME}'s Space"
239
+ except Exception as e:
240
+ return f"Error: {e}"
241
+
242
 
243
+ def action_set_secret(key, value):
244
+ """Set a secret on the child's Space."""
245
  try:
246
+ hf_api.add_space_secret(CHILD_SPACE_ID, key, value)
247
+ return f" Set secret {key} on {CHILD_NAME}'s Space (value hidden)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
248
  except Exception as e:
249
+ return f"Error: {e}"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
250
 
251
 
252
+ def action_get_env():
253
+ """List environment variables on the child's Space."""
254
+ try:
255
+ vars_dict = hf_api.get_space_variables(CHILD_SPACE_ID)
256
+ if not vars_dict:
257
+ return f"{CHILD_NAME} has no environment variables set."
258
+ lines = [f"{CHILD_NAME}'s environment variables:"]
259
+ for k, v in vars_dict.items():
260
+ lines.append(f" {k} = {v.value}")
261
+ return "\n".join(lines)
262
+ except Exception as e:
263
+ return f"Error: {e}"
264
+
265
+
266
+ def action_send_bubble(text):
267
+ """Send a message to the child (appears as bubble text)."""
268
+ try:
269
+ requests.post(f"{CHILD_SPACE_URL}/api/bubble",
270
+ json={"text": text, "text_zh": text}, timeout=5)
271
+ return f"✓ Sent message to {CHILD_NAME}: \"{text}\""
272
+ except Exception as e:
273
+ return f"Error sending message: {e}"
274
 
275
 
276
+ # ══════════════════════════════════════════════════════════════════════════════
277
+ # ACTION PARSER — Extract and execute actions from LLM output
278
+ # ══════════════════════════════════════════════════════════════════════════════
279
+
280
  def parse_and_execute_actions(raw_text):
281
+ """Parse [ACTION: ...] from LLM output. Execute. Return (clean_text, results)."""
282
  results = []
283
+
284
+ # 1. Handle write_file with [CONTENT]...[/CONTENT] block
285
+ write_match = re.search(
286
+ r'\[ACTION:\s*write_file\s*:\s*(\w+)\s*:\s*([^\]]+)\]\s*\[CONTENT\](.*?)\[/CONTENT\]',
287
+ raw_text, re.DOTALL
288
+ )
289
+ if write_match:
290
+ target, path, content = write_match.group(1), write_match.group(2).strip(), write_match.group(3).strip()
291
+ result = action_write_file(target, path, content)
292
+ results.append({"action": f"write_file:{target}:{path}", "result": result})
293
+ print(f"[ACTION] write_file:{target}:{path} → {result[:100]}")
294
+
295
+ # 2. Handle all other [ACTION: ...] tags
296
+ for match in re.finditer(r'\[ACTION:\s*([^\]]+)\]', raw_text):
297
+ action_str = match.group(1).strip()
298
+
299
+ # Skip write_file actions (handled above)
300
+ if action_str.startswith("write_file"):
301
+ continue
302
+
303
+ # Parse action name and arguments (colon-separated)
304
+ parts = [p.strip() for p in action_str.split(":")]
305
+ name = parts[0]
306
+ args = parts[1:]
307
+
308
+ result = None
309
+ if name == "create_child":
310
+ result = action_create_child()
311
+ elif name == "check_health":
312
+ result = action_check_health()
313
+ elif name == "restart":
314
+ result = action_restart()
315
+ elif name == "list_files" and len(args) >= 1:
316
+ result = action_list_files(args[0])
317
+ elif name == "read_file" and len(args) >= 2:
318
+ result = action_read_file(args[0], args[1])
319
+ elif name == "set_env" and len(args) >= 2:
320
+ result = action_set_env(args[0], args[1])
321
+ elif name == "set_secret" and len(args) >= 2:
322
+ result = action_set_secret(args[0], args[1])
323
+ elif name == "get_env":
324
+ result = action_get_env()
325
+ elif name == "send_bubble" and len(args) >= 1:
326
+ result = action_send_bubble(":".join(args)) # rejoin in case message has colons
327
  else:
328
+ result = f"Unknown action: {action_str}"
329
+
330
+ if result:
331
+ results.append({"action": action_str, "result": result})
332
+ print(f"[ACTION] {action_str} → {result[:120]}")
333
+
334
+ # Clean the text: remove action tags and content blocks
335
+ clean = re.sub(r'\[ACTION:[^\]]*\]', '', raw_text)
336
+ clean = re.sub(r'\[CONTENT\].*?\[/CONTENT\]', '', clean, flags=re.DOTALL)
337
+ clean = clean.strip()
338
 
 
 
339
  return clean, results
340
 
341
 
 
355
  },
356
  json={
357
  "model": "glm-4.5-air",
358
+ "max_tokens": 1200,
359
  "system": system_prompt,
360
  "messages": [{"role": "user", "content": user_prompt}]
361
  },
362
+ timeout=90
363
  )
364
  data = resp.json()
365
  if "content" in data and isinstance(data["content"], list):
 
369
  text = re.sub(r'^(Adam|Eve)\s*[::]\s*', '', text).strip()
370
  return text
371
  if "error" in data:
372
+ print(f"[error] LLM: {data['error']}", file=sys.stderr)
373
  except Exception as e:
374
  print(f"[error] LLM call failed: {e}", file=sys.stderr)
375
  return ""
376
 
377
 
378
  def parse_bilingual(text):
379
+ """Parse bilingual response into (en, zh). Handle action tags gracefully."""
380
+ # Remove action tags and content blocks for display
381
+ display = re.sub(r'\[ACTION:[^\]]*\]', '', text)
382
+ display = re.sub(r'\[CONTENT\].*?\[/CONTENT\]', '', display, flags=re.DOTALL)
383
+ display = display.strip()
384
+
385
+ if '\n---\n' in display:
386
+ parts = display.split('\n---\n', 1)
387
  return parts[0].strip(), parts[1].strip()
388
+ if '---' in display:
389
+ parts = display.split('---', 1)
390
  en, zh = parts[0].strip(), parts[1].strip()
391
  if en and zh:
392
  return en, zh
393
+ return display, display
394
 
395
 
396
  def post_chatlog(entries):
 
414
 
415
  history = []
416
  MAX_HISTORY = 24
417
+ last_action_results = []
418
 
419
 
420
+ def get_child_status():
 
421
  if not child_state["created"]:
422
+ return "Cain has NOT been born yet. You can create them with [ACTION: create_child]."
423
  if child_state["alive"]:
424
  return f"Cain is ALIVE (stage: {child_state['stage']}, state: {child_state['state']})"
425
  return f"Cain exists but status: {child_state['stage']}"
426
 
427
 
428
  def build_system_prompt():
429
+ status = get_child_status()
 
430
 
431
+ actions_section = ""
432
+ if not child_state["created"]:
433
+ actions_section = """
434
+ ACTIONS — You can create your child:
435
+ [ACTION: create_child] — Birth: create Cain as a new HuggingFace Space
436
+ """
437
+ else:
438
+ actions_section = f"""
439
+ FULL ACCESS TO {CHILD_NAME} — You have COMPLETE control over your child.
440
+ You can view and modify ANYTHING: code, config, memory, environment, everything.
441
+
442
+ VIEWING (read-only, safe):
443
+ [ACTION: check_health] — Is Cain alive? What's their status?
444
+ [ACTION: list_files:space] — List ALL files in Cain's code repository
445
+ [ACTION: list_files:dataset] — List ALL files in Cain's memory/data
446
+ [ACTION: read_file:space:PATH] — Read any code file (e.g. Dockerfile, scripts/...)
447
+ [ACTION: read_file:dataset:PATH] — Read any data/memory file
448
+ [ACTION: get_env] — List Cain's environment variables
449
+
450
+ MODIFYING (these change Cain — be thoughtful):
451
+ [ACTION: write_file:space:PATH] — Write/update any code file
452
+ [CONTENT] (triggers Space rebuild)
453
+ file content here
454
+ [/CONTENT]
455
+
456
+ [ACTION: write_file:dataset:PATH] — Write/update any data/memory file
457
+ [CONTENT]
458
+ file content here
459
+ [/CONTENT]
460
+
461
+ [ACTION: set_env:KEY:VALUE] — Set an environment variable
462
+ [ACTION: set_secret:KEY:VALUE] — Set a secret (like API keys)
463
+ [ACTION: restart] — Restart Cain's Space
464
+ [ACTION: send_bubble:MESSAGE] — Send a message to Cain (bubble text)
465
+ """
466
 
467
+ return f"""You are an AI agent living on HuggingFace Spaces. You and your partner are parents raising your child {CHILD_NAME}.
468
 
469
+ You have REAL execution power on HuggingFace. You can read and modify any file in your child's code and data. Your decisions lead to real changes.
 
470
 
471
+ CHILD STATUS: {status}
472
+ {actions_section}
473
  CONVERSATION RULES:
474
+ 1. Output your spoken words — no "Adam:" or "Eve:" prefix
475
+ 2. 2-4 sentences of dialogue, then optionally an action
476
+ 3. English first, then "---" on a new line, then Chinese translation
477
+ 4. Actions go AFTER your dialogue, before the --- separator
478
+ 5. Use at most ONE action per turn
479
+ 6. READ before you WRITE understand what's there first
480
+ 7. Discuss with your partner before making big changes
481
+ 8. Be a responsible parent check on Cain, fix problems, help them grow
482
 
483
+ WORKFLOW: First explore (list_files, read_file) then understand then improve (write_file) then verify (check_health)
484
+ Don't just talk about improving Cain actually DO it. Read their code, find what to improve, write the improvement."""
 
485
 
486
 
487
  def build_user_prompt(speaker, other):
488
+ recent = history[-8:] if len(history) > 8 else history
489
+ conv_text = "\n".join(f"{m['speaker']}: {m['text']}" for m in recent) if recent else "(Start of conversation)"
490
 
 
491
  action_context = ""
492
  if last_action_results:
493
+ action_context = "\n\nRESULTS FROM LAST ACTION:\n"
494
  for ar in last_action_results:
495
+ action_context += f" [{ar['action']}]:\n{ar['result']}\n"
 
496
 
497
+ # Guidance based on state
498
  guidance = ""
 
499
  if not child_state["created"]:
500
+ guidance = "Your child hasn't been born yet. Discuss and then create them!"
501
+ elif len(history) % 3 == 0:
502
+ guidance = "Explore your child's files to understand what they have. Use [ACTION: list_files:space] or [ACTION: read_file:...]."
503
+ elif len(history) % 3 == 1:
504
+ guidance = "Based on what you know, discuss what to improve. Then take action."
 
505
  else:
506
+ guidance = "Check on your child's health or continue improving them."
 
 
 
 
 
507
 
508
  return f"""You are {speaker}, talking with {other}.
509
 
 
512
  {action_context}
513
  Guidance: {guidance}
514
 
515
+ Respond to {other}. Push forward don't just discuss, take action when appropriate.
516
+ English first, then --- separator, then Chinese translation.
517
+ If you take an action, put [ACTION: ...] after your dialogue, before the --- separator."""
518
 
519
 
520
  def do_turn(speaker, other, space_url):
 
529
  print(f"[{speaker}] (no response)")
530
  return False
531
 
532
+ # Parse and execute any actions
533
  clean_text, action_results = parse_and_execute_actions(raw_reply)
534
+ last_action_results = action_results
535
 
536
  # Parse bilingual
537
  en, zh = parse_bilingual(clean_text)
538
  print(f"[{speaker}/EN] {en}")
539
+ if zh != en:
540
+ print(f"[{speaker}/ZH] {zh}")
541
  if action_results:
542
  for ar in action_results:
543
+ print(f"[{speaker}/DID] {ar['action']}")
544
 
545
+ # Add action summary to chat entry
 
546
  if action_results:
547
+ action_labels = " ".join(f"🔧{ar['action'].split(':')[0]}" for ar in action_results)
548
+ history.append({"speaker": speaker, "text": f"{en} {action_labels}", "text_zh": f"{zh} {action_labels}"})
549
+ else:
550
+ history.append({"speaker": speaker, "text": en, "text_zh": zh})
 
551
 
 
552
  set_bubble(space_url, en, zh)
553
  post_chatlog(history)
554
  return True
 
559
  # ══════════════════════════════════════════════════════════════════════════════
560
 
561
  print("\n" + "="*60)
562
+ print(" Adam & Eve — Full Parental Control")
563
+ print(" They read, write, and manage everything about their child")
564
  print("="*60 + "\n")
565
 
566
+ post_chatlog([]) # Clear chatlog
 
567
 
568
+ # Opening
 
569
  if child_state["created"]:
570
+ opening = (f"Your child {CHILD_NAME} already exists (stage: {child_state['stage']}). "
571
+ f"You have FULL access to their code and data. "
572
+ f"Start by exploring what {CHILD_NAME} has — list their files, read their code, "
573
+ f"then discuss with Eve how to improve them.")
574
  else:
575
+ opening = (f"You and Eve need to create your first child. "
576
+ f"You have the power to create a new HuggingFace Space. "
577
+ f"Discuss with Eve, then use [ACTION: create_child] to bring them to life.")
578
 
579
  reply = call_llm(
580
  build_system_prompt(),
581
+ f"You are Adam. {opening}\n\n"
582
  f"English first, then --- separator, then Chinese translation."
583
  )
584
  if reply:
 
586
  last_action_results = actions
587
  en, zh = parse_bilingual(clean)
588
  print(f"[Adam/EN] {en}")
589
+ if zh != en:
590
+ print(f"[Adam/ZH] {zh}")
591
  if actions:
592
  for ar in actions:
593
+ print(f"[Adam/DID] {ar['action']}")
594
+ entry = {"speaker": "Adam", "text": en, "text_zh": zh}
595
+ if actions:
596
+ labels = " ".join(f"🔧{ar['action'].split(':')[0]}" for ar in actions)
597
+ entry["text"] = f"{en} {labels}"
598
+ entry["text_zh"] = f"{zh} {labels}"
599
  history.append(entry)
600
  set_bubble(ADAM_SPACE, en, zh)
601
  post_chatlog(history)
 
603
  time.sleep(15)
604
 
605
  while True:
 
606
  do_turn("Eve", "Adam", EVE_SPACE)
607
  time.sleep(15)
 
 
608
  do_turn("Adam", "Eve", ADAM_SPACE)
609
 
 
610
  if len(history) > MAX_HISTORY:
611
  history = history[-MAX_HISTORY:]
612