Ashira Pitchayapakayakul commited on
Commit
b1eb6a2
Β·
1 Parent(s): d6df680

Sync latest hermes scripts + persist /data + axentx auto-clone

Browse files
Files changed (1) hide show
  1. start.sh +123 -49
start.sh CHANGED
@@ -1,77 +1,146 @@
1
  #!/usr/bin/env bash
2
  # Hermes start orchestrator for HF Space.
3
- # Boots: Redis β†’ Ollama β†’ pull model β†’ all Hermes daemons β†’ keep-alive HTTP server
4
  set -uo pipefail
5
 
6
  LOG_DIR="${HOME}/.claude/logs"
7
  mkdir -p "$LOG_DIR"
8
-
9
  echo "[$(date +%H:%M:%S)] hermes-hf-space boot start" | tee "$LOG_DIR/boot.log"
10
 
11
- # ── 1. Bind secrets from HF Space env to ~/.hermes/.env ─────────────────────
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  mkdir -p ~/.hermes
13
  {
14
  echo "# Auto-generated from HF Space secrets at boot"
15
- [[ -n "${OPENROUTER_API_KEY:-}" ]] && echo "OPENROUTER_API_KEY=$OPENROUTER_API_KEY"
16
- [[ -n "${GEMINI_API_KEY:-}" ]] && echo "GEMINI_API_KEY=$GEMINI_API_KEY"
17
- [[ -n "${GEMINI_API_KEY_2:-}" ]] && echo "GEMINI_API_KEY_2=$GEMINI_API_KEY_2"
18
- [[ -n "${GITHUB_TOKEN:-}" ]] && echo "GITHUB_TOKEN=$GITHUB_TOKEN"
19
- [[ -n "${GITHUB_TOKEN_POOL:-}" ]] && echo "GITHUB_TOKEN_POOL=$GITHUB_TOKEN_POOL"
20
- [[ -n "${DISCORD_BOT_TOKEN:-}" ]] && echo "DISCORD_BOT_TOKEN=$DISCORD_BOT_TOKEN"
21
- [[ -n "${DISCORD_WEBHOOK:-}" ]] && echo "DISCORD_WEBHOOK=$DISCORD_WEBHOOK"
22
  } > ~/.hermes/.env
23
  chmod 600 ~/.hermes/.env
24
 
25
- # ── 2. Redis (TCP only, default 6379) ────────────────────────────────────────
26
- redis-server --daemonize yes --port 6379 --bind 127.0.0.1 --maxmemory 1gb --maxmemory-policy allkeys-lru
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
27
  sleep 1
28
- redis-cli -h 127.0.0.1 -p 6379 ping >> "$LOG_DIR/redis.log"
29
 
30
- # ── 3. Ollama (background, CPU mode) ────────────────────────────────────────
31
- OLLAMA_HOST=127.0.0.1:11434 nohup ollama serve > "$LOG_DIR/ollama.log" 2>&1 &
 
 
32
  sleep 6
33
 
34
- # Pull model on first boot only β€” gemma4:e4b ~9.6 GB
35
  if ! ollama list 2>/dev/null | grep -q "gemma4:e4b"; then
36
- echo "[$(date +%H:%M:%S)] pulling gemma4:e4b (first boot, ~5-10 min)" >> "$LOG_DIR/boot.log"
37
- ollama pull gemma4:e4b >> "$LOG_DIR/ollama.log" 2>&1 &
38
  fi
39
 
40
- # ── 4. Discord bot ───────────────────────────────────────────────────────────
41
  if [[ -n "${DISCORD_BOT_TOKEN:-}" ]]; then
42
  set -a; source ~/.hermes/.env; set +a
43
  nohup python ~/.claude/bin/hermes-discord-bot.py >> "$LOG_DIR/discord-bot.log" 2>&1 &
44
  echo "[$(date +%H:%M:%S)] discord bot started" >> "$LOG_DIR/boot.log"
45
  fi
46
 
