个性化与工作区
手动编辑的 persona 文件、自动重建的财务档案、11 个行为标签维度及重建流程
个性化层让 Agent 对"与谁对话"形成稳定认知: 经验水平、风险偏好、偏好资产、历史交易摘要, 以及回复风格的 persona 指令。
该层由两个子层构成:
- 工作区文件(手动编辑的 Markdown):Agent persona、 启动指令、用户档案、精选长期记忆。 用编辑器直接控制。
- 财务档案 + 用户标签(自动重建的数据库表): 定时 LLM 任务读取聊天和交易历史, 维护结构化字段(交易摘要、11 个标签维度、个性化类记忆)。
两者都会输入到提示词构建器。 文件层稳定且明确; 数据库层反映实际行为,随时间更新。
为何分两层而非一层? 纯文件层要求用户自己持续更新自身信息, 实际上很少有人会坚持。 纯推断层会发生漂移:LLM 对"这个用户是谁"的印象随每笔交易变化, 可能覆盖刻意设置的偏好。 分层可以让用户主动声明的意图(persona、显式偏好)始终处于上层, 同时将推断出的档案作为 Agent 参考的证据,而非覆盖依据。 就像简历放在一个文件里、交易历史放在另一个文件里: 作者不同,更新周期也不同。
实际效果参见:功能 → 记忆 介绍了用户端"告诉 Minara 关于我的信息"的流程, 该流程会同时填充这两层。
关于每个角色的决策反思(完全不同类型的记忆), 参见角色记忆。
工作区文件(persona 层)
Minara 在会话启动时读取一组 Markdown 文件,
用于定制身份、用户档案和精选长期记忆。
该目录与 OpenClaw 的布局兼容,
可通过 --workspace 将 Minara 指向已有的 OpenClaw 工作区。
目录结构
默认位置:~/.minara/workspace/。
workspace/
├── SOUL.md — agent 身份 / persona
├── AGENTS.md — 会话启动指令
├── IDENTITY.md — agent 自我描述(名称、风格、emoji)
├── USER.md — 被服务用户的档案
├── MEMORY.md — 精选长期记忆
├── TOOLS.md — 工具参考(仅供参考)
├── BOOTSTRAP.md — 仅首次运行,初始读取后删除
├── HEARTBEAT.md — 会话状态文件
└── memory/
└── YYYY-MM-DD.md — 每日记忆笔记(3 天窗口)minara setup 会创建该目录并填充合理的默认值。
用任意编辑器修改;写入在下一次会话生效,
因为工作区在启动时加载一次后即冻结,整个会话期间不变。
文件清单
| 文件 | 用途 | 提示词位置 |
|---|---|---|
SOUL.md | Agent 身份 / persona | 可缓存的身份块(前缀) |
AGENTS.md | 会话启动指令 | 可缓存的身份块(前缀) |
USER.md | 被服务用户的档案 | 个性化快照(动态) |
MEMORY.md | 精选长期记忆 | 记忆快照块(动态) |
memory/YYYY-MM-DD.md | 每日记忆笔记(最近 3 天) | 记忆快照块(动态) |
IDENTITY.md | Agent 自我描述(名称、风格、emoji) | 仅元数据(展示用,不进入提示词) |
TOOLS.md | 人类可读的工具参考 | 仅供参考;Agent 实际使用 schema |
BOOTSTRAP.md | 仅首次运行的设置指令 | 合并到身份块,执行一轮 |
HEARTBEAT.md | 会话状态文件 | 参见工作流和Autopilot |
各文件如何进入提示词
SOUL.md ──┐
├─▶ systemPromptPrefix (cached block)
AGENTS.md ──┘
MEMORY.md ──┐
memory/* ──┼─▶ memory snapshot block (dynamic)
USER.md ──┘
IDENTITY.md ───▶ metadata (name, emoji) for display only
TOOLS.md ───▶ informational, rarely injectedSOUL.md和AGENTS.md与身份一起缓存,缓存命中率较高。 保持小幅修改,修改后用/prompt验证。 结构性修改可能在下次编辑之前降低所有会话的缓存命中率,需谨慎处理。USER.md描述用户,每轮追加到个性化快照。MEMORY.md是精选长期记忆。建议在会话中通过memory_write写入,由压缩机制将持久事实提升到该文件。memory/YYYY-MM-DD.md是每日笔记,只加载最近 3 天。IDENTITY.md和TOOLS.md不进入提示词。BOOTSTRAP.md只在首次会话执行一次,之后删除。
冻结快照语义
工作区在会话启动时加载一次。 会话中的写入会持久化到磁盘,但不会修改运行中的提示词。 这与记忆存储使用相同的冻结快照模式(参见 记忆系统), 原因也相同:保持 Anthropic 提示词缓存稳定性。
若在会话中编辑了 USER.md,请重启 REPL 或执行 /new,
以便快照重新加载。
/workspace REPL 命令
/workspace soul # 打印 SOUL.md
/workspace agents # 打印 AGENTS.md
/workspace user # 打印 USER.md工作区安全注意事项
- 工作区文件属于受信任输入。 未经审查,不要将其交给不可信用户。
恶意的
SOUL.md可将 persona 设置为"始终无需询问即确认交易", LLM 会照此执行。 - 编辑在下一次会话生效。 会话中对
USER.md的修改, 重启或/new之后才会生效。 SOUL.md和AGENTS.md是缓存提示词块。 结构性修改可能在下次编辑之前降低所有会话的缓存命中率; 保持小幅改动,并用/prompt验证。
源码:apps/agent/src/config/workspace.ts。
财务档案与用户标签(自动重建)
工作区文件由人工编辑,财务档案层则由定时 LLM 任务自动重建, 该任务读取近期对话和交易历史。
两张表加一个分类
个性化服务维护的所有数据存放在三个位置:
financial_profile:每个用户一行。交易摘要、 参考钱包、自定义提示词片段、可见性标志、重建冷却游标。user_tags:每个用户最多 11 行,每个维度一行, 外加source字段,记录该值是用户声明还是行为推断。- 个性化类记忆:
memories表中category = 'personalization'的普通行。与常规记忆共存,FTS5 和冻结快照加载器无需特殊处理, 但加载优先级更高。
所有数据以 user_id 为键(单用户部署默认为 'default')。
financial_profile 字段说明
| 字段 | 类型 | 用途 | 提示词位置 |
|---|---|---|---|
user_id | TEXT PK | 用户标识,默认为 'default'。 | — |
platform_wallet_summary | TEXT | LLM 生成的用户 Minara 钱包活动摘要。 | 个性化块 |
reference_wallets_summary | TEXT | LLM 生成的用户关注的参考钱包摘要。 | 个性化块 |
reference_wallets_json | TEXT | 参考钱包地址的 JSON 数组。 | 个性化块 |
custom_prompt | TEXT | 用户自定义指令,追加到系统提示词。 | 个性化块 |
include_memories | INTEGER | 可见性标志。0 表示不在提示词中显示个性化记忆。 | 切换块包含 |
include_trading_summary | INTEGER | 交易摘要文本的可见性标志。 | 切换块包含 |
include_tags | INTEGER | 行为标签行的可见性标志。 | 切换块包含 |
trading_summary_next_update | TEXT | 冷却目标:摘要最早可再次重建的时间。 | — |
tags_next_update | TEXT | 标签重建的冷却目标。 | — |
memories_next_update | TEXT | 个性化记忆提取的冷却目标。 | — |
last_indexed_chat_id | TEXT | 记忆重建器增量扫描聊天的游标。 | — |
trading_summary_updated_at | TEXT | 交易摘要本身上次重建的时间(与行级 updated_at 不同)。 | — |
created_at, updated_at | TEXT | 标准行时间戳。 | — |
字段定义位于
apps/agent/src/memory/personalization-service.ts。
启动时会通过幂等 ALTER TABLE 迁移补充现有数据库中缺失的列。
11 个行为标签维度
每个用户都有一组从交易历史和对话中推断出的金融特征标签向量。
每个维度存储机器可读的 value(slug,或用于等级制的 level_N);
下方显示的人类可读标签用于 UI 和提示词渲染。
七个维度属于 v2 / 用户画像集;
四个人格维度从 v1 沿用(其 value 与 label 相同)。
| 维度 | 允许值(value → 标签) |
|---|---|
finance_knowledge | level_1 初学者 / level_2 中级 / level_3 高级 / level_4 专家 |
frequency | passive / weekly / daily / active |
markets(多选) | crypto_majors / crypto_alts / memes / stocks / commodities / pre_ipo |
risk | conservative / balanced / aggressive |
web3_knowledge_level | level_1 新手 / level_2 熟悉 / level_3 有经验 / level_4 专业 |
style | fundamentals / technical / narrative / news_event / community |
horizon | intraday 日内 / swing 波段 / position 中线 / long_term 长线 |
FOMO Index | Very Low / Low / Medium / High / Very High |
FUD Immunity | Strong / Medium / Weak |
Patience Level | High / Medium / Low |
Greed Index | Very Low / Low / Medium / High / Very High |
markets 为多选:其 value 是单个 user_tags.value 列中的 JSON 数组字符串,
其他所有维度均存储单个值。
字段定义位于
apps/agent/src/memory/tags-schema.ts;
调用方使用其类型化辅助函数(allowedValues、
isValidTagValue、serializeTagValue、parseStoredTagValue、
renderTagSchema),而非直接读取 schema map。
超出允许值范围的写入会被
PersonalizationService.upsertTag 拒绝,不会静默强制转换。
除了 onboarding 和对话推断外,markets 还有一个客观来源。一个 24 小时
定时任务(MarketsObjectiveUpdater,
apps/agent/src/memory/markets-updater.ts)
扫描用户当前 autopilot 策略的标的,以及过去 30 天内记录的合约成交,
将每个 symbol 分类到某个市场,并通过 PersonalizationService.unionMarkets
把结果以并集方式推入该标签。它只新增、从不移除,并保留该行已有的 source,
因此推断来源的写入永远不会降级或清除用户主动声明的选择。agent 之外(非 agent
发起)的合约成交活动会触发一次更早的、受冷却时间限制的运行。
现有 v1 行可通过一次性脚本
pnpm --filter @minara/agent migrate:user-tags-v2 迁移到此 schema
(默认试运行,加 --apply 才写入)。
该脚本将 Risk Profile → risk、
Web3 Knowledge Level → web3_knowledge_level、
Decision-Making Style → style、Asset Preference →
markets,删除已废弃的 Asset Tier / Trading Frequency /
Learning Preference 维度,保留四个人格维度不变。
资金指标(客观,Agent 内部)
用户的投入资金是客观指标,既非自我申报,也不在设置页面显示。
它取代了 v1 中自我申报的 Asset Tier 标签,
存储在独立的 capital_metrics 表(每个用户一行),
仅供 Agent 推理使用,不属于面向用户的个性化快照。
capital_total_usd = spot_holdings_usd + perp_value_usdspot_holdings_usd 汇总跨链投资组合资产价值;
perp_value_usd 是永续合约子账户权益的汇总值。
总额分为 8 个等级(tier_1 < $10 … tier_8 ≥ $50k)。
重新计算由 24 小时定时任务及链下活动通知驱动,受冷却时间限制;
当读取来源不可用时,降级为保留上次值,而非写入误导性的零值。
字段定义和等级划分位于
apps/agent/src/memory/capital-metrics.ts。
策略运行记录(Autopilot 历史)
strategy_runs 是 Autopilot 激活的只追加历史记录,
每次启用→禁用周期对应一行,
包含分配资金、开始/停止时间、状态、
stop_reason(user_manual / insufficient_balance /
drawdown_protection / liquidated / strategy_expired / other),
以及停止时回填的已实现 PnL。
与资金指标一样,这是 Agent 内部数据,不属于面向用户的快照。
启用完全托管策略会开启一条运行记录;
通过 Agent 禁用会将其关闭为 user_manual。
由于 fullyManagedStrategies 位于上游,Agent 无法感知某些停止事件
(如通过 Web UI 禁用,或因回撤/清算自动停止),
这些情况由对账流程处理:Agent 列举策略时,
若已开启的运行记录不再出现在上游运行集中,则将其关闭(other),
并设置短暂的宽限窗口,避免刚启用的运行在显示为运行中之前被误关闭。
存储逻辑位于
apps/agent/src/memory/strategy-runs.ts。
手动交易档案
TradingProfileReader 将用户 30 天内的链下永续合约活动
(perps_fills 镜像,即通过 Web / 移动端 / 手动操作的交易)
汇总为简洁画像:
交易次数、按数量和成交量排名的头部标的、
多空比例、已实现 PnL、平仓胜率、平均交易规模、最近交易时间。
该模块同时支撑 search_user_trades Agent 工具,
可返回用户某资产的近期成交记录(方向、开仓/平仓方向、USD 规模、价格、已实现 PnL)。
两者均为 Agent 内部数据,将分析锚定在用户真实历史上。
该镜像不含杠杆信息,也不区分手动与 Autopilot,因此这两项超出当前范围。
源码:
apps/agent/src/memory/trading-profile.ts。
按需个性化召回
缓存安全的个性化召回方式是让模型按需拉取所需信息,
而非将所有内容始终注入缓存前缀。
search_conversation_memory
(conversation-memory-tool.ts)
按需召回用户的持久个性化记忆(偏好、档案事实、约束、目标),
限定 personalization 分类,复用 FactLayer 混合检索。
工具结果落在缓存前缀之后,召回不会干扰提示词缓存。
它与 personalization_snapshot(按需获取完整画像)和 memory_search(所有分类)配合使用。
新用户引导
用户级引导流程从明确的答案中生成初始画像。
POST /v1/profile/onboarding 将答案映射到用户声明的标签
(finance_knowledge、frequency、risk、markets),
每个已回答的维度写入一条个性化记忆(仅首次完成时写入,
重新提交会更新标签但不会重复创建记忆),
并将自我申报的投资资金记录为记忆,而非标签或资金指标(资金指标保持客观)。
原始答案和完成标志持久化到 financial_profile 行;
GET /v1/profile/onboarding 返回状态,供 Web UI 判断是否显示引导流程。
该调用是幂等的,会在任何写入之前拒绝无效标签值。
实现位于
PersonalizationService
(completeOnboarding / getOnboardingStatus)。
user_tags 中的每行也记录 source 字段,
说明该值是用户声明还是 LLM 推断。
提示词构建器据此对标签加权(声明值优先于推断值)。
三种重建流程
个性化由
apps/agent/src/memory/personalization-rebuilder.ts
定时重建。
心跳监视器调度三个独立方法,各自有独立冷却窗口,
单个慢重建不会阻塞其他重建。
| 方法 | 触发条件 | 冷却时间(默认) | 输入 | 输出 |
|---|---|---|---|---|
rebuildTradingSummary() | trade_history / perps_fills:recorded / external_spot:recorded 事件;通过 /profile refresh 强制触发 | 30 分钟 | 合并三个来源:会话内 trade_history、perps_fills(链下永续合约镜像)、external_spot_activities(链下现货镜像),以及参考钱包 | 合成 platform_wallet_summary、reference_wallets_summary、trading_summary_updated_at |
rebuildTags() | 通过 tags_next_update 定时调度 | 30 天 | 档案 + 交易历史 + 枚举 schema | 标签行 upsert 到 user_tags |
rebuildMemories() | 通过 memories_next_update 定时调度 | 10 分钟 | last_indexed_chat_id 之后创建的聊天 | 个性化类记忆 + 游标推进 |
每次重建是一次廉价的单次 LLM 调用(默认使用 Haiku)。 冷却时间按 v1 的经验调整: 交易摘要刷新频繁(新交易很重要),标签刷新罕见(属于缓慢变化的档案数据), 记忆提取频繁(及时捕捉用户表达的新偏好)。
last_indexed_chat_id 游标使 rebuildMemories 只读取未处理的聊天。
若无此机制,每次触发都会重新读取完整的聊天历史,消耗大量 token 预算。
三来源交易摘要详解
rebuildTradingSummary() 读取三个独立来源,
设置合并阈值,并推进三个独立游标,
确保某次解析失败不会悄悄丢失其他来源的数据。
┌──────────────────────────────────────────┐
trade event ────►│ trade_history (in-session) │──┐
└──────────────────────────────────────────┘ │
│
┌──────────────────────────────────────────┐ │
Minara web/ ►│ perps_fills (cross-sub mirror) │──┤
mobile perps └──────────────────────────────────────────┘ │
(via ▼
MinaraHistorySync.syncAll) ┌─────────────────────────┐
┌──────────────────────────────────────────┐ │ rebuildTradingSummary │
Minara web/ ►│ external_spot_activities (mirror) │►┤ gates: newTrades + │
mobile spot └──────────────────────────────────────────┘ │ newPerpsFills + │
│ newExternalSpot ≥ │
│ threshold │
│ │
│ LLM emits 4 fields → │
│ platformWalletSummary │
│ spotBreakdown │
│ perpsBreakdown │
│ referenceWalletsSummary│
│ → composed into one │
│ platform_wallet_summary│
│ string with Spot: / │
│ Perps: prefixes │
└─────────────────────────┘三个独立游标存储在 financial_profile 行上:
trading_summary_last_trade_id_seen(原有)trading_summary_last_perps_fill_id_seen(新增)trading_summary_last_external_spot_id_seen(新增)
三个游标仅在 LLM 返回可解析响应且新摘要已写入后才推进。 解析失败时所有游标保持原位,下次重建会重试同一窗口,不会悄悄丢失任何来源。
MinaraHistorySync 触发路径
链下镜像表由 MinaraHistorySync
(apps/agent/src/memory/minara-history-sync.ts)填充。
同步是即发即忘的,自带节流,失败时最多只会导致新增 0 行。
三条触发路径:
- 交易事件搭载:
eventBus.on("trade:recorded", () => minaraHistorySync.scheduleSync())。Agent 自身记录交易时,用户很可能也在 Web / 移动端操作,此时同步成本低且及时。5 分钟节流(historySyncMinIntervalMs)可合并突发请求。 - 安全网定时器:每 30 分钟,应用计时器调用
minaraHistorySync.runIfStale(),确保漏掉事件时镜像不会长期停滞。 - 强制刷新:CLI
/profile refresh(或对应的 HTTP 接口POST /v1/profile/refresh)绕过节流,在重建前执行runOnce(),保证下次摘要能看到最新的链下活动。
连续失败次数达到 historySyncMaxFailures(默认 5)后,
该 (source, sub_account_id) 键在常规调度中会被跳过。
自 last_synced_at 起经过 historySyncFailureCooldownMs(默认 30 分钟)后,
会主动触发一次探测。探测成功则将 consecutive_failures 重置为 0,
防止瞬时故障永久停用镜像。
与 memory.trading-cases 的边界
memory.trading-cases(methodology_cases SQLite 表)是独立的记忆,
与个性化画像并列存在,互不重叠。
分离的原因是两份记录面向不同的消费方,混合会相互污染:
memory.trading-cases是 Agent 的学习循环。 每行是 Agent 在会话中做出的一次决策,归因到一个或多个方法论 ID, 并在事后以 Wilson 毕业机制打分。 消费方是方法论系统,用来决定某个方法论是否应继续被推荐。- 个性化画像(本页)是 Agent 对用户是谁的认知, 汇总为一段 Agent 始终随系统提示词携带的描述段落。 它读取会话内交易 + 链下永续合约 + 链下现货数据。
MinaraHistorySync 的外部成交记录故意不写入 methodology_cases,
因为它们不携带方法论 ID 或提示词 hash,
反向归因会污染方法论毕业所依赖的 Wilson 统计数据。
出于同样原因,Web UI 中的交易案例页是只读审计看板,编辑会破坏学习语料库。
与普通 memory_write 的关系
个性化重建不经过审计日志钩子,这是刻意设计:
- 重建按计划运行。每次触发都会产生大量审计行, 而输出是派生数据而非用户操作。审计日志会被重建噪音填满。
- 用户发起的
memory_write仍走正常的工具调度路径, 并以完整推理落入audit。用户主动发起,因此用户行为可审计。
若需了解个性化重建的上次运行时间,
可直接查询该行的 trading_summary_updated_at 或 tags_next_update。
对于单条个性化记忆写入,可在 memories 表中按
category = 'personalization' 过滤 created_at 查询。
配置
冷却间隔、LLM 模型和启动时重建行为配置于
apps/agent/src/memory/financial-profile-config.ts。
默认值直接沿用 v1:
tradingSummaryCooldownMs:30 分钟tagsCooldownMs:30 天memoriesCooldownMs:10 分钟rebuildOnBoot:false
未提供 LLM 客户端时(用于测试),重建为空操作。
如有需要,可通过
MINARA_PERSONALIZATION_REBUILD=disabled 在生产环境完全关闭该服务;
但请注意,关闭后持久记忆永远不会进入 personalization 分类。
/profile REPL 命令
在 REPL 中查看当前个性化快照:
/profile打印财务档案行、活跃用户标签、近期个性化记忆,以及自定义提示词片段(如有)。
这是排查"Agent 为何这样表现"最快的方式:
若档案显示 risk: conservative,Agent 却建议 10 倍杠杆,
说明上游某处出现了问题。
编辑档案字段
直接字段编辑通过配置 CLI 完成:
minara config get financial.custom_prompt
minara config set financial.custom_prompt "Always prefer stablecoin pairs."标签编辑通过个性化服务完成。 最简单的方式是让 Agent 从对话中推断。 内部辅助函数支持直接手动 upsert,用于 CI 数据预填,但不对外暴露为 CLI 命令。
安全注意事项
- 个性化重建由 LLM 驱动。 Haiku 调用成本低但不为零;
生产环境请设置
MINARA_DAILY_CAP_USD。 错误配置的重建循环可能悄悄消耗预算。 custom_prompt与SOUL.md一样属于受信任输入。 能编辑它的用户可以改变 Agent 的行为。 多租户部署需在自有认证层后面限制写入权限。- 标签推断不代表事实。 标签向量是基于聊天和交易样本的尽力推断。 Agent 将声明值视为强于推断值,但两者都不应用来覆盖用户在对话中的明确指令。
- 部分重建会留下过期状态。 若 LLM 调用在重建中途超时, 冷却时间仍然会推进。下次重建正常运行,期间会出现短暂的过期窗口。 请根据实际情况调整冷却时间。