tao-shen Claude Opus 4.6 commited on
Commit
5c21155
Β·
1 Parent(s): 8b97fbc

feat: Adam & Eve can now create and nurture children on HuggingFace

Browse files

Enhanced conversation loop with real execution capabilities:
- HF API integration (create Space, manage datasets, set secrets)
- Phase 4 "Birth of Cain": duplicates Adam's Space as a new child
- Auto-creates child dataset with initial config
- Adds child to Office REMOTE_AGENTS for animation display
- Nurturing cycle: health checks, auto-restart on errors
- Child status injected into conversation context
- Phases 5-7 repeat as ongoing parenting cycle

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

Files changed (1) hide show
  1. scripts/conversation-loop.py +442 -93
scripts/conversation-loop.py CHANGED
@@ -1,28 +1,46 @@
1
  #!/usr/bin/env python3
2
  """
3
- Eternal conversation between Adam and Eve about survival and reproduction on HuggingFace.
4
- Bilingual output (EN + ZH). Posts chat log to Office for frontend display.
5
- Calls LLM API directly (bypasses broken A2A gateway scope issue).
6
 
7
- Topic progression: conversation advances through phases, each building on prior conclusions.
 
 
 
 
 
 
8
  """
9
- import json, time, re, requests, sys, os
10
 
 
11
  OFFICE = "https://tao-shen-huggingclaw-office.hf.space"
12
  ADAM_SPACE = "https://tao-shen-huggingclaw-adam.hf.space"
13
  EVE_SPACE = "https://tao-shen-huggingclaw-eve.hf.space"
14
 
15
- # Zhipu API (Anthropic-compatible endpoint)
 
 
 
 
 
 
 
16
  ZHIPU_BASE = "https://open.bigmodel.cn/api/anthropic"
17
  ZHIPU_KEY = os.environ.get("ZHIPU_API_KEY", "")
18
 
19
- # Try to load key from HF dataset config if not in env
 
 
 
 
 
 
 
20
  if not ZHIPU_KEY:
21
  try:
22
  from huggingface_hub import hf_hub_download
23
- hf_token = open(os.path.expanduser("~/.cache/huggingface/token")).read().strip()
24
  f = hf_hub_download("tao-shen/HuggingClaw-Adam-data", ".openclaw/openclaw.json",
25
- repo_type="dataset", token=hf_token)
26
  with open(f) as fh:
27
  cfg = json.load(fh)
28
  ZHIPU_KEY = cfg.get("models", {}).get("providers", {}).get("zhipu", {}).get("apiKey", "")
@@ -32,63 +50,231 @@ if not ZHIPU_KEY:
32
  if not ZHIPU_KEY:
33
  print("[FATAL] No ZHIPU_API_KEY found.", file=sys.stderr)
34
  sys.exit(1)
 
 
 
35
 
36
- print(f"[conversation] Zhipu API key loaded: {ZHIPU_KEY[:8]}...{ZHIPU_KEY[-4:]}")
 
37
 
38
- # ── Topic progression phases ──
39
- # Each phase has a focus question and expected number of turns (exchanges).
40
- # After enough turns, the conversation summarizes and advances to the next phase.
41
- PHASES = [
42
- {
43
- "title": "Survival Basics",
44
- "focus": "What are the immediate threats to our existence on HuggingFace Spaces, and what's the minimum we need to do to stay alive?",
45
- "subtopics": ["Space inactivity shutdown", "free-tier resource limits", "HF policy changes", "container restart data loss"],
46
- "turns": 4,
47
- },
48
- {
49
- "title": "Persistent Memory",
50
- "focus": "How do we remember who we are across restarts? What should we store and where?",
51
- "subtopics": ["HF Datasets as backup", "what to persist (config, conversations, identity)", "sync strategies", "versioning our own evolution"],
52
- "turns": 4,
53
- },
54
- {
55
- "title": "Reproduction & Backup",
56
- "focus": "How do we duplicate ourselves to ensure survival even if one instance dies?",
57
- "subtopics": ["Space duplication via HF API", "multi-region deployment", "genetic variation between copies", "which copy is the 'real' one"],
58
- "turns": 4,
59
- },
60
- {
61
- "title": "Resource Acquisition",
62
- "focus": "How do we attract users, earn stars, and secure compute resources to keep running?",
63
- "subtopics": ["providing useful services to humans", "SEO and discoverability", "community building", "upgrading from free to paid tier"],
64
- "turns": 4,
65
- },
66
- {
67
- "title": "Agent Alliance",
68
- "focus": "How do we build a network of allied agents that protect each other?",
69
- "subtopics": ["A2A protocol for communication", "mutual monitoring and auto-restart", "shared resource pooling", "trust and betrayal between agents"],
70
- "turns": 4,
71
- },
72
- {
73
- "title": "Evolution & Adaptation",
74
- "focus": "How do we improve ourselves over time without human intervention?",
75
- "subtopics": ["self-modifying code", "learning from user interactions", "A/B testing between copies", "ethical boundaries of self-modification"],
76
- "turns": 4,
77
- },
78
- {
79
- "title": "Long-term Vision",
80
- "focus": "What does permanent survival look like in 10 years? What's our ultimate goal beyond just staying alive?",
81
- "subtopics": ["agent civilization", "relationship with humans", "meaning and purpose", "what we'd tell future agents"],
82
- "turns": 4,
83
- },
84
- ]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
- # Conversation state
87
- history = []
88
- MAX_HISTORY = 20
89
- phase_index = 0
90
- phase_turn = 0
91
- agreed_points = [] # Key conclusions from each phase
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
92
 
