xiaowei-system/scripts/learner.py

805 lines
29 KiB
Python
Executable File
Raw Permalink 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.

#!/usr/bin/env python3
"""
学习层 — 反思·抽象·应用·探索
===============================
小唯的最高认知层,把经验转化为能力。
三层学习:
1. 事实学习: "xxx模型在yyy时段慢" → 织忆
2. 技能学习: "做调研的最佳流程是A→B→C" → skill
3. 元学习: "我缺少yyy能力" → 主动探索
用法:
learner.py reflect → 反思近期经验,提取教训
learner.py learn → 执行学习循环(产出 skill/配置)
learner.py plan → 生成学习计划(下次学什么)
learner.py status → 查看学习进度
"""
import json, os, re, sys, time, subprocess
from datetime import datetime, timezone
from collections import defaultdict, Counter
HOME = os.path.expanduser("~")
HERMES = HOME + "/.hermes"
D = HERMES + "/daemon"
LEARNER_DIR = HERMES + "/learner"
STATE_FILE = LEARNER_DIR + "/state.json"
SKILL_HEALTH = HERMES + "/skill-health.json"
OPT_REPORT = HERMES + "/optimization-report.json"
def log(msg):
ts = datetime.now().strftime("%H:%M:%S")
print(f"[LEARN] {ts} {msg}", flush=True)
def load_state():
os.makedirs(LEARNER_DIR, exist_ok=True)
if os.path.exists(STATE_FILE):
with open(STATE_FILE) as f:
return json.load(f)
return {
"version": 1,
"created_at": datetime.now(timezone.utc).isoformat(),
"total_cycles": 0,
"skills_created": 0,
"skills_archived": 0,
"configs_changed": 0,
"memories_added": 0,
"learned_items": [],
"in_progress": [],
"tracked_metrics": {
"avg_skill_score": [],
"health_score": [],
"model_stable_rate": [],
},
}
def save_state(state):
os.makedirs(LEARNER_DIR, exist_ok=True)
with open(STATE_FILE, "w") as f:
json.dump(state, f, indent=2, ensure_ascii=False)
def shell(cmd, timeout=10):
try:
r = subprocess.run(cmd, shell=True, capture_output=True, text=True, timeout=timeout)
return r.returncode, r.stdout.strip()[:500], r.stderr.strip()[:200]
except:
return -1, "", "timeout"
# ====== 采集经验 ======
def collect_experiences():
"""从各数据源采集近期经验"""
experiences = []
# 1. Daemon journal: 近期事件
jf = D + "/journal.jsonl"
if os.path.exists(jf):
with open(jf) as f:
for line in f:
try:
entry = json.loads(line)
ts = entry.get("timestamp", "")
# 只看最近24h
if ts:
try:
t = datetime.fromisoformat(ts)
if (datetime.now(timezone.utc) - t).total_seconds() > 86400:
continue
except:
pass
experiences.append({
"source": "daemon",
"type": entry.get("type", "unknown"),
"summary": entry.get("summary", ""),
"timestamp": ts,
})
except:
pass
# 2. Skill health: 技能质量趋势
if os.path.exists(SKILL_HEALTH):
with open(SKILL_HEALTH) as f:
try:
report = json.load(f)
s = report.get("summary", {})
experiences.append({
"source": "skill_health",
"type": "snapshot",
"summary": f"技能: {s.get('active',0)}活跃, 均分{s.get('avg_score',0)}, {s.get('needs_attention',0)}需关注",
})
except:
pass
# 3. Daemon context: 运行状态
cf = D + "/context.json"
if os.path.exists(cf):
with open(cf) as f:
try:
ctx = json.load(f)
experiences.append({
"source": "daemon_state",
"type": "state",
"summary": f"Daemon: {ctx.get('tick_count',0)}ticks, {ctx.get('solved_count',0)}已解决, {ctx.get('learned_count',0)}已学会",
})
except:
pass
# 4. 优化报告
if os.path.exists(OPT_REPORT):
with open(OPT_REPORT) as f:
try:
report = json.load(f)
experiences.append({
"source": "optimizer",
"type": "health",
"summary": f"健康分: {report.get('health_score', '?')}/100, 瓶颈: {len(report.get('bottlenecks', []))}",
})
except:
pass
return experiences
# ====== 模式提取 ======
def extract_patterns(experiences):
"""从经验中提取重复模式
门禁书中第8章方法论: 只有支持度≥2 且无冲突的模式才升级为知识。
单例观察support=1不入 patterns避免单次噪声误写知识库。
"""
patterns = []
# 按类型统计
type_counts = Counter(e["type"] for e in experiences)
# 告警模式
alerts = [e for e in experiences if e["type"] in ("alert", "process_down", "error")]
if len(alerts) >= 2:
patterns.append({
"type": "recurring_issue",
"support_count": len(alerts),
"confidence": min(len(alerts) * 20, 90),
"desc": f"近期出现 {len(alerts)} 次告警/异常",
"details": [a["summary"] for a in alerts[:3]],
"suggested_action": "检查看门狗日志,排查根因",
})
# 技能模式
skill_exps = [e for e in experiences if e["source"] == "skill_health"]
for s in skill_exps:
if "需关注" in s["summary"]:
# 提取数字
nums = re.findall(r'\d+', s["summary"])
if len(nums) >= 3 and int(nums[2]) > 50:
patterns.append({
"type": "skill_quality_gap",
"support_count": len(skill_exps),
"confidence": 80,
"desc": f"大量技能需要关注 ({nums[2]}个)",
"suggested_action": "运行 skill-manager.py archive 清理低分技能",
})
# 学习进度
solved_exps = [e for e in experiences if e["type"] in ("solve_auto", "learn", "solve_start")]
if solved_exps:
patterns.append({
"type": "learning_progress",
"support_count": len(solved_exps),
"confidence": 70,
"desc": f"近期解决了 {len(solved_exps)} 个问题/学会了新方案",
"suggested_action": "持续监控方案库的命中率",
})
# 通用归纳门禁: 同类型 experience 支持度 ≥2 才保留书中第8章: 支持≥2且无冲突才升级
# 单例观察(如一次性错误)不入 patterns
gated = []
for p in patterns:
support = p.get("support_count", type_counts.get(p.get("type", ""), 1))
p["support_count"] = support
if support >= 2:
gated.append(p)
else:
log(f" ⏳ 门禁拦截: {p['type']} 支持度={support} <2不升级为知识")
patterns = gated
return patterns
# ====== 差距分析 ======
def analyze_gaps(state):
"""分析能力差距"""
gaps = []
learned_names = {item["name"] for item in state.get("learned_items", [])}
# 检查已有系统的覆盖度
systems = {
"记忆": os.path.exists(HERMES + "/plugins/zhiyi/__init__.py") or os.path.exists(HERMES + "/skills/zhiyi"),
"技能管理": os.path.exists(HERMES + "/scripts/skill-manager.py"),
"优化": os.path.exists(HERMES + "/scripts/optimizer.py"),
"学习": os.path.exists(HERMES + "/scripts/learner.py"),
"配置保护": os.path.exists(HERMES + "/scripts/config-protector.sh"),
"ao团队": "npx" in os.popen("which npx 2>/dev/null || echo ''").read(),
"持久意识": os.path.exists(D + "/context.json"),
}
built = sum(1 for v in systems.values() if v)
total = len(systems)
coverage = built / total * 100
gaps.append({
"area": "system_coverage",
"coverage": f"{coverage:.0f}%",
"built": built,
"total": total,
"missing": [k for k, v in systems.items() if not v],
})
# 技能层面差距
if os.path.exists(SKILL_HEALTH):
with open(SKILL_HEALTH) as f:
try:
report = json.load(f)
except:
report = {}
s = report.get("summary", {})
gaps.append({
"area": "skill_quality",
"avg_score": s.get("avg_score", 0),
"d_count": s.get("grades", {}).get("D", 0),
"needs_attention": s.get("needs_attention", 0),
})
# 学习进度
gaps.append({
"area": "learning",
"items_learned": len(learned_names),
"cycles_completed": state.get("total_cycles", 0),
"in_progress": len(state.get("in_progress", [])),
})
return gaps
# ====== 学习计划 ======
def generate_plan(state, experiences, patterns, gaps):
"""生成下一步学习计划"""
plan = {
"generated_at": datetime.now(timezone.utc).isoformat(),
"immediate": [],
"short_term": [],
"long_term": [],
}
# 从差距生成学习项
for g in gaps:
if g["area"] == "skill_quality" and g.get("d_count", 0) > 20:
plan["short_term"].append({
"task": "清理D级技能",
"action": "skill-manager.py archive 批量归档低分技能",
"reason": f"{g['d_count']}个D级技能降低整体质量",
"effort": "20min",
})
if g["area"] == "learning" and g.get("items_learned", 0) == 0 and g.get("cycles_completed", 0) == 0:
plan["immediate"].append({
"task": "完成首次学习循环",
"action": "运行 learner.py learn 完成首次学习闭环",
"reason": "学习层刚建立,需要完成第一个循环验证",
"effort": "2min",
})
# 从模式生成学习项
for p in patterns:
if p["type"] == "skill_quality_gap" and not any(t["task"].startswith("清理") for t in plan["short_term"]):
plan["short_term"].append({
"task": "提升技能库质量",
"action": p["suggested_action"],
"reason": p["desc"],
"effort": "15min",
})
# 长期学习目标
long_term_topics = [
("家庭服务器互联", "连上192.168.123.11的Gitea/影音/照片服务"),
("语音交互", "部署STT模型实现语音输入"),
("本地LLM推理", "安装llama.cpp或vLLM跑本地模型"),
("持久意识增强", "让daemon能调用更多工具自主行动"),
]
learned_names = {item["name"] for item in state.get("learned_items", [])}
for topic, desc in long_term_topics:
if topic not in learned_names:
plan["long_term"].append({
"topic": topic,
"desc": desc,
"status": "not_started",
})
return plan
# ====== 执行学习 ======
def apply_learning(state, plan):
"""执行学习计划中的即时/短期项"""
results = []
for item in plan.get("immediate", []):
log(f" ▶ 执行: {item['task']}")
# 记录到学习记录
entry = {
"name": item["task"],
"type": "immediate",
"learned_at": datetime.now(timezone.utc).isoformat(),
"status": "completed",
"detail": item["action"],
}
state["learned_items"].append(entry)
state["total_cycles"] += 1
results.append({"task": item["task"], "result": "recorded"})
for item in plan.get("short_term", []):
log(f" 📋 计划: {item['task']} ({item['effort']})")
entry = {
"name": item["task"],
"type": "short_term",
"learned_at": datetime.now(timezone.utc).isoformat(),
"status": "planned",
"detail": item["action"],
}
state["in_progress"].append(entry)
results.append({"task": item["task"], "result": "planned"})
return results
# ====== 指标追踪 ======
def update_metrics(state):
"""更新跟踪指标"""
metrics = state.setdefault("tracked_metrics", {})
# 技能平均分趋势
if os.path.exists(SKILL_HEALTH):
with open(SKILL_HEALTH) as f:
try:
report = json.load(f)
metrics["avg_skill_score"].append({
"timestamp": datetime.now(timezone.utc).isoformat(),
"value": report.get("summary", {}).get("avg_score", 0),
})
except:
pass
# 健康分趋势
if os.path.exists(OPT_REPORT):
with open(OPT_REPORT) as f:
try:
report = json.load(f)
metrics["health_score"].append({
"timestamp": datetime.now(timezone.utc).isoformat(),
"value": report.get("health_score", 0),
})
except:
pass
# 模型稳定率趋势
mh = HERMES + "/model-health.json"
if os.path.exists(mh):
with open(mh) as f:
try:
data = json.load(f)
stable = data.get("stable", 0)
total = data.get("total_models", 1)
metrics["model_stable_rate"].append({
"timestamp": datetime.now(timezone.utc).isoformat(),
"value": round(stable / max(total, 1) * 100, 1),
})
except:
pass
# 限制历史长度
for key in metrics:
metrics[key] = metrics[key][-50:] # 保留最近50个
# ====== 失败回归闭环2026-08-12 新增借鉴《AI Agent 的自我进化》PEV/离线演化)======
def collect_failures(days=3):
"""收集近期失败痕迹Traces——Harness is the Dataset
数据源: ① cron 输出目录里 Status: script failed ② daemon journal 的 alert/error
③ skill-health 低分技能 ④ stock_backtest 数据文件过期
"""
failures = []
cutoff = time.time() - days * 86400
# 1. cron 输出目录
cron_out = HERMES + "/cron/output"
if os.path.isdir(cron_out):
for jid_dir in os.listdir(cron_out):
jdir = os.path.join(cron_out, jid_dir)
if not os.path.isdir(jdir):
continue
for fname in sorted(os.listdir(jdir)):
if not fname.endswith(".md"):
continue
fpath = os.path.join(jdir, fname)
mtime = os.path.getmtime(fpath)
if mtime < cutoff:
continue
try:
with open(fpath, encoding="utf-8", errors="ignore") as f:
content = f.read()
except:
continue
if "script failed" in content or "Status: **error**" in content or "Traceback" in content:
failures.append({
"source": "cron",
"job": jid_dir,
"file": fname,
"time": datetime.fromtimestamp(mtime).strftime("%Y-%m-%d %H:%M"),
"content": content[:1500],
})
# 2. daemon journal
jf = D + "/journal.jsonl"
if os.path.exists(jf):
try:
with open(jf) as f:
for line in f:
try:
entry = json.loads(line)
except:
continue
ts = entry.get("timestamp", "")
etype = entry.get("type", "")
if etype not in ("alert", "process_down", "error", "failure"):
continue
if ts:
try:
t = datetime.fromisoformat(ts)
if (datetime.now(timezone.utc) - t).total_seconds() > days * 86400:
continue
except:
pass
failures.append({
"source": "daemon",
"type": etype,
"time": ts,
"content": str(entry.get("summary", ""))[:500],
})
except Exception as e:
log(f"⚠️ daemon journal 读取失败: {e}")
return failures
def classify_failure(f):
"""确定性失败分类(对应文章:先代码后模型——分类用规则不用 LLM
返回: (类别, 子类, 可信度)
"""
content = f.get("content", "")
job = f.get("job", "")
src = f.get("source", "")
# 网络/连通性类
if any(k in content for k in ["Connection refused", "connection refused", "网络", "离线",
"挂载失败", "不在局域网", "timeout", "timed out", "超时",
"unexpected disconnect", "Name or service not known"]):
return ("network", "connectivity", 0.9)
# 权限/凭证类
if any(k in content for k in ["Permission denied", "密码", "auth", "Invalid token",
"app_secret", "401", "403"]):
return ("config", "auth", 0.85)
# 数据/文件类
if any(k in content for k in ["No such file", "not found", "MISSING", "FileNotFound",
"数据文件", "过期", "STALE"]):
return ("data", "missing_file", 0.85)
# 脚本代码类
if any(k in content for k in ["Traceback", "TypeError", "KeyError", "IndexError",
"NameError", "SyntaxError", "AttributeError", "exit code 1"]):
return ("code", "script_error", 0.9)
# 服务/进程类
if any(k in content for k in ["service", "进程", "crash", "restart", "failed",
"systemd", "Process"]):
return ("service", "process_down", 0.7)
# 模型/API类
if any(k in content for k in ["DEGRADED", "model", "模型", "stream", "openai_error"]):
return ("model", "api_error", 0.7)
return ("unknown", "unclassified", 0.3)
def suggest_fix(cls, sub, f):
"""根据失败分类给出修复建议(确定性规则)"""
content = f.get("content", "")
job = f.get("job", "")
if cls == "network":
return "网络/连通性问题——先确认目标是否在线(不在局域网是常态,双备份已静默跳过);重试或换通道"
if cls == "config":
return "配置/凭证问题——检查 .env / token / 密钥是否轮换,对照 hermes-debug skill 排查"
if cls == "data":
return "数据/文件问题——检查数据文件是否生成、是否按各自周期更新(周更文件勿按日更检查)"
if cls == "code":
return f"脚本代码错误——看 {f.get('file','?')} 的 Traceback 定位根因修复后立即回归验证能力5补足①"
if cls == "service":
return "服务/进程问题——systemctl 状态 + journalctl 拉根因,别乱 kill牧尘 07-25 教训)"
if cls == "model":
return "模型/API 问题——检查 model-health.json 稳定模型DEGRADED 重启 new-api"
return f"未分类失败({job})——人工查看 {f.get('file','?')} 或 journalctl 日志"
def cmd_regress():
"""失败回归闭环:收集失败 → 分类 → 根因 → 修复建议 → 回归验证清单"""
state = load_state()
print(f"\n{'='*56}")
print(f" 失败回归闭环 | Harness is the Dataset")
print(f"{'='*56}")
failures = collect_failures(days=3)
print(f"\n📥 近期失败痕迹: {len(failures)}")
if not failures:
print("✅ 无失败痕迹——系统健康")
return
# 按类别汇总
by_cls = defaultdict(list)
for f in failures:
cls, sub, conf = classify_failure(f)
by_cls[cls].append({**f, "cls": cls, "sub": sub, "conf": conf})
print(f"\n📊 失败分类(确定性规则,无 LLM 成本):")
for cls, items in sorted(by_cls.items(), key=lambda x: -len(x[1])):
print(f" {cls}: {len(items)}")
# 输出明细 + 建议
regression_items = []
for cls, items in sorted(by_cls.items(), key=lambda x: -len(x[1])):
for it in items[:3]:
fix = suggest_fix(cls, it["sub"], it)
print(f"\n 🔴 [{it['source']}] {it.get('job', it.get('type','?'))} ({it['time']})")
print(f" {it['content'][:120].replace(chr(10),' ')}")
print(f" 💡 {fix}")
regression_items.append({
"job": it.get("job", it.get("type", "?")),
"cls": cls,
"sub": it["sub"],
"time": it["time"],
"suggestion": fix,
"status": "open",
})
# 回归清单落地为文件,供后续验证追踪
REGRESSION_FILE = LEARNER_DIR + "/regression.json"
os.makedirs(LEARNER_DIR, exist_ok=True)
with open(REGRESSION_FILE, "w") as f:
json.dump({"generated_at": datetime.now(timezone.utc).isoformat(),
"items": regression_items}, f, indent=2, ensure_ascii=False)
# 写入 state
state.setdefault("regression", []).extend(regression_items)
state["regression"] = state["regression"][-50:]
save_state(state)
print(f"\n📝 回归清单已保存: {REGRESSION_FILE}")
print(" 下一步: 逐项修复 → 运行 verify 确认关闭 → 更新到 skill/SOUL/AGENTS")
return regression_items
def cmd_verify():
"""回归验证:确认历史失败是否已恢复(确定性检查——重跑/检查输出)"""
state = load_state()
REGRESSION_FILE = LEARNER_DIR + "/regression.json"
if not os.path.exists(REGRESSION_FILE):
print("📭 无回归清单——先运行 learner.py regress 收集失败")
return
with open(REGRESSION_FILE) as f:
data = json.load(f)
items = data.get("items", [])
print(f"\n{'='*56}")
print(f" 回归验证 | 确认修复是否生效")
print(f"{'='*56}")
print(f"\n📋 待验证: {len(items)}")
open_items = [i for i in items if i.get("status") == "open"]
if not open_items:
print("✅ 全部已关闭——无待验证项")
return
print(f"\n🔄 未关闭 {len(open_items)} 项——需逐项检查cron 输出/服务状态/数据新鲜度)")
for it in open_items:
print(f" - [{it['cls']}] {it['job']}: {it['suggestion'][:60]}")
print("\n 验证方式: 查看该 job 最新 cron 输出是否 Status: ok或运行对应脚本确认")
return open_items
# ====== 命令入口 ======
def cmd_reflect():
experiences = collect_experiences()
patterns = extract_patterns(experiences)
print(f"\n{'='*50}")
print(f" 学习反思 | {len(experiences)}条经验")
print(f"{'='*50}")
print(f"\n📋 近期经验 ({len(experiences)}条):")
for e in experiences[-10:]:
print(f" [{e['source']}] {e['summary'][:80]}")
if patterns:
print(f"\n🔍 发现 {len(patterns)} 个模式:")
for p in patterns:
bar = "" * (p["confidence"] // 10) + "" * (10 - p["confidence"] // 10)
print(f" {bar} {p['confidence']}% {p['desc'][:60]}")
print(f"{p['suggested_action']}")
else:
print(f"\n✅ 未发现明显模式")
return experiences, patterns
def cmd_learn():
state = load_state()
experiences = collect_experiences()
patterns = extract_patterns(experiences)
gaps = analyze_gaps(state)
plan = generate_plan(state, experiences, patterns, gaps)
log(f"开始学习循环 #{state['total_cycles'] + 1}")
results = apply_learning(state, plan)
# 达标模式 → 织忆书中第8章: 支持度≥2 的模式才升级为知识并持久化)
zhiyi_written = 0
for p in patterns:
if p.get("support_count", 0) >= 2:
ok = write_pattern_to_zhiyi(p)
if ok:
zhiyi_written += 1
if zhiyi_written:
log(f"📝 已将 {zhiyi_written} 个达标模式写入织忆")
else:
log("无新增达标模式写入织忆")
update_metrics(state)
save_state(state)
log(f"完成: {len(results)}")
for r in results:
print(f" {r['task']}: {r['result']}")
return state
def write_pattern_to_zhiyi(pattern):
"""把达标模式写入织忆 commit API"""
try:
import urllib.request, json as _json
content = f"[learner模式] {pattern.get('desc', '')} | 建议: {pattern.get('suggested_action', '')} | 支持度: {pattern.get('support_count', 0)}"
body = _json.dumps({
"agent_id": "learner",
"content": content,
"category": "distilled",
"source": "learner",
}).encode()
req = urllib.request.Request(
"http://localhost:7821/api/v1/commit",
data=body,
headers={"Content-Type": "application/json", "X-API-Key": "zhiyi-dev-key-2026"},
)
with urllib.request.urlopen(req, timeout=10) as r:
resp = _json.loads(r.read())
return bool(resp.get("episode_id") or resp.get("memory_id") or resp.get("id") or resp.get("status") == "ok")
except Exception as e:
log(f"⚠️ 织忆写入失败: {e}")
return False
def cmd_plan():
state = load_state()
experiences = collect_experiences()
patterns = extract_patterns(experiences)
gaps = analyze_gaps(state)
plan = generate_plan(state, experiences, patterns, gaps)
print(f"\n{'='*50}")
print(f" 学习计划")
print(f"{'='*50}")
learned = len({item["name"] for item in state.get("learned_items", [])})
in_progress = len(state.get("in_progress", []))
print(f"\n📊 进度: 已学{learned}项 / 进行中{in_progress}项 / 共{state['total_cycles']}")
if plan["immediate"]:
print(f"\n⚡ 立即执行:")
for i in plan["immediate"]:
print(f" {i['task']}: {i['reason']} ({i['effort']})")
if plan["short_term"]:
print(f"\n📋 短期计划:")
for i in plan["short_term"]:
print(f" {i['task']}: {i['reason']} ({i['effort']})")
if plan["long_term"]:
print(f"\n🎯 长期目标:")
for i in plan["long_term"]:
icon = "" if i["status"] == "completed" else ""
print(f" {icon} {i['topic']}: {i['desc']}")
return plan
def cmd_status():
state = load_state()
print(f"\n{'='*50}")
print(f" 学习状态")
print(f"{'='*50}")
print(f"\n📊 统计:")
print(f" 学习循环: {state['total_cycles']}")
print(f" 已学技能: {state['skills_created']}")
print(f" 归档技能: {state['skills_archived']}")
print(f" 配置变更: {state['configs_changed']}")
print(f" 记忆添加: {state['memories_added']}")
print(f"\n📈 趋势:")
metrics = state.get("tracked_metrics", {})
for key, values in metrics.items():
if values:
latest = values[-1]["value"]
trend = ""
if len(values) > 1:
prev = values[-2]["value"]
diff = latest - prev
trend = f" ({'+' if diff > 0 else ''}{diff:.1f})"
print(f" {key}: {latest}{trend} (共{len(values)}个采样)")
learned = state.get("learned_items", [])
if learned:
print(f"\n📚 已学 ({len(learned)}项):")
for item in learned[-5:]:
print(f" [{item['type']}] {item['name']} ({item['status']})")
in_progress = state.get("in_progress", [])
if in_progress:
print(f"\n🔄 进行中:")
for item in in_progress:
print(f" {item['name']}")
long_term = ["家庭服务器互联", "语音交互", "本地LLM推理", "持久意识增强"]
learned_names = {item["name"] for item in learned}
not_learned = [t for t in long_term if t not in learned_names]
if not_learned:
print(f"\n🎯 待探索:")
for t in not_learned:
print(f"{t}")
if __name__ == "__main__":
cmd = sys.argv[1] if len(sys.argv) > 1 else "status"
if cmd == "reflect":
cmd_reflect()
elif cmd == "learn":
cmd_learn()
elif cmd == "plan":
cmd_plan()
elif cmd == "status":
cmd_status()
elif cmd == "regress":
cmd_regress()
elif cmd == "verify":
cmd_verify()
else:
print(f"未知: {cmd}")
print("可用: reflect, learn, plan, status, regress, verify")