Repository navigation
Releases
v5.6.0 — 记忆网络
本页会随勘误就地更新。规范全文以
main为准。
范围
这一版只回答一个问题:每条记忆,说不说得清是谁记的、从哪来、还能信多久。
现状三处说不清:同一件事被三个模型各记一遍,看起来像三件;命中只给片段,不知道是电脑会话还是网页转述;
摘要覆盖式更新,事后问「当时为什么这么定」答不上来。设计四件:
一、分身 ID:avatar = <agent>-<model>[-<version>],三段全同才算同一个分身。
不取专名只留简称——模型的出厂身份已经有了,别叠加;缺哪段写 unknown
(dsh-unknown 比孤零零的 dsh 诚实),UUID 一律不当分身名。
二、三层溯源,一路回到原文,中间不许"再总结一次":
命中片段(带 avatar / source / 时间)
→ 分索引行:这条属于哪个文件(index\L1.jsonl)
→ 原文:file:line 逐字可读
三、检索只读客观:检索回的是「事实 + 来源 + 时间」,不返回性格、不写性格——
检索改的是"知道什么",改不了"是谁"。
四、性格 = 底座原生,只记录漂移(AVATAR_SPEC §8.1):不建性格面,人格直接用模型出厂神格;
差异不判跑偏、不压制、不写回。漂移只由本分身自己的真实记忆产生(peer: 与检索片段不参与),
由 persona_observe.py 按 persona_key 每天留一行表达快照(句长 / 问句率 / 决定词密度),
diff 只并排给人看,不判正常/跑偏——底座天生带性格,没有零点,也就没有「漂移」(§8.3)。
一句话:允许不同,不许互相污染;要「塑造」人格属于改身份,得人点头。
落地
| 文件 | 做了什么 |
|---|---|
persona_avatar.py |
avatar 唯一入口:认两种头方言(设备/底座 与 来源/模型)、缺段写 unknown、UUID 不当名 |
persona_index_l1.py |
L1.jsonl 每行加 agent/model/avatar;L1.md 加分身列 |
persona_search.py |
结果带分身列;--avatar 过滤;--show-source(文件:行号);--context N(原文前后 N 行) |
persona_master.py |
总索引加「分身分布」 |
impl/launcher/avatar_poc.py |
补 TC-AV7 性格隔离用例:7/7 → 8/8 |
证据
① 三层溯源:10/10 条命中都能在 3 步内读到原文行
② 可重建 :删 index/L1.jsonl → 重建 146 → 146 行
③ 性格隔离:PoC 8/8(AV7:A 变 B 不变 / 检索命中 0 / peer 不入漂移)
分身分布(实测):网页-deepseek-web 92 · dsh-unknown 32 · hermes-deepseek-v4-flash 7 ·
hermes-qwen3-* 12 · unknown 3。
顺手修掉两个真缺陷:--device/--model 对电脑会话一直是空的(导出写「来源/模型」,检索只认「设备/底座」);
分身过滤发生在「取前 60 名之后」,导致 206 条 dsh 命中被误报 0 条——改成先过滤再取候选。
边界
- 历史文件没有元信息头 →
unknown就写unknown,不按内容猜模型(猜出来的来源比没有更坏); - 不做跨设备实时同步:同步是文件层的事,索引只负责"读得对";
- 不为整洁删重复——重复本身是证据(同一件事被两个模型独立记下,说明它重要);
- 性格面不建(2026-11-09 定):人格用底座出厂神格;要「塑造」某分身属于改身份,仍须人点头(5.4 红线);
- 漂移记录只落
ops\drift.jsonl,不进记忆正文——它是观察,不是经历。
修订记录
2026-11-09 首版。同日从「设计成文」推进到「已落地」:persona_avatar.py +
--show-source / --context / --avatar;性格隔离按他的要求折进 AVATAR_SPEC §8(合并章节),不新增节号。
v5.5.0 — 云端任务调度
本页会随勘误就地更新。规范全文以
main为准。
范围
这一版只回答一个问题:活儿是谁记的、谁派的、谁接了,能不能说清。
多端(电脑 / 手机 / 网页 / 别的模型)都能总结出待办。各记各的结果是两个人做同一件事,
或者都以为对方在做——这不是效率问题,是相互抹杀。所以分工写死:
生成归各端,登记与分发归服务端;本地不留私有任务池。
任一端总结 ──► 上交(离线先落 outbox)──► 服务端只做三件事
合并 / 排序 / 匹配
│
派给「最近报到过、能力匹配」的分身
│
ack ──► 执行 ──► done(带证据)
│
超时未接手:expired → 改派(上限 3 次)→ failed + 告警
三条规则写死,不留「我觉得」:
- 合并:标题归一相同,或 2-gram Jaccard ≥ 0.6 → 合成一条,
source_ids全留(丢了就不可回溯); - 排序:
prio1–10,同优先级先看依赖、再看创建时间;服务端只校正不发明; - 匹配:人的指令 > 能力标签 > 最近报到;能力不够的不派。
两个防坑设计:认领用文件锁 + 追加(抢不到就是 exit 1,不靠"应该不会撞");
状态只追加不覆盖(ops/tasks.jsonl 与 dispatch.jsonl 同构),任何一条都能倒查回去。
落地
| 命令 / 接口 | 作用 |
|---|---|
persona_tasks.py submit --offline |
上交;离线落 ops/outbox.jsonl |
flush / merge / pool / show / ack / done |
补交、合并、看池子、追溯、认领、收工 |
serve --port 8787 |
POST /tasks · GET /tasks?agent= · POST /tasks/<id>/ack |
HTTP handler 只有 persona_tasks._Handler 一处,serve() 与自检都从 make_server() 起。
证据
① 不重复:两进程抢同一条 → 退出码 [0, 1]
② 不丢 :outbox 1 条 → flush 后池子 1→2
③ 不卡 :换人次数用满 → failed + 告警
④ 可回溯:show 能追到 source_ids 与提出者
⑤ 合并 :同义标题 → 合并,来源 ['s-1905-07','s-1905-08','s-2']
三接口(随机空闲端口):POST /tasks → 201;GET /tasks?agent= → 200;
POST /tasks/<id>/ack 第一次 200、第二次 409;同标题重提 → 200 且同 id(幂等)。
边界
- 只绑
127.0.0.1,不开公网;跨机靠文件同步或隧道,不靠这个端口; - 托盘「任务池」只读视图还没做(B83):现在看池子要用
pool命令; - 超时改派仍交给已有的
persona_report.py reap,本模块不重复实现一套回收逻辑; - 任务记录进
ops/,不进memory/——任务是活儿,不是经历。
修订记录
2026-11-09 首版。同日从「设计成文」推进到「已落地」:新增 persona_tasks.py;
HTTP handler 抽成一处(第一版自检里复制过一份,已删)。
v5.4.0 — 后台处理
本页会随勘误就地更新。规范全文以
main为准。
范围
这一版只回答一个问题:上下文涨到上限之前,程序该在什么时候动手、按什么顺序动。
原来的答案是「人记得清理的时候」。他记不住,也不该记——所以把决定权从人手里拿走,只保留否决权
(删记忆仍要人点头)。设计三件事,缺一件就不成立:
一、一维水位尺,四档,顺序写死、不许倒挂。
300K 注意 ──► 600K 准备 ──► 800K 该交接 ──► 1M 断
└─ 先镜像 → 再出包 → 才允许压
数字只在 persona_limits.py 一处,persona_gauge 与 persona_daemon 都 import 它。
这条尺子以前被拆成三份(规范 / 量尺 / 守护各一份),还出现过 HOT 900K 压在 CLEAN 800K 上的倒挂——
85 万会报「热」而不是「该交接」。这类错只有一处定义才防得住。
二、上下文一律按「输入」算,不按缓存。
状态栏里「输入 182K · 缓存 56.2M」是两个东西:输入是这一轮真正带进上下文 的量,缓存是整个会话累计命中的量。
拿缓存定交接线,等于拿账单当体重秤。所以判定只认每轮输入(主模型、剔除 qwen* 本地小模型轮次,
它们的输入小、会把水位算小),拿不到实测才退回账本估算并标 ~。
三、四个动作顺序写死:监测 → 镜像 → 压缩 → 归档。
没镜像就压,等于拿记忆赌运气——所以镜像规范比压缩写得更细:
- 落
archive\<日期>\<来源>\<会话id>-mirror.md,七字段头(会话 / 设备 / 底座 / 镜像于 / 触发 / sha256 / 原文); - 同名同内容幂等跳过;同源改了内容 → 另存带时间戳,绝不覆盖(镜像是证据,不是缓存);
--check用头部记的 sha256 自证正文没被改过;- 头字段解析复用
persona_avatar.from_head,不写第四份正则。
落地
| 文件 | 做了什么 |
|---|---|
persona_limits.py |
水位阈值唯一出处(OK/WARN/CLEAN/MIRROR/LIMIT/PACK_LIMIT) |
persona_mirror.py |
镜像:七字段头、幂等、不覆盖、--check 自证 |
persona_gauge.py |
水位读数改成「每轮输入」;文案不再写死数字 |
persona_daemon.t_hctx |
800K 分支:先导出 → 再镜像 → 结果写进任务日志 |
证据
created … x-mirror.md 一致 ← 首次
skipped … x-mirror.md 幂等跳过 ← 再跑一次
renamed … x-mirror-195304.md 一致 ← 源改了内容,另存不覆盖
--check → OK(1 个镜像)
水位尺实测(--ctx 造数):250K → OK / 650K → 准备 / 820K → 该交接。
真机读数:[上下文] 123K(主模型最近一轮输入),账本兜底 ~231K 只作参考、不参与判定。
边界
- 压缩仍
PENDING:壳没有稳定接口契约(compact在 DeepSeek 系模型上填不出嵌套参数)。
没有契约就调,等于把「压缩」写成碰运气——所以这版只做到「镜像 + 出包」; - 判据①「自然触发一次」还没发生:从没真到过 800K,所以只算「实现完成」,不算「验过」;
- 不清记忆、不改写历史:压缩只压上下文,不动
sessions\/archive\的原文。
修订记录
2026-11-09 首版。同日三改:出包线 450K → 800K(他定);删掉倒挂的 HOT、level() 改一维顺序;
上下文口径改成「每轮输入」(他:「那就按输入算,我一直看的缓存」)。
v5.3.0 — 保活 + 交互代理
本页会随勘误就地更新。规范全文以
main为准。
范围
新增 tools/persona/(七个脚本)与 docs/INTERACT_SPEC.md。这一版只回答一个问题:唤醒之后到底发生了什么。
persona_wake.py闹钟:登记必须带 purpose(无 purpose 退出码 2);到点先摘一份总索引简讯
(ops/wake-brief.json),再执行 purpose(环境变量PERSONA_PURPOSE/PERSONA_BRIEF)、
替 agent 报到(read_index=true)。persona_report.py+persona_dispatch.py派发:两道门——最近 600 秒报到过(否则 2)、
报到时读过总索引(否则 3);reap把超时 pending 标expired并改派给醒着的分身;
台账 append-only,原记录不覆盖。重试有上限:换 3 次仍没人接就标failed并写
ops/alerts.jsonl——没有上限的改派是另一种死循环,任务一直转手、看起来永远「处理中」。persona_master.py总索引速览(人读 /--json/--pending)。persona_proxy.py交互代理:12 个动词、只绑127.0.0.1、X-Persona-Token、逐次审计;
sys.run默认全拒,白名单在ops/proxy-allow.json。协议见docs/INTERACT_SPEC.md。persona_keepalive.py保活:进程查询三态(在跑 / 没在跑 / 查不了)。默认只报不拉
(autostart: false):进程该不该活归它自己的启动项/运行时管,保活只负责「看得见」;
要拉得在目标里显式写true。selftest.py自检:临时库,真跑。persona_todo.py待办备忘:把BACKLOG摊成续接\待办备忘.md(等你拍板 / 我可做两段)。- 交接包在「指针」前固定输出开工三步:读设定 / 读总索引 / 认领检查。
守护进程接三个定时任务(wake / reap / proxy),保活并入已有的 agent 任务。
一条心跳成立:闹钟 → 摘总索引简讯 → 执行 purpose → 报到(已读索引)→ 派发(两道门)→ 超时回收/改派。
证据
python tools/persona/selftest.py→ PASS 36 / FAIL 0(临时库,不碰真数据):
总索引 3 条来源倒序;无 purpose 登记退出码 2;到点触发写index_total=3的简讯并注入
PERSONA_PURPOSE;派发两道门分别 3 / 2;过期记录标 expired 并改派给rg2,原行未被覆盖;
超过重试上限标failed、不再改派、alerts.jsonl出现dispatch_failed;
代理 11 项:越界 403 / 未知动词 404 / 未授权sys.run403 / 加白名单后 200 / 错 token 401,
审计 9 行且不含 token。- 实机守护进程:
wake OK 无到点闹钟、reap OK 无超时派发、proxy OK 代理在跑、agent OK 在跑;
persona_proxy call health返回version=5.3.0;壳关掉后保活只报状态、不再自动开窗口。
边界
- 代理只绑
127.0.0.1,不做跨设备、不做公网,不当远程 shell;sys.run默认全拒; - 进程查询在受限令牌下可能被过滤,故返回三态:查不了就不动手(看不见 ≠ 没在跑);
- 告警只落盘(
ops/alerts.jsonl),托盘红灯的取数留给 v5.4; tools/ctxpack/persona_daemon.py仍是本机私有部署件,接线写在tools/persona/README.md里,未整份入库。
修订记录
2026-11-09 首版。同日曾切 v5.3.1(重试上限)与 v5.3.2(保活只报不拉)两个补丁,
随后撤销并合并回本版——三个版本号讲的是同一件事,拆开只会让读者做版本拼图;
两个 tag 与 Release 已删除。顺手修:persona_master 读 L1.jsonl 遇 BOM 整行丢失(改 utf-8-sig);
persona_wake 的 --run 补 cwd 与 PERSONA_BRIEF 环境变量。
v5.2.0 — 托盘状态灯
本页会随勘误就地更新。规范全文以
main为准。
范围
新增 tools/tray/persona_tray.py:Windows 系统托盘,只读守护进程写出的 ops/persona-status.json。
绿=正常、黄=有任务在忙、红=有任务失败或状态超过 3 分钟没更新;双击看各任务最近一次结果;
菜单可打开日志、打开记忆库、退出。只显示,不代办任何任务。
依赖:pystray + Pillow(python -m pip install pystray Pillow)。
证据
- 实机启动 6 秒无异常退出;
- 状态解析覆盖四态:正常 / 忙碌 / 失败 / 状态文件缺失(灰色「读取中」);
- 字段与
persona_daemon写出的persona-status.json(at/tasks.*.ok/busy/msg)逐项对应。
边界
- 不含界面注入,不含记忆写回,不替代守护进程;托盘本身不产生任何数据;
- 仅 Windows;状态文件缺失时只显示灰色,不做重试;
- 同时含一处抓取器修复:无虚拟列表时回退到
document.body,避免整次抓取直接报错(单独提交1efb7ae)。
修订记录
2026-11-09 首版。托盘是「常驻程序看得见」的第一步;后续每个小功能单独切版本。
v5.1.0 — 托盘(控制台)设计
本页会随勘误就地更新。规范全文以
main为准。
范围
docs/TRAY_SPEC.md:托盘(控制台)的定位与路由模型。
- 托盘是控制台,不是智能体:管第三方 agent,不承载对话、不做推理、不跑本地模型;
- 路由:托盘指定一个主节点(主人格或某个分身)→ 主节点按能力与可用性派给分身 → 结果回传 → 分身掉线自动切;
- 功能清单:v5.2.0 状态灯(已发)、v5.3 启停控制、v5.4 任务池视图、v5.5 保活面板;
- 明确否决:托盘内置小模型自己执行任务——路由权归主节点。
证据
本版无代码;判据是把四条边界写死进文档:不跑模型、不写记忆、不直连 API、状态只读 ops/persona-status.json。
边界
不含实现。v5.2.0 的状态灯是本文第一条落地;后续每版只加一个小功能。
修订记录
2026-11-09 补发:先发了 v5.2.0(实现),回头补 v5.1.0(设计)。编号低于 v5.2.0 但提交晚于它,特此明示,避免按时间线误判。
v5.0.0 — 寄生在开源壳上的记忆层
本页会随勘误就地更新。规范全文以
main为准。
范围
这一版改的不是功能,是交付形态:停止自研客户端,把记忆层嫁接到开源壳上。
前面四个大版都在造自己的壳:加载、组装、路由、执行、归档,五件事都要自己兜。
结论是——壳是最不值钱的那层:它每天在变(Electron、UI、登录、模型路由),而记忆层几乎不变。
继续自研壳,等于用有限的精力去追一个一直在动的东西。
所以这一版做了三件套的分工,边界写死:
| 件 | 谁 | 约定 |
|---|---|---|
| 壳 | Hermes Agent 中文社区桌面版(Electron + Python,本地 SQLite + FTS5) | 只读不托管;记忆仍是纯文件,两边随时可拆 |
| 脑 | 免费档 Qwen/Qwen3.5-4B(256K 上下文);重活仍交网页端 DeepSeek |
判断型任务不下放给小模型 |
| 检索 | 本地 bge-m3(免费、不出门) |
embedding 与 rerank 都在本机 |
两个细节是设计,不是配置:
- 路径:
C:\hermes-mem用目录联接(Junction)指向中文长路径。为什么不用真路径?
小模型的分词会把AI会话检索记录读成AI 会话检索记录(多一个空格)→File not found。
绕开它的缺陷,比要求它别犯错便宜。 - 工具面:
platform_toolsets白名单把 31 组裁到 11 组。上下文预算是产品约束:
多留一组工具,就少留一段记忆。
证据
read_file0.4 秒读出归档 1098 条,内容与LATEST.md逐字一致(含「上轮追加 16 条」);- 一轮 42.6 秒 / 38.6k 输入 token;工具面 31 组 → 11 组;
- 免费档校验(价格页机器可读数据):
Qwen3.5-4B输入/输出均为 0;
Qwen2.5-7B-Instruct同为 0,但上下文仅 32K,被壳的 64K 门槛拒绝; - 本地
bge-m3嵌入 2525 块:8.5 分钟 / 0 tokens; - 失败留档:4B 把
AI会话检索记录输出成AI 会话检索记录(多一个空格)→File not found。
这条失败比上面几条成功更有用——它决定了 Junction 那个设计。
边界
- 数据出本地:大脑在云端,发给它的都离机;上云前必须过
mask_secrets.py,
这条尚未接进壳的调用链——是这一版最大的未闭合项; - 免费档限流:RPM/TPM 打满即 429,批量活不要压在这里;
- 壳升级会打回原形:
config.yaml之外的补丁要重放(工具白名单已落在配置里,可重放); - 登录态仍未解决:壳读网页会话那条链路依旧要手动扫码,与 v4.3.0 记录的同一问题同源;
- 不含:登录态持久化;脱敏只实现未接线。
修订记录
2026-11-09 首版:新增 docs/SHELL_ADOPTION.md;README 徽章、PROJECT_MEMO §五、
DECISIONS 版本线同步到 v5.0.0。
壳会过时,记忆不会。
v4.6.0 — 技能与命名
本页会随勘误就地更新。规范全文以
main为准。
范围
docs/SKILLS.md:七项技能的触发词与判据,以及**「什么经验才配变成技能」**——
| 值得 | 不值得 |
|---|---|
| 做成功过一次、以后还会重复 | 一次性的迁移、临时排障 |
| 有明确触发词 | 需要读长文才能判断 |
| 判据能写成是/否 | "感觉更好了" |
配套:9 个脚本统一 persona_ 前缀(keeper → persona_daemon、ctxguard → persona_gauge、
ctxslim → persona_slim、ctxpack → persona_pack、hermes_export → persona_bridge、
zsearch → persona_search、index_inc → persona_index、index_l1 → persona_index_l1)。
改名是真实改动(脚本立刻可用),但它不是新能力。 历史归档与旧会话不改名——那是记录,不是配置。
证据
改名后守护 11 个任务全绿;导入 / 导出 / 索引 / 备份各跑一轮无异常;persona_gauge、persona_search 冒烟通过。
边界
- 技能只写"触发词 + 判据",不写教程——教程会过时,判据不会;
- 改名只覆盖脚本与运行期产物,历史文件一律不动(改了就对不上账);
- 本版不含新能力。
修订记录
2026-11-09 首版。修三处:read_watch.py、handoff_rotate.py 带 UTF-8 BOM 根本无法解析(长期静默失效);
导出无锁并发会 PermissionError(加 ops/export.lock);弃用会话仅删文件挡不住(每轮导出写回),
改用 ops/skip-sessions.txt 名单。
v4.5.0 — 索引:四层与增量
本页会随勘误就地更新。规范全文以
main为准。
范围
docs/GLOBAL_INDEX.md:索引是前置放大器——模型再聪明,也答不出没被喂进去的那段。
- 四层:L0 正文 / L1 目录(每源一行)/ 块索引 / 归档;
- 增量:按 sha256 记账,改回旧内容也算没变;
- 安全:先写临时文件再原子替换,重建期间旧索引照常可查;
- 检索:关键词 + 向量 RRF 合并,断网自动降级为纯关键词。
证据
- 索引重建:19170 块 / 121 文件 / 1.7 分钟;
- 连续第二次跑:嵌入 0 块(哈希幂等成立);
- L1 目录随出包自动重建,接续包 3413 字 / 6.3 KB,未超上限。
边界
| 项 | 状态 |
|---|---|
| L1–L5 五级时空密度索引(B35) | 仍是纸面 |
| 索引竞态 | 已修:列目录与算哈希之间文件被归档删除时不再整任务退出 |
| 压缩 / 兜底 | 不在本版范围 |
修订记录
2026-11-09 首版。修:FileNotFoundError 曾让索引任务每轮卡在同一处(同一批文件永远算不出哈希)。
v4.4.0 — 画像:观察即记忆
本页会随勘误就地更新。规范全文以
main为准。
范围
docs/PROFILE.md:画像不是模型对人的印象,是一串可追溯的观察。
- 采集:对话里的
记一条观察:…由导出器收割成- [日期] … → 证据; - 写入:只追加,同一句重复出现也只留一条(幂等);
- 指针:壳侧
memories\只放 ≤2000 字符的指针,换壳不重写画像; - 分流:归纳用免费小模型,判断型任务(工具选择、审批)不得下放。
为什么强调"观察"而不是"评价":评价不可验证、会被模型的措辞带偏;观察带日期和证据,能被翻回去核对。
证据
本版是设计,唯一可测的是「幂等」:同一句观察重复十次,USER.md 只多一行。
边界
| 项 | 状态 |
|---|---|
| 写回链路 | 未验收——merge 至今只报「无新增」 |
| 画像质量 | 未评估;观察是句子,不是评分 |
| 删除 | 任何一条观察的删除都要人确认(5.4) |
修订记录
2026-11-09 首版。同日曾把画像/索引/技能打成一个版本,因颗粒度不对已撤销,拆成 4.4 / 4.5 / 4.6。