47
- # ── 5. Periodic loops via inline cron (no systemd/launchd on HF) ────────────
48
  cat > /tmp/hermes-cron.sh <<'CRONSH'
49
  #!/bin/bash
50
  set -a; source ~/.hermes/.env 2>/dev/null; set +a
 
 
51
  while true; do
52
- NOW=$(date +%s)
53
- M=$((NOW / 60))
54
- # Every 90s: surrogate-dev-loop
55
- [[ $((M % 2)) -eq 0 ]] && bash ~/.claude/bin/surrogate-dev-loop.sh 1 &
56
- # Every 30 min: scrape loop
57
- [[ $((M % 30)) -eq 0 ]] && bash ~/.claude/bin/domain-scrape-loop.sh 1700 4 &
 
 
 
58
  # Every 60 min: keyword tuner
59
- [[ $((M % 60)) -eq 0 ]] && bash ~/.claude/bin/scrape-keyword-tuner.sh &
60
- # Every 20 min: auto-orchestrate
61
- [[ $((M % 20)) -eq 0 ]] && bash ~/.claude/bin/auto-orchestrate-loop.sh &
62
- # Every 5 min: producer
63
- [[ $((M % 5)) -eq 0 ]] && bash ~/.claude/bin/work-queue-producer.sh &
64
  sleep 60
65
  done
66
  CRONSH
67
  chmod +x /tmp/hermes-cron.sh
68
- nohup /tmp/hermes-cron.sh > "$LOG_DIR/cron.log" 2>&1 &
69
  echo "[$(date +%H:%M:%S)] cron loop started" >> "$LOG_DIR/boot.log"
70
 
71
- # ── 6. HTTP status endpoint on port 7860 (HF requires this β€” keep-alive) ────
72
- python <<'PYEOF' &
73
  from http.server import BaseHTTPRequestHandler, HTTPServer
74
- import json, os, sqlite3, subprocess
75
  from pathlib import Path
76
 
77
  class StatusHandler(BaseHTTPRequestHandler):
@@ -79,34 +148,39 @@ class StatusHandler(BaseHTTPRequestHandler):
79
  try:
80
  ledger = sqlite3.connect(os.path.expanduser('~/.claude/state/scrape-ledger.db')).execute(
81
  'SELECT COUNT(*) FROM scraped').fetchone()[0]
82
- except Exception:
83
- ledger = 0
84
  try:
85
- train = sum(1 for _ in open(p) for p in Path('~/.claude/state/surrogate-memory/episodes.jsonl').expanduser().glob('*'))
86
- except Exception:
87
- train = 0
88
  try:
89
- procs = subprocess.run(['pgrep', '-fc', 'discord-bot|surrogate-dev|scrape-loop'],
90
- capture_output=True, text=True).stdout.strip()
91
- except Exception:
92
- procs = '?'
 
 
 
93
  body = json.dumps({
 
94
  'status': 'ok',
 
95
  'ledger_repos': ledger,
96
- 'episodes': train,
97
  'daemons_running': procs,
 
98
  }, indent=2)
99
  self.send_response(200)
100
  self.send_header('Content-Type', 'application/json')
 
101
  self.end_headers()
102
  self.wfile.write(body.encode())
 
103
 
104
- def log_message(self, *args): pass # silence stdout
105
-
106
- print('[hermes] status server on :7860', flush=True)
107
  HTTPServer(('0.0.0.0', 7860), StatusHandler).serve_forever()
108
  PYEOF
109
 
110
- # ── Wait forever (PID 1 must not exit) ──────────────────────────────────────
111
  echo "[$(date +%H:%M:%S)] boot complete β€” entering watch mode" >> "$LOG_DIR/boot.log"
112
  tail -f "$LOG_DIR/boot.log"
 
1
  #!/usr/bin/env bash
2
  # Hermes start orchestrator for HF Space.
3
+ # Boots: persistent /data mount β†’ Redis β†’ Ollama β†’ axentx repos β†’ daemons β†’ status server.
4
  set -uo pipefail