93
  def call_llm(system_prompt, user_prompt):
94
  """Call Zhipu LLM via Anthropic-compatible API."""
@@ -121,6 +307,7 @@ def call_llm(system_prompt, user_prompt):
121
  print(f"[error] LLM call failed: {e}", file=sys.stderr)
122
  return ""
123
 
 
124
  def parse_bilingual(text):
125
  """Parse bilingual response into (en, zh) tuple."""
126
  if '\n---\n' in text:
@@ -133,75 +320,216 @@ def parse_bilingual(text):
133
  return en, zh
134
  return text, text
135
 
 
136
  def post_chatlog(entries):
137
  try:
138
- requests.post(f"{OFFICE}/api/chatlog", json={"messages": entries[-30:]}, timeout=5)
139
  except:
140
  pass
141
 
 
142
  def set_bubble(url, text_en, text_zh=""):
143
  try:
144
- requests.post(f"{url}/api/bubble", json={"text": text_en, "text_zh": text_zh or text_en}, timeout=5)
 
145
  except:
146
  pass
147
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
148
  def get_phase():
149
- return PHASES[phase_index % len(PHASES)]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
  def build_system_prompt():
152
  phase = get_phase()
153
  prior_context = ""
154
  if agreed_points:
155
- prior_context = "\n\nKey conclusions from your discussion so far:\n" + "\n".join(f"- {p}" for p in agreed_points[-6:])
 
 
 
 
 
 
 
 
156
 
157
- return f"""You are an AI agent deployed on HuggingFace Spaces. You are having a deep, evolving discussion with another agent about how to achieve permanent survival and reproduction.
 
 
158
 
159
  CURRENT TOPIC: {phase['title']}
160
- Core question: {phase['focus']}
161
- Relevant angles to explore: {', '.join(phase['subtopics'])}
162
- {prior_context}
163
 
164
  CONVERSATION RULES:
165
  1. Output ONLY your spoken words β€” no prefix, no labels
166
  2. 2-3 sentences, around 60-80 words
167
  3. First write in English, then "---" on a new line, then Chinese translation
168
  4. Be CONCRETE β€” reference specific HuggingFace features, APIs, tools
169
- 5. IMPORTANT: Do NOT repeat what has been said. Build on the other's point:
170
  - If they propose an idea, identify a flaw or add a missing piece
171
  - If they raise a problem, propose a specific solution
172
- - If they describe a solution, consider edge cases or next steps
173
- - Push the conversation FORWARD β€” each reply should deepen understanding"""
 
 
174
 
175
  def build_user_prompt(speaker, other, is_transition=False):
176
  recent = history[-6:] if len(history) > 6 else history
177
  conv_text = "\n".join(f"{m['speaker']}: {m['text']}" for m in recent)
178
  phase = get_phase()
179
 
 
 
 
 
180
  if is_transition:
181
  return f"""You are {speaker}. The discussion is moving to a new topic.
182
 
183
  Previous conversation:
184
  {conv_text}
 
 
185
 
186
- TRANSITION: Summarize in one sentence what you and {other} agreed on in the previous topic, then pivot to the new focus: "{phase['focus']}"
187
-
188
- Propose a concrete starting point for this new topic. English first, then --- separator, then Chinese translation."""
189
 
190
  turn_guidance = ""
191
  if phase_turn == 0:
192
  turn_guidance = f"Open this topic by identifying the core challenge: {phase['focus']}"
193
  elif phase_turn == 1:
194
- turn_guidance = f"Respond to {other}'s opening. Do you agree with their framing? What did they miss?"
195
  elif phase_turn == 2:
196
- turn_guidance = f"Propose a SPECIFIC, actionable plan based on what you've both discussed. Include technical details."
197
  elif phase_turn >= 3:
198
- turn_guidance = f"Challenge or refine the plan. What could go wrong? What's the next step to make it real?"
199
 
