180 lines
7.4 KiB
Bash
Executable File
180 lines
7.4 KiB
Bash
Executable File
#!/bin/bash
|
||
# ============================================================
|
||
# memory-system-check.sh — 记忆系统统一自检(方案A架构)
|
||
# 每小时由 cron 触发(no_agent 模式)
|
||
# 正常静默,异常飞书报警
|
||
# ============================================================
|
||
set -e
|
||
|
||
FEISHU_WEBHOOK="https://open.feishu.cn/open-apis/bot/v2/hook/446db983-e392-4d2c-bfb8-f9060e5df3ad"
|
||
STATE_DIR="$HOME/.hermes/watchdog"
|
||
STATE_FILE="$STATE_DIR/memory_state.json"
|
||
mkdir -p "$STATE_DIR"
|
||
|
||
ALERT=0
|
||
LINES=()
|
||
|
||
log() { echo "[$(date '+%H:%M:%S')] $*"; }
|
||
|
||
# ── L7 统一层:llm_context.json v2 ──────────────────────────
|
||
check_unified_layer() {
|
||
local ctx_file="$HOME/.hermes/llm_context.json"
|
||
local issues=()
|
||
|
||
if [ ! -f "$ctx_file" ]; then
|
||
issues+=("llm_context.json 不存在")
|
||
else
|
||
# 验证 v2 格式 9 个关键字段
|
||
local keys
|
||
keys=$(python3 -c "import json; d=json.load(open('$ctx_file')); print(','.join(sorted(d.keys())))" 2>/dev/null || echo "ERROR")
|
||
if [ "$keys" = "ERROR" ]; then
|
||
issues+=("llm_context.json JSON 格式错误")
|
||
else
|
||
for field in user_profile active_scenes short_term cares recent_moments distill_status; do
|
||
if ! echo "$keys" | grep -q "$field"; then
|
||
issues+=("缺少字段: $field")
|
||
fi
|
||
done
|
||
fi
|
||
|
||
# 检查 daemon 在跑
|
||
if ! pgrep -f "daemon.py" > /dev/null 2>&1; then
|
||
issues+=("daemon.py 未运行")
|
||
fi
|
||
fi
|
||
|
||
if [ ${#issues[@]} -eq 0 ]; then
|
||
local upd
|
||
upd=$(python3 -c "import json; d=json.load(open('$ctx_file')); print(d.get('updated_at','?'))" 2>/dev/null || echo "?")
|
||
local st
|
||
st=$(python3 -c "import json; d=json.load(open('$ctx_file')); st=d.get('short_term',{}); print(f\"L1={st.get('l1_observations_count',0)} L2={st.get('l2_patterns_new',0)} L3={st.get('l3_scenes_updated',0)}\")" 2>/dev/null || echo "?")
|
||
log "[OK] 统一层: updated=$upd $st"
|
||
else
|
||
ALERT=1
|
||
log "[FAIL] 统一层: ${issues[*]}"
|
||
LINES+=("统一层异常: ${issues[*]}")
|
||
fi
|
||
}
|
||
|
||
# ── L1 织忆 ──────────────────────────────────────────────────
|
||
check_zhiyi() {
|
||
local issues=()
|
||
local ok=true
|
||
|
||
if ! pgrep -f "zhiyi" > /dev/null 2>&1; then
|
||
issues+=("zhiyid进程不存在"); ok=false
|
||
fi
|
||
if ! pgrep -f "bge_embed" > /dev/null 2>&1; then
|
||
issues+=("bge-embed进程不存在"); ok=false
|
||
fi
|
||
|
||
local health
|
||
health=$(curl -s --max-time 3 -H "X-API-Key: zhiyi-dev-key-2026" http://127.0.0.1:7821/api/v1/health 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status','unknown'))" 2>/dev/null || echo "unreachable")
|
||
if [ "$health" != "ok" ]; then
|
||
issues+=("API=$health"); ok=false
|
||
fi
|
||
|
||
if [ "$ok" = true ]; then
|
||
local stats nodes edges
|
||
stats=$(curl -s --max-time 3 -H "X-API-Key: zhiyi-dev-key-2026" http://127.0.0.1:7821/api/v1/stats 2>/dev/null || echo "{}")
|
||
nodes=$(echo "$stats" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('total_episodes','?'))" 2>/dev/null)
|
||
# graph.db 里 pattern 节点数
|
||
local patterns
|
||
patterns=$(python3 -c "import sqlite3; g=sqlite3.connect('$HOME/.hermes/graph.db'); c=g.cursor(); c.execute(\"SELECT COUNT(*) FROM graph_nodes WHERE type='pattern'\"); print(c.fetchone()[0]); g.close()" 2>/dev/null || echo "?")
|
||
log "[OK] 织忆: nodes=$nodes patterns=$patterns"
|
||
else
|
||
ALERT=1
|
||
log "[FAIL] 织忆: ${issues[*]}"
|
||
LINES+=("织忆异常: ${issues[*]}")
|
||
fi
|
||
}
|
||
|
||
# ── L5/L6 Soulful ────────────────────────────────────────────
|
||
check_soulful() {
|
||
local ok=true
|
||
local issues=()
|
||
|
||
for f in "$HOME/.hermes/soulful/cares-queue.json" "$HOME/.hermes/soulful/heart-traces.jsonl" "$HOME/.hermes/soulful/user-profile.json"; do
|
||
if [ ! -f "$f" ]; then
|
||
issues+=("文件缺失: $(basename $f)"); ok=false
|
||
fi
|
||
done
|
||
|
||
local cares heart_count
|
||
cares=$(python3 -c "import json; print(len(json.load(open('$HOME/.hermes/soulful/cares-queue.json')).get('cares',[])))" 2>/dev/null || echo "?")
|
||
heart_count=$(wc -l < "$HOME/.hermes/soulful/heart-traces.jsonl" 2>/dev/null || echo 0)
|
||
local distilled
|
||
distilled=$(python3 -c "import json; print(len(json.load(open('$HOME/.hermes/soulful/user-profile.json')).get('distilled_rules',[])))" 2>/dev/null || echo "?")
|
||
|
||
# 过期牵挂检查
|
||
local overdue
|
||
overdue=$(python3 -c "
|
||
import json, datetime
|
||
d = json.load(open('$HOME/.hermes/soulful/cares-queue.json'))
|
||
today = datetime.date.today()
|
||
for c in d.get('cares', []):
|
||
due = c.get('follow_up_date','')
|
||
if due:
|
||
try:
|
||
dd = datetime.date.fromisoformat(due)
|
||
if (today - dd).days > 7:
|
||
print('OLD')
|
||
except: pass
|
||
" 2>/dev/null)
|
||
[ -n "$overdue" ] && issues+=("有过期>7天牵挂")
|
||
|
||
if [ "$ok" = true ]; then
|
||
log "[OK] Soulful: cares=$cares 心迹=$heart_count distilled=$distilled"
|
||
else
|
||
ALERT=1
|
||
log "[FAIL] Soulful: ${issues[*]}"
|
||
LINES+=("Soulful异常: ${issues[*]}")
|
||
fi
|
||
}
|
||
|
||
# ── L3/L4 TencentDB ──────────────────────────────────────────
|
||
check_tddb() {
|
||
local ok=true
|
||
local issues=()
|
||
|
||
if ! pgrep -f "tdai" > /dev/null 2>&1; then
|
||
issues+=("tdai进程不存在"); ok=false
|
||
fi
|
||
|
||
local health
|
||
health=$(curl -s --max-time 3 http://127.0.0.1:8420/health 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('status','unknown'))" 2>/dev/null || echo "unreachable")
|
||
[ "$health" != "ok" ] && { issues+=("API=$health"); ok=false; }
|
||
|
||
# 测试写入能力
|
||
local test_result
|
||
test_result=$(curl -s --max-time 5 -H "Content-Type: application/json" \
|
||
-d '{"session_key":"health-check","user_content":"ping","assistant_content":"pong"}' \
|
||
http://127.0.0.1:8420/capture 2>/dev/null | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('l0_recorded','?'))" 2>/dev/null || echo "?")
|
||
if [ "$test_result" = "?" ] || [ -z "$test_result" ]; then
|
||
issues+=("capture写入失败"); ok=false
|
||
fi
|
||
|
||
if [ "$ok" = true ]; then
|
||
log "[OK] TencentDB: status=$health l0=$test_result"
|
||
else
|
||
ALERT=1
|
||
log "[FAIL] TencentDB: ${issues[*]}"
|
||
LINES+=("TencentDB异常: ${issues[*]}")
|
||
fi
|
||
}
|
||
|
||
# ── 主流程 ────────────────────────────────────────────────────
|
||
log "=== 记忆系统统一自检 $(date '+%Y-%m-%d %H:%M') ==="
|
||
check_unified_layer
|
||
check_zhiyi
|
||
check_soulful
|
||
check_tddb
|
||
|
||
# ── 告警 ─────────────────────────────────────────────────────
|
||
if [ "$ALERT" = 1 ]; then
|
||
local msg
|
||
msg="⚠️ 记忆系统异常\n$(printf '%s\n' "${LINES[@]}")"
|
||
curl -s -X POST "$FEISHU_WEBHOOK" \
|
||
-H "Content-Type: application/json" \
|
||
-d "{\"msg_type\":\"text\",\"text\":\"$msg\"}" > /dev/null 2>&1
|
||
fi |