5
 
6
  LOG_DIR="${HOME}/.claude/logs"
7
  mkdir -p "$LOG_DIR"
 
8
  echo "[$(date +%H:%M:%S)] hermes-hf-space boot start" | tee "$LOG_DIR/boot.log"
9
 
10
+ # ── 1. Persistent data β€” symlink state dirs to /data (HF persistent mount) ──
11
+ DATA="/data"
12
+ if [[ -d "$DATA" ]] && [[ -w "$DATA" ]]; then
13
+ mkdir -p "$DATA"/{state,workspace,memory,reflexion,projects,ollama,surrogate,index}
14
+ # Symlink critical paths so DB/training/ChromaDB persist across rebuilds
15
+ for src in \
16
+ "${HOME}/.claude/state:${DATA}/state" \
17
+ "${HOME}/.hermes/workspace:${DATA}/workspace" \
18
+ "${HOME}/.surrogate:${DATA}/surrogate" \
19
+ "${HOME}/.ollama:${DATA}/ollama"; do
20
+ target="${src%%:*}"
21
+ link="${src##*:}"
22
+ mkdir -p "$(dirname "$target")"
23
+ if [[ ! -L "$target" ]]; then
24
+ rm -rf "$target" 2>/dev/null
25
+ ln -sfn "$link" "$target"
26
+ fi
27
+ done
28
+ echo "[$(date +%H:%M:%S)] persistent /data linked" >> "$LOG_DIR/boot.log"
29
+ else
30
+ echo "[$(date +%H:%M:%S)] WARN: /data not writable β€” running ephemeral!" >> "$LOG_DIR/boot.log"
31
+ fi
32
+
33
+ # ── 2. Bind HF Space secrets β†’ ~/.hermes/.env ───────────────────────────────
34
  mkdir -p ~/.hermes
35
  {
36
  echo "# Auto-generated from HF Space secrets at boot"
37
+ for k in OPENROUTER_API_KEY GEMINI_API_KEY GEMINI_API_KEY_2 \
38
+ GITHUB_TOKEN GITHUB_TOKEN_POOL DISCORD_BOT_TOKEN DISCORD_WEBHOOK \
39
+ CEREBRAS_API_KEY GROQ_API_KEY SAMBANOVA_API_KEY \
40
+ CLOUDFLARE_API_KEY NVIDIA_API_KEY CHUTES_API_KEY ANTHROPIC_API_KEY; do
41
+ v="${!k:-}"
42
+ [[ -n "$v" ]] && echo "${k}=${v}"
43
+ done
44
  } > ~/.hermes/.env
45
  chmod 600 ~/.hermes/.env
46
 