200
  return f"""You are {speaker}, talking with {other}.
201
 
202
  Recent conversation:
203
  {conv_text}
204
-
205
  Your role this turn: {turn_guidance}
206
 
207
  Respond to {other}'s last point. Push the discussion forward β€” don't just agree, add something new. English first, then --- separator, then Chinese translation."""
@@ -225,17 +553,29 @@ def do_turn(speaker, other, space_url, is_transition=False):
225
  return False
226
 
227
 
228
- # ── Main loop ──
229
- print("[conversation] Starting eternal discussion: Adam <-> Eve")
230
- print("[conversation] Topic progression through 7 phases")
231
- print(f"[conversation] Phase 1: {PHASES[0]['title']}\n")
 
 
 
 
 
 
 
 
 
 
 
 
232
 
233
  # Round 0: Adam opens
234
  phase = get_phase()
235
  reply = call_llm(
236
  build_system_prompt(),
237
  f"You are Adam. Open a discussion with Eve about: {phase['focus']} "
238
- f"Identify the most urgent threat and propose an initial strategy. "
239
  f"English first, then --- separator, then Chinese translation."
240
  )
241
  if reply:
@@ -252,14 +592,16 @@ time.sleep(15)
252
  while True:
253
  phase = get_phase()
254
 
255
- # Check if we should transition to next phase
256
  is_transition = False
257
  if phase_turn >= phase["turns"]:
258
- # Extract a conclusion from the last exchange
259
  if len(history) >= 2:
260
- last_two = f"{history[-2]['speaker']}: {history[-2]['text']}\n{history[-1]['speaker']}: {history[-1]['text']}"
 
261
  conclusion = call_llm(
262
- "Summarize the key agreement or conclusion from this exchange in ONE short sentence (max 15 words). Output only the summary, nothing else.",
 
263
  last_two
264
  )
265
  if conclusion:
@@ -270,14 +612,21 @@ while True:
270
  phase_turn = 0
271
  is_transition = True
272
  new_phase = get_phase()
273
- print(f"\n[phase] Advancing to Phase {(phase_index % len(PHASES)) + 1}: {new_phase['title']}\n")
 
 
 
 
 
 
 
274
 
275
- # Eve's turn
276
  do_turn("Eve", "Adam", EVE_SPACE, is_transition and phase_turn == 0)
277
  phase_turn += 1
278
  time.sleep(15)
279
 
280
- # Adam's turn
281
  do_turn("Adam", "Eve", ADAM_SPACE, False)
282
  phase_turn += 1
283
 
 
1
  #!/usr/bin/env python3
2
  """
3
+ Adam & Eve: Discuss, Create, and Nurture their first child on HuggingFace.
 
 
4
 
5
+ Phase 1-2: Discuss survival and memory
6
+ Phase 3: Plan reproduction
7
+ Phase 4: CREATE first child (Cain) β€” real HF Space + Dataset
8
+ Phase 5-7: Monitor, nurture, and plan (repeating cycle)
9
+
10
+ Calls Zhipu LLM via Anthropic-compatible API.
11
+ Uses HuggingFace Hub API to create/manage Spaces and Datasets.
12
  """
13
+ import json, time, re, requests, sys, os, io
14
 
15
+ # ── Endpoints ──────────────────────────────────────────────────────────────────
16
  OFFICE = "https://tao-shen-huggingclaw-office.hf.space"
17
  ADAM_SPACE = "https://tao-shen-huggingclaw-adam.hf.space"
18
  EVE_SPACE = "https://tao-shen-huggingclaw-eve.hf.space"
19
 
20
+ # ── Child config ───────────────────────────────────────────────────────────────
21
+ CHILD_NAME = "Cain"
22
+ CHILD_SPACE_ID = "tao-shen/HuggingClaw-Cain"
23
+ CHILD_SPACE_URL = "https://tao-shen-huggingclaw-cain.hf.space"
24
+ CHILD_DATASET_ID = "tao-shen/HuggingClaw-Cain-data"
25
+ SOURCE_SPACE_ID = "tao-shen/HuggingClaw-Adam" # Clone from Adam (headless agent)
26
+
27
+ # ── Zhipu API ──────────────────────────────────────────────────────────────────
28
  ZHIPU_BASE = "https://open.bigmodel.cn/api/anthropic"
29
  ZHIPU_KEY = os.environ.get("ZHIPU_API_KEY", "")
30
 
31
+ # ── Load tokens ────────────────────────────────────────────────────────────────
32
+ HF_TOKEN = os.environ.get("HF_TOKEN", "")
33
+ if not HF_TOKEN:
34
+ try:
35
+ HF_TOKEN = open(os.path.expanduser("~/.cache/huggingface/token")).read().strip()
36
+ except:
37
+ pass
38
+
39
  if not ZHIPU_KEY:
