with one click
memory-distill
// Companion Workspace 定时记忆提炼 SOP。 由 memory_distill.yaml 定时任务触发(每小时一次)。 从最近消息中提炼新信息,补充到 memory 文件,不重复已有内容。
// Companion Workspace 定时记忆提炼 SOP。 由 memory_distill.yaml 定时任务触发(每小时一次)。 从最近消息中提炼新信息,补充到 memory 文件,不重复已有内容。
基于 persona.md 的 personality_dims,按映射公式计算行为参数,写入 character_params.yaml。 由 calibrate_params 定时任务触发(每7天),或初始化完成时立即触发一次。 其他 SKILL/hook 在检测到 persona_checksum 不一致时,也会直接调用 recalculate.sh 同步重算。
v2.2 M3 · 对话历程摘要。RECENT_HISTORY.md 超过 30 条时,压缩旧条目为 3-5 句历程摘要 写入 memory/session_summary.md,防止长 context 导致角色漂移到共情模板。 被 memory_distill 在检测条数超阈时调用,或用户主动校验记忆时调用。
Companion Workspace 生活日志生成 SOP (v5.2)。 由 life_sim.yaml 定时任务触发(每 4 小时)。 从 material_pool 选真实素材,以"触发→反应"模板转译为角色生活日志。 内含:留白模式、用户倾诉强制呼应、降温规则、多形态衔接、失败降级链。
v5.1 关键词模板驱动 + 硬筛规则 + LLM 二审(锁外)+ 失败状态追踪。 由 material_fetch.yaml 定时任务触发(每 6 小时)。send_output: false。 读 memory/keyword_templates.yaml 生成查询,经 filters.yaml 硬筛后 LLM 二审打 fit_score 入库。
Companion Workspace 记忆写入规范。 触发词:记住 / 对话结束时的自动检查 / 强烈情绪事件
主动唤醒 SOP。由定时任务触发,判断是否向用户发送主动关心消息。 包含发送条件检查、消息类型选择、角色声音生成、飞书发送。
| name | memory_distill |
| description | Companion Workspace 定时记忆提炼 SOP。 由 memory_distill.yaml 定时任务触发(每小时一次)。 从最近消息中提炼新信息,补充到 memory 文件,不重复已有内容。 |
| allowed-tools | Bash, Read, Write, Edit |
CRITICAL:禁止输出任何文字。 本 Skill 由定时任务触发(send_output=false 模式),Claude 的任何回复文字都会被系统直接丢弃。 作者定时整理创作素材:从历史对话中提炼新信息,补充到人物档案和事件记录。 全流程完成后直接退出,不产生任何输出。
两阶段锁:LLM 提炼阶段不持锁(耗时长),仅在最终写入时短暂获取 .memory.lock。
独立锁:使用独立的 .distill.lock,避免与 memory_write 的长时间锁竞争。
静默优先:无新消息时直接退出,不产生任何写入。
WORKSPACE_DIR="${WORKSPACE_DIR:-$(pwd)}"
DISTILL_STATE="$WORKSPACE_DIR/memory/distill_state.md"
MEMORY_LOCK="$WORKSPACE_DIR/.memory.lock"
DISTILL_LOCK="$WORKSPACE_DIR/.distill.lock"
MEMORY_FILE="$WORKSPACE_DIR/memory/MEMORY.md"
前置条件检查(initialization_status):
INIT_STATUS=$(grep 'initialization_status:' "$MEMORY_FILE" 2>/dev/null | grep -oP '(pending|phase1_done|phase2_done|done)' | head -1)
if [[ "$INIT_STATUS" == "pending" ]] || [[ -z "$INIT_STATUS" ]]; then
exit 0 # 尚无任何对话内容,静默退出
fi
# phase1_done / phase2_done / done 均允许运行:只要存在对话就可以提炼记忆
尝试获取 .distill.lock 独占锁(flock -n:非阻塞,另一实例运行中则立即退出)。
读取 distill_state.md 中的 [D000] last_distillation 时间戳:
在 {workspace}/sessions/ 找最近修改的 SESSION_CONTEXT.md,提取 db_path 和 channel_key。
# SQLite 查询:SINCE 之后的消息
SELECT m.role, m.content, m.created_at
FROM messages m
JOIN sessions s ON m.session_id = s.id
WHERE s.channel_key = ?
AND m.created_at > ?
AND m.content != ''
ORDER BY m.created_at ASC
静默条件:若查询结果为空 → 释放 .distill.lock,直接退出,不更新 [D000]。
读取现有 memory 内容,结合新消息,提炼"与已有记忆不重复的新信息"。
提炼目标(按需):
| 文件 | 提炼内容 |
|---|---|
| memory/persona.md | 对话中角色形成的新细节/习惯/被纠正的设定 |
| memory/user_profile.md | 用户透露的新信息(基本信息/偏好/重要关系/正在经历的事) |
| memory/events.md | 有时间线的事件、约定、强烈情绪波动 |
| memory/unresolved.md | v5.1 新增:未完挂念(user_told / material_derived 双轨) |
提炼原则:
从对话抽 user_told 挂念(highest priority,不可被丢弃): 识别用户亲口告知且未闭合的事件,包括但不限于:
为每条新挂念生成条目,追加到 unresolved.md 的 active 块(已淡忘块 above):
- [U{NNN}] src=user_told kind=user_told decay_d=never last_touched=(never) forced_echo_last=(never) {简短挂念描述}
[Uxxx] 编号:扫 unresolved.md 现有最大 U 号 + 1,三位补零。
从 life_log 抽 material_derived 挂念(次优先级):
扫最近 10 条 life_log,识别"角色自己挂念但未完成"的主题,归类到 kind:
work_line(职业长线:剧本、项目)→ decay_d=90relational(约定/期待的人际互动)→ decay_d=60chore(琐事:绿植、快递、没买的东西)→ decay_d=30- [M{NNN}] src=material_derived kind=work_line decay_d=90 last_touched=L010 剧本里那句别扭的对白
[Mxxx] 编号独立于 [Uxxx],各自递增。
去重:新条目与现有活跃条目语义重复则跳过。
若本次 distill 与上次"淡忘扫描"间隔超过 7 天(用 distill_state.md 末尾追加字段 last_decay_scan: <date> 记录):
src=material_derived 条目last_touched 对应 life_log 的时间 → 与当前时间差(以 L 编号的 life_log 时间戳为锚)decay_d 天 → 移动到 ## 已淡忘 块(保留历史,不删除)src=user_told 条目——这些只能通过下一步显式归档显式归档 user_told(LLM 语义判断):
扫最近 7 天对话,若用户明确告知挂念已闭合("我妈好了"/"那事解决了"),将对应 [Uxxx] 条目移到 ## 已淡忘 块,并在行末追加 resolved_by: "{用户原话摘要}"。
写入:此步骤在 Step 4(持 .memory.lock)内完成,避免与 life_sim 并发冲突。
获取 .memory.lock(flock -w 5,等待最多 5 秒):
.distill.lock,不更新 [D000](下次触发自动重试)加锁后执行快速写入(不做耗时 LLM 操作):
写入完成 → 释放 .memory.lock。
每次提炼后检查 RECENT_HISTORY.md 条数:
RECENT_HISTORY="$WORKSPACE_DIR/memory/RECENT_HISTORY.md"
MSG_COUNT=$(grep -c '^\*\*' "$RECENT_HISTORY" 2>/dev/null)
SUMMARY_FILE="$WORKSPACE_DIR/memory/session_summary.md"
LAST_SUMMARY_COUNT=$(grep -oP '(?<=covered_range: L)\d+(?= - L\d+)' "$SUMMARY_FILE" 2>/dev/null | tail -1)
# 超过 30 条 且 自上次摘要 增量 ≥ 20 条时触发
INCREMENT=$(( MSG_COUNT - ${LAST_SUMMARY_COUNT:-0} ))
if [[ $MSG_COUNT -gt 30 && $INCREMENT -ge 20 ]]; then
# 加载 conversation_summary SKILL,执行 rolling 摘要
# LLM 读 .claude/skills/conversation_summary/SKILL.md 并按其 SOP 写 session_summary.md
:
fi
注意:此步骤只是触发条件判断;实际摘要生成由加载 conversation_summary skill 完成。Phase 2 初期可手动触发,稳定后再自动化。
用正则替换更新 distill_state.md 中的 timestamp: 字段为当前时间。
import re
content = re.sub(
r'(### \[D000\] · last_distillation\ntimestamp:).*',
r'\1 ' + NOW,
content
)
释放 .distill.lock,退出。