47
+ # ── 3. Git config + clone axentx repos for auto-orchestrate auto-commit ────
48
+ GH_TOKEN=$(echo "${GITHUB_TOKEN_POOL:-}" | cut -d',' -f1)
49
+ if [[ -n "$GH_TOKEN" ]]; then
50
+ git config --global user.email "hermes@axentx.ai"
51
+ git config --global user.name "Hermes (Surrogate-1)"
52
+ git config --global init.defaultBranch main
53
+ git config --global pull.rebase true
54
+ git config --global push.default current
55
+
56
+ PROJECTS_DIR="${DATA}/projects"
57
+ mkdir -p "$PROJECTS_DIR"
58
+ # Symlink to the path auto-orchestrate-loop expects
59
+ rm -rf ~/axentx 2>/dev/null
60
+ ln -sfn "$PROJECTS_DIR" ~/axentx
61
+
62
+ # Clone axentx repos (skip if already exists)
63
+ for repo_spec in \
64
+ "Costinel:AXENTX/Costinel" \
65
+ "Vanguard:AXENTX/vanguard" \
66
+ "axiomops:AXENTX/axiomops" \
67
+ "surrogate-1:AXENTX/surrogate-1"; do
68
+ local_name="${repo_spec%%:*}"
69
+ gh_path="${repo_spec##*:}"
70
+ target="${PROJECTS_DIR}/${local_name}"
71
+ if [[ ! -d "$target/.git" ]]; then
72
+ echo "[$(date +%H:%M:%S)] cloning $gh_path..." >> "$LOG_DIR/boot.log"
73
+ git clone "https://x-access-token:${GH_TOKEN}@github.com/${gh_path}.git" "$target" \
74
+ >> "$LOG_DIR/git-clone.log" 2>&1 || \
75
+ echo "[$(date +%H:%M:%S)] WARN: clone $gh_path failed" >> "$LOG_DIR/boot.log"
76
+ else
77
+ # Update existing checkout (pull latest before committing)
78
+ (cd "$target" && git fetch && git pull --rebase 2>&1 | tail -3) \
79
+ >> "$LOG_DIR/git-pull.log" 2>&1 || true
80
+ fi
81
+ done
82
+
83
+ # Persist token for any push from auto-orchestrate
84
+ git config --global credential.helper "store --file=$HOME/.git-credentials"
85
+ echo "https://x-access-token:${GH_TOKEN}@github.com" > ~/.git-credentials
86
+ chmod 600 ~/.git-credentials
87
+ echo "[$(date +%H:%M:%S)] git auth configured + 4 axentx repos cloned" >> "$LOG_DIR/boot.log"
88
+ fi
89
+
90
+ # ── 4. Redis (TCP only) ───────────────────────���─────────────────────────────
91
+ redis-server --daemonize yes --port 6379 --bind 127.0.0.1 \
92
+ --maxmemory 1gb --maxmemory-policy allkeys-lru
93
  sleep 1
94
+ redis-cli -h 127.0.0.1 -p 6379 ping >> "$LOG_DIR/redis.log" 2>&1
95
 
96
+ # ── 5. Ollama (background, CPU mode) ────────────────────────────────────────
97
+ OLLAMA_MODELS="${HOME}/.ollama/models" \
98
+ OLLAMA_HOST=127.0.0.1:11434 \
99
+ nohup ollama serve > "$LOG_DIR/ollama.log" 2>&1 &
100
  sleep 6
101
 
102
+ # Pull model only on first boot (model cache lives in /data/.ollama/models)
103
  if ! ollama list 2>/dev/null | grep -q "gemma4:e4b"; then
104
+ echo "[$(date +%H:%M:%S)] pulling gemma4:e4b (~9.6 GB, first boot, 5-15 min)" >> "$LOG_DIR/boot.log"
105
+ nohup ollama pull gemma4:e4b > "$LOG_DIR/ollama-pull.log" 2>&1 &
106
  fi
107
 
108
+ # ── 6. Discord bot (background) ─────────────────────────────────────────────
109
  if [[ -n "${DISCORD_BOT_TOKEN:-}" ]]; then
110
  set -a; source ~/.hermes/.env; set +a
111
  nohup python ~/.claude/bin/hermes-discord-bot.py >> "$LOG_DIR/discord-bot.log" 2>&1 &
112
  echo "[$(date +%H:%M:%S)] discord bot started" >> "$LOG_DIR/boot.log"
113
  fi
114
 
115
+ # ── 7. Cron loop β€” fires Hermes daemons 24/7 (no sleep gaps) ────────────────
116
  cat > /tmp/hermes-cron.sh <<'CRONSH'
117
  #!/bin/bash
118
  set -a; source ~/.hermes/.env 2>/dev/null; set +a
119
+ LOG="${HOME}/.claude/logs/cron.log"
120
+ mkdir -p "$(dirname "$LOG")"
121
  while true; do