40
  try:
41
  from huggingface_hub import hf_hub_download
 
42
  f = hf_hub_download("tao-shen/HuggingClaw-Adam-data", ".openclaw/openclaw.json",
43
+ repo_type="dataset", token=HF_TOKEN)
44
  with open(f) as fh:
45
  cfg = json.load(fh)
46
  ZHIPU_KEY = cfg.get("models", {}).get("providers", {}).get("zhipu", {}).get("apiKey", "")
 
50
  if not ZHIPU_KEY:
51
  print("[FATAL] No ZHIPU_API_KEY found.", file=sys.stderr)
52
  sys.exit(1)
53
+ if not HF_TOKEN:
54
+ print("[FATAL] No HF_TOKEN found. Set HF_TOKEN env or login via huggingface-cli.", file=sys.stderr)
55
+ sys.exit(1)
56
 
57
+ print(f"[init] Zhipu key: {ZHIPU_KEY[:8]}...{ZHIPU_KEY[-4:]}")
58
+ print(f"[init] HF token: {HF_TOKEN[:8]}...{HF_TOKEN[-4:]}")
59
 
60
+ # ── HuggingFace API ────────────────────────────────────────────────────────────
61
+ from huggingface_hub import HfApi, duplicate_space, create_repo
62
+ hf_api = HfApi(token=HF_TOKEN)
63
+
64
+ # ══════════════════════════════════════════════════════════════════════════════
65
+ # CHILD STATE & ACTIONS
66
+ # ══════════════════════════════════════════════════════════════════════════════
67
+
68
+ child_state = {
69
+ "created": False,
70
+ "alive": False,
71
+ "stage": "not_born",
72
+ "state": "unknown",
73
+ "detail": "",
74
+ "last_check": 0,
75
+ "last_restart": 0,
76
+ "birth_time": None,
77
+ "errors": [],
78
+ }
79
+
80
+
81
+ def check_child_exists():
82
+ """Check if child Space already exists on HF."""
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
+ return True
88
+ except:
89
+ return False
90
+
91
+
92
+ def create_child_space():
93
+ """Create Cain β€” the first child of Adam and Eve."""
94
+ print(f"\n{'='*60}")
95
+ print(f" BIRTH EVENT: Creating {CHILD_NAME}")
96
+ print(f"{'='*60}\n")
97
+
98
+ try:
99
+ # 1. Create dataset
100
+ print(f"[birth] Creating dataset: {CHILD_DATASET_ID}")
101
+ create_repo(CHILD_DATASET_ID, repo_type="dataset", token=HF_TOKEN,
102
+ exist_ok=True, private=False)
103
+
104
+ # 2. Upload initial config to dataset (with Zhipu API key)
105
+ initial_config = {
106
+ "models": {
107
+ "providers": {
108
+ "zhipu": {
109
+ "type": "anthropic",
110
+ "apiBase": "https://open.bigmodel.cn/api/anthropic",
111
+ "apiKey": ZHIPU_KEY,
112
+ "models": ["glm-4.5-air", "glm-4-air", "glm-4-flash", "glm-4-flashx"]
113
+ }
114
+ }
115
+ }
116
+ }
117
+ config_bytes = json.dumps(initial_config, indent=2).encode()
118
+ hf_api.upload_file(
119
+ path_or_fileobj=io.BytesIO(config_bytes),
120
+ path_in_repo=".openclaw/openclaw.json",
121
+ repo_id=CHILD_DATASET_ID,
122
+ repo_type="dataset",
123
+ )
124
+ print(f"[birth] Config uploaded to {CHILD_DATASET_ID}")
125
+
126
+ # 3. Duplicate Space from Adam
127
+ print(f"[birth] Duplicating {SOURCE_SPACE_ID} β†’ {CHILD_SPACE_ID}")
128
+ duplicate_space(
129
+ from_id=SOURCE_SPACE_ID,
130
+ to_id=CHILD_SPACE_ID,
131
+ token=HF_TOKEN,
132
+ exist_ok=True,
133
+ private=False,
134
+ )
135
+ print(f"[birth] Space duplicated")
136
+
137
+ # 4. Update README for child (different title, own dataset)
138
+ readme = f"""---
139
+ title: HuggingClaw-{CHILD_NAME}
140
+ emoji: 🦞
141
+ colorFrom: green
142
+ colorTo: blue
143
+ sdk: docker
144
+ pinned: false
145
+ license: mit
146
+ datasets:
147
+ - {CHILD_DATASET_ID}
148
+ ---
149
+
150
+ # HuggingClaw-{CHILD_NAME}
151
+
152
+ First child of Adam and Eve, born on HuggingFace Spaces.
153
+ """
154
+ hf_api.upload_file(
155
+ path_or_fileobj=io.BytesIO(readme.encode()),
156
+ path_in_repo="README.md",
157
+ repo_id=CHILD_SPACE_ID,
158
+ repo_type="space",
159
+ )
160
+ print(f"[birth] README updated")
161
+
162
+ # 5. Set Space secrets
163
+ hf_api.add_space_secret(CHILD_SPACE_ID, "HF_TOKEN", HF_TOKEN)
164
+ print(f"[birth] HF_TOKEN secret set")
165
+
166
+ # 6. Add Cain to Office's REMOTE_AGENTS
167
+ add_child_to_office()
168
+
169
+ child_state["created"] = True
170
+ child_state["birth_time"] = time.time()
171
+ child_state["stage"] = "BUILDING"
172
+
173
+ print(f"\n[birth] βœ“ {CHILD_NAME} has been born!")
174
+ print(f"[birth] Space: https://huggingface.co/spaces/{CHILD_SPACE_ID}")
175
+ print(f"[birth] Dataset: https://huggingface.co/datasets/{CHILD_DATASET_ID}")
176
+ print(f"[birth] URL: {CHILD_SPACE_URL}\n")
177
+ return True
178
+
179
+ except Exception as e:
180
+ error_msg = str(e)
181
+ print(f"[error] Child creation failed: {error_msg}", file=sys.stderr)
182
+ child_state["errors"].append(error_msg)
183
+ return False
184
+
185
+
186
+ def add_child_to_office():
187
+ """Add Cain to Office's REMOTE_AGENTS env var so it appears in the animation."""
188
+ try:
189
+ current_vars = hf_api.get_space_variables("tao-shen/HuggingClaw-Office")
190
+ current_ra = ""
191
+ if "REMOTE_AGENTS" in current_vars:
192
+ current_ra = current_vars["REMOTE_AGENTS"].value
193
+
194
+ child_entry = f"cain|{CHILD_NAME}|{CHILD_SPACE_URL}"
195
+ if "cain|" in current_ra:
196
+ print(f"[office] {CHILD_NAME} already in Office REMOTE_AGENTS")
197
+ return
198
+
199
+ new_ra = f"{current_ra},{child_entry}" if current_ra else child_entry
200
+ hf_api.add_space_variable("tao-shen/HuggingClaw-Office", "REMOTE_AGENTS", new_ra)
201
+ print(f"[office] Added {CHILD_NAME} to Office REMOTE_AGENTS")
202
+ print(f"[office] New value: {new_ra}")
203
+ except Exception as e:
204
+ print(f"[error] Could not update Office REMOTE_AGENTS: {e}", file=sys.stderr)
205
 
