xiaowei-system/scripts/memory-system-check.sh

180 lines
7.4 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/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