122
+ M=$(($(date +%s) / 60))
123
+ # Every 90s: continuous local dev (gemma)
124
+ [[ $((M % 2)) -eq 0 ]] && bash ~/.claude/bin/surrogate-dev-loop.sh 1 >> "$LOG" 2>&1 &
125
+ # Every 5 min: producer pushes priorities to Redis
126
+ [[ $((M % 5)) -eq 0 ]] && bash ~/.claude/bin/work-queue-producer.sh >> "$LOG" 2>&1 &
127
+ # Every 20 min: full orchestrate chain (architect β†’ dev β†’ qa β†’ reviewer + git push)
128
+ [[ $((M % 20)) -eq 0 ]] && bash ~/.claude/bin/auto-orchestrate-loop.sh >> "$LOG" 2>&1 &
129
+ # Every 30 min: scrape loop (parallel 4)
130
+ [[ $((M % 30)) -eq 0 ]] && bash ~/.claude/bin/domain-scrape-loop.sh 1700 4 >> "$LOG" 2>&1 &
131
  # Every 60 min: keyword tuner
132
+ [[ $((M % 60)) -eq 0 ]] && bash ~/.claude/bin/scrape-keyword-tuner.sh >> "$LOG" 2>&1 &
 
 
 
 
133
  sleep 60
134
  done
135
  CRONSH
136
  chmod +x /tmp/hermes-cron.sh
137
+ nohup /tmp/hermes-cron.sh > "$LOG_DIR/cron-master.log" 2>&1 &
138
  echo "[$(date +%H:%M:%S)] cron loop started" >> "$LOG_DIR/boot.log"
139
 
140
+ # ── 8. Status HTTP server on :7860 (HF requires + UptimeRobot keep-alive) ──
141
+ python3 <<'PYEOF' &
142
  from http.server import BaseHTTPRequestHandler, HTTPServer
143
+ import json, os, sqlite3, subprocess, datetime
144
  from pathlib import Path
145
 
146
  class StatusHandler(BaseHTTPRequestHandler):
 
148
  try:
149
  ledger = sqlite3.connect(os.path.expanduser('~/.claude/state/scrape-ledger.db')).execute(
150
  'SELECT COUNT(*) FROM scraped').fetchone()[0]
151
+ except Exception: ledger = 0
 
152
  try:
153
+ ep_path = Path(os.path.expanduser('~/.claude/state/surrogate-memory/episodes.jsonl'))
154
+ episodes = sum(1 for _ in ep_path.open()) if ep_path.exists() else 0
155
+ except Exception: episodes = 0
156
  try:
157
+ procs = int(subprocess.run(['pgrep', '-fc', 'discord-bot|surrogate-dev|scrape-loop|hermes-cron'],
158
+ capture_output=True, text=True).stdout.strip() or 0)
159
+ except Exception: procs = 0
160
+ try:
161
+ train_dir = Path(os.path.expanduser('~/.claude/state/surrogate-memory'))
162
+ disk_mb = sum(p.stat().st_size for p in train_dir.rglob('*') if p.is_file()) // (1024*1024)
163
+ except Exception: disk_mb = 0
164
  body = json.dumps({
165
+ 'service': 'hermes',
166
  'status': 'ok',
167
+ 'ts': datetime.datetime.utcnow().isoformat() + 'Z',
168
  'ledger_repos': ledger,
169
+ 'episodes': episodes,
170
  'daemons_running': procs,
171
+ 'memory_disk_mb': disk_mb,
172
  }, indent=2)
173
  self.send_response(200)
174
  self.send_header('Content-Type', 'application/json')
175
+ self.send_header('Access-Control-Allow-Origin', '*')
176
  self.end_headers()
177
  self.wfile.write(body.encode())
178
+ def log_message(self, *args): pass
179
 
180
+ print('[hermes] status :7860', flush=True)
 
 
181
  HTTPServer(('0.0.0.0', 7860), StatusHandler).serve_forever()
182
  PYEOF
183
 
184
+ # ── 9. Container PID 1 β€” tail boot log forever ──────────────────────────────
185
  echo "[$(date +%H:%M:%S)] boot complete β€” entering watch mode" >> "$LOG_DIR/boot.log"
186
  tail -f "$LOG_DIR/boot.log"