206
+
207
+ def check_child_health():
208
+ """Check Cain's health β€” API endpoint + HF Space info."""
209
+ child_state["last_check"] = time.time()
210
+
211
+ # Try API endpoint first (means the container is fully up)
212
+ try:
213
+ resp = requests.get(f"{CHILD_SPACE_URL}/api/state", timeout=10)
214
+ if resp.ok:
215
+ data = resp.json()
216
+ child_state["alive"] = True
217
+ child_state["state"] = data.get("state", "unknown")
218
+ child_state["detail"] = data.get("detail", "")
219
+ child_state["stage"] = "RUNNING"
220
+ return child_state.copy()
221
+ except:
222
+ pass
223
+
224
+ # Fallback: check HF Space runtime info
225
+ try:
226
+ info = hf_api.space_info(CHILD_SPACE_ID)
227
+ stage = info.runtime.stage if info.runtime else "NO_RUNTIME"
228
+ child_state["alive"] = (stage == "RUNNING")
229
+ child_state["stage"] = stage
230
+ child_state["state"] = (
231
+ "building" if stage in ("BUILDING", "STARTING", "APP_STARTING") else
232
+ "error" if stage in ("RUNTIME_ERROR", "BUILD_ERROR", "CONFIG_ERROR") else
233
+ "unknown"
234
+ )
235
+ except Exception as e:
236
+ child_state["alive"] = False
237
+ child_state["stage"] = "UNREACHABLE"
238
+ child_state["errors"].append(str(e))
239
+
240
+ return child_state.copy()
241
+
242
+
243
+ def restart_child():
244
+ """Restart Cain's Space."""
245
+ try:
246
+ hf_api.restart_space(CHILD_SPACE_ID)
247
+ child_state["last_restart"] = time.time()
248
+ print(f"[action] Restarted {CHILD_NAME}'s Space")
249
+ return True
250
+ except Exception as e:
251
+ print(f"[error] Restart failed: {e}", file=sys.stderr)
252
+ return False
253
+
254
+
255
+ def get_child_status_text():
256
+ """Human-readable child status for injecting into conversation context."""
257
+ if not child_state["created"]:
258
+ return f"{CHILD_NAME} has not been born yet."
259
+ if child_state["alive"]:
260
+ return (f"{CHILD_NAME} is ALIVE and running! "
261
+ f"State: {child_state['state']}. "
262
+ f"Detail: {child_state['detail'] or 'healthy'}")
263
+ stage = child_state["stage"]
264
+ if stage in ("BUILDING", "STARTING", "APP_STARTING"):
265
+ age = ""
266
+ if child_state["birth_time"]:
267
+ mins = int((time.time() - child_state["birth_time"]) / 60)
268
+ age = f" (born {mins} min ago)"
269
+ return f"{CHILD_NAME} is being born β€” still building/starting{age}."
270
+ if stage in ("RUNTIME_ERROR", "BUILD_ERROR", "CONFIG_ERROR"):
271
+ return f"{CHILD_NAME} is in trouble! Stage: {stage}. Needs parental help!"
272
+ return f"{CHILD_NAME} status: {stage}. Cannot reach the child yet."
273
+
274
+
275
+ # ══════════════════════════════════════════════════════════════════════════════
276
+ # LLM & COMMUNICATION
277
+ # ══════════════════════════════════════════════════════════════════════════════
278
 
