23 KiB
Ombre Brain — 内部开发文档 / INTERNALS
本文档面向开发者和维护者。记录功能总览、环境变量、模块依赖、硬编码值和核心设计决策。 最后更新:2026-04-19
0. 功能总览——这个系统到底做了什么
记忆能力
存储与组织
- 每条记忆 = 一个 Markdown 文件(YAML frontmatter 存元数据),直接兼容 Obsidian 浏览/编辑
- 四种桶类型:
dynamic(普通,会衰减)、permanent(固化,不衰减)、feel(模型感受,不浮现)、archived(已遗忘) - 按主题域分子目录:
dynamic/日常/、dynamic/情感/、dynamic/编程/等 - 钉选桶(pinned):importance 锁 10,永不衰减/合并,始终浮现为「核心准则」
每条记忆追踪的元数据
id(12位短UUID)、name(可读名≤80字)、tags(10~15个关键词)domain(1~2个主题域,从 8 大类 30+ 细分域选)valence(事件效价 01)、1)、arousal(唤醒度 0model_valence(模型独立感受)importance(1~10)、activation_count(被想起次数)resolved(已解决/沉底)、digested(已消化/写过 feel)、pinned(钉选)created、last_active时间戳
四种检索模式
- 自动浮现(
breath()无参数):按衰减分排序推送,钉选桶始终展示,Top-1 固定 + Top-20 随机打乱(引入多样性),有 token 预算(默认 10000) - 关键词+向量双通道搜索(
breath(query=...)):rapidfuzz 模糊匹配 + Gemini embedding 余弦相似度,合并去重 - Feel 独立检索(
breath(domain="feel")):按创建时间倒序返回所有 feel - 随机浮现:搜索结果 <3 条时 40% 概率漂浮 1~3 条低权重旧桶(模拟人类随机联想)
四维搜索评分(归一化到 0~100)
- topic_relevance(权重 4.0):name×3 + domain×2.5 + tags×2 + body
- emotion_resonance(权重 2.0):Russell 环形模型欧氏距离
- time_proximity(权重 2.5):
e^(-0.1×days) - importance(权重 1.0):importance/10
- resolved 桶全局降权 ×0.3
记忆随时间变化
- 衰减引擎:改进版艾宾浩斯遗忘曲线
- 公式:
Score = Importance × activation_count^0.3 × e^(-λ×days) × combined_weight - 短期(≤3天):时间权重 70% + 情感权重 30%
- 长期(>3天):情感权重 70% + 时间权重 30%
- 新鲜度加成:
1.0 + e^(-t/36h),刚存入 ×2.0,~36h 半衰,72h 后 ≈×1.0 - 高唤醒度(arousal>0.7)且未解决 → ×1.5 紧迫度加成
- resolved → ×0.05 沉底;resolved+digested → ×0.02 加速淡化
- 公式:
- 自动归档:score 低于阈值(0.3) → 移入 archive
- 自动结案:importance≤4 且 >30天 → 自动 resolved
- 永不衰减:permanent / pinned / protected / feel
记忆间交互
- 智能合并:新记忆与相似桶(score>75)自动 LLM 合并,valence/arousal 取均值,tags/domain 并集
- 时间涟漪:touch 一个桶时,±48h 内创建的桶 activation_count +0.3(上限 5 桶/次)
- 向量相似网络:embedding 余弦相似度 >0.5 建边
- Feel 结晶化:≥3 条相似 feel(相似度>0.7)→ 提示升级为钉选准则
情感记忆重构
- 搜索时若指定 valence,展示层对匹配桶 valence 微调 ±0.1,模拟「当前心情影响回忆色彩」
模型感受/反思系统
- Feel 写入(
hold(feel=True)):存模型第一人称感受,标记源记忆为 digested - Dream 做梦(
dream()):返回最近 10 条 + 自省引导 + 连接提示 + 结晶化提示 - 对话启动流程:breath() → dream() → breath(domain="feel") → 开始对话
自动化处理
- 存入时 LLM 自动分析 domain/valence/arousal/tags/name
- 大段日记 LLM 拆分为 2~6 条独立记忆
- 浮现时自动脱水压缩(LLM 压缩保语义,API 不可用降级到本地关键词提取)
- Wikilink
[[]]由 LLM 在内容中标记
技术能力
6 个 MCP 工具
| 工具 | 关键参数 | 功能 |
|---|---|---|
breath |
query, max_tokens, domain, valence, arousal, max_results | 检索/浮现记忆 |
hold |
content, tags, importance, pinned, feel, source_bucket, valence, arousal | 存储记忆 |
grow |
content | 日记拆分归档 |
trace |
bucket_id, name, domain, valence, arousal, importance, tags, resolved, pinned, digested, content, delete | 修改元数据/内容/删除 |
pulse |
include_archive | 系统状态 |
dream |
(无) | 做梦自省 |
工具详细行为
breath — 两种模式:
- 浮现模式(无 query):无参调用,按衰减引擎活跃度排序返回 top 记忆,permanent/pinned 始终浮现
- 检索模式(有 query):关键词 + 向量双通道搜索,四维评分(topic×4 + emotion×2 + time×2.5 + importance×1),阈值过滤
- Feel 检索(
domain="feel"):特殊通道,按创建时间倒序返回所有 feel 类型桶,不走评分逻辑 - 若指定 valence,对匹配桶的 valence 微调 ±0.1(情感记忆重构)
hold — 两种模式:
- 普通模式(
feel=False,默认):自动 LLM 分析 domain/valence/arousal/tags/name → 向量相似度查重 → 相似度>0.85 则合并到已有桶 → 否则新建 dynamic 桶 → 生成 embedding - Feel 模式(
feel=True):跳过 LLM 分析,直接存为feel类型桶(存入feel/目录),不参与普通浮现/衰减/合并。若提供source_bucket,标记源记忆为digested=True并写入model_valence。返回格式:🫧feel→{bucket_id}
dream — 做梦/自省触发器:
- 返回最近 10 条 dynamic 桶摘要 + 自省引导词
- 检测 feel 结晶化:≥3 条相似 feel(embedding 相似度>0.7)→ 提示升级为钉选准则
- 检测未消化记忆:列出
digested=False的桶供模型反思
trace — 记忆编辑:
- 修改任意元数据字段(name/domain/valence/arousal/importance/tags/resolved/pinned)
digested=0/1:隐藏/取消隐藏记忆(控制是否在 dream 中出现)content="...":替换正文内容并重新生成 embeddingdelete=True:删除桶文件
grow — 日记拆分:
- 大段日记文本 → LLM 拆为 2~6 条独立记忆 → 每条走 hold 普通模式流程
pulse — 系统状态:
- 返回各类型桶数量、衰减引擎状态、未解决/钉选/feel 统计
REST API(17 个端点)
| 端点 | 方法 | 功能 |
|---|---|---|
/health |
GET | 健康检查 |
/breath-hook |
GET | SessionStart 钩子 |
/dream-hook |
GET | Dream 钩子 |
/dashboard |
GET | Dashboard 页面 |
/api/buckets |
GET | 桶列表 |
/api/bucket/{id} |
GET | 桶详情 |
/api/search?q= |
GET | 搜索 |
/api/network |
GET | 向量相似网络 |
/api/breath-debug |
GET | 评分调试 |
/api/config |
GET | 配置查看(key 脱敏) |
/api/config |
POST | 热更新配置 |
/api/import/upload |
POST | 上传并启动历史对话导入 |
/api/import/status |
GET | 导入进度查询 |
/api/import/pause |
POST | 暂停/继续导入 |
/api/import/patterns |
GET | 导入完成后词频规律检测 |
/api/import/results |
GET | 已导入记忆桶列表 |
/api/import/review |
POST | 批量审阅/批准导入结果 |
Dashboard(5 个 Tab)
- 记忆桶列表:6 种过滤器 + 主题域过滤 + 搜索 + 详情面板
- Breath 模拟:输入参数 → 可视化五步流程 → 四维条形图
- 记忆网络:Canvas 力导向图(节点=桶,边=相似度)
- 配置:热更新脱水/embedding/合并参数
- 导入:历史对话拖拽上传 → 分块处理进度条 → 词频规律分析 → 导入结果审阅
部署选项
- 本地 stdio(
python server.py) - Docker + Cloudflare Tunnel(
docker-compose.yml) - Docker Hub 预构建镜像(
docker-compose.user.yml,p0luz/ombre-brain) - Render.com 一键部署(
render.yaml) - Zeabur 部署(
zbpack.json) - GitHub Actions 自动构建推送 Docker Hub(
.github/workflows/docker-publish.yml)
迁移/批处理工具:migrate_to_domains.py、reclassify_domains.py、reclassify_api.py、backfill_embeddings.py、write_memory.py、check_buckets.py、import_memory.py(历史对话导入引擎)
降级策略
- 脱水 API 不可用 → 本地关键词提取 + 句子评分
- 向量搜索不可用 → 纯 fuzzy match
- 逐条错误隔离(grow 中单条失败不影响其他)
安全:路径遍历防护(safe_path())、API Key 脱敏、API Key 不持久化到 yaml、输入范围钳制
监控:结构化日志、Health 端点、Breath Debug 端点、Dashboard 统计栏、衰减周期日志
1. 环境变量清单
| 变量名 | 用途 | 必填 | 默认值 / 示例 |
|---|---|---|---|
OMBRE_API_KEY |
脱水/打标/嵌入的 LLM API 密钥,覆盖 config.yaml 的 dehydration.api_key |
否(无则 API 功能降级到本地) | "" |
OMBRE_BASE_URL |
API base URL,覆盖 config.yaml 的 dehydration.base_url |
否 | "" |
OMBRE_TRANSPORT |
传输模式:stdio / sse / streamable-http |
否 | "" → 回退到 config 或 "stdio" |
OMBRE_BUCKETS_DIR |
记忆桶存储目录路径 | 否 | "" → 回退到 config 或 ./buckets |
OMBRE_HOOK_URL |
SessionStart 钩子调用的服务器 URL | 否 | "http://localhost:8000" |
OMBRE_HOOK_SKIP |
设为 "1" 跳过 SessionStart 钩子 |
否 | 未设置(不跳过) |
环境变量优先级:环境变量 > config.yaml > 硬编码默认值。所有环境变量在 utils.py 中读取并注入 config dict。
2. 模块结构与依赖关系
┌──────────────┐
│ server.py │ MCP 主入口,6 个工具 + Dashboard + Hook
└──────┬───────┘
┌───────────────┼───────────────┬────────────────┐
▼ ▼ ▼ ▼
bucket_manager.py dehydrator.py decay_engine.py embedding_engine.py
记忆桶 CRUD+搜索 脱水压缩+打标 遗忘曲线+归档 向量化+语义检索
│ │ │
└───────┬───────┘ │
▼ ▼
utils.py ◄────────────────────────────────────┘
配置/日志/ID/路径安全/token估算
| 文件 | 职责 | 依赖(项目内) | 被谁调用 |
|---|---|---|---|
server.py |
MCP 服务器主入口,注册工具 + Dashboard API + 钩子端点 | bucket_manager, dehydrator, decay_engine, embedding_engine, utils |
test_tools.py |
bucket_manager.py |
记忆桶 CRUD、多维索引搜索、wikilink 注入、激活更新 | utils |
server.py, check_buckets.py, backfill_embeddings.py |
decay_engine.py |
衰减引擎:遗忘曲线计算、自动归档、自动结案 | 无(接收 bucket_mgr 实例) |
server.py |
dehydrator.py |
数据脱水压缩 + 合并 + 自动打标(LLM API + 本地降级) | utils |
server.py |
embedding_engine.py |
向量化引擎:Gemini embedding API + SQLite + 余弦搜索 | utils |
server.py, backfill_embeddings.py |
utils.py |
配置加载、日志、路径安全、ID 生成、token 估算 | 无 | 所有模块 |
write_memory.py |
手动写入记忆 CLI(绕过 MCP) | 无(独立脚本) | 无 |
backfill_embeddings.py |
为存量桶批量生成 embedding | utils, bucket_manager, embedding_engine |
无 |
check_buckets.py |
桶数据完整性检查 | bucket_manager, utils |
无 |
import_memory.py |
历史对话导入引擎(支持 Claude JSON/ChatGPT/DeepSeek/Markdown/纯文本),分块处理+断点续传+词频分析 | utils |
server.py |
reclassify_api.py |
用 LLM API 重打标未分类桶 | 无(直接用 openai) |
无 |
reclassify_domains.py |
基于关键词本地重分类 | 无 | 无 |
migrate_to_domains.py |
平铺桶 → 域子目录迁移 | 无 | 无 |
test_smoke.py |
冒烟测试 | utils, bucket_manager, dehydrator, decay_engine |
无 |
test_tools.py |
MCP 工具端到端测试 | utils, server, bucket_manager |
无 |
3. 硬编码值清单
3.1 固定分数 / 特殊返回值
| 值 | 位置 | 用途 |
|---|---|---|
999.0 |
decay_engine.py calculate_score |
pinned / protected / permanent 桶永不衰减 |
50.0 |
decay_engine.py calculate_score |
feel 桶固定活跃度分数 |
0.02 |
decay_engine.py resolved_factor |
resolved + digested 时的权重乘数(加速淡化) |
0.05 |
decay_engine.py resolved_factor |
仅 resolved 时的权重乘数(沉底) |
1.5 |
decay_engine.py urgency_boost |
arousal > 0.7 且未解决时的紧迫度加成 |
3.2 衰减公式参数
| 值 | 位置 | 用途 |
|---|---|---|
36.0 |
decay_engine.py _calc_time_weight |
新鲜度半衰期(小时),1.0 + e^(-t/36) |
0.3 (指数) |
decay_engine.py calculate_score |
activation_count ** 0.3(记忆巩固指数) |
3.0 (天) |
decay_engine.py calculate_score |
短期/长期切换阈值 |
0.7 / 0.3 |
decay_engine.py combined_weight |
短期权重分配:time×0.7 + emotion×0.3 |
0.7 |
decay_engine.py urgency_boost |
arousal 紧迫度触发阈值 |
4 / 30 (天) |
decay_engine.py execute_cycle |
自动结案:importance≤4 且 >30天 |
3.3 搜索/评分参数
| 值 | 位置 | 用途 |
|---|---|---|
×3 / ×2.5 / ×2 |
bucket_manager.py _calc_topic_score |
桶名 / 域名 / 标签的 topic 评分权重 |
1000 (字符) |
bucket_manager.py _calc_topic_score |
正文截取长度 |
0.1 |
bucket_manager.py _calc_time_score |
时间亲近度衰减系数 e^(-0.1 × days) |
0.3 |
bucket_manager.py search_multi |
resolved 桶的归一化分数乘数 |
0.5 |
server.py breath/search |
向量搜索相似度下限 |
0.7 |
server.py dream |
feel 结晶相似度阈值 |
3.4 Token 限制 / 截断
| 值 | 位置 | 用途 |
|---|---|---|
10000 |
server.py breath 默认 max_tokens |
浮现/搜索 token 预算 |
20000 |
server.py breath 上限 |
max_tokens 硬上限 |
50 / 20 |
server.py breath |
max_results 上限 / 默认值 |
3000 |
dehydrator.py dehydrate |
API 脱水内容截断 |
2000 |
dehydrator.py merge |
API 合并内容各截断 |
5000 |
dehydrator.py digest |
API 日记整理内容截断 |
2000 |
embedding_engine.py |
embedding 文本截断 |
100 |
dehydrator.py |
内容 < 100 token 跳过脱水 |
3.5 时间/间隔/重试
| 值 | 位置 | 用途 |
|---|---|---|
60.0s |
dehydrator.py |
OpenAI 客户端 timeout |
30.0s |
embedding_engine.py |
Embedding API timeout |
60s |
server.py keepalive |
保活 ping 间隔 |
48.0h |
bucket_manager.py touch |
时间涟漪窗口 ±48h |
2s |
backfill_embeddings.py |
批次间等待 |
3.6 随机浮现
| 值 | 位置 | 用途 |
|---|---|---|
3 |
server.py breath search |
结果不足 3 条时触发 |
0.4 |
server.py breath search |
40% 概率触发随机浮现 |
2.0 |
server.py breath search |
随机池:score < 2.0 的低权重桶 |
1~3 |
server.py breath search |
随机浮现数量 |
3.7 情感/重构
| 值 | 位置 | 用途 |
|---|---|---|
0.2 |
server.py breath search |
情绪重构偏移系数 (q_valence - 0.5) × 0.2(最大 ±0.1) |
3.8 其他
| 值 | 位置 | 用途 |
|---|---|---|
12 |
utils.py gen_id |
bucket ID 长度(UUID hex[:12]) |
80 |
utils.py sanitize_name |
桶名最大长度 |
1.5 / 1.3 |
utils.py count_tokens_approx |
中文/英文 token 估算系数 |
8000 |
server.py |
MCP 服务器端口 |
30 字符 |
server.py grow |
短内容快速路径阈值 |
10 |
server.py dream |
取最近 N 个桶 |
4. Config.yaml 完整键表
| 键路径 | 默认值 | 用途 |
|---|---|---|
transport |
"stdio" |
传输模式 |
log_level |
"INFO" |
日志级别 |
buckets_dir |
"./buckets" |
记忆桶目录 |
merge_threshold |
75 |
合并相似度阈值 (0-100) |
dehydration.model |
"deepseek-chat" |
脱水用 LLM 模型 |
dehydration.base_url |
"https://api.deepseek.com/v1" |
API 地址 |
dehydration.api_key |
"" |
API 密钥 |
dehydration.max_tokens |
1024 |
脱水返回 token 上限 |
dehydration.temperature |
0.1 |
脱水温度 |
embedding.enabled |
true |
启用向量检索 |
embedding.model |
"gemini-embedding-001" |
Embedding 模型 |
decay.lambda |
0.05 |
衰减速率 λ |
decay.threshold |
0.3 |
归档分数阈值 |
decay.check_interval_hours |
24 |
衰减扫描间隔(小时) |
decay.emotion_weights.base |
1.0 |
情感权重基值 |
decay.emotion_weights.arousal_boost |
0.8 |
唤醒度加成系数 |
matching.fuzzy_threshold |
50 |
模糊匹配下限 |
matching.max_results |
5 |
匹配返回上限 |
scoring_weights.topic_relevance |
4.0 |
主题评分权重 |
scoring_weights.emotion_resonance |
2.0 |
情感评分权重 |
scoring_weights.time_proximity |
2.5 |
时间评分权重 |
scoring_weights.importance |
1.0 |
重要性评分权重 |
scoring_weights.content_weight |
3.0 |
正文评分权重 |
wikilink.enabled |
true |
启用 wikilink 注入 |
wikilink.use_tags |
false |
wikilink 包含标签 |
wikilink.use_domain |
true |
wikilink 包含域名 |
wikilink.use_auto_keywords |
true |
wikilink 自动关键词 |
wikilink.auto_top_k |
8 |
wikilink 取 Top-K 关键词 |
wikilink.min_keyword_len |
2 |
wikilink 最短关键词长度 |
wikilink.exclude_keywords |
[] |
wikilink 排除关键词表 |
5. 核心设计决策记录
5.1 为什么用 Markdown + YAML frontmatter 而不是数据库?
决策:每个记忆桶 = 一个 .md 文件,元数据在 YAML frontmatter 里。
理由:
- 与 Obsidian 原生兼容——用户可以直接在 Obsidian 里浏览、编辑、搜索记忆
- 文件系统即数据库,天然支持 git 版本管理
- 无外部数据库依赖,部署简单
- wikilink 注入让记忆之间自动形成知识图谱
放弃方案:SQLite/PostgreSQL 全量存储。过于笨重,失去 Obsidian 可视化优势。
5.2 为什么 embedding 单独存 SQLite 而不放 frontmatter?
决策:向量存 embeddings.db(SQLite),与 Markdown 文件分离。
理由:
- 3072 维浮点向量无法合理存入 YAML frontmatter
- SQLite 支持批量查询和余弦相似度计算
- embedding 是派生数据,丢失可重新生成(
backfill_embeddings.py) - 不污染 Obsidian 可读性
5.3 为什么搜索用双通道(关键词 + 向量)而不是纯向量?
决策:关键词模糊匹配(rapidfuzz)+ 向量语义检索并联,结果去重合并。
理由:
- 纯向量在精确名词匹配上表现差("2024年3月"这类精确信息)
- 纯关键词无法处理语义近似("很累" → "身体不适")
- 双通道互补,关键词保精确性,向量补语义召回
- 向量不可用时自动降级到纯关键词模式
5.4 为什么有 dehydration(脱水)这一层?
决策:存入前先用 LLM 压缩内容(保留信息密度,去除冗余表达),API 不可用时降级到本地关键词提取。
理由:
- MCP 上下文有 token 限制,原始对话冗长,需要压缩
- LLM 压缩能保留语义和情感色彩,纯截断会丢信息
- 降级到本地确保离线可用——关键词提取 + 句子排序 + 截断
放弃方案:只做截断。信息损失太大。
5.5 为什么 feel 和普通记忆分开?
决策:feel=True 的记忆存入独立 feel/ 目录,不参与普通浮现、不衰减、不合并。
理由:
- feel 是模型的自省产物,不是事件记录——两者逻辑完全不同
- 事件记忆应该衰减遗忘,但"我从中学到了什么"不应该被遗忘
- feel 的 valence 是模型自身感受(不等于事件情绪),混在一起会污染情感检索
- feel 可以通过
breath(domain="feel")单独读取
5.6 为什么 resolved 不删除记忆?
决策:resolved=True 让记忆"沉底"(权重 ×0.05),但保留在文件系统中,关键词搜索仍可触发。
理由:
- 模拟人类记忆:resolved 的事不会主动想起,但别人提到时能回忆
- 删除是不可逆的,沉底可随时
resolved=False重新激活 resolved + digested进一步降权到 ×0.02(已消化 = 更释然)
放弃方案:直接删除。不可逆,且与人类记忆模型不符。
5.7 为什么用分段式短期/长期权重?
决策:≤3 天时间权重占 70%,>3 天情感权重占 70%。
理由:
- 刚发生的事主要靠"新鲜"驱动浮现(今天的事 > 昨天的事)
- 时间久了,决定记忆存活的是情感强度(强烈的记忆更难忘)
- 这比单一衰减曲线更符合人类记忆的双重存储理论
5.8 为什么 dream 设计成对话开头自动执行?
决策:每次新对话启动时,Claude 执行 dream() 消化最近记忆,有沉淀写 feel,能放下的 resolve。
理由:
- 模拟睡眠中的记忆整理——人在睡觉时大脑会重放和整理白天的经历
- 让 Claude 对过去的记忆有"第一人称视角"的自省,而不是冷冰冰地搬运数据
- 自动触发确保每次对话都"接续"上一次,而非从零开始
5.9 为什么新鲜度用连续指数衰减而不是分段阶梯?
决策:bonus = 1.0 + e^(-t/36),t 为小时,36h 半衰。
理由:
- 分段阶梯(0-1天=1.0,第2天=0.9...)有不自然的跳变
- 连续指数更符合遗忘曲线的物理模型
- 36h 半衰期使新桶在前两天有明显优势,72h 后接近自然回归
- 值域 1.0~2.0 保证老记忆不被惩罚(×1.0),只是新记忆有额外加成(×2.0)
放弃方案:分段线性(原实现)。跳变点不自然,参数多且不直观。
5.10 情感记忆重构(±0.1 偏移)的设计动机
决策:搜索时如果指定了 valence,会微调结果桶的 valence 展示值 (q_valence - 0.5) × 0.2。
理由:
- 模拟认知心理学中的"心境一致性效应"——当前心情会影响对过去的回忆
- 偏移量很小(最大 ±0.1),不会扭曲事实,只是微妙的"色彩"调整
- 原始 valence 不被修改,只影响展示层
6. 目录结构约定
buckets/
├── permanent/ # pinned/protected 桶,importance=10,永不衰减
├── dynamic/
│ ├── 日常/ # domain 子目录
│ ├── 情感/
│ ├── 自省/
│ ├── 数字/
│ └── ...
├── archive/ # 衰减归档桶
└── feel/ # 模型自省 feel 桶
桶文件格式:
---
id: 76237984fa5d
name: 桶名
domain: [日常, 情感]
tags: [关键词1, 关键词2]
importance: 5
valence: 0.6
arousal: 0.4
activation_count: 3
resolved: false
pinned: false
digested: false
created: 2026-04-17T10:00:00+08:00
last_active: 2026-04-17T14:00:00+08:00
type: dynamic
---
桶正文内容...