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

226 lines
9.2 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 observations patterns scenes policies distilled_rules cares recent_moments; 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
upd=$(python3 -c "import json; d=json.load(open('$ctx_file')); print(d.get('updated_at','?'))" 2>/dev/null || echo "?")
v=$(python3 -c "import json; d=json.load(open('$ctx_file')); v=d.get('version','N/A'); obs=len(d.get('observations',[])); pat=len(d.get('patterns',[])); sc=len(d.get('scenes',[])); pol=len(d.get('policies',[])); dr=len(d.get('distilled_rules',[])); print(f'v{v} L1={obs} L2={pat} L3={sc} L4={pol} L5={dr}')" 2>/dev/null || echo "?")
log "[OK] 统一层: updated=$upd $v"
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=()
# systemd 管理的服务,用 systemctl 检查
# 2026-08-02 修复: tencentdb.service 是幽灵服务(与 tdai-gateway 同源 server.ts
# 端口被占后崩溃循环 278 次骗过 is-active已停用。真正服务是 tdai-gateway.service
for svc in zhiyid.service bge-embed.service zhiyi-consolidate.service xiaowei-daemon.service tdai-gateway.service; do
if ! systemctl --user is-active "$svc" &>/dev/null; then
issues+=("$svc 未运行"); ok=false
fi
done
# 检查端口
if ! ss -tlnp 2>/dev/null | grep -q ":8420"; then
issues+=("TencentDB 端口未监听"); 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
}
# ── CBM 代码记忆 ──────────────────────────────────────────────
check_cbm() {
local ok=true
local issues=()
if ! command -v codebase-memory-mcp &>/dev/null; then
issues+=("CBM 未安装"); ok=false
fi
local projects
projects=$(codebase-memory-mcp cli list_projects 2>/dev/null | python3 -c "
import sys, json
try:
d = json.load(sys.stdin)
for p in d.get('projects',[]):
print(p['name'] + ':' + str(p['nodes']) + ':' + str(p['edges']))
except: print('FAIL')
" 2>/dev/null)
if [ -z "$projects" ] || [ "$projects" = "FAIL" ]; then
issues+=("CBM 查询失败"); ok=false
fi
if [ "$ok" = true ]; then
local total_nodes=0 total_edges=0 count=0
while IFS=: read -r name nodes edges; do
total_nodes=$((total_nodes + nodes))
total_edges=$((total_edges + edges))
count=$((count + 1))
done <<< "$projects"
log "[OK] CBM: ${count}项目 ${total_nodes}节点 ${total_edges}"
else
ALERT=1
log "[FAIL] CBM: ${issues[*]}"
LINES+=("CBM异常: ${issues[*]}")
fi
}
# ── 主流程 ────────────────────────────────────────────────────
log "=== 记忆系统统一自检 $(date '+%Y-%m-%d %H:%M') ==="
check_unified_layer
check_zhiyi
check_soulful
check_tddb
check_cbm
# ── 告警 ─────────────────────────────────────────────────────
if [ "$ALERT" = 1 ]; then
msg="记忆系统异常: $(printf '%s,' "${LINES[@]}")"
curl -s -X POST "$FEISHU_WEBHOOK" \
-H "Content-Type: application/json" \
-d "{\"msg_type\":\"text\",\"content\":{\"text\":\"$msg\"}}" > /dev/null 2>&1
fi