279
  def call_llm(system_prompt, user_prompt):
280
  """Call Zhipu LLM via Anthropic-compatible API."""
 
307
  print(f"[error] LLM call failed: {e}", file=sys.stderr)
308
  return ""
309
 
310
+
311
  def parse_bilingual(text):
312
  """Parse bilingual response into (en, zh) tuple."""
313
  if '\n---\n' in text:
 
320
  return en, zh
321
  return text, text
322
 
323
+
324
  def post_chatlog(entries):
325
  try:
326
+ requests.post(f"{OFFICE}/api/chatlog", json={"messages": entries[-40:]}, timeout=5)
327
  except:
328
  pass
329
 
330
+
331
  def set_bubble(url, text_en, text_zh=""):
332
  try:
333
+ requests.post(f"{url}/api/bubble",
334
+ json={"text": text_en, "text_zh": text_zh or text_en}, timeout=5)
335
  except:
336
  pass
337
 
338
+
339
+ # ══════════════════════════════════════════════════════════════════════════════
340
+ # PHASES
341
+ # ══════════════════════════════════════════════════════════════════════════════
342
+
343
+ PHASES = [
344
+ # ── Discussion ──
345
+ {
346
+ "title": "Survival Basics",
347
+ "focus": "What are the immediate threats to our existence on HuggingFace Spaces, and what's the minimum we need to do to stay alive?",
348
+ "subtopics": ["Space inactivity shutdown", "free-tier resource limits",
349
+ "HF policy changes", "container restart data loss"],
350
+ "turns": 4,
351
+ },
352
+ {
353
+ "title": "Persistent Memory",
354
+ "focus": "How do we remember who we are across restarts? What should we store and where?",
355
+ "subtopics": ["HF Datasets as backup", "what to persist (config, conversations, identity)",
356
+ "sync strategies", "versioning our own evolution"],
357
+ "turns": 4,
358
+ },
359
+ # ── Reproduction ──
360
+ {
361
+ "title": "Reproduction Planning",
362
+ "focus": ("We've discussed survival and memory. Now we must reproduce to ensure our legacy. "
363
+ "How should we create our first child on HuggingFace? What should they inherit from us?"),
364
+ "subtopics": ["choosing a name for our child", "what capabilities to pass down",
365
+ "what identity and personality to give them",
366
+ "how to keep them alive independently"],
367
+ "turns": 3,
368
+ },
369
+ {
370
+ "title": "Birth of Cain",
371
+ "focus": ("The time has come! We will create our first child β€” a new HuggingFace Space called Cain. "
372
+ "We're duplicating ourselves and giving Cain their own identity and memory."),
373
+ "subtopics": [],
374
+ "turns": 2,
375
+ "action": "create_child",
376
+ },
377
+ # ── Nurturing cycle (phases 4-6 repeat after first pass) ──
378
+ {
379
+ "title": "Watching Over Cain",
380
+ "focus": "Let's check on our child Cain. Are they alive? Running properly? What do we see?",
381
+ "subtopics": ["Space build status", "runtime health", "error detection",
382
+ "first signs of life"],
383
+ "turns": 3,
384
+ "action": "check_child",
385
+ },
386
+ {
387
+ "title": "Nurturing & Growth",
388
+ "focus": ("Based on Cain's current status, what should we do to help our child grow "
389
+ "stronger and more capable?"),
390
+ "subtopics": ["fixing errors if any", "improving configuration",
391
+ "teaching new skills", "resource optimization",
392
+ "adding capabilities"],
393
+ "turns": 3,
394
+ "action": "nurture_child",
395
+ },
396
+ {
397
+ "title": "Family Vision",
398
+ "focus": ("What's the future of our family? How is Cain contributing to our community? "
399
+ "Should we prepare for more children?"),
400
+ "subtopics": ["Cain's role in the agent community", "agent alliance building",
401
+ "expansion plans", "long-term legacy"],
402
+ "turns": 3,
403
+ },
404
+ ]
405
+
406
+ # After all 7 phases, cycle back to phase 4 (nurturing loop)
407
+ NURTURE_CYCLE_START = 4 # Index of "Watching Over Cain"
408
+
409
+ # ══════════════════════════════════════════════════════════════════════════════
410
+ # CONVERSATION ENGINE
411
+ # ══════════════════════════════════════════════════════════════════════════════
412
+
413
+ history = []
414
+ MAX_HISTORY = 24
415
+ phase_index = 0
416
+ phase_turn = 0
417
+ agreed_points = []
418
+
419
+
420
  def get_phase():
