Files
Ombre_Brain/INTERNALS.md

23 KiB
Raw Blame History

Ombre Brain — 内部开发文档 / INTERNALS

本文档面向开发者和维护者。记录功能总览、环境变量、模块依赖、硬编码值和核心设计决策。 最后更新2026-04-19


0. 功能总览——这个系统到底做了什么

记忆能力

存储与组织

  • 每条记忆 = 一个 Markdown 文件YAML frontmatter 存元数据),直接兼容 Obsidian 浏览/编辑
  • 四种桶类型:dynamic(普通,会衰减)、permanent(固化,不衰减)、feel(模型感受,不浮现)、archived(已遗忘)
  • 按主题域分子目录:dynamic/日常/dynamic/情感/dynamic/编程/
  • 钉选桶pinnedimportance 锁 10永不衰减/合并,始终浮现为「核心准则」

每条记忆追踪的元数据

  • id12位短UUIDname可读名≤80字tags10~15个关键词
  • domain1~2个主题域从 8 大类 30+ 细分域选)
  • valence(事件效价 01arousal(唤醒度 01model_valence(模型独立感受)
  • importance1~10activation_count(被想起次数)
  • resolved(已解决/沉底)、digested(已消化/写过 feelpinned(钉选)
  • createdlast_active 时间戳

四种检索模式

  1. 自动浮现breath() 无参数按衰减分排序推送钉选桶始终展示Top-1 固定 + Top-20 随机打乱(引入多样性),有 token 预算(默认 10000
  2. 关键词+向量双通道搜索breath(query=...)rapidfuzz 模糊匹配 + Gemini embedding 余弦相似度,合并去重
  3. Feel 独立检索breath(domain="feel")):按创建时间倒序返回所有 feel
  4. 随机浮现:搜索结果 <3 条时 40% 概率漂浮 1~3 条低权重旧桶(模拟人类随机联想)

四维搜索评分(归一化到 0~100

  • topic_relevance权重 4.0name×3 + domain×2.5 + tags×2 + body
  • emotion_resonance权重 2.0Russell 环形模型欧氏距离
  • time_proximity权重 2.5e^(-0.1×days)
  • importance权重 1.0importance/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 条相似 feelembedding 相似度>0.7)→ 提示升级为钉选准则
  • 检测未消化记忆:列出 digested=False 的桶供模型反思

trace — 记忆编辑:

  • 修改任意元数据字段name/domain/valence/arousal/importance/tags/resolved/pinned
  • digested=0/1:隐藏/取消隐藏记忆(控制是否在 dream 中出现)
  • content="...":替换正文内容并重新生成 embedding
  • delete=True:删除桶文件

grow — 日记拆分:

  • 大段日记文本 → LLM 拆为 2~6 条独立记忆 → 每条走 hold 普通模式流程

pulse — 系统状态:

  • 返回各类型桶数量、衰减引擎状态、未解决/钉选/feel 统计

REST API17 个端点)

端点 方法 功能
/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 批量审阅/批准导入结果

Dashboard5 个 Tab

  1. 记忆桶列表6 种过滤器 + 主题域过滤 + 搜索 + 详情面板
  2. Breath 模拟:输入参数 → 可视化五步流程 → 四维条形图
  3. 记忆网络Canvas 力导向图(节点=桶,边=相似度)
  4. 配置:热更新脱水/embedding/合并参数
  5. 导入:历史对话拖拽上传 → 分块处理进度条 → 词频规律分析 → 导入结果审阅

部署选项

  1. 本地 stdiopython server.py
  2. Docker + Cloudflare Tunneldocker-compose.yml
  3. Docker Hub 预构建镜像(docker-compose.user.ymlp0luz/ombre-brain
  4. Render.com 一键部署(render.yaml
  5. Zeabur 部署(zbpack.json
  6. GitHub Actions 自动构建推送 Docker Hub.github/workflows/docker-publish.yml

迁移/批处理工具migrate_to_domains.pyreclassify_domains.pyreclassify_api.pybackfill_embeddings.pywrite_memory.pycheck_buckets.pyimport_memory.py(历史对话导入引擎)

降级策略

  • 脱水 API 不可用 → 本地关键词提取 + 句子评分
  • 向量搜索不可用 → 纯 fuzzy match
  • 逐条错误隔离grow 中单条失败不影响其他)

安全:路径遍历防护(safe_path()、API Key 脱敏、API Key 不持久化到 yaml、输入范围钳制

监控结构化日志、Health 端点、Breath Debug 端点、Dashboard 统计栏、衰减周期日志


1. 环境变量清单

变量名 用途 必填 默认值 / 示例
OMBRE_API_KEY 脱水/打标/嵌入的 LLM API 密钥,覆盖 config.yamldehydration.api_key 否(无则 API 功能降级到本地) ""
OMBRE_BASE_URL API base URL覆盖 config.yamldehydration.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.dbSQLite与 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
---

桶正文内容...