学习系统
Agent 如何在重复任务中持续改进
Minara Agent 内置了一个学习循环,可记录成功的工具调用序列,并在后续轮次中以建议形式呈现。与模型微调不同,整个机制均基于 SQLite 行操作,无需训练任务、模型更新或离线流水线。本页说明各组成部分及其与技能系统的协作方式。
"学习"的含义。 在 Minara 中,"学习"不涉及微调或权重更新,模型本身保持不变。每轮开始前,Agent 会从 SQLite 中检索内容:一组已验证的
{tool_name, args}序列、自由文本指导说明,以及结构化方法论。当前轮次与历史成功轮次相似时,历史序列会作为建议提供。设计上以可审计性换取复杂度:所有"已学习"的行为均以数据库行的形式存储,可读取、编辑或删除,运营者可完整检查。
实际效果请参阅:功能 → 自我提升介绍了面向用户的界面,包括如何引导 Agent 保存一条经验,以及这些经验在后续会话中的呈现方式。
关于并行的决策反思循环(对特定技能调用是否正确进行两阶段 LLM 分类,按角色隔离),请参阅 角色记忆。该系统与本系统并行运行,回答的是另一个问题:不是"我如何成功",而是"这个具体决策是否正确,原因是什么"。
学到什么
三类产物存储于 learnings 表及 apps/agent/src/learning/ 下的关联表中:
- 工具序列。 Agent 成功完成任务时所执行的
{tool_name, args}有序列表,在 Agent 显式调用skill_learn时于轮次末尾记录。 - 指导说明。 简短的自由文本片段,例如"获取 Polymarket 价格时,请对具体市场 URL 使用
web_extract,API 限速为每分钟 10 次"。与工具序列一同存储。 - 方法论。 带有成功标准的结构化多步计划,由
learning/structured-methodology.ts存储,适用于纯工具序列表达力不足的深度研究工作流。
反馈循环
review-engine
learning/review-engine.ts 是一个轻量级 LLM 处理过程,在每轮结束时运行(通过安装在 app.ts 中的 review-engine-hook.ts)。其步骤如下:
- 检查本轮工具调用序列。
- 过滤掉调用次数少于 N 次或明显失败的轮次。
- 以快速档模型调用结构化提示词,询问"任务是否完成?新颖性如何?可复用性如何?"
- 输出包含
{score, summary, suggested_trigger, suggested_tool_sequence}的ReviewResult。
若评分超过阈值,结果将传递给技能管理器。
skill-manager
learning/skill-manager.ts 负责管理 learnings 表。收到合格评审结果后,写入以下数据:
{
id: uuid,
name: "hyperliquid_open_long_with_tp_sl",
trigger: "open long on hyperliquid with tp/sl",
tool_sequence: [...],
guidance: "always set TP before SL; Hyperliquid's 'reduce_only' flag...",
created_at,
success_count: 1,
failure_count: 0,
last_used_at: null,
}同时执行去重:若触发词相近的条目已存在(通过 learning/similarity.ts 计算余弦相似度,通过 learning/tfidf.ts 计算 TF-IDF),则更新现有行的计数器,而不新建重复条目。
evaluation-loop
learning/evaluation-loop.ts 在每轮开始时运行,步骤如下:
- 基于用户消息和路由上下文构建 TF-IDF 查询。
- 对所有学习条目评分。
- 返回前 K 个匹配项(默认 3 个)。
- 传给提示词构建器,作为
<learnings>块追加到系统提示词中,包含触发词、工具序列摘要和指导说明。
LLM 可自由选择采纳或忽略该建议;两种选择均会更新学习条目的计数器。采纳并成功完成的轮次会使 success_count 递增;被忽略的学习条目则会缓慢衰减。
方法论:结构化计划
深度研究轮次会生成另一类产物:方法论。工具序列是扁平列表,而方法论是带有成功标准的阶段树:
{
id, name,
phases: [
{name: "Gather", criteria: [...], tools_used: [...]},
{name: "Synthesize", criteria: [...], depends_on: ["Gather"]},
{name: "Verify", criteria: [...], depends_on: ["Synthesize"]},
],
asset_class: "crypto_alt",
...
}存储层为 learning/methodology-store.ts,深度研究技能从中读取数据以初始化多阶段研究计划。对于"收集哪些中间证据"比"调用哪些工具"更重要的任务,方法论是更精细的学习产物。
与向量记忆的区别
简单的向量记忆只存储事实并按需召回。学习系统存储的是过程:"如何完成此类任务",并将其作为可执行建议提供。二者的区别如下:
- 向量记忆回答"我了解 BTC 的哪些信息?"
- 学习系统回答"我通常如何处理在 Hyperliquid 带 TP/SL 做多 BTC 的请求?"
两者互补,Agent 同时使用。记忆查询在技能层通过 memory_search 发起;学习查询在 Agent 循环中于首次 LLM 调用之前发起,作为提示词组装的一部分。
安全属性
学习条目是建议,绝非强制指令。具体而言:
- 学习条目无法绕过权限等级钩子。 若建议的
tool_sequence包含四级工具,在轮次来源不允许的情况下仍会被阻止。 - 学习条目无法绕过 L3 风险门控。 若建议的序列需要激活带
requires_user_confirmation的技能,正常确认流程照常执行。 - 学习条目不能存储密钥。 工具序列中记录的
args经过与审计日志相同的脱敏处理。 - 失败的轮次不会成为学习条目。 评审引擎在技能管理器接触之前已将其过滤。
检查与管理
# 按成功率排序的顶级学习条目
sqlite3 $dataDir/minara.db \
"SELECT name, success_count, failure_count
FROM learnings
ORDER BY success_count - failure_count DESC LIMIT 20;"
# 最近使用的条目
sqlite3 $dataDir/minara.db \
"SELECT name, last_used_at FROM learnings
WHERE last_used_at IS NOT NULL
ORDER BY last_used_at DESC LIMIT 10;"
# 删除有问题的学习条目
sqlite3 $dataDir/minara.db "DELETE FROM learnings WHERE id = '...'"不提供"降级"操作。若某条学习条目存在误导,直接删除即可;如果该条目确实有用,Agent 会重新推导出来。
配置
相关环境变量(参见 env-vars):
MINARA_LEARNING_ENABLED为总开关(默认true)。MINARA_LEARNING_MIN_CALLS为触发评审所需的最低工具调用次数(默认3)。MINARA_LEARNING_SCORE_THRESHOLD为写入学习条目所需的评审评分(0–10,默认7)。MINARA_LEARNING_TOP_K为每轮提供的学习建议数量(默认3)。
将 MINARA_LEARNING_ENABLED=false 可完全禁用该循环:不写入、不建议、不评审。Agent 仍正常运行,只是不会随时间加速。
预算追踪
学习系统发起的每次 LLM 调用均通过 learning/budget-tracker.ts 执行,该模块按类别和时间窗口强制设置硬性上限。此设计源于一项评审警告:两阶段评判加上事后探测,若出现 bug 或对抗性提示词,可能导致 LLM 费用暴增 10 倍。硬性预算是熔断器。
四个类别,各自拥有独立的每日和每月上限:
| 类别 | 用途 |
|---|---|
learning | 评审引擎、方法论提取、角色反思、技能学习 |
agent | 主 Agent 循环轮次本身 |
workflow | 工作流与 Autopilot 轮次 |
experiment | 离线实验、回测、A/B 测试,生产环境不使用此类别 |
状态持久化至 llm_usage SQLite 表:
CREATE TABLE llm_usage (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category TEXT NOT NULL,
task TEXT NOT NULL,
model TEXT NOT NULL,
input_tokens INTEGER NOT NULL,
output_tokens INTEGER NOT NULL,
cost_usd REAL NOT NULL,
date TEXT NOT NULL,
ts TEXT NOT NULL
);每次 LLM 调用时,追踪器会:
- 将预估费用与该类别当前的每日和每月累计值相加。
- 若预估总量超过硬性上限,在调用发出前抛出
BudgetExceededError。 - 若预估总量超过软阈值(低于硬性上限),记录 warn 级别的结构化日志,但允许调用继续。
- 调用完成后,将实际 token 数量和费用写回
llm_usage。
预算在重启后保持有效,因为状态存储于 SQLite,不依赖内存计数器。
不启动 Agent 也可检查消费情况:
sqlite3 $dataDir/minara.db \
"SELECT category, SUM(cost_usd) FROM llm_usage
WHERE date = date('now') GROUP BY 1;"REPL 的 /budget 命令提供相同的交互式视图。
方法论存储
learning/methodology-store.ts 是第二阶段的核心:Agent 学习哪类分析方法能为哪类资产产生有价值的信号,并在未来类似分析中调取。
每条方法论存储以下字段:
| 字段 | 含义 |
|---|---|
id | UUID |
asset_class | 已知资产类别之一(major_crypto、layer_1、defi_blue_chip、meme_coin、stock 等) |
methodology | 方法的自由文本描述 |
evidence | 支撑证据文本 |
confidence | Wilson 下界置信度分数,范围 [0, 1] |
times_used | 成功应用次数 |
times_correct | 通过结果验证的应用次数 |
quarantine | 值为 1 直至该方法通过足够多次成功使用且未触发异常检测为止 |
dedup_key | 结构化字段的哈希值,用于 O(1) 语义去重 |
structured_json | 归一化的 StructuredMethodology(见下文) |
隔离与注入防御
新方法论初始处于 隔离 状态,confidence: 0.1,在通过足够多次成功使用前不会注入提示词。每次写入时,异常检测会通过 scanMethodologyForInjection 扫描方法论文本,在提示词注入模式落库前将其拦截。
置信度提升
每次应用某方法论并验证结果后:
- 成功:递增
times_correct和times_used,以二项分布的 Wilson 下界重新计算confidence(对小样本有惩罚)。 - 失败:仅递增
times_used,重新计算置信度。失败频繁的方法论,置信度会降至注入阈值以下。 - 毕业:
confidence >= INJECTION_THRESHOLD且times_used >= MIN_USES时,将quarantine置为0,该方法论即可用于提示词注入。
Wilson 下界优于直接计算 times_correct / times_used,因为它不会让 1 次成功的幸运结果盖过 30 次中成功 15 次的稳定表现。
机构模式:反思阶梯
机构模式是写入方法论存储最频繁的来源。每次运行都会召集多个 LLM 角色(分析师、多空辩论、风险委员会、投资组合经理)并记录它们的决策。这些决策进入一个延迟的反思循环,将每次判断与实际结果对照评分,并把经验晋级回上文所述的存储。
为何决策角色采用强制结构化工具调用
每个产出决策的角色发出的工具调用都必须匹配一个 Zod schema(AnalystReportSchema、TraderProposalSchema、PortfolioDecisionSchema 等)。与调用并存的自由文本会被丢弃。两点原因,都与反思循环有关:
- 确定性。 反思评分需要跨运行比较相同字段。对自由文本的补救式解析会随模型升级而漂移;固定 schema 则不会。
- 可比性。 今天置信度 0.71 的
Buy,只有在 schema 恒定时,才能与上周置信度 0.62 的Buy直接比较。
当 INSTITUTION_LEARNING_ENABLED=1 时,捕获钩子在每次运行后写入 institution_runs(运行元数据)和 institution_role_outputs(每个角色的结构化输出)。反思阶梯稍后在各窗口到期时写入 institution_reflections。
阶梯
learning/institution/reflect.ts 中的运行器按固定计划重新审视每次运行:
| 窗口 | 触发 | 问题 |
|---|---|---|
| 1d | 运行后 24 小时 | 触发条件是否成立? |
| 7d | 运行后 7 天 | 基准情形是否兑现? |
| 30d | 运行后 30 天 | 时间范围估计是否正确? |
| 90d / 180d / 365d | 更长 | 论点是否持久? |
lazy | 下次查询该 ticker 时 | 复用为该运行的 Phase 0 回溯 |
每个标准窗口将该次运行与实际价格走势(price-source.ts)对照评分,并为运行角色输出中引用的每条方法论,将结果反馈给 methodologyStore.recordOutcome()。方法论在其 Wilson 置信边界(>= 0.55 才显现)处晋级,与上文置信度提升路径所用门控相同,因此少数几次运行无法晋级一条不稳定的规则。lazy 与手动反思是快照,不参与该循环。
结构化方法论去重
自由文本难以去重。"Buy BTC on RSI dip"与"Enter long when RSI oversold"表达同一思路,却几乎没有共同 token。更糟的是,Jaccard 相似度可能将"Buy BTC at support"与"Sell BTC at support"合并(相同 token,相反操作)。
learning/structured-methodology.ts 的解决方案是要求评判 LLM 输出有限词表的归一化字段:
| 字段 | 允许值 |
|---|---|
direction | bullish / bearish / neutral |
primary_signal | momentum / mean_reversion / technical / fundamental / on_chain / sentiment / macro / event |
timeframe | intraday / short / medium / long |
indicators | 已知指标数组(rsi、macd、funding_rate 等) |
去重使用结构化字段的哈希值(direction + primary_signal + timeframe + 排序后的 indicators + asset_class)。哈希相同的两条方法论视为重复,存储层递增现有行的计数器而非插入新行。
自由文本描述仍会保留,供人工阅读和提示词注入使用;结构化字段仅作为去重键。
相似度:Jaccard(旧版)与 TF-IDF
引入结构化去重之前,回退方案是文本相似度。目前存在两种实现:
- Jaccard 4-gram(
learning/similarity.ts)是 v1 旧版实现,计算成本低、与语言无关,但在改写时易出错,且对语义取反("Buy BTC / Sell BTC"陷阱)的判断有误。 - TF-IDF 余弦相似度(
learning/tfidf.ts)是推荐的替代方案,基于词级别、具备停用词感知能力,同样与语言无关,处理改写时表现更好。findMostSimilarTfidf是方法论存储采用的默认路径。
仅当 TF-IDF 失败时(极少发生,如语料库为空或分词异常),存储层才会回退到 Jaccard。两者均只在结构化去重哈希未命中时才被调用,因此调用频率远低于 v1 时期。
编写新的学习产物时,请直接使用 findMostSimilarTfidf,不要另行实现第三种相似度函数。
审计子系统
学习闭环会写很多逐行 forensic 数据(methodology_lifecycle_events、methodology_cases、methodology_cron_runs),但这些表只能一次回答一个问题。审计子系统(learning/methodology-audit.ts)是聚合视图,读取这些 forensic 行,在六个维度上计算 0-100 复合健康分,每次 pass 落一行到 methodology_audit_reports,附结构化 findings 和运维侧 advisory action。
子系统永不变更学习状态。它的唯一写盘点是审计报告行,以及学习 cron 自己写的心跳行(见下文隔离不变式)。
六个评分维度
每个维度都是 learning/methodology-audit-scoring.ts 里的纯函数。函数返回 { score: number | null, findings, advisory_actions }。null 分数表示"样本不足以诚实评分",会从复合分里 drop 掉并把权重重分配给其他维度。
| 维度 | 读什么 | 测什么 |
|---|---|---|
synthesis_quality | reflection_adjusted.reason_text parse | 惩罚 flag-side 与 recovery-side 判决之间的反复振荡,单向 flag 或单向 recovery 的稳定走势满分。低样本窗口下也会浮现 market_stress_freeze 和 synthesis_auto_demote 事件。 |
graduation_fp_rate | graduated 后续 30 天内的 demoted/requantized_by_judge | 在 FP 率上取 Wilson 下界。毕业后还没走完观察窗口又未反转的不计入分子分母,所以一批新毕业不会把分数拉高。 |
attribution_integrity | 窗口内 methodology_cases.outcome_state | (0.7 × 解析率 + 0.3 × (1 − 积压占比)) × 100。无 closed case 且无 14 天以上 pending 积压时返回 null(健康的全新安装,没东西可评)。不做 attribution_model drift 检测,因为记录值是有意保留的快照。 |
coverage_health | methodologies 中 graduated 且 times_used ≥ 10 | 按 CLAUDE.md §13 资产类标准对五大顶层组(crypto / stock / index / commodity / forex)做覆盖度评估。五组各持有 ≥ 3 条活跃方法论得满分,低于三组阈值后线性扣分。 |
quarantine_churn | methodology_lifecycle_events 的状态变更类 kind | 排除 reflection_adjusted(在 6 小时 synthesis 节奏下每天可合理触发 4 次)。高 churn = 同一条方法论在窗口内 ≥ 3 次状态变更。 |
cron_health | methodology_cron_runs 心跳 + pending 积压 | 滞后时间相对 2× 预期间隔加积压惩罚。滞后 > 7 天分数归零(loop 看起来已死)。心跳表空 + pending 积压非零也归零(loop 明显不在干活)。 |
复合得分与档位
默认权重和档位:
composite = 0.22·synthesis_quality + 0.22·graduation_fp_rate + 0.22·attribution_integrity
+ 0.14·coverage_health + 0.08·quarantine_churn + 0.12·cron_health
档位:≥ 80 healthy · 60-79 watch · 40-59 degraded · < 40 alarm · disabled(off switch)null 维度会被 drop,剩余权重重新归一化到和为 1。落库的报告记录 dimension_weights_used 字段,运维读 JSON 时能看到具体哪些维度参与了打分。
隔离不变式
审计读四张学习表(methodologies、methodology_lifecycle_events、methodology_cases、methodology_case_hints),对它们零写入。三层保障叠加:
- 合作式空闲调度。审计 cron(
learning/methodology-audit-cron.ts)和AgentLoop.run()共享一个BusyTracker(core/busy-tracker.ts)。每个 tick 启动前检查inFlight > 0(跳过)和 idle 时长(不足则延期)。Orchestrator 在每个 SQL 阶段之间用yieldIfBusy让步,turn 进来时立即暂停。连续 N 次延期后饥饿守卫强制执行,避免常忙的 agent 长期不被审计。 - 纯函数评分边界。
methodology-audit-scoring.ts里的维度评分函数只接受Methodology/MethodologyLifecycleEvent/MethodologyCase类型的纯数组,结构上看不到MemoryStorehandle,无法误调到.prepare(...).run(...)。 - E2E 表哈希不变式。
tests/e2e/methodology-audit.test.ts在每次审计 pass 前后对所有学习表做 SHA-256 哈希,要求字节级一致。这比行数比对更严,一个仅改updated_at的 UPDATE 行数对得上但哈希过不去。
审计对学习的唯一反向接触点是 methodology_cron_runs 心跳行,在每次学习 cron tick 末尾写入。in-process 调度器(learning/methodology-cron.ts)和 CLI cron 路径(gateway/learning-cli.ts 的 runFullCronCli)都要写这一行,保证文档化的 system-cron 部署下审计 cron_health 维度也能正常工作。
运维这个审计子系统
cron 是 opt-in。默认参数面向日级被动监控;当学习闭环跑了一两周积累足够数据后,把 METHODOLOGY_AUDIT_CRON_ENABLED=1 翻开。完整 env 参考见环境变量 → 方法论审计子系统。
四个 CLI 命令覆盖典型运维流程:
minara learning audit run [--window-days N] # 内联跑一次审计
minara learning audit show [--latest|--pass <id>] # 查单条报告
minara learning audit trend [--days N] # 复合分历史 + sparkline
minara learning audit findings [--severity high|medium|low] # 下钻 findings完整 CLI 接口见 CLI 子命令 → audit。
延期项:主动重评
早期方案曾设想第七个维度,随机让 agent 在历史"确定性错"的 trading case 上重新决策,看学习闭环现在会不会做出不同选择。这块工作被推迟。要做诚实的回放需要先冻结历史决策上下文(价格、新闻、sentiment、当时活跃的方法论、工具输出),让重新决策看到的信息和原决策一致。否则问 agent "现在 ETH 该买吗"测的是当前判断,不是学习是否纠正了过去的错误。两条前置依赖:(a) 在 case-recorder 写 hint / case 时附带的快照表,(b) 一个独立的 ProbeAgentLoop,不走 createApp(),所以重放和线上 agent 零共享状态(skill session、tool registry、hooks)。