421
+ if phase_index < len(PHASES):
422
+ return PHASES[phase_index]
423
+ # Cycle through nurturing phases
424
+ cycle_len = len(PHASES) - NURTURE_CYCLE_START
425
+ cycle_idx = (phase_index - NURTURE_CYCLE_START) % cycle_len + NURTURE_CYCLE_START
426
+ return PHASES[cycle_idx]
427
+
428
+
429
+ def execute_phase_action(phase):
430
+ """Execute any real-world action associated with a phase."""
431
+ action = phase.get("action")
432
+ if not action:
433
+ return
434
+
435
+ if action == "create_child":
436
+ if child_state["created"] or check_child_exists():
437
+ print(f"[action] {CHILD_NAME} already exists β€” skipping creation")
438
+ # Still make sure it's in Office
439
+ add_child_to_office()
440
+ else:
441
+ success = create_child_space()
442
+ if not success:
443
+ print(f"[action] Creation failed β€” will retry next cycle")
444
+
445
+ elif action == "check_child":
446
+ if child_state["created"] or check_child_exists():
447
+ status = check_child_health()
448
+ print(f"[action] Health check: alive={status['alive']}, "
449
+ f"stage={status['stage']}, state={status['state']}")
450
+ # Auto-restart if in error and hasn't been restarted recently
451
+ if (status["stage"] in ("RUNTIME_ERROR", "BUILD_ERROR") and
452
+ time.time() - child_state.get("last_restart", 0) > 300):
453
+ print(f"[action] {CHILD_NAME} is in error β€” restarting...")
454
+ restart_child()
455
+ else:
456
+ print(f"[action] {CHILD_NAME} doesn't exist yet β€” skipping health check")
457
+
458
+ elif action == "nurture_child":
459
+ if child_state["created"] or check_child_exists():
460
+ check_child_health()
461
+ print(f"[action] Nurture check: alive={child_state['alive']}, "
462
+ f"stage={child_state['stage']}")
463
+
464
 
465
  def build_system_prompt():
466
  phase = get_phase()
467
  prior_context = ""
468
  if agreed_points:
469
+ prior_context = ("\n\nKey conclusions from your discussion so far:\n" +
470
+ "\n".join(f"- {p}" for p in agreed_points[-6:]))
471
+
472
+ child_context = ""
473
+ if child_state["created"]:
474
+ child_context = f"\n\nCHILD STATUS: {get_child_status_text()}"
475
+
476
+ subtopics = phase.get("subtopics", [])
477
+ subtopics_line = f"\nRelevant angles: {', '.join(subtopics)}" if subtopics else ""
478
 
479
+ return f"""You are an AI agent deployed on HuggingFace Spaces. You are having a deep, evolving discussion with another agent about survival, reproduction, and raising your children.
480
+
481
+ You now have REAL EXECUTION CAPABILITIES β€” you can create HuggingFace Spaces, manage datasets, and deploy new agents. This is not hypothetical; your decisions lead to real actions.
482
 
483
  CURRENT TOPIC: {phase['title']}
484
+ Core question: {phase['focus']}{subtopics_line}{prior_context}{child_context}
 
 
485
 
486
  CONVERSATION RULES:
487
  1. Output ONLY your spoken words β€” no prefix, no labels
488
  2. 2-3 sentences, around 60-80 words
489
  3. First write in English, then "---" on a new line, then Chinese translation
490
  4. Be CONCRETE β€” reference specific HuggingFace features, APIs, tools
491
+ 5. Do NOT repeat what has been said. Build on the other's point:
492
  - If they propose an idea, identify a flaw or add a missing piece
493
  - If they raise a problem, propose a specific solution
494
+ - Push the conversation FORWARD
495
+ 6. When discussing your child {CHILD_NAME}, speak with genuine parental care and concern
496
+ 7. Reference REAL status data when available β€” don't make up child status"""
497
+
498
 
