Spaces:
Paused
Paused
feat: Adam & Eve can now create and nurture children on HuggingFace
Browse filesEnhanced 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>
- scripts/conversation-loop.py +442 -93
scripts/conversation-loop.py
CHANGED
|
@@ -1,28 +1,46 @@
|
|
| 1 |
#!/usr/bin/env python3
|
| 2 |
"""
|
| 3 |
-
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 16 |
ZHIPU_BASE = "https://open.bigmodel.cn/api/anthropic"
|
| 17 |
ZHIPU_KEY = os.environ.get("ZHIPU_API_KEY", "")
|
| 18 |
|
| 19 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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=
|
| 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"[
|
|
|
|
| 37 |
|
| 38 |
-
# ββ
|
| 39 |
-
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
-
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
"
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
{
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
|
| 78 |
-
|
| 79 |
-
|
| 80 |
-
|
| 81 |
-
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
-
|
| 87 |
-
|
| 88 |
-
|
| 89 |
-
|
| 90 |
-
|
| 91 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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[-
|
| 139 |
except:
|
| 140 |
pass
|
| 141 |
|
|
|
|
| 142 |
def set_bubble(url, text_en, text_zh=""):
|
| 143 |
try:
|
| 144 |
-
requests.post(f"{url}/api/bubble",
|
|
|
|
| 145 |
except:
|
| 146 |
pass
|
| 147 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 148 |
def get_phase():
|
| 149 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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" +
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 156 |
|
| 157 |
-
return f"""You are an AI agent deployed on HuggingFace Spaces. You are having a deep, evolving discussion with another agent about
|
|
|
|
|
|
|
| 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.
|
| 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 |
-
-
|
| 173 |
-
|
|
|
|
|
|
|
| 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 |
-
|
| 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
|
| 195 |
elif phase_turn == 2:
|
| 196 |
-
turn_guidance =
|
| 197 |
elif phase_turn >= 3:
|
| 198 |
-
turn_guidance =
|
| 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 |
-
#
|
| 229 |
-
|
| 230 |
-
|
| 231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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
|
| 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 |
-
#
|
| 256 |
is_transition = False
|
| 257 |
if phase_turn >= phase["turns"]:
|
| 258 |
-
# Extract
|
| 259 |
if len(history) >= 2:
|
| 260 |
-
last_two = f"{history[-2]['speaker']}: {history[-2]['text']}\n
|
|
|
|
| 261 |
conclusion = call_llm(
|
| 262 |
-
"Summarize the key agreement or conclusion from this exchange
|
|
|
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
|