Doc-code consistency (per BEHAVIOR_SPEC.md ground truth):
- INTERNALS.md, dehydrator.py, README.md, config.example.yaml: drop the
outdated "API 不可用自动降级到本地关键词提取" claims; align with the
"RuntimeError on API outage, no silent fallback" design decision
- INTERNALS.md & BEHAVIOR_SPEC.md narrative: activation_count=1 → 0 (B-04)
- server.py header: 5 MCP tools → 6 (add dream)
OMBRE_PORT (T5/T6):
- Replace hardcoded 8000 in FastMCP / uvicorn / keepalive URL
with int(os.environ.get("OMBRE_PORT", "8000"))
OMBRE_HOOK_URL / OMBRE_HOOK_SKIP webhook (T7):
- Implement _fire_webhook() helper: fire-and-forget POST with 5s timeout,
failures logged at WARNING but never propagated
- Wired into breath / dream MCP tools and /breath-hook + /dream-hook routes
- Push payload: {event, timestamp, payload:{...}}; documented in ENV_VARS.md
Dashboard host-vault input (T12, per user request):
- New /api/host-vault GET/POST endpoints persist OMBRE_HOST_VAULT_DIR
to project-root .env (idempotent upsert, preserves other entries,
rejects quotes/newlines)
- Settings tab gains a "宿主机记忆桶目录 (Docker)" panel with
load/save buttons and a clear "需要 docker compose down/up 生效" notice
30 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, importance_min | 检索/浮现记忆 |
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 记忆,钉选桶始终展示;冷启动检测(
activation_count==0 && importance>=8)的桶最多 2 个插入最前,再 Top-1 固定 + Top-20 随机打乱 - 检索模式(有 query):关键词 + 向量双通道搜索,四维评分(topic×4 + emotion×2 + time×2.5 + importance×1),阈值过滤
- Feel 检索(
domain="feel"):特殊通道,按创建时间倒序返回所有 feel 类型桶,不走评分逻辑 - 重要度批量模式(
importance_min>=1):跳过语义搜索,直接筛选 importance≥importance_min 的桶,按 importance 降序,最多 20 条 - 若指定 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/status |
GET | 系统状态(版本/桶数/引擎)🔒 |
/api/import/upload |
POST | 上传并启动历史对话导入 🔒 |
/api/import/status |
GET | 导入进度查询 🔒 |
/api/import/pause |
POST | 暂停/继续导入 🔒 |
/api/import/patterns |
GET | 导入完成后词频规律检测 🔒 |
/api/import/results |
GET | 已导入记忆桶列表 🔒 |
/api/import/review |
POST | 批量审阅/批准导入结果 🔒 |
/auth/status |
GET | 认证状态(是否需要初始化密码) |
/auth/setup |
POST | 首次设置密码 |
/auth/login |
POST | 密码登录,颁发 session cookie |
/auth/logout |
POST | 注销 session |
/auth/change-password |
POST | 修改密码 🔒 |
🔒 = 需要 Dashboard 认证(未认证返回 401 JSON)
Dashboard 认证
- 密码存储:SHA-256 + 随机 salt,保存于
{buckets_dir}/.dashboard_auth.json - 环境变量
OMBRE_DASHBOARD_PASSWORD设置后,覆盖文件密码(只读,不可通过 Dashboard 修改) - Session:内存字典(服务重启失效),cookie
ombre_session(HttpOnly, SameSite=Lax, 7天) /health,/breath-hook,/dream-hook,/mcp*路径不受保护(公开)
Dashboard(6 个 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 不可用 → 直接抛 RuntimeError(设计决策,详见 BEHAVIOR_SPEC.md 三、降级行为表)
- 向量搜索不可用 → 纯 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 钩子 |
否 | 未设置(不跳过) |
OMBRE_DASHBOARD_PASSWORD |
预设 Dashboard 访问密码;设置后覆盖文件密码,首次访问不弹设置向导 | 否 | "" |
环境变量优先级:环境变量 > 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,不可用时报 RuntimeError) | 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 不可用时直接抛出 RuntimeError,不静默降级。
理由:
- MCP 上下文有 token 限制,原始对话冗长,需要压缩
- LLM 压缩能保留语义和情感色彩,纯截断会丢信息
- 本地关键词提取质量不足以替代语义打标与合并,静默降级会产生错误分类记忆,比报错更危险。详见 BEHAVIOR_SPEC.md 三、降级行为表。
放弃方案:只做截断。信息损失太大。
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
---
桶正文内容...
7. Bug 修复记录 (B-01 至 B-10)
B-01 — update(resolved=True) 自动归档 🔴 高
- 文件:
bucket_manager.py→update() - 问题:
resolved=True时立即调用_move_bucket(archive_dir)将桶移入archive/ - 修复: 移除
_move_bucket逻辑;resolved 桶留在dynamic/,由 decay 引擎自然淘汰 - 影响: 已解决的桶仍可被关键词检索命中(降权但不消失)
- 测试:
tests/regression/test_issue_B01.py,tests/integration/test_scenario_07_trace.py
B-03 — int() 截断浮点 activation_count 🔴 高
- 文件:
decay_engine.py→calculate_score() - 问题:
max(1, int(activation_count))将_time_ripple写入的 1.3 截断为 1,涟漪加成失效 - 修复: 改为
max(1.0, float(activation_count)) - 影响: 时间涟漪效果现在正确反映在 score 上;高频访问的桶衰减更慢
- 测试:
tests/regression/test_issue_B03.py,tests/unit/test_calculate_score.py
B-04 — create() 初始化 activation_count=1 🟠 中
- 文件:
bucket_manager.py→create() - 问题:
activation_count=1导致冷启动检测条件== 0永不满足,新建重要桶无法浮现 - 修复: 改为
activation_count=0;touch()首次命中后变 1 - 测试:
tests/regression/test_issue_B04.py,tests/integration/test_scenario_01_cold_start.py
B-05 — 时间衰减系数 0.1 过快 🟠 中
- 文件:
bucket_manager.py→_calc_time_score() - 问题:
math.exp(-0.1 * days)导致 30 天后得分仅剩 ≈0.05,远快于人类记忆曲线 - 修复: 改为
math.exp(-0.02 * days)(30 天后 ≈0.549) - 影响: 记忆保留时间更符合人类认知模型
- 测试:
tests/regression/test_issue_B05.py,tests/unit/test_score_components.py
B-06 — w_time 默认值 2.5 过高 🟠 中
- 文件:
bucket_manager.py→_calc_final_score()(或评分调用处) - 问题:
scoring.get("time_proximity", 2.5)— 时间权重过高,近期低质量记忆得分高于高质量旧记忆 - 修复: 改为
scoring.get("time_proximity", 1.5) - 测试:
tests/regression/test_issue_B06.py,tests/unit/test_score_components.py
B-07 — content_weight 默认值 3.0 过高 🟠 中
- 文件:
bucket_manager.py→_calc_topic_score() - 问题:
scoring.get("content_weight", 3.0)— 内容权重远大于名字权重(×3),导致内容重复堆砌的桶得分高于名字精确匹配的桶 - 修复: 改为
scoring.get("content_weight", 1.0) - 影响: 名字完全匹配 > 标签匹配 > 内容匹配的得分层级现在正确
- 测试:
tests/regression/test_issue_B07.py,tests/unit/test_topic_score.py
B-08 — run_decay_cycle() 同轮 auto_resolve 后 score 未降权 🟡 低
- 文件:
decay_engine.py→run_decay_cycle() - 问题:
auto_resolve标记后立即用旧meta(stale)计算 score,resolved_factor=0.05未生效 - 修复: 在
bucket_mgr.update(resolved=True)后立即执行meta["resolved"] = True,确保同轮降权 - 测试:
tests/regression/test_issue_B08.py,tests/integration/test_scenario_08_decay.py
B-09 — hold() 用 analyze() 覆盖用户传入的 valence/arousal 🟡 低
- 文件:
server.py→hold() - 问题: 先调
analyze(),再直接用结果覆盖用户传入的情感值,情感准确性丢失 - 修复: 使用
final_valence = user_valence if user_valence is not None else analyze_result.get("valence") - 影响: 用户明确传入的情感坐标(包括 0.0)不再被 LLM 结果覆盖
- 测试:
tests/regression/test_issue_B09.py,tests/integration/test_scenario_03_hold.py
B-10 — feel 桶 domain=[] 被填充为 ["未分类"] 🟡 低
- 文件:
bucket_manager.py→create() - 问题:
if not domain: domain = ["未分类"]对所有桶类型生效,feel 桶的空 domain 被错误填充 - 修复: 改为
if not domain and bucket_type != "feel": domain = ["未分类"] - 影响:
breath(domain="feel")通道过滤逻辑现在正确(feel 桶 domain 始终为空列表) - 测试:
tests/regression/test_issue_B10.py,tests/integration/test_scenario_10_feel.py
Bug 修复汇总表
| ID | 严重度 | 文件 | 方法 | 一句话描述 |
|---|---|---|---|---|
| B-01 | 🔴 高 | bucket_manager.py |
update() |
resolved 桶不再自动归档 |
| B-03 | 🔴 高 | decay_engine.py |
calculate_score() |
float activation_count 不被 int() 截断 |
| B-04 | 🟠 中 | bucket_manager.py |
create() |
初始 activation_count=0 |
| B-05 | 🟠 中 | bucket_manager.py |
_calc_time_score() |
时间衰减系数 0.02(原 0.1) |
| B-06 | 🟠 中 | bucket_manager.py |
评分权重配置 | w_time 默认 1.5(原 2.5) |
| B-07 | 🟠 中 | bucket_manager.py |
_calc_topic_score() |
content_weight 默认 1.0(原 3.0) |
| B-08 | 🟡 低 | decay_engine.py |
run_decay_cycle() |
auto_resolve 同轮应用 ×0.05 |
| B-09 | 🟡 低 | server.py |
hold() |
用户 valence/arousal 优先 |
| B-10 | 🟡 低 | bucket_manager.py |
create() |
feel 桶 domain=[] 不被填充 |