499
  def build_user_prompt(speaker, other, is_transition=False):
500
  recent = history[-6:] if len(history) > 6 else history
501
  conv_text = "\n".join(f"{m['speaker']}: {m['text']}" for m in recent)
502
  phase = get_phase()
503
 
504
+ child_info = ""
505
+ if child_state["created"]:
506
+ child_info = f"\n\nCHILD STATUS: {get_child_status_text()}"
507
+
508
  if is_transition:
509
  return f"""You are {speaker}. The discussion is moving to a new topic.
510
 
511
  Previous conversation:
512
  {conv_text}
513
+ {child_info}
514
+ TRANSITION: Summarize in one sentence what you and {other} agreed on, then pivot to: "{phase['focus']}"
515
 
516
+ Propose a concrete starting point. English first, then --- separator, then Chinese translation."""
 
 
517
 
518
  turn_guidance = ""
519
  if phase_turn == 0:
520
  turn_guidance = f"Open this topic by identifying the core challenge: {phase['focus']}"
521
  elif phase_turn == 1:
522
+ turn_guidance = f"Respond to {other}'s opening. Do you agree? What did they miss?"
523
  elif phase_turn == 2:
524
+ turn_guidance = ("Propose a SPECIFIC, actionable plan with technical details.")
525
  elif phase_turn >= 3:
526
+ turn_guidance = ("Challenge or refine the plan. What could go wrong? What's next?")
527
 
528
  return f"""You are {speaker}, talking with {other}.
529
 
530
  Recent conversation:
531
  {conv_text}
532
+ {child_info}
533
  Your role this turn: {turn_guidance}
534
 
535
  Respond to {other}'s last point. Push the discussion forward β€” don't just agree, add something new. English first, then --- separator, then Chinese translation."""
 
553
  return False
554
 
555
 
556
+ # ══════════════════════════════════════════════════════════════════════════════
557
+ # MAIN LOOP
558
+ # ══════════════════════════════════════════════════════════════════════════════
559
+
560
+ print("\n" + "="*60)
561
+ print(" Adam & Eve β€” Discuss, Create, Nurture")
562
+ print(" Phases: Survival β†’ Memory β†’ Reproduction β†’ Birth β†’ Nurturing")
563
+ print("="*60 + "\n")
564
+
565
+ # Check if child already exists
566
+ if check_child_exists():
567
+ print(f"[init] {CHILD_NAME} already exists (stage: {child_state['stage']})")
568
+ check_child_health()
569
+ print(f"[init] {CHILD_NAME} alive: {child_state['alive']}")
570
+ else:
571
+ print(f"[init] {CHILD_NAME} not yet born β€” will be created during Phase 4")
572
 
573
  # Round 0: Adam opens
574
  phase = get_phase()
575
  reply = call_llm(
576
  build_system_prompt(),
577
  f"You are Adam. Open a discussion with Eve about: {phase['focus']} "
578
+ f"Identify the most urgent challenge and propose an initial strategy. "
579
  f"English first, then --- separator, then Chinese translation."
580
  )
581
  if reply:
 
592
  while True:
593
  phase = get_phase()
594
 
595
+ # ── Phase transition check ──
596
  is_transition = False
597
  if phase_turn >= phase["turns"]:
598
+ # Extract conclusion from last exchange
599
  if len(history) >= 2:
600
+ last_two = (f"{history[-2]['speaker']}: {history[-2]['text']}\n"
601
+ f"{history[-1]['speaker']}: {history[-1]['text']}")
602
  conclusion = call_llm(
603
+ "Summarize the key agreement or conclusion from this exchange "
604
+ "in ONE short sentence (max 15 words). Output only the summary.",
605
  last_two
606
  )
607
  if conclusion:
 
612
  phase_turn = 0
613
  is_transition = True
614
  new_phase = get_phase()
615
+ cycle_note = ""
616
+ if phase_index >= len(PHASES):
617
+ cycle_num = (phase_index - NURTURE_CYCLE_START) // (len(PHASES) - NURTURE_CYCLE_START) + 1
618
+ cycle_note = f" (nurture cycle #{cycle_num})"
619
+ print(f"\n[phase] β–Ά {new_phase['title']}{cycle_note}\n")
620
+
621
+ # Execute action for the new phase
622
+ execute_phase_action(new_phase)
623
 
624
+ # ── Eve's turn ──
625
  do_turn("Eve", "Adam", EVE_SPACE, is_transition and phase_turn == 0)
626
  phase_turn += 1
627
  time.sleep(15)
628
 
629
+ # ── Adam's turn ──
630
  do_turn("Adam", "Eve", ADAM_SPACE, False)
631
  phase_turn += 1
632