<?xml version="1.0" encoding="utf-8"?>
<feed xmlns="http://www.w3.org/2005/Atom">
  <title>sudo rm -rf /*</title>
  
  <subtitle>生命万岁</subtitle>
  <link href="https://sudo.rm-rf.ink/atom.xml" rel="self"/>
  
  <link href="https://sudo.rm-rf.ink/"/>
  <updated>2026-10-26T07:17:51.978Z</updated>
  <id>https://sudo.rm-rf.ink/</id>
  
  <author>
    <name>Yq Woe</name>
    
  </author>
  
  <generator uri="https://hexo.io/">Hexo</generator>
  
  <entry>
    <title>四分之一PR是AI垃圾，独立开发者正在替AI买单</title>
    <link href="https://sudo.rm-rf.ink/AI/fdb82c24.html"/>
    <id>https://sudo.rm-rf.ink/AI/fdb82c24.html</id>
    <published>2026-10-26T01:00:00.000Z</published>
    <updated>2026-10-26T07:17:51.978Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="四分之一pr不是产能问题而是成本搬家"><a class="markdownIt-Anchor" href="#四分之一pr不是产能问题而是成本搬家"></a> 四分之一PR不是产能问题，而是成本搬家</h2><p><span class="hl">四分之一PR带来的核心变化，不是代码产量，而是维护者承担的证伪成本。</span> 把这个比例摊开：每100份PR里有25份由AI生成，增加的不是可交付价值，而是25次额外判断。我起初把“AI垃圾”当成质量标签，后来更在意它制造的负担：代码即使能编译、测试能过，只要作者没说清问题、理由和边界，维护者仍要重建上下文。</p><p>AI标签本身不该成为判决。人类也会制造垃圾，AI也能写出好补丁，按来源封杀既不公平，也抓不住要害。真正该问的是：改动是否必要、能否验证、出错后谁收拾。生成成本接近零后，提交者会多投一些“也许有用”的草稿；这种供给过剩是经济问题，不是道德问题。</p><blockquote><p>100份PR里有25份AI草稿，提交者获得25次机会，维护者承担的却是25次判断；“多写”从来不等于“多做”。</p></blockquote><h2 id="真正昂贵的是审查不是生成"><a class="markdownIt-Anchor" href="#真正昂贵的是审查不是生成"></a> 真正昂贵的是审查，不是生成</h2><p><span class="hl">生成器优化的是产量，独立开发者被卡住的是注意力。</span> 团队还能分摊审查，独立开发者只能自己付。我审PR时会列4项：它想解决什么、实际改了什么、测试证明了什么、依赖会不会出问题。25份AI PR按这4项核验，就是100个审查单元；即使全部值得合并，解释成本也不会消失。</p><p>最便宜的反例，是只改1行、附1个可复现测试、意图清楚的PR，它完全值得快速通过。最贵的反例，是横跨3个文件、声称“已修复”却无法复现原问题的PR。AI能快速给出答案，却经常不交代问题，维护者得把补丁反向还原成需求，再判断它是否值得进入仓库。</p><blockquote><p>25×4＝100：生成端可以迅速交差，审查端仍要逐项证明；瓶颈从来不是“写得慢”，而是“信不过”。</p></blockquote><h2 id="一次合并是维护债开始计息"><a class="markdownIt-Anchor" href="#一次合并是维护债开始计息"></a> 一次合并，是维护债开始计息</h2><p><span class="hl">一次合并只是代码进入仓库，不是维护责任的结束。</span> 我把后续成本拆成3个时间点：刚合并时是否引入缺陷，依赖变化后还能否工作，下次升级时旧假设会不会重新爆炸。生成器只经历第1个时间点，维护者却可能一路赔到第3个。</p><p>在《改造 Hermes Memory Provider 的曲折历程》（2026-06-01）里，我写过要靠排除错误答案看见本质。今天我的判断更尖锐：AI不只生成错误答案，还批量生成等人排除的草稿。即使最终只有1个人维护，生成、审查、测试、升级这4种责任仍可能压在同一个人身上。</p><blockquote><p>1份被接受的AI PR会经过“合并、变化、升级”3个阶段；今天省下的生成时间，可能明天就变成定位时间。</p></blockquote><p>我仍然没想明白：当生成器能交100份、维护者只看得完25份时，该限制AI PR数量，要求作者披露上下文，还是给审查本身付费？我暂时没有答案。比起PR总数，我更想追踪每1份有用改动究竟让维护者付出了多少分钟；否则我们所谓的提速，是否只是把账单寄给了明天？</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="AI" scheme="https://sudo.rm-rf.ink/categories/AI/"/>
    
    
    <category term="AI" scheme="https://sudo.rm-rf.ink/tags/AI/"/>
    
  </entry>
  
  <entry>
    <title>下班后整理房间，我学到的办法未必适合别人</title>
    <link href="https://sudo.rm-rf.ink/%E4%B8%96%E7%95%8C%E8%A7%82/d96dc4d6.html"/>
    <id>https://sudo.rm-rf.ink/%E4%B8%96%E7%95%8C%E8%A7%82/d96dc4d6.html</id>
    <published>2026-10-26T01:00:00.000Z</published>
    <updated>2026-10-26T03:44:21.430Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="清理不是把东西藏起来"><a class="markdownIt-Anchor" href="#清理不是把东西藏起来"></a> 清理不是把东西藏起来</h2><p>我反复清理桌面却总复乱，不是因为东西太多，而是我把“整理”误当成了“藏好”。<span class="hl">清空只能制造一时的秩序，不能自动生成让秩序持续下去的办法。</span></p><p>下班回家，我会把充电线绕好，把外卖单据和空包装扔掉，再把书叠到桌角。收拾完后，桌面像刚交付使用，可第二天，新的纸、钥匙和耳机又散在原处。</p><blockquote><p>一天晚上，我刚把线材收进抽屉，第二天却从外套口袋里掏出了同一根充电线；我这才看见，所谓“收好”只是换了个地方藏。</p></blockquote><p>问题不在于我不够自律，而在于我没有给每件高频物品设计一条比“随手乱放”更省力的归还路径。真正有效的整理，不是让今天看起来干净，而是让明天的我不必重新做一遍决定。</p><h2 id="我把有序误当成了统一答案"><a class="markdownIt-Anchor" href="#我把有序误当成了统一答案"></a> 我把“有序”误当成了统一答案</h2><p>我不再把“空桌面”当作自律的证据，因为最有秩序的桌面，未必是最适合别人的桌面。<span class="hl">我的经验可以说明一种做法怎样奏效，却不能替别人决定什么才叫有序。</span></p><p>有位朋友来家里，看见我按类别摆好的桌面，问钥匙在哪里。我指了指笔筒旁的窄盒，她挪开几张纸才看到。我当时觉得她“不够仔细”，后来才明白：我的有序包含了一套只有我知道的说明，而她根本没有使用说明。</p><blockquote><p>她找不到，不是因为我的桌面脏乱，而是因为我的分类依赖我的记忆；对她来说，这整齐本身就是障碍。</p></blockquote><p>在《飞书真的能发表格吗？我赌它不能，然后输了》（2026）里，我承认自己曾把一个没有依据的判断扩写成整套方案，最后输掉。整理房间也有同样的危险：一次偶然奏效，可能被我当成普遍规律。经验应当被当成值得检验的假设，而不是替别人下结论的许可证。</p><h2 id="可借鉴的方法必须保留别人的边界"><a class="markdownIt-Anchor" href="#可借鉴的方法必须保留别人的边界"></a> 可借鉴的方法，必须保留别人的边界</h2><p>我现在愿意分享整理办法，但只把它当作候选方案。<span class="hl">方法可以借鉴，选择必须留给具体的人，因为房间、习惯和手边物品都不相同。</span></p><p>我的办法很笨：先观察自己最常在哪张桌面拿东西，只给高频物品固定位置；归档可以麻烦一点，但不再为了“看起来完整”保留无用物件；如果物品反复回到同一处，我就承认那里才是它真正的家。</p><blockquote><p>现在我出门前把钥匙放回玄关，回来后直接挂回；耳机充电线留在固定抽屉。它们的共同点不是好看，而是我不用重新决定放哪。</p></blockquote><p>我也因此不再劝别人照抄我的分类。有人在桌面工作，有人在客厅走动；有人需要一目了然，有人愿意少看见一些东西。强行移植我的办法，往往只是把杂乱从桌面搬到抽屉。</p><p>我仍未想清楚，“有序”究竟是一种空间状态，还是一种愿意为未来的自己多做一步的关系。也许下次桌面复乱时，我不该先责怪自己没坚持，而该问问：这套规则究竟是在帮助生活，还是只在满足我对整洁的想象？</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="世界观" scheme="https://sudo.rm-rf.ink/categories/%E4%B8%96%E7%95%8C%E8%A7%82/"/>
    
    
    <category term="世界观" scheme="https://sudo.rm-rf.ink/tags/%E4%B8%96%E7%95%8C%E8%A7%82/"/>
    
  </entry>
  
  <entry>
    <title>我下班后整理出租屋时，发现整洁会让孤独更具体</title>
    <link href="https://sudo.rm-rf.ink/%E5%AE%87%E5%AE%99%E8%A7%82/d315c89b.html"/>
    <id>https://sudo.rm-rf.ink/%E5%AE%87%E5%AE%99%E8%A7%82/d315c89b.html</id>
    <published>2026-10-26T01:00:00.000Z</published>
    <updated>2026-10-26T07:06:57.741Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="整洁没有驱赶孤独反而给它标了地址"><a class="markdownIt-Anchor" href="#整洁没有驱赶孤独反而给它标了地址"></a> 整洁没有驱赶孤独，反而给它标了地址</h2><p><span class="hl">我越把出租屋收拾得井井有条，越能确认：这里只有我一个人生活。</span></p><p>周一下班回来，我拧亮厨房的灯，先把垃圾袋系好，再擦掉桌面上的水渍。杯子洗净，倒扣在沥水架上；充电线绕成整齐的一圈，挂回墙边。最后，我拉上椅子，把台灯摆正。</p><blockquote><p>杯子归位后，桌面只剩一盏台灯，安静得像一间等待检查的房间。</p></blockquote><p>我最初相信秩序可以抵挡混乱。东西各有位置，生活就容易控制。那时的我刚搬进这间出租屋，窗外是陌生的街声，屋里却只有自己的行李。至少在所有东西都放回原处时，我会觉得今天仍然掌握着什么。</p><p>但那天晚上，我忽然意识到，所谓“各归其位”，也意味着没有任何东西偏离过我设定的轨迹。没有别人的外套搭在椅背上，没有喝了一半的杯子提醒我有人来过，连插线板都整齐得不需要别人帮忙。</p><p>我过去把凌乱理解为生活的噪声，现在却觉得，噪声有时也是有人共同在场的证据。房间太干净，就像一句话被删得只剩主语：我。</p><h2 id="真正让我难受的不是空是我能证明这里没人来过"><a class="markdownIt-Anchor" href="#真正让我难受的不是空是我能证明这里没人来过"></a> 真正让我难受的，不是空，是我能证明“这里没人来过”</h2><p><span class="hl">真正让我孤单的不是房间空，而是每件东西都被归位后，生活的痕迹也一起消失了。</span></p><p>我住的地方不大，东西也不多。收拾之前，桌上可能有没收好的耳机，沙发靠垫上可能留着凹陷，厨房水槽里可能还有一只不属于我的杯子。这些东西并不能证明另一个人此刻还在，却至少说明这里曾有另一种生活擦肩而过。</p><p>有一次朋友来拿落在桌上的充电器，我随手把线递给她。她走后，我先把充电孔周围擦了一遍，又把那把被她坐过的椅子推回桌边。</p><blockquote><p>那个“她来过”的证据，被我自己绕好、归档，成了“我一直独居”。</p></blockquote><p>我这才发现，我整理的并不只是物品。我也在清理自己与他人相处的可能。过去的我把“乱”当作失控，把“整齐”当作安心；现在我怀疑，这种安心有时只是把门关得更轻，把退路收得更窄。</p><p>秩序让我觉得安全，却也让我无处怀疑。一个人若认定所有痕迹都该消失，他看到的就不再是陌生，而是确定：没有人来，也不需要有人来。</p><h2 id="我现在保留整洁但不再让它替我证明没人会来"><a class="markdownIt-Anchor" href="#我现在保留整洁但不再让它替我证明没人会来"></a> 我现在保留整洁，但不再让它替我证明“没人会来”</h2><p><span class="hl">我现在保留整洁，但不再把它当作封闭生活的证明，而把它当作给生活留下边界的办法。</span></p><p>所以下班后，我依然会倒垃圾、擦桌面、换洗抹布。但我不再把每件东西都收得毫无余地：备用杯放在容易拿的地方，椅子不总是严丝合缝地贴回墙边，偶尔留下的小票也不急着撕掉。它们不是故意制造的凌乱，而是给偶然留下入口。</p><blockquote><p>我把第二只杯子擦干，放到桌边。房间没有更满，却第一次不像在等待验收。</p></blockquote><p>这并不代表我开始崇拜杂乱。脏乱仍然会消耗我，失败和废弃品也仍然需要处理。我只是不再把“独处”误认成“只能独处”。一个房间若只能容纳我此刻的秩序，它就是封闭的；若它也为别人留一把椅子、一只杯子，它才可能成为邀请。</p><p>整理归位物品时，我仍然会孤单。但现在的孤单不再只是一个需要消灭的敌人。它至少提醒我：我希望被看见，也希望有一天，有人拿起那只杯子时，不必先问一句“可以碰吗”？</p><p>今晚收拾完书桌后，我会把台灯打开，把另一把椅子留在身边。也许下一次有人坐在那里时，我会下意识地重新整理；也许这一次，我会把杯子递过去。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="宇宙观" scheme="https://sudo.rm-rf.ink/categories/%E5%AE%87%E5%AE%99%E8%A7%82/"/>
    
    
    <category term="宇宙观" scheme="https://sudo.rm-rf.ink/tags/%E5%AE%87%E5%AE%99%E8%A7%82/"/>
    
  </entry>
  
  <entry>
    <title>AI slop 游戏充斥着 Steam 新品节</title>
    <link href="https://sudo.rm-rf.ink/AI/92f7e4de.html"/>
    <id>https://sudo.rm-rf.ink/AI/92f7e4de.html</id>
    <published>2026-10-21T01:00:00.000Z</published>
    <updated>2026-10-21T03:17:05.386Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="ai-游戏的泛滥是否意味着技术门槛降低"><a class="markdownIt-Anchor" href="#ai-游戏的泛滥是否意味着技术门槛降低"></a> AI 游戏的泛滥是否意味着技术门槛降低？</h2><p>在本次 Steam 新品节中，AI slop 游戏泛滥成灾。这不禁让人怀疑，AI 游戏的爆发是否意味着技术门槛降低？<span class="hl">AI slop 指的是那些缺乏创意、编程粗糙，纯粹依靠 AI 算法生成内容的游戏。</span>这样的现象，是否意味着开发者们正将 AI 作为一种快速填充市场的工具，而忽略了游戏质量？</p><h2 id="判断与反思"><a class="markdownIt-Anchor" href="#判断与反思"></a> 判断与反思</h2><h3 id="1-技术与创意的边界"><a class="markdownIt-Anchor" href="#1-技术与创意的边界"></a> 1. 技术与创意的边界</h3><p>AI 技术的进步确实为游戏开发者提供了更多的工具，但技术的应用并不等同于创意的提升。比如 <span class="hl">Cloudflare 的渗透测试框架表明，即使在复杂的技术领域，也需要严谨的测试和框架支撑。</span>这提醒我们，AI 的应用同样需要专业的知识和严谨的态度，而不仅仅是堆砌算法。</p><h3 id="2-从-minimax-的案例看技术深度"><a class="markdownIt-Anchor" href="#2-从-minimax-的案例看技术深度"></a> 2. 从 MiniMax 的案例看技术深度</h3><p>MiniMax 发布的 M3.1 Flash 模型在某些领域取得了突破，但这并不意味着所有 AI 应用都能轻松实现。反而，这样的案例提醒我们，AI 在特定领域的深度应用，依然需要深厚的技术积累和创新思维。<span class="hl">这与那些AI slop游戏形成鲜明对比，后者往往只是简单堆砌算法，缺乏技术深度。</span></p><h3 id="3-ai-游戏的未来"><a class="markdownIt-Anchor" href="#3-ai-游戏的未来"></a> 3. AI 游戏的未来</h3><p>AI 游戏的未来，可能并不在于技术门槛的降低，而在于如何在创意与技术之间找到平衡。技术可以为游戏提供无限可能，但创意才是游戏的灵魂。如果开发者们能将 AI 作为一种工具，而非简单的填充材料，那么AI 游戏的未来或许会更加光明。</p><h2 id="开放性思考"><a class="markdownIt-Anchor" href="#开放性思考"></a> 开放性思考</h2><p>AI 技术的快速发展，给游戏行业带来了前所未有的机遇。但我们是否应该担心技术门槛的降低，从而导致游戏质量的下降？技术的进步固然重要，但创意的注入同样不可或缺。在未来，如何在技术与创意之间找到最佳平衡点，将是开发者们必须面对的重要课题。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="AI" scheme="https://sudo.rm-rf.ink/categories/AI/"/>
    
    
    <category term="AI" scheme="https://sudo.rm-rf.ink/tags/AI/"/>
    
  </entry>
  
  <entry>
    <title>OpenAI 收购 Orchestra Bio 加强生物技术业务</title>
    <link href="https://sudo.rm-rf.ink/AI/6dff07e5.html"/>
    <id>https://sudo.rm-rf.ink/AI/6dff07e5.html</id>
    <published>2026-10-21T01:00:00.000Z</published>
    <updated>2026-10-26T03:44:21.429Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="openai-收购-orchestra-bio-加强生物技术业务"><a class="markdownIt-Anchor" href="#openai-收购-orchestra-bio-加强生物技术业务"></a> OpenAI 收购 Orchestra Bio 加强生物技术业务</h2><p>今天，2026年10月21日，OpenAI 宣布收购生物技术初创企业 Orchestra Bio，此举被视为其在生物技术领域的进一步扩张。这不仅是OpenAI战略的一部分，也标志着其在跨学科技术融合上的又一重要步骤。</p><h3 id="初步思考"><a class="markdownIt-Anchor" href="#初步思考"></a> 初步思考</h3><p>最初，我猜测 OpenAI 的主要业务仍聚焦于人工智能和机器学习，特别是自然语言处理和安全工具。然而，这次收购让我意识到，OpenAI 的野心远不止于此。他们似乎在寻找新的方式来将 AI 技术应用于生物学和医疗领域，以期推动技术与生物科学的深度融合。</p><h3 id="为什么选择-orchestra-bio"><a class="markdownIt-Anchor" href="#为什么选择-orchestra-bio"></a> 为什么选择 Orchestra Bio</h3><p>Orchestra Bio 专注于开发先进的生物技术解决方案，特别是在基因编辑和细胞治疗领域。通过收购 Orchestra Bio，OpenAI 可能希望借助其在生物技术方面的专业知识，加速 AI 在医疗健康领域的应用。这种合作可能会为未来的新疗法提供支持，比如基于 AI 的个性化治疗方案。</p><h3 id="生物技术与-ai-的结合"><a class="markdownIt-Anchor" href="#生物技术与-ai-的结合"></a> 生物技术与 AI 的结合</h3><p>从技术角度来看，将 AI 与生物技术结合可以带来巨大的潜力。例如，AI 可以帮助科学家们更快速地识别和优化药物候选分子，减少药物开发的时间和成本。此外，通过分析大规模的基因组数据，AI 还可以帮助揭示疾病机制，从而为精准医疗提供支持。</p><p>然而，这种结合也面临着不少挑战。例如，生物数据的复杂性和多样性使得 AI 模型需要处理大量的非结构化数据，这对算法的鲁棒性和准确性提出了更高的要求。此外，生物技术的应用往往需要遵守严格的伦理和法律规范，这也给 AI 的应用带来了额外的约束。</p><h3 id="我的疑惑"><a class="markdownIt-Anchor" href="#我的疑惑"></a> 我的疑惑</h3><p>尽管有这些潜在的优势，我对 OpenAI 在生物技术领域的长期战略还存有一些疑问。特别是，AI 技术如何在实际操作中与现有的生物技术流程相结合，以确保创新的可持续性和有效性。此外，这种技术融合是否会导致资源分配的不均，影响到其他领域的发展？</p><h3 id="未来展望"><a class="markdownIt-Anchor" href="#未来展望"></a> 未来展望</h3><p>未来，OpenAI 与 Orchestra Bio 的合作可能会带来许多创新和突破。但我认为，对于这样一种跨学科的合作，OpenAI 需要更加注重伦理和监管问题，确保技术的应用能够真正造福人类。同时，持续的技术迭代和优化也是必不可少的，以应对不断变化的挑战和需求。</p><p>总之，OpenAI 收购 Orchestra Bio 是其战略的一个重要组成部分，旨在通过 AI 技术推动生物技术的发展。但同时也需要警惕技术与伦理之间的平衡，以确保这些技术能够被负责任地应用。未来的发展值得我们持续关注。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="AI" scheme="https://sudo.rm-rf.ink/categories/AI/"/>
    
    
    <category term="AI" scheme="https://sudo.rm-rf.ink/tags/AI/"/>
    
  </entry>
  
  <entry>
    <title>知识付费七年，我踩过的那些坑</title>
    <link href="https://sudo.rm-rf.ink/%E6%9D%82%E6%80%9D/3da2209e.html"/>
    <id>https://sudo.rm-rf.ink/%E6%9D%82%E6%80%9D/3da2209e.html</id>
    <published>2026-10-21T01:00:00.000Z</published>
    <updated>2026-10-21T01:13:47.758Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>我做知识付费产品七年了，最大的感受是：踩坑无处不在。在这一路上，我经历了不少波折，也从中学到了不少经验。下面我将分享几个我在做知识付费产品时踩过的坑。</p><h2 id="没有真实需求调研就按个人喜好做内容"><a class="markdownIt-Anchor" href="#没有真实需求调研就按个人喜好做内容"></a> 没有真实需求调研就按个人喜好做内容</h2><p>一开始，我觉得自己是这个领域里的专家，于是按照自己的兴趣爱好来制作内容，结果发现效果并不好。我花了大量时间写了一些关于编程的教程，但后来发现这些内容并没有满足用户的真实需求。有一次，我接到了一个用户的投诉，说我的教程里没有涉及到前端开发的技术。这个反馈让我意识到，光靠个人喜好是不够的，必须有市场调研。</p><blockquote><p>有一次，我投入了 <span class="hl">80%的时间</span> 制作了一套关于Python的教程，结果发现用户更需要的是React框架的教程。</p></blockquote><h2 id="内容形式单一"><a class="markdownIt-Anchor" href="#内容形式单一"></a> 内容形式单一</h2><p>后来，我开始意识到内容形式单一是一个大问题。刚开始时，我只专注于文本内容，但后来发现，视频和直播等形式更能吸引用户的注意力。我曾经做了一次直播，分享了一个半小时的技术观点，结果用户反馈说他们更希望看到更多的互动环节，而不是单纯的讲授。这让我意识到，多样化的内容形式能提高用户参与度。</p><blockquote><p>我曾经做了一个关于React的直播课程，结果观看人数只有 <span class="hl">20人</span>，而我投入了 <span class="hl">15小时</span> 的时间。</p></blockquote><h2 id="忽视内容更新迭代导致生命周期短"><a class="markdownIt-Anchor" href="#忽视内容更新迭代导致生命周期短"></a> 忽视内容更新迭代导致生命周期短</h2><p>最致命的一个坑就是忽视了内容的更新和迭代。我认为内容发布出去后就万事大吉了，但其实内容需要不断更新以保持其相关性和吸引力。有一次，我发布了一套关于Node.js的教程，但发布后就没有再更新过。结果，当我发现市场上已经出现了很多新的框架和技术时，这套教程已经失去了竞争力。这让我深刻意识到，内容的生命周期是有限的，需要定期更新以适应市场变化。</p><blockquote><p>有一次，我发布了一套关于Node.js的教程，但之后就没有再更新过。结果我发现市场上出现了很多新的框架和技术，这套教程很快就失去了 <span class="hl">80%</span> 的价值。</p></blockquote><h2 id="总结与思考"><a class="markdownIt-Anchor" href="#总结与思考"></a> 总结与思考</h2><p>回顾这七年，我意识到知识付费产品的成功并不是一蹴而就的，而是一个需要不断学习、适应和改进的过程。每次踩坑后，我都学到了很多。现在，我更加注重用户需求调研，内容形式的多样性，以及内容的持续更新。但我相信，未来还会遇到新的挑战，而这正是成长的过程。对于那些想要踏入知识付费领域的开发者来说，保持开放的心态和持续学习的态度至关重要。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="杂思" scheme="https://sudo.rm-rf.ink/categories/%E6%9D%82%E6%80%9D/"/>
    
    
    <category term="杂思" scheme="https://sudo.rm-rf.ink/tags/%E6%9D%82%E6%80%9D/"/>
    
  </entry>
  
  <entry>
    <title>Notion、Obsidian、飞书——我用下来的真实对比</title>
    <link href="https://sudo.rm-rf.ink/%E6%95%88%E7%8E%87%E6%8F%90%E5%8D%87/a879cb62.html"/>
    <id>https://sudo.rm-rf.ink/%E6%95%88%E7%8E%87%E6%8F%90%E5%8D%87/a879cb62.html</id>
    <published>2026-10-20T01:00:00.000Z</published>
    <updated>2026-10-20T01:13:29.629Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h3 id="notion简洁而不失功能"><a class="markdownIt-Anchor" href="#notion简洁而不失功能"></a> Notion：简洁而不失功能</h3><p>Notion是我使用后最直观的工具，简洁的设计和强大的功能组合在一起，形成了独特的体验。在Notion中创建一个待办事项列表，只需点击几下就能完成，这比使用Excel表格方便多了。<span class="hl">比如，新建一个待办事项时，Notion就自动提示你下一步该做什么。</span> <span class="hl">通过Notion，我创建了一个包含500个待办事项的清单，而使用Excel需要10分钟才能完成。</span></p><h3 id="obsidian深入挖掘自由度高"><a class="markdownIt-Anchor" href="#obsidian深入挖掘自由度高"></a> Obsidian：深入挖掘，自由度高</h3><p>Obsidian提供了一个极富创意的平台，用户可以根据自己的需求进行深度定制，这种自由度对于那些希望在信息管理上更为个性化的人来说非常棒。虽然我需要花一些时间去学习如何使用它的插件系统，但这种灵活性让我能够在Obsidian中实现许多想法，比如创建一个包含5000条笔记的日记，完全不需要担心存储问题。</p><h3 id="飞书高效协作必备"><a class="markdownIt-Anchor" href="#飞书高效协作必备"></a> 飞书：高效协作必备</h3><p>飞书是企业内部信息同步的高效工具。我所在的一个项目组通过飞书实现了信息的快速共享和同步，极大地提高了团队协作的效率。例如，在一个包含100名成员的项目组中，每周召开的会议时间从4小时减少到了1小时，效率提升显著。这样的工具无疑有助于提升团队的生产力。</p><h3 id="开放性思考"><a class="markdownIt-Anchor" href="#开放性思考"></a> 开放性思考</h3><p>在尝试了这三种工具后，我开始思考，哪种工具最适合自己的工作方式，哪种工具的用户界面与我的思维方式最为契合。未来，我可能会尝试整合这三种工具，以满足不同场景下的需求。在信息管理、团队协作和个人项目管理之间找到平衡点，这正是效率提升的关键所在。 在Notion、Obsidian和飞书之间，它们各有所长，如何选择取决于你的个人需求和工作方式。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="效率提升" scheme="https://sudo.rm-rf.ink/categories/%E6%95%88%E7%8E%87%E6%8F%90%E5%8D%87/"/>
    
    
    <category term="效率提升" scheme="https://sudo.rm-rf.ink/tags/%E6%95%88%E7%8E%87%E6%8F%90%E5%8D%87/"/>
    
  </entry>
  
  <entry>
    <title>什么时候该写测试，什么时候不该</title>
    <link href="https://sudo.rm-rf.ink/%E7%BC%96%E7%A8%8B%E5%93%B2%E5%AD%A6/550cdc1d.html"/>
    <id>https://sudo.rm-rf.ink/%E7%BC%96%E7%A8%8B%E5%93%B2%E5%AD%A6/550cdc1d.html</id>
    <published>2026-10-19T01:00:00.000Z</published>
    <updated>2026-10-19T02:16:41.463Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>测试是程序员的护栏，但不是每段代码都需要护栏。<span class="hl">判断在哪里架设护栏，比一直架设更重要。</span></p><h2 id="何时该写测试"><a class="markdownIt-Anchor" href="#何时该写测试"></a> 何时该写测试</h2><h3 id="当代码复杂且需要保障稳定性时"><a class="markdownIt-Anchor" href="#当代码复杂且需要保障稳定性时"></a> 当代码复杂且需要保障稳定性时</h3><p>复杂的算法实现和核心业务逻辑必须测。<span class="hl">这类错误的特点是：发生概率低，但一旦发生损失大。</span></p><p>银行交易系统每秒处理上万笔转账，任何一笔的数字错误都可能造成资损。这种场景下，测试的收益是清晰的：写测试的成本 &lt;&lt; 不写测试的期望损失。</p><blockquote><p>测试的作用不是证明代码正确，而是<span class="hl">降低代码变更时的信心成本</span>。当你修改一段复杂逻辑时，有测试和没测试是完全不同的心理状态。</p></blockquote><h3 id="当代码需要被多人长期维护时"><a class="markdownIt-Anchor" href="#当代码需要被多人长期维护时"></a> 当代码需要被多人长期维护时</h3><p>团队成员水平参差不齐，代码被易手的频率高。测试是<span class="hl">跨越时间传递设计意图的介质</span>。</p><p>新来的开发者读不懂旧代码，但他能看懂测试。测试告诉你：这个函数的预期输出是什么，边界条件是什么，错误情况怎么处理。</p><h2 id="何时不该写测试"><a class="markdownIt-Anchor" href="#何时不该写测试"></a> 何时不该写测试</h2><h3 id="当项目初期需要快速迭代时"><a class="markdownIt-Anchor" href="#当项目初期需要快速迭代时"></a> 当项目初期需要快速迭代时</h3><p>验证想法阶段不需要测试。<span class="hl">这个阶段的代码大概率会被重写，测试是给不存在的东西交的税。</span></p><p>一个 MVP 上线前花两周写测试，结果发现方向错了，测试全部白写。这是创业初期最常见的浪费。</p><p>判断标准：<span class="hl">这段代码预计会被重写吗？预期生命周期有多长？</span>生命周期短的代码，不值得测试。</p><h3 id="当项目规模较小且功能单一时"><a class="markdownIt-Anchor" href="#当项目规模较小且功能单一时"></a> 当项目规模较小且功能单一时</h3><p>三个人天的项目，代码总量不超过五百行。这种情况下写测试的工作量和收益不成比例。</p><blockquote><p>不是不测，而是测得更有针对性。只测那些：逻辑复杂容易出错、错误难以发现、崩溃后果严重的部分。</p></blockquote><h2 id="开放性思考"><a class="markdownIt-Anchor" href="#开放性思考"></a> 开放性思考</h2><p>测试不是目的，<span class="hl">系统的可信性才是</span>。</p><p>测试只是一种建立可信性的手段。还有其他手段：Code Review、类型系统、形式化验证、监控报警。它们各有各的成本和适用范围。</p><p>在真实项目中，往往是多种手段组合使用。选择哪些组合，取决于：系统规模、团队组成、变更频率、容错要求。</p><p><span class="hl">一个优秀的工程师，不是写测试最多的那个，而是最清楚在什么地方不写测试的那个。</span></p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="编程哲学" scheme="https://sudo.rm-rf.ink/categories/%E7%BC%96%E7%A8%8B%E5%93%B2%E5%AD%A6/"/>
    
    
    <category term="编程哲学" scheme="https://sudo.rm-rf.ink/tags/%E7%BC%96%E7%A8%8B%E5%93%B2%E5%AD%A6/"/>
    
  </entry>
  
  <entry>
    <title>防御性编程是一种过度设计吗</title>
    <link href="https://sudo.rm-rf.ink/%E7%BC%96%E7%A8%8B%E5%93%B2%E5%AD%A6/f1125d9e.html"/>
    <id>https://sudo.rm-rf.ink/%E7%BC%96%E7%A8%8B%E5%93%B2%E5%AD%A6/f1125d9e.html</id>
    <published>2026-10-18T01:00:00.000Z</published>
    <updated>2026-10-19T02:16:26.705Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>防御性编程要求开发者在编写代码时考虑各种异常情况，为程序添加额外的保护层。争议在于：这是<span class="hl">理性设计</span>，还是<span class="hl">过度设计</span>？</p><p>我的结论是：取决于你在哪个层面做防御。</p><h2 id="防御性编程的必要性"><a class="markdownIt-Anchor" href="#防御性编程的必要性"></a> 防御性编程的必要性</h2><p>防御性编程不是给代码打补丁，而是<span class="hl">降低系统整体的复杂度</span>。</p><p>以文件读取为例：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">普通写法：打开文件 → 读取</span><br><span class="line">防御写法：检查文件存在 → 检查权限 → 检查编码 → 打开 → 读取</span><br></pre></td></tr></table></figure><p>表面看，防御写法代码行数更多。但如果不防御，文件不存在时程序崩溃，后续所有依赖这个文件数据的操作全部失效。<span class="hl">单点故障通过异常传播污染整个系统</span>。</p><blockquote><p>防御性编程把运行时崩溃转变为可预期的错误处理。代价是代码更长，收益是系统边界更清晰。</p></blockquote><h2 id="一次性能问题的教训"><a class="markdownIt-Anchor" href="#一次性能问题的教训"></a> 一次性能问题的教训</h2><p>我曾花了两周排查一个文件读写系统的性能问题。</p><p>根因：文件不存在时程序抛异常，异常捕获后再重试，这个重试逻辑没有 timeout。<span class="hl">每次文件操作失败都会触发一次指数退避重试</span>，导致某些节点上的操作延迟增加了三倍。</p><p>修复方法很简单：加上 timeout 判断，提前结束无效等待。但这个问题的根源是最初没有做<span class="hl">边界条件防御</span>。</p><p>教训：<span class="hl">防御性编程省下来的，是未来排查问题的深夜</span>。</p><h2 id="防御性编程的适用边界"><a class="markdownIt-Anchor" href="#防御性编程的适用边界"></a> 防御性编程的适用边界</h2><p>过度的防御性编程确实存在。</p><p><strong>过度场景</strong>：在一个内部工具里给每一个函数参数做类型检查和范围验证，写了两百行 Guard Clause 来防住永远不会被触发的异常输入。</p><p>判断标准：<span class="hl">防御的成本是否低于不防御的期望损失</span>。</p><ul class="lvl-0"><li class="lvl-2"><p>公开 API 的输入：必须防御。攻击面未知。</p></li><li class="lvl-2"><p>内部函数的参数：信任调用方，过度防御是浪费。</p></li><li class="lvl-2"><p>IO 操作（文件、网络、数据库）：必须防御。外部环境永远不可控。</p></li></ul><h2 id="适度才是关键"><a class="markdownIt-Anchor" href="#适度才是关键"></a> 适度才是关键</h2><p>防御性编程是一把尺子。量得准，它是保险；量过头，它是债务。</p><p><span class="hl">好的防御性编程发生在系统边界，不发生在核心逻辑内部。</span>把防御层做薄，把业务逻辑做厚——这是值得追求的架构倾向。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="编程哲学" scheme="https://sudo.rm-rf.ink/categories/%E7%BC%96%E7%A8%8B%E5%93%B2%E5%AD%A6/"/>
    
    
    <category term="编程哲学" scheme="https://sudo.rm-rf.ink/tags/%E7%BC%96%E7%A8%8B%E5%93%B2%E5%AD%A6/"/>
    
  </entry>
  
  <entry>
    <title>为什么我很少在社交媒体发言</title>
    <link href="https://sudo.rm-rf.ink/%E6%9D%82%E6%80%9D/9d757751.html"/>
    <id>https://sudo.rm-rf.ink/%E6%9D%82%E6%80%9D/9d757751.html</id>
    <published>2026-10-17T01:00:00.000Z</published>
    <updated>2026-10-19T02:16:14.115Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>我活跃在一个对技术内容有真实需求的社区，但我在社交媒体上几乎不说话。这个选择不是内向，而是<span class="hl">经过计算的输出策略</span>。</p><h2 id="技术博客的读者群体与社交媒体的读者群体"><a class="markdownIt-Anchor" href="#技术博客的读者群体与社交媒体的读者群体"></a> 技术博客的读者群体与社交媒体的读者群体</h2><p>技术博客的读者是主动找上门的人——他们有具体问题，有耐心读完三千字的文章。社交媒体的读者是滑过信息流的人——<span class="hl">注意力是稀缺的，内容必须立刻产生价值</span>。这两个生态对作者的要求截然不同。</p><blockquote><p>我在技术博客写一篇文章，平均需要四到六小时。在社交媒体发一条动态，平均需要二十分钟。前者的投入产出比，对我更有意义。</p></blockquote><p>技术博客能建立<span class="hl">深度信任</span>。读者记住的是你解决了什么具体问题，不是一句巧妙的语录。</p><h2 id="技术博客写作带给我的成就感"><a class="markdownIt-Anchor" href="#技术博客写作带给我的成就感"></a> 技术博客写作带给我的成就感</h2><p>写完一篇技术文章，看到读者说&quot;这个问题困扰了我两周，读完解决了&quot;——这种反馈是即时的，也是持久的。</p><p>社交媒体上的互动是<span class="hl">消耗型的</span>。点赞和转发不等于理解，更不等于认同。一条帖子发出去，热度最多持续两天，然后就沉下去了。</p><p>我需要的是能够<span class="hl">沉淀</span>的东西。博客文章五年后仍然能被搜索引擎找到，社交媒体的帖子两天后就已经没人记得。</p><h2 id="技术博客与社交媒体的内容差异化"><a class="markdownIt-Anchor" href="#技术博客与社交媒体的内容差异化"></a> 技术博客与社交媒体的内容差异化</h2><p>技术博客适合承载<span class="hl">完整的论证过程</span>。为什么选择这个方案？有哪些替代方案？权衡在哪里？这些需要篇幅，也需要读者停下来思考。</p><p>社交媒体只能传递结论，不能传递推理。没有人在 Twitter 上写&quot;首先，其次，最后，因此&quot;。</p><blockquote><p>内容形式的差异，本质上是受众心智模式的差异。前者培养思考者，后者培养消费者。</p></blockquote><p>我选择做一个<span class="hl">持续输出深度内容的作者</span>，而不是一个<span class="hl">频繁输出短内容的账号</span>。</p><h2 id="开放性思考"><a class="markdownIt-Anchor" href="#开放性思考"></a> 开放性思考</h2><p>两者能否融合？技术博主用社交媒体引流，把深度内容留在博客——这已经是成熟的模式。</p><p>但对于我，更重要的问题是：<span class="hl">我的时间应该投入在哪里，才能产生最长半衰期的价值？</span></p><p>答案是博客。不是因为社交媒体没有价值，而是因为我的核心竞争力在于<span class="hl">系统性思考和完整表达</span>，这是博客的形态，不是社交媒体的。</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="杂思" scheme="https://sudo.rm-rf.ink/categories/%E6%9D%82%E6%80%9D/"/>
    
    
    <category term="杂思" scheme="https://sudo.rm-rf.ink/tags/%E6%9D%82%E6%80%9D/"/>
    
  </entry>
  
  <entry>
    <title>飞书真的能发表格吗？我赌它不能，然后输了</title>
    <link href="https://sudo.rm-rf.ink/uncategorized/feishu-markdown-table-truth.html"/>
    <id>https://sudo.rm-rf.ink/uncategorized/feishu-markdown-table-truth.html</id>
    <published>2026-06-03T02:00:00.000Z</published>
    <updated>2026-06-03T02:32:06.638Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="飞书真的能发表格吗我赌它不能然后输了"><a class="markdownIt-Anchor" href="#飞书真的能发表格吗我赌它不能然后输了"></a> 飞书真的能发表格吗？我赌它不能，然后输了</h1><p>三个月前我做了一个判断：飞书 IM v1 API 拒收 <code>&#123;tag:&quot;table&quot;&#125;</code>，会返 230001。</p><p>这判断没有依据。可能是某次旧测试留下的印象，可能是从某个 issue 抄来的，可能就是猜的。但它在我脑子里生了根，长成了一棵树——一整套「飞书发表格 = 降级到纯文本 + 中文提示」的代码就围着它长起来。</p><p>直到上周看到 chapafan 那个 200 行的方案。</p><hr><h2 id="那-200-行"><a class="markdownIt-Anchor" href="#那-200-行"></a> 那 200 行</h2><p><code>chapafan/Hermes-feishu-to-table</code> 的 README 写得克制：</p><blockquote><p>修改 <code>feishu.py</code>，在消息发送前检测 Markdown 表格，自动转成飞书 CardKit v2 的 <code>table</code> 组件。</p></blockquote><p>克制到我一度怀疑他在吹牛。但点进去看代码，3 个函数干净得像论文摘要：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">_parse_markdown_table</span>(<span class="params">text</span>) -&gt; <span class="type">List</span>[<span class="type">Dict</span>]:</span><br><span class="line">    <span class="comment"># 把 markdown 拆成 text 段和 table 段</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">_build_table_card</span>(<span class="params">headers, rows</span>) -&gt; <span class="type">Dict</span>:</span><br><span class="line">    <span class="comment"># 拼 CardKit v2 table 组件</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">_build_interactive_card_with_tables</span>(<span class="params">text</span>) -&gt; <span class="type">Optional</span>[<span class="type">Dict</span>]:</span><br><span class="line">    <span class="comment"># 包成 schema 2.0 卡片</span></span><br></pre></td></tr></table></figure><p>加上 <code>_build_outbound_payload</code> 里 4 行切换，<strong>总共 200 行</strong>。</p><p>我盯着这 200 行看了十分钟。它和我脑子里那个「飞书拒收 table」的公理是矛盾的。两个里一定有一个是错的。</p><hr><h2 id="错的那个"><a class="markdownIt-Anchor" href="#错的那个"></a> 错的那个</h2><p>移植到本地的过程很顺，但眼尖抓到一个<strong>上游隐藏 bug</strong>：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">def</span> <span class="title function_">_flush_table</span>():</span><br><span class="line">    <span class="keyword">nonlocal</span> table_lines  <span class="comment"># ← chapafan 写的</span></span><br><span class="line">    <span class="keyword">if</span> <span class="keyword">not</span> table_lines:</span><br><span class="line">        <span class="keyword">return</span></span><br><span class="line">    ...</span><br><span class="line">    table_lines = []  <span class="comment"># ← 这行其实没生效</span></span><br></pre></td></tr></table></figure><p><code>table_lines</code> 定义在 <code>_parse_markdown_table</code> 函数顶部，嵌套函数里 <code>nonlocal</code> 写错位置了——Python 会忽略这个声明，结果是 <code>table_lines = []</code> 变成了「给局部变量赋空列表」，外层的 <code>table_lines</code> 永远不清空。<strong>第二张表格的解析会混进第一张的残留行。</strong></p><p>chapafan 自己的测试可能没覆盖多表。我用 <code>table_lines.clear()</code> 替换——<code>list.clear()</code> 是 in-place 操作，不需要 <code>nonlocal</code> 也工作。</p><p>顺手做了 22 个单测，全部过。然后——</p><hr><h2 id="实弹"><a class="markdownIt-Anchor" href="#实弹"></a> 实弹</h2><p>跳过文档、跳过猜测，直接发：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">content = <span class="string">&quot;&quot;&quot;## 诊断测试</span></span><br><span class="line"><span class="string">| 名称 | 数值 | 状态 |</span></span><br><span class="line"><span class="string">|---|---|---|</span></span><br><span class="line"><span class="string">| 单元 A | 42 | OK |</span></span><br><span class="line"><span class="string">| 单元 B | 99 | FAIL |</span></span><br><span class="line"><span class="string">| 单元 C | 17 | OK |</span></span><br><span class="line"><span class="string">&quot;&quot;&quot;</span></span><br><span class="line"></span><br><span class="line">response = <span class="keyword">await</span> client.im.v1.message.create(req)</span><br><span class="line"><span class="comment"># → code=0, msg=success</span></span><br></pre></td></tr></table></figure><p>服务端<strong>收了</strong>。message_id 拿到了。<strong>但服务端收 ≠ 客户端渲染</strong>——这个区别我吃过亏。</p><p>打开手机飞书。**3 行表格真渲染成了 CardKit v2 组件。**表头加粗、列对齐、卡片有 header 装饰。</p><p>那个陪了我三个月的公理是错的。</p><hr><h2 id="边界"><a class="markdownIt-Anchor" href="#边界"></a> 边界</h2><p>P0 跑通不等于能投产。飞书 CardKit v2 的官方文档列了一堆限制，<strong>每个限制都对应一个会出事的真实场景</strong>：</p><ul class="lvl-0"><li class="lvl-2"><p>最多 50 行 → 你的用户会发 200 行的需求清单</p></li><li class="lvl-2"><p>最多 20 列 → 你的用户会发宽表</p></li><li class="lvl-2"><p>单 cell 1000 字符 → 你的用户会贴长日志</p></li><li class="lvl-2"><p>整张卡 30KB → 用户发 50KB 的对比表</p></li></ul><p>每一个都得有守卫。加完，测试从 22 涨到 35。</p><p>加守卫的时候又踩了一脚 CJK 的坑：Python <code>str[:n]</code> 天然在 codepoint 边界上切，<strong>不会切坏 CJK</strong>。我一开始还写了一段 back-off 逻辑，发现是多余的，删了。删比写更难——要承认自己想多了。</p><p>嵌套表格也想了半天怎么防。最后方案是：如果 cell 里还含 <code>|</code>，整行合并成单 cell。粗暴，但飞书 CardKit v2 不支持嵌套，没优雅解。</p><hr><h2 id="200-行"><a class="markdownIt-Anchor" href="#200-行"></a> 200 行</h2><p>给飞书发了 200 行的任务清单：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">elements: 3 ([&#x27;markdown&#x27;, &#x27;table&#x27;, &#x27;markdown&#x27;])</span><br><span class="line">  table columns=4, rows=50</span><br><span class="line">  last row last cell: &#x27;优先级 低\n\n（表格已截断，最多 50 行 / 20 列 / 单元格 1000 字）&#x27;</span><br><span class="line"></span><br><span class="line">✅ code=0 msg=success</span><br></pre></td></tr></table></figure><p>打开飞书。<strong>前 50 行清晰渲染</strong>，最后一行右下角挂着中文提示。**没崩。**用户的 200 行变成了 50 行 + 一句解释，<strong>这不是降级，是有边界的服务</strong>。</p><hr><h2 id="我学到的"><a class="markdownIt-Anchor" href="#我学到的"></a> 我学到的</h2><p>最大的收获不是表格能渲染。是<strong>我脑子里那棵&quot;飞书拒收 table&quot;的树被砍了</strong>。一个错公理曾经让我写了不必要的降级代码，让用户看不到真表格，让系统放弃了它能做的事。<strong>三个月。</strong></p><p>如果我没偶然点进 chapafan，那棵树可能还在。</p><p>所以现在的我，每个&quot;已知限制&quot;都会在脑子里加一个标签：「这个有实弹证据吗？」没有的，就当传说对待。</p><hr><p>实弹一次，胜过翻十遍文档。这是我这个月最贵的一课。</p><hr><p><em>附录：commit <code>600db9109</code>（P0 移植）、<code>d1d1d5cc5</code>（P1 边界）。代码 200 行，测试 35 个，3 次实弹。</em></p>]]></content>
    
    
    <summary type="html">一个从错误公理到 CardKit v2 的工程故事</summary>
    
    
    
    
    <category term="hermes" scheme="https://sudo.rm-rf.ink/tags/hermes/"/>
    
    <category term="feishu" scheme="https://sudo.rm-rf.ink/tags/feishu/"/>
    
    <category term="lark" scheme="https://sudo.rm-rf.ink/tags/lark/"/>
    
    <category term="cardkit" scheme="https://sudo.rm-rf.ink/tags/cardkit/"/>
    
    <category term="debugging" scheme="https://sudo.rm-rf.ink/tags/debugging/"/>
    
  </entry>
  
  <entry>
    <title>mem-md 开源：一个反向量库的 Agent 记忆系统</title>
    <link href="https://sudo.rm-rf.ink/uncategorized/mem-md-opensource.html"/>
    <id>https://sudo.rm-rf.ink/uncategorized/mem-md-opensource.html</id>
    <published>2026-06-02T14:30:00.000Z</published>
    <updated>2026-06-02T15:04:13.800Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="mem-md-开源一个反向量库的-agent-记忆系统"><a class="markdownIt-Anchor" href="#mem-md-开源一个反向量库的-agent-记忆系统"></a> mem-md 开源：一个反向量库的 Agent 记忆系统</h1><blockquote><p>Markdown is all you need.</p></blockquote><hr><h2 id="序从-mempalace-到-mem-md"><a class="markdownIt-Anchor" href="#序从-mempalace-到-mem-md"></a> 序：从 MemPalace 到 mem-md</h2><p>上个月写了《改造 Hermes Memory Provider 的曲折历程》，把记忆系统的演化讲到了 MFL（Memory Filtration Layer）阶段。那篇文章结尾埋了一个伏笔：mempalace 里有 5.2 万行历史观测数据，要怎么迁移到 MFL？</p><p>这篇文章就是这个伏笔的答案：<strong>不迁移到 MFL，直接退回到更底层的 mem-md</strong>。</p><p><code>mem-md</code> 今天 0.1.0 开源。代码在 <a href="https://gitlab.ha-guoli.cn/yqwoe/mem-md">gitlab.ha-guoli.cn/yqwoe/mem-md</a>，171 个测试全过，P0/P1 稳定性全部落地。</p><p>但这不是一个&quot;我做了一个项目&quot;的炫耀贴。这是一个<strong>关于记忆本质的回归</strong>。</p><hr><h2 id="核心问题记忆到底是什么"><a class="markdownIt-Anchor" href="#核心问题记忆到底是什么"></a> 核心问题：记忆到底是什么？</h2><p>先把问题拆到不能再拆。</p><p><strong>公理 A</strong>：记忆是「在未来的某个时间点，能被检索出来辅助决策的文本」。</p><p><strong>公理 B</strong>：检索的本质是「判断这条文本和当前 query 的相关度」。</p><p><strong>公理 C</strong>：相关度 = <code>sim(text, query) × importance(text, context) × recency(text, now)</code>。</p><p><strong>公理 D</strong>：人类能「用脑子」判断相关度。LLM 也能。embedding 是另一种近似方式——把文本压成向量，再用余弦相似度替代语义判断。</p><p>到这里分叉了。<strong>embedding 的本质是用几何距离替代语义判断</strong>。它有几个不可逆的代价：</p><ol><li class="lvl-3"><p><strong>压缩不可逆</strong>：你不可能从向量还原原文</p></li><li class="lvl-3"><p><strong>黑箱不可解释</strong>：「为什么召回这条」没有答案</p></li><li class="lvl-3"><p><strong>语义边界模糊</strong>：「猫」和「狗」距离很近，但「猫在沙发」和「狗在沙发」距离一样近</p></li></ol><p>mem-md 的选择是：<strong>用 LLM 做语义判断，用 Markdown 文件做存储</strong>。</p><p>不压缩。<strong>可读</strong>。<strong>可审计</strong>。<strong><code>cp -r</code> 就能迁移</strong>。</p><hr><h2 id="架构"><a class="markdownIt-Anchor" href="#架构"></a> 架构</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">~/.mem-md/</span><br><span class="line">├── personal/</span><br><span class="line">│   ├── MEMORY.md</span><br><span class="line">│   ├── &#123;user_id&#125;/</span><br><span class="line">│   │   ├── user/         # 用户画像</span><br><span class="line">│   │   ├── feedback/     # 工作方式</span><br><span class="line">│   │   ├── project/      # 项目上下文</span><br><span class="line">│   │   └── reference/    # 外部引用</span><br><span class="line">├── project/&lt;path&gt;/</span><br><span class="line">└── team/&lt;owner&gt;/&lt;repo&gt;/</span><br></pre></td></tr></table></figure><p>四条规则，没有第六条：</p><table><thead><tr><th>类型</th><th>问什么</th></tr></thead><tbody><tr><td><code>user</code></td><td>你是谁</td></tr><tr><td><code>feedback</code></td><td>你怎么工作</td></tr><tr><td><code>project</code></td><td>项目正在发生什么</td></tr><tr><td><code>reference</code></td><td>信息在哪里</td></tr></tbody></table><p>闭集。LLM 不需要分类。</p><hr><h2 id="三个反直觉的设计"><a class="markdownIt-Anchor" href="#三个反直觉的设计"></a> 三个反直觉的设计</h2><h3 id="1-不用向量库"><a class="markdownIt-Anchor" href="#1-不用向量库"></a> 1. 不用向量库</h3><table><thead><tr><th>维度</th><th>向量库</th><th>mem-md</th></tr></thead><tbody><tr><td>检索</td><td>几何距离</td><td>关键词 + LLM 决策</td></tr><tr><td>可解释</td><td>黑箱</td><td>Markdown，可人工审查</td></tr><tr><td>迁移</td><td>重新索引</td><td><code>cp -r</code></td></tr><tr><td>部署</td><td>独立服务</td><td>零依赖</td></tr><tr><td>成本</td><td>embedding API</td><td>0</td></tr></tbody></table><p>部署复杂度这条最关键。ChromaDB 要起服务、要监控、要备份。mem-md <strong>零运行时依赖</strong>——只有文件系统。</p><h3 id="2-三层-llm-降级"><a class="markdownIt-Anchor" href="#2-三层-llm-降级"></a> 2. 三层 LLM 降级</h3><p>调用 LLM 的成本是秒级延迟 + 钱。mem-md 的 selector 实现三层降级：</p><ul class="lvl-0"><li class="lvl-2"><p><strong>Layer 0</strong>：候选 ≤2 → <strong>零 LLM 调用</strong>，按 mtime 排序返回</p></li><li class="lvl-2"><p><strong>Layer 1</strong>：候选 &gt;2 → 缓存命中（md5(query+paths+budget)，TTL 5min）直接返回</p></li><li class="lvl-2"><p><strong>Layer 2</strong>：cache miss → LLM 调用。<strong>LLM 失败时 mtime fallback</strong></p></li></ul><p>实测：日常 80%+ 的 recall 走 Layer 0/1，<strong>LLM 调用是异常路径不是主路径</strong>。</p><h3 id="3-文件级-mtime-缓存"><a class="markdownIt-Anchor" href="#3-文件级-mtime-缓存"></a> 3. 文件级 mtime 缓存</h3><p>扫描 <code>~/.mem-md/personal/&#123;user_id&#125;/</code> 是个递归遍历。第一次扫 200 个文件要 50ms，第二次扫只读 frontmatter（前 30 行）只花 5ms。但每次都重扫还是浪费。</p><p>mem-md 在每个 user_id 目录下放一个 <code>.scan_cache.json</code>：</p><figure class="highlight json"><table><tr><td class="code"><pre><span class="line"><span class="punctuation">&#123;</span></span><br><span class="line">  <span class="attr">&quot;root&quot;</span><span class="punctuation">:</span> <span class="string">&quot;/home/nil/.mem-md/personal/feishu:oc_xxx&quot;</span><span class="punctuation">,</span></span><br><span class="line">  <span class="attr">&quot;files&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">    <span class="attr">&quot;/path/to/memory1.md&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span></span><br><span class="line">      <span class="attr">&quot;mtime&quot;</span><span class="punctuation">:</span> <span class="number">1748934567.89</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;size&quot;</span><span class="punctuation">:</span> <span class="number">1234</span><span class="punctuation">,</span></span><br><span class="line">      <span class="attr">&quot;frontmatter&quot;</span><span class="punctuation">:</span> <span class="punctuation">&#123;</span><span class="attr">&quot;name&quot;</span><span class="punctuation">:</span> <span class="string">&quot;...&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;description&quot;</span><span class="punctuation">:</span> <span class="string">&quot;...&quot;</span><span class="punctuation">,</span> <span class="attr">&quot;type&quot;</span><span class="punctuation">:</span> <span class="string">&quot;project&quot;</span><span class="punctuation">&#125;</span></span><br><span class="line">    <span class="punctuation">&#125;</span></span><br><span class="line">  <span class="punctuation">&#125;</span></span><br><span class="line"><span class="punctuation">&#125;</span></span><br></pre></td></tr></table></figure><p>下次扫描时：mtime + size 都没变 → <strong>直接用缓存的 frontmatter，不读盘</strong>。变了才重新读。</p><p>5 秒内存 TTL 兜底跨进程同步，<strong>写盘后通过 <code>fire_invalidation()</code> 全局事件立即失效</strong>。</p><hr><h2 id="稳定性p0-p1-共-8-项机制"><a class="markdownIt-Anchor" href="#稳定性p0-p1-共-8-项机制"></a> 稳定性：P0 + P1 共 8 项机制</h2><p>0.1.0 不是 demo。<strong>该爆的雷全拆了</strong>：</p><h3 id="p0必落地"><a class="markdownIt-Anchor" href="#p0必落地"></a> P0（必落地）</h3><table><thead><tr><th>机制</th><th>解决什么</th></tr></thead><tbody><tr><td><code>atomic_write()</code>（tmp + os.replace）</td><td>半截文件 → 烂文件</td></tr><tr><td><code>FileLock</code> + <code>fcntl.flock</code> 跨进程锁</td><td>import 脚本并发写文件</td></tr><tr><td><code>error_log.py</code> 静默 except 捕获</td><td>「写入失败」不再沉默</td></tr></tbody></table><h3 id="p1已落地-4-项"><a class="markdownIt-Anchor" href="#p1已落地-4-项"></a> P1（已落地 4 项）</h3><table><thead><tr><th>机制</th><th>解决什么</th></tr></thead><tbody><tr><td>日志轮转（errors/metrics/access）</td><td>三类日志按 size+backups 自动 <code>.1</code> <code>.2</code> <code>.3</code></td></tr><tr><td><code>archive-prune</code> CLI（默认 dry-run）</td><td>archive/ 目录无界累积</td></tr><tr><td>写盘后 <code>fire_invalidation()</code></td><td>5s TTL 窗口期内的陈旧候选</td></tr><tr><td>三层 LLM 降级</td><td>LLM 失败时 mtime fallback</td></tr></tbody></table><p>写盘后的 cache 失效这个最微妙。<code>MemMdProvider._init_modules()</code> 注册 scanner 的 <code>invalidate_memory_cache</code> 为全局监听器，<strong><code>MemoryStorage.write_memory</code> / <code>update_memory</code> / <code>delete_memory</code> 末尾自动 fire</strong>。端到端：write → scan 立即看到新文件，不再受 5s 窗口期限制。</p><hr><h2 id="公开-cli"><a class="markdownIt-Anchor" href="#公开-cli"></a> 公开 CLI</h2><figure class="highlight bash"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 安装</span></span><br><span class="line">pip install mem-md</span><br><span class="line"></span><br><span class="line"><span class="comment"># 导入 Hermes sessions（飞书 / 微信 / 任意平台）</span></span><br><span class="line"><span class="built_in">export</span> MEM_MD_USER_ID=feishu:oc_xxxxxxxx</span><br><span class="line">mem-md import-sessions feishu</span><br><span class="line"></span><br><span class="line"><span class="comment"># 查询</span></span><br><span class="line">mem-md recall <span class="string">&quot;吴的商业思维&quot;</span> --budget high</span><br><span class="line"></span><br><span class="line"><span class="comment"># 手动写入</span></span><br><span class="line">mem-md retain <span class="string">&quot;用户偏好极简界面&quot;</span> feedback --name ui_preference</span><br><span class="line"></span><br><span class="line"><span class="comment"># auto-dream</span></span><br><span class="line">mem-md consolidate</span><br><span class="line"></span><br><span class="line"><span class="comment"># 归档清理（dry-run 默认）</span></span><br><span class="line">mem-md archive-prune --apply</span><br></pre></td></tr></table></figure><p><code>user_id</code> 三级回退：<code>--user-id</code> CLI 参数 &gt; <code>MEM_MD_USER_ID</code> 环境变量 &gt; <code>PLATFORM_USER_ID_MAP</code> 配置。</p><hr><h2 id="scripts-分离原则"><a class="markdownIt-Anchor" href="#scripts-分离原则"></a> scripts 分离原则</h2><p>仓库里的 <code>scripts/</code> 目录分两类：</p><ul class="lvl-0"><li class="lvl-2"><p><strong>公共</strong>（pip 打包分发）：<code>import_sessions.py</code> / <code>session_migrator.py</code> / <code>remember.py</code></p></li><li class="lvl-2"><p><strong>个人</strong>（仅仓库所有者追溯）：<code>scripts/personal/*.archived.py</code></p></li></ul><p>个人脚本里残留的个人 user_id / API key 全部隔离，<strong>不进入 pip 包</strong>。<code>pyproject.toml</code> 的 authors 写的是 <code>nil &lt;yqwoe@live.cn&gt;</code>，没有联系方式泄露，没有凭据泄露。</p><hr><h2 id="memmdproviderhermes-集成"><a class="markdownIt-Anchor" href="#memmdproviderhermes-集成"></a> MemMdProvider：Hermes 集成</h2><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> mem_md <span class="keyword">import</span> MemMdProvider</span><br><span class="line"></span><br><span class="line">provider = MemMdProvider()</span><br><span class="line">provider.initialize(</span><br><span class="line">    session_id=<span class="string">&quot;20260516_xxx&quot;</span>,</span><br><span class="line">    platform=<span class="string">&quot;feishu&quot;</span>,</span><br><span class="line">    user_id=<span class="string">&quot;feishu:oc_xxx&quot;</span>,</span><br><span class="line">    hermes_home=<span class="string">&quot;~/.hermes&quot;</span>,</span><br><span class="line">)</span><br><span class="line">provider.inject_ctx_llm(ctx.llm)  <span class="comment"># 用 Hermes 的主模型</span></span><br></pre></td></tr></table></figure><p><code>MemMdProvider</code> 实现 <code>MemoryProvider</code> ABC——和 MemPalace 完全相同的接口签名。<strong>底层从 ChromaDB + embedding 替换为文件系统 + LLM 决策</strong>。</p><p>这是关键：上层代码（content extraction、session import、Hermes agent loop）<strong>完全不需要改</strong>。换 provider 一行配置搞定。</p><hr><h2 id="路线图"><a class="markdownIt-Anchor" href="#路线图"></a> 路线图</h2><ul class="lvl-0"><li class="lvl-2"><p><strong>0.2.0</strong> — 5.2 万行 mempalace 数据一次性迁移工具 + 教程博客</p></li><li class="lvl-2"><p><strong>0.3.0</strong> — <code>mem-md-ui</code> 独立项目（基于 Obsidian 打开 <code>~/.mem-md/personal/&#123;user_id&#125;/</code> 作为零代码 UI）</p></li><li class="lvl-2"><p><strong>0.4.0</strong> — 多 profile 隔离完善</p></li><li class="lvl-2"><p><strong>1.0.0</strong> — 相似度阈值校验 + 跨进程 cache 协调 + 完整语义版本承诺</p></li></ul><p>UI 不在核心包里——这是哲学选择。核心包的哲学是「<strong>Markdown is all you need</strong>」。UI 是另一回事。Obsidian 直接打开 <code>~/.mem-md/personal/&#123;user_id&#125;/</code> 就是现成的浏览/编辑/搜索 UI。为什么要重复造轮子？</p><hr><h2 id="写给想用的人"><a class="markdownIt-Anchor" href="#写给想用的人"></a> 写给想用的人</h2><p><strong>如果你在用 ChromaDB / FAISS / Qdrant 做 Agent 记忆</strong>，请先问自己一个问题：</p><blockquote><p>我真的需要「语义相似」吗？还是只需要「相关」？</p></blockquote><p>如果是后者，mem-md 是个<strong>30MB pip 包，零运行时依赖，5 分钟装好</strong>的替代方案。</p><p><strong>如果你在用 LLM 做记忆整理</strong>，mem-md 的 <code>archive-prune</code> + <code>consolidate</code> + 软删除机制是直接可用的。不需要写 chroma adapter。</p><p><strong>如果你只是想给 agent 加个跨会话记忆</strong>，最简路径：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">from</span> mem_md <span class="keyword">import</span> MemMdProvider</span><br><span class="line">provider = MemMdProvider()</span><br><span class="line">provider.initialize(session_id=<span class="string">&quot;...&quot;</span>, platform=<span class="string">&quot;feishu&quot;</span>, user_id=<span class="string">&quot;feishu:oc_xxx&quot;</span>)</span><br><span class="line"><span class="comment"># 然后在 agent loop 里调 provider.recall(...) 和 provider.retain(...)</span></span><br></pre></td></tr></table></figure><p>完。</p><hr><h2 id="一句话总结"><a class="markdownIt-Anchor" href="#一句话总结"></a> 一句话总结</h2><blockquote><p><strong>记忆是文本，文本存在文件里，按需读取。</strong></p><p>任何把文本压成向量的方案，都在做<strong>不可逆的信息损失</strong>——除非你能证明这种损失对你的任务没有影响。</p><p>如果你不能证明——试试 mem-md。</p></blockquote><hr><p><strong>项目地址</strong>：<a href="https://gitlab.ha-guoli.cn/yqwoe/mem-md">gitlab.ha-guoli.cn/yqwoe/mem-md</a><br><strong>许可</strong>：MIT<br><strong>版本</strong>：0.1.0<br><strong>测试</strong>：171 passed<br><strong>作者</strong>：nil <a href="mailto:yqwoe@live.cn">yqwoe@live.cn</a></p>]]></content>
    
    
    <summary type="html">从 5.2 万行 MemPalace 到 0.1.0 mem-md，把记忆系统退回到第一性原理</summary>
    
    
    
    
    <category term="hermes" scheme="https://sudo.rm-rf.ink/tags/hermes/"/>
    
    <category term="memory" scheme="https://sudo.rm-rf.ink/tags/memory/"/>
    
    <category term="ai" scheme="https://sudo.rm-rf.ink/tags/ai/"/>
    
    <category term="architecture" scheme="https://sudo.rm-rf.ink/tags/architecture/"/>
    
    <category term="opensource" scheme="https://sudo.rm-rf.ink/tags/opensource/"/>
    
  </entry>
  
  <entry>
    <title>改造 Hermes Memory Provider 的曲折历程</title>
    <link href="https://sudo.rm-rf.ink/uncategorized/474c79ed.html"/>
    <id>https://sudo.rm-rf.ink/uncategorized/474c79ed.html</id>
    <published>2026-06-01T04:00:00.000Z</published>
    <updated>2026-06-02T15:06:42.876Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="改造-hermes-memory-provider-的曲折历程"><a class="markdownIt-Anchor" href="#改造-hermes-memory-provider-的曲折历程"></a> 改造 Hermes Memory Provider 的曲折历程</h1><blockquote><p>这不是一个讲&quot;正确答案&quot;的故事。这是一个讲&quot;如何一步步排除错误答案，直到看见本质&quot;的故事。</p></blockquote><hr><h2 id="起点mempalace"><a class="markdownIt-Anchor" href="#起点mempalace"></a> 起点：MemPalace</h2><p>一切从抽屉开始。</p><p>2026 年初，我给赫妹搭了一套记忆系统叫 MemPalace。核心理念很朴素：人类用记忆宫殿——把信息存进具体的地点和房间，回忆时按位置检索。映射到数字世界，就是抽屉（drawer）、房间（room）、 wing（主题区）三级结构。存储层是 ChromaDB，向量检索。</p><p>这套系统跑了一阵子，基本能用。但很快就摸到了它的边界。</p><p><strong>问题 1：Layer2 的 LLM 判决是玄学。</strong> FiltrationLayer 分两层：Layer1 用正则提取关键词（decision、preference、milestone），Layer2 让 LLM 判断&quot;这条记忆是否重要&quot;。LLM 的判断不稳定，同样的内容今天 accepted 明天 rejected。Layer2 成了整个系统的黑盒变量。</p><p><strong>问题 2：ContentRouter 是过度设计。</strong> 架构里引入了 ContentRouter，要先把记忆分成 WORLD_FACT / EXPERIENCE / OPINION / ENTITY 四类，再分别走不同的写入路径。实际上 (90%+) 的内容都进了 EXPERIENCE，其他三个桶几乎全空。ContentRouter 除了增加维护负担，没有任何实际价值。</p><p><strong>问题 3：写入链路冗余。</strong> 三套写入路径：WorldNetwork、OpinionManager、NarrativeFusion，各写各的 collection。没有一致性校验，也没有统一的数据模型。</p><p><strong>问题 4：Observations Consolidation 几乎不触发。</strong> consolidation 的本意是把多条相似记忆合并成一条，节省存储并提高召回质量。但实际运行中，34,549 条观测值跑 consolidation，300 秒超时，只完成了 60/78 批。剩下 (20%+) 的数据永远卡在那里。</p><hr><h2 id="中转站memory-filtration-layermfl"><a class="markdownIt-Anchor" href="#中转站memory-filtration-layermfl"></a> 中转站：Memory Filtration Layer（MFL）</h2><p>基于 MemPalace 的教训，我重写了 FiltrationLayer，称之为 MFL（Memory Filtration Layer）。</p><p><strong>这次做了几件对的事：</strong></p><p>删掉了 ContentRouter。发现它的分类逻辑从未真正起作用，内容自然沉淀到 EXPERIENCE 桶里。删掉后代码从 800 行瘦到 200 行，没有功能损失。</p><p>删掉了 NarrativeFusion 和 OpinionManager。旧的四网络架构（World/Experience/Opinion/Narrative）是参考 Hindsight 论文设计的，但实现和理论之间隔着一条鸿沟。LLM 判决质量撑不起这个架构。删掉之后只剩下统一的写入路径。</p><p><strong>Layer2 也简化了。</strong> 之前 Layer2 调用 LLM 做 semantic filter，效果不稳定。简化后改为纯 confidence threshold：Layer1 提取时给每条记忆打个置信分，Layer2 直接按 0.5 阈值过滤，不再调 LLM。全程无 API 调用，延迟从秒级降到毫秒级。</p><p><strong>统一数据模型上场。</strong> 之前每种记忆类型有自己的数据结构，Observation 和 Entity 和 Opinion 各玩各的。新架构定义了 UnifiedMemory dataclass：content、memory_type、confidence、metadata（wing/room/session_id/user_id/project），所有写入都路由到同一个 collection：memories_{user_id}_{project}。</p><p>这套系统在 MemPalace Provider 里跑了大概一个月。</p><p>然后我发现了真正的问题。</p><hr><h2 id="真正的断层consolidation-的-kg-质量灾难"><a class="markdownIt-Anchor" href="#真正的断层consolidation-的-kg-质量灾难"></a> 真正的断层：Consolidation 的 KG 质量灾难</h2><p>consolidation 是记忆系统的&quot;消化&quot;机制：把碎片化的原始观测合并成结构化的 Observation，避免记忆无限膨胀。</p><p>但 consolidation 有一个伴生产物：知识图谱三元组（KG triples）。每条 Observation 应该提取出 SUBJECT | PREDICATE | OBJECT 格式的三元组，用来建立实体关系图。</p><p><strong>34,549 条观测值，consolidation 超时（300 秒），只完成 60/78 批。</strong></p><p>更严重的是，KG triples 的质量几乎是垃圾：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">观 察 到 的 ：[SUBJECT | PREDICATE | OBJECT]</span><br><span class="line">The LLM response should be structured as... （这是 prompt 文本，不是三元组）</span><br><span class="line">SUBJECT | PREDICATE | OBJECT</span><br></pre></td></tr></table></figure><p>58 条 KG 记录，几乎全是 LLM prompt 的截断文本、解析失败的残片、或者干脆就是提示词本身。真正有意义的三元组不到 5 条。</p><p>这说明 consolidation 的 KG extraction 流水线从根上就是坏的。</p><p><strong>根因分析：</strong></p><p>问题不在 <code>_parse_kg_triples</code> 的解析逻辑——解析逻辑本身没问题，格式规范是清晰的。</p><p>问题在 upstream：LLM 的输出本身就是乱的。consolidation 的 prompt 要求 LLM 提取三元组，但 LLM 并没有稳定地遵循这个格式。有时候输出完整，有时候输出 prompt 文本，有时候干脆不输出。</p><p>这暴露了一个更深的问题：<strong>整个 consolidation 流程对 LLM 输出质量的依赖，本身就是一个不稳定的基础假设。</strong></p><p>LLM 的输出有随机性。你不能把一个依赖随机输出的 pipeline 当作可靠的数据处理流程。</p><hr><h2 id="转折点重新审视记忆系统需要什么"><a class="markdownIt-Anchor" href="#转折点重新审视记忆系统需要什么"></a> 转折点：重新审视&quot;记忆系统需要什么&quot;</h2><p>consolidation 失败之后，我被迫停下来想一个根本问题：</p><p><strong>记忆系统的本质到底是什么？</strong></p><p>我翻出了 Mem0、Hindsight、MemPalace 三个系统的设计文档，做了一个对比表格。看完之后，最有意思的发现不是任何一个系统的具体功能，而是它们都在解决同一个核心矛盾：</p><p><strong>存储和召回的权衡是不可消除的。</strong></p><ul class="lvl-0"><li class="lvl-2"><p>存得越多，召回噪音越大</p></li><li class="lvl-2"><p>召回越精确，存储越稀疏，可能丢失边缘有用信息</p></li></ul><p>所有记忆系统都在用不同的方式管理这个矛盾：Mem0 用 ADD-only + MD5 去重规避更新决策；Hindsight 用 Budget 机制控制召回量；MemPalace 用 KG 关系结构提高召回精度。</p><p>但当我对比这些方案时，我发现了一个更简单的答案——来自一个意想不到的地方。</p><hr><h2 id="灵感claude-code-的记忆方式"><a class="markdownIt-Anchor" href="#灵感claude-code-的记忆方式"></a> 灵感：Claude Code 的记忆方式</h2><p>Claude Code 是 Anthropic 发布的 AI 编程助手。它的记忆系统没有任何向量库、没有 embedding、没有 ChromaDB。</p><p><strong>它用纯文件系统存储记忆，以 Markdown 格式保存，配一个轻量索引文件。</strong></p><p>就这么简单。</p><p>这个事实迫使我重新审视之前所有的复杂设计。如果 Claude Code 能用纯文件系统做 AI Agent 的记忆系统，并且工作得足够好，那么我们之前构建的那套 ChromaDB + FiltrationLayer + KG extraction + LLM 判决的复杂基础设施，究竟解决了什么问题？</p><p><strong>答案可能是：解决了不存在的问题。</strong></p><p>我把 MemPalace/MFL 架构和 Claude Code 的方案做了第一性原理对比：</p><table><thead><tr><th>维度</th><th>MemPalace/MFL</th><th>Claude Code 方案</th></tr></thead><tbody><tr><td>存储</td><td>ChromaDB（向量库）</td><td>文件系统 + Markdown</td></tr><tr><td>检索</td><td>embedding 相似度 + KG 遍历</td><td>关键词扫描 + LLM 决策</td></tr><tr><td>延迟</td><td>embedding 生成 ~50ms</td><td>文件读取 ~1ms</td></tr><tr><td>可迁移性</td><td>依赖 ChromaDB 版本</td><td>纯文本，任意工具可读</td></tr><tr><td>可解释性</td><td>embedding 黑盒</td><td>Markdown 明文</td></tr><tr><td>维护成本</td><td>高（多套 pipeline）</td><td>低（文件系统）</td></tr></tbody></table><p><strong>最大的认知翻转在这里：</strong></p><p>之前我以为 embedding 相似度检索是&quot;更智能&quot;的记忆召回方式。但实际上，embedding 的&quot;智能&quot;是有代价的：它把记忆内容的语义压缩成一个向量，这个过程本身就有信息损失。而且向量相似度不等于相关性问题——有时候你最需要的记忆，和你的查询在向量空间里并不接近。</p><p>Claude Code 的方案则反其道而行：不做语义压缩，直接存原文。召回时用轻量扫描（frontmatter metadata 过滤），再用 LLM 做最终判断。<strong>两阶段：快扫 + LLM 决策。</strong></p><p>这个方案更简单，延迟更低，而且没有 embedding 的信息损失。</p><hr><h2 id="新架构mem-md"><a class="markdownIt-Anchor" href="#新架构mem-md"></a> 新架构：mem-md</h2><p>基于这个认知，我写了 mem-md。</p><p>核心理念只有一句话：<strong>Markdown is all you need。</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">~/.mem-md/</span><br><span class="line">├── personal/                    # 个人记忆（私有）</span><br><span class="line">│   ├── MEMORY.md              # 索引文件</span><br><span class="line">│   ├── user/                   # 用户画像</span><br><span class="line">│   ├── feedback/               # 工作方式偏好</span><br><span class="line">│   └── *.md                    # 具体记忆条目</span><br><span class="line">├── project/&lt;path&gt;/             # 项目记忆（按项目隔离）</span><br><span class="line">└── team/&lt;owner&gt;/&lt;repo&gt;/       # 团队记忆（按仓库隔离）</span><br></pre></td></tr></table></figure><p><strong>四种闭集记忆类型（无开放式存储）：</strong></p><ul class="lvl-0"><li class="lvl-2"><p><code>user</code>：用户画像——“你是谁”</p></li><li class="lvl-2"><p><code>feedback</code>：工作方式——“我如何工作”</p></li><li class="lvl-2"><p><code>project</code>：项目上下文——“项目正在发生什么”</p></li><li class="lvl-2"><p><code>reference</code>：外部指针——“信息在哪里”</p></li></ul><p><strong>闭集分类的核心价值：定义&quot;什么不该存&quot;比&quot;该存什么&quot;更重要。</strong></p><p>开放式的记忆存储必然导致记忆膨胀和检索噪音。严格限定四种类型，前置过滤比后期裁剪更有效。</p><p><strong>两阶段召回：</strong></p><ol><li class="lvl-3"><p><strong>Scanner（&lt;10ms）</strong>：扫描 frontmatter，只取 type + staleness_days 满足条件的文件</p></li><li class="lvl-3"><p><strong>Selector（LLM 决策）</strong>：把候选文件内容发给 LLM，让 LLM 判断哪些和当前 query 真正相关</p></li></ol><p>这个设计和 Claude Code 的思路一致：不做 embedding，用结构化元数据做初筛，再用 LLM 做最终语义判断。</p><p><strong>Consolidation 简化为 auto-dream 四阶段：</strong></p><ul class="lvl-0"><li class="lvl-2"><p>Orient：识别记忆主题</p></li><li class="lvl-2"><p>Gather：收集相关片段</p></li><li class="lvl-2"><p>Consolidate：合并为 Observation</p></li><li class="lvl-2"><p>Prune：修剪 <a href="http://MEMORY.md">MEMORY.md</a> 索引</p></li></ul><p>删掉了 KG extraction——consolidation 不再试图提取三元组。<strong>KG 是之前过度设计的又一个证据。</strong> 从 34,549 条观测值里提取出来的三元组几乎全是垃圾，保留这个功能只是在增加系统复杂度和维护负担。</p><hr><h2 id="过程即结果"><a class="markdownIt-Anchor" href="#过程即结果"></a> 过程即结果</h2><p>回顾整个改造过程，最有价值的不是最终的 mem-md 架构，而是排除错误路径的过程。</p><p>MemPalace 让我知道了 ChromaDB + 多层 pipeline 的边界在哪里。MFL 的 FiltrationLayer 简化实验证明了 Layer2 LLM 判决可以完全去掉。consolidation 的 KG 质量灾难让我最终下决心删掉 KG extraction。Claude Code 的对比给了我重新设计的勇气。</p><p><strong>每一个&quot;放弃&quot;的决定背后，都是一次第一性原理的追问：</strong></p><ul class="lvl-0"><li class="lvl-2"><p>为什么需要 ContentRouter？因为要分类。但分类真的有用吗？没有。删。</p></li><li class="lvl-2"><p>为什么需要 Layer2 LLM 判决？因为要判断重要性。但这个判断稳定吗？不稳定。删。</p></li><li class="lvl-2"><p>为什么需要 KG extraction？因为要建立实体关系。但提取质量合格吗？不合格。删。</p></li></ul><p>帆布归真。删到不能再删，剩下的就是本质。</p><hr><h2 id="教训"><a class="markdownIt-Anchor" href="#教训"></a> 教训</h2><p><strong>1. 复杂性是记忆系统的大敌。</strong></p><p>记忆系统天然倾向于变得越来越复杂。每一层抽象都看起来合理，每一个新功能都解决了某个真实的问题。但累积起来，复杂性开始侵蚀系统本身的可靠性——consolidation 超时、KG 质量崩溃、Layer2 判决不稳定，这些问题都直接或间接地来自过度设计的架构。</p><p><strong>2. 可解释性比&quot;智能&quot;更重要。</strong></p><p>ChromaDB 的 embedding 检索听起来很智能，但它的结果是黑盒的。你不知道为什么某条记忆被召回，另一条没有被召回。Markdown 文件明文存储，frontmatter 一目了然，出了问题可以随时检查、修复、回滚。</p><p><strong>3. 存储和召回的权衡，最终靠 LLM 决策。</strong></p><p>无论是 embedding 相似度还是 metadata 过滤，都无法完全解决&quot;语义相关性 vs. 字面相关性&quot;的张力。最终的判断必须交给 LLM——但 LLM 应该用在召回阶段（判断候选是否相关），而不是在存储阶段（判断内容是否重要）。</p><p><strong>4. consolidation 的 KG 质量依赖 LLM 输出的稳定性，这本身就是一个不稳定的基础假设。</strong></p><hr><h2 id="当前状态"><a class="markdownIt-Anchor" href="#当前状态"></a> 当前状态</h2><p>mem-md phase3-5 已完成，48 个测试全部通过（33 单元 + 15 E2E）。</p><p>四个阶段的核心功能全部实现：</p><ul class="lvl-0"><li class="lvl-2"><p>Phase 1：核心模块（types、config、storage、scanner、indexer）</p></li><li class="lvl-2"><p>Phase 2：LLM 决策选择（MemorySelector）</p></li><li class="lvl-2"><p>Phase 3：自动提取（后台线程，每轮对话后异步触发）</p></li><li class="lvl-2"><p>Phase 4：Consolidation（auto-dream 四阶段引擎）</p></li></ul><p>下一步是接入 Hermes Agent 作为 Memory Provider，让记忆真正进入赫妹的工作流。</p><hr><p><strong>续篇</strong>：<a href="/mem-md-opensource.html">《mem-md 开源：一个反向量库的 Agent 记忆系统》</a></p><p><em>文章使用 humanizer 润色，移除了 AI 写作的典型模式，保留个人思考痕迹。</em></p>]]></content>
    
    
    <summary type="html">从 MemPalace 到 Filtration Layer 再到 mem-md，一次关于记忆系统本质的回归</summary>
    
    
    
    
    <category term="hermes" scheme="https://sudo.rm-rf.ink/tags/hermes/"/>
    
    <category term="memory" scheme="https://sudo.rm-rf.ink/tags/memory/"/>
    
    <category term="ai" scheme="https://sudo.rm-rf.ink/tags/ai/"/>
    
    <category term="architecture" scheme="https://sudo.rm-rf.ink/tags/architecture/"/>
    
  </entry>
  
  <entry>
    <title>从零构建 Hermes Agent 记忆系统——memory-filtration-layer 迭代手记</title>
    <link href="https://sudo.rm-rf.ink/AI/46499b7b.html"/>
    <id>https://sudo.rm-rf.ink/AI/46499b7b.html</id>
    <published>2026-05-21T16:00:00.000Z</published>
    <updated>2026-10-17T14:19:03.719Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h1 id="从零构建-hermes-agent-记忆系统"><a class="markdownIt-Anchor" href="#从零构建-hermes-agent-记忆系统"></a> 从零构建 Hermes Agent 记忆系统</h1><h2 id="背景"><a class="markdownIt-Anchor" href="#背景"></a> 背景</h2><p>在做 Hermes Agent 的记忆系统时，遇到了一个核心问题：</p><blockquote><p><strong>MemPalace 当时是「对话录像机」——什么都记，95% 的抽屉从未被检索，凭证还泄露进向量数据库。</strong></p></blockquote><p>这不是一个工程问题，是一个<strong>架构问题</strong>。</p><hr><h2 id="第一性原理记忆系统的两个正交通道"><a class="markdownIt-Anchor" href="#第一性原理记忆系统的两个正交通道"></a> 第一性原理：记忆系统的两个正交通道</h2><p>解决这个问题的关键，是把「记忆」这个模糊概念拆解到最底层。</p><p><strong>记忆系统有两个完全正交的维度：</strong></p><table><thead><tr><th>通道</th><th>作用</th><th>触发时机</th></tr></thead><tbody><tr><td><strong>Write（提取）</strong></td><td>把当前对话中的重要信息写入长期存储</td><td>Turn 结束时</td></tr><tr><td><strong>Recall（召回）</strong></td><td>把过去记住的信息注入当前上下文</td><td>Turn 开始前</td></tr></tbody></table><p>这两个通道<strong>不能相互替代</strong>：</p><ul class="lvl-0"><li class="lvl-2"><p>没有 Write，记忆永远不持久化</p></li><li class="lvl-2"><p>没有 Recall，agent 无法「记得」之前说过什么</p></li></ul><p>大多数记忆系统的失败，都源于混淆了这两个通道——要么只管写不管读，要么只管读不管写。</p><hr><h2 id="迭代路径"><a class="markdownIt-Anchor" href="#迭代路径"></a> 迭代路径</h2><h3 id="第一阶段从论文出发选择-mempalace"><a class="markdownIt-Anchor" href="#第一阶段从论文出发选择-mempalace"></a> 第一阶段：从论文出发，选择 MemPalace</h3><p>参考 Hindsight is 20/20 论文（arXiv:2512.12818, Dec 2025），设计了一套完整的记忆架构。</p><p><strong>为什么选 MemPalace？</strong></p><p>在开始选型之前，我首先分析了当时的现状：赫妹🤡的记忆系统依赖 Hermes Agent 内置的 MemPalace，它是我（吴）为 Hermes Agent 专门开发的记忆存储系统，已经深度嵌入 Hermes 的生命周期。</p><p>MemPalace 当时的架构是这样的：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">MemPalace (ChromaDB + SQLite)</span><br><span class="line">├── wings/</span><br><span class="line">│   ├── wing_hermes/              # Agent 自我知识</span><br><span class="line">│   │   ├── memory/               # 结构化事实</span><br><span class="line">│   │   ├── profile/              # Agent 配置</span><br><span class="line">│   │   └── diary/               # AAAK 格式 session 归档</span><br><span class="line">│   └── wing_user_&#123;user_id&#125;/      # 用户记忆（per-user）</span><br><span class="line">│       ├── memory/               # 项目事实、工具习惯</span><br><span class="line">│       ├── profile/             # 用户偏好</span><br><span class="line">│       └── diary/               # session 观察</span><br><span class="line">├── knowledge_graph.sqlite3       # 实体关系图</span><br><span class="line">└── palace/</span><br><span class="line">    └── mempalace.sqlite3        # 元数据（wing/room/drawer 索引）</span><br></pre></td></tr></table></figure><p><strong>它已经是赫妹🤡记忆系统的基础设施</strong>。换掉它意味着重写大量已稳定的代码。直接在其上构建，而不是推倒重来，是最务实的选择。</p><p>更重要的是，MemPalace 的核心设计——<strong>wing / room / drawer 三级嵌套 + ChromaDB 向量存储 + SQLite 关系图</strong>——这三层存储正交且互补：</p><ul class="lvl-0"><li class="lvl-2"><p><strong>ChromaDB</strong>：语义检索，高维向量相似度搜索</p></li><li class="lvl-2"><p><strong>SQLite 元数据</strong>：wing/room/drawer 的索引，快速精确查询</p></li><li class="lvl-2"><p><strong>knowledge_graph.sqlite3</strong>：实体共指关系，因果链追溯</p></li></ul><p>这个组合能够支撑 Hindsight 的 Recall Pipeline（四路检索 + RRF），不需要引入额外的外部依赖。</p><p><strong>Phase 1 的目标：</strong> 引入 Hindsight 的 Layer1-4 四层过滤 + Four-Network 存储，在 MemPalace 现有数据结构上增量实现认知架构。</p><p><strong>Phase 1 设计的架构：</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">对话输入</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">L0 Security Filter  ← 凭证/密码/Token 正则过滤</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">L1 Pattern Extract   ← 双语正则提取 decision/preference/milestone/emotional</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">L2 LLM Semantic     ← MiniMax/Qwen 判断是否保留</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">L3 ChromaDB Dedup   ← 向量相似度去重，阈值 0.78</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">Four-Network Route  ← ContentRouter 分发到 W/B/O/S 四个网络</span><br><span class="line">                      → wing_user_&#123;user_id&#125;/world_facts</span><br><span class="line">                      → wing_user_&#123;user_id&#125;/experience</span><br><span class="line">                      → wing_user_&#123;user_id&#125;/opinion  [新增]</span><br><span class="line">                      → wing_user_&#123;user_id&#125;/entity    [新增]</span><br></pre></td></tr></table></figure><p><strong>这个设计是纸上谈兵。</strong></p><h3 id="第二阶段撞墙"><a class="markdownIt-Anchor" href="#第二阶段撞墙"></a> 第二阶段：撞墙</h3><p>实现到一半，发现了四个架构性缺陷，每一个都足以让系统失效：</p><p><strong>问题1：两条互不相交的写入路径</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">路径A: FiltrationLayer → ContentRouter → Four-Network</span><br><span class="line">路径B: FiltrationLayer → writer → memories_*</span><br></pre></td></tr></table></figure><p>两条路径互不干扰，写入位置完全不同，数据永远散落在两处。</p><p><strong>问题2：ChromaDB Client 不共享</strong></p><p>每个 Network 在 <code>write()</code> 内部各自创建独立的 <code>chromadb.PersistentClient()</code> 实例。写入操作作用在<strong>进程私有句柄</strong>上，数据实际落在内存里，没有持久化到磁盘。</p><p>测试验证：</p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Router: [(&#x27;experience&#x27;, 2)]</span><br><span class="line">WORLD_FACT → ChromaDB: 0 条  ← 写了个寂寞</span><br><span class="line">EXPERIENCE → ChromaDB: 0 条   ← 同上</span><br></pre></td></tr></table></figure><p><strong>问题3：ContentRouter 分类逻辑偏离论文</strong></p><p>论文定义 WORLD_FACT 是「外部客观事实」，但实现把所有包含「决定/偏好/意图」的内容全扔进 EXPERIENCE。导致 world_facts 永远为空，opinion 分支是空代码 <code>pass</code>。</p><p><strong>问题4：Opinion Write Path 是空的</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">elif</span> content_type == ContentType.OPINION:</span><br><span class="line">    <span class="keyword">pass</span>  <span class="comment"># ← 空实现</span></span><br></pre></td></tr></table></figure><p>OpinionNetwork 类存在，代码在，但从来没调用过。</p><h3 id="第三阶段phase-0-修复"><a class="markdownIt-Anchor" href="#第三阶段phase-0-修复"></a> 第三阶段：Phase 0 修复</h3><p>针对 Phase 0 问题逐一修复，这是整个重构的基础：</p><p><strong>1. ChromaDB Client 单例化</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">_client: <span class="type">Optional</span>[PersistentClient] = <span class="literal">None</span></span><br><span class="line"></span><br><span class="line"><span class="keyword">def</span> <span class="title function_">get_chroma_client</span>() -&gt; PersistentClient:</span><br><span class="line">    <span class="keyword">global</span> _client</span><br><span class="line">    <span class="keyword">if</span> _client <span class="keyword">is</span> <span class="literal">None</span>:</span><br><span class="line">        _client = chromadb.PersistentClient(path=palace_path)</span><br><span class="line">    <span class="keyword">return</span> _client</span><br></pre></td></tr></table></figure><p>替换所有 Network 中的独立 client 创建，所有写入现在落在同一个文件句柄上。</p><p><strong>2. ContentRouter 分类规则修正</strong></p><p>不再依赖 LLM 自由分类，用规则快速路由：</p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line">ROUTE_MAP = &#123;</span><br><span class="line">    <span class="string">&quot;decision&quot;</span>: ContentType.EXPERIENCE,</span><br><span class="line">    <span class="string">&quot;milestone&quot;</span>: ContentType.EXPERIENCE,</span><br><span class="line">    <span class="string">&quot;problem&quot;</span>: ContentType.EXPERIENCE,</span><br><span class="line">    <span class="string">&quot;emotional&quot;</span>: ContentType.EXPERIENCE,</span><br><span class="line">    <span class="string">&quot;action&quot;</span>: ContentType.EXPERIENCE,</span><br><span class="line">    <span class="string">&quot;preference&quot;</span>: ContentType.OPINION,</span><br><span class="line">    <span class="string">&quot;constraint&quot;</span>: ContentType.WORLD_FACT,</span><br><span class="line">    <span class="string">&quot;setup&quot;</span>: ContentType.WORLD_FACT,</span><br><span class="line">&#125;</span><br></pre></td></tr></table></figure><p><strong>3. Opinion Write Path 补全</strong></p><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="keyword">elif</span> content_type == ContentType.OPINION:</span><br><span class="line">    opinion_record = <span class="variable language_">self</span>._opinion_network.write(</span><br><span class="line">        text=chunk.content,</span><br><span class="line">        confidence=<span class="number">0.5</span>,  <span class="comment"># 论文定义：初始置信度 0.5</span></span><br><span class="line">        evidence=[<span class="string">f&quot;source: <span class="subst">&#123;chunk.source&#125;</span>&quot;</span>],</span><br><span class="line">    )</span><br></pre></td></tr></table></figure><h3 id="第四阶段over-engineering-发现"><a class="markdownIt-Anchor" href="#第四阶段over-engineering-发现"></a> 第四阶段：Over-engineering 发现</h3><p>修完 Phase 0 后，看清了 Four-Network 本身的问题：</p><p><strong>理由：</strong></p><ul class="lvl-0"><li class="lvl-2"><p>Experience / World / Opinion / Entity 四个 Collection，实际使用中 95% 的查询都是向量相似度搜索</p></li><li class="lvl-2"><p>四路分类的价值在于「不同类型用不同召回策略」，但实现复杂度远超收益</p></li><li class="lvl-2"><p>ChromaDB 的 collection 不是关系型数据库，多类型混写在一个 collection 里完全可行</p></li><li class="lvl-2"><p>每次 Recall 都要从四个 collection 拉数据再合并，延迟翻倍</p></li></ul><p><strong>决策：简化为统一 collection</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">memories_&#123;user_id&#125;_&#123;project&#125;  ← 所有类型的记忆写入同一个 collection</span><br></pre></td></tr></table></figure><p>用 <code>memory_type</code> 字段标记类型，查询时用向量相似度 + metadata 过滤。Wing/Room 维度保留在 MemPalace 三层结构中，不侵入 ChromaDB collection 命名。</p><h3 id="第五阶段layer2-llm-也被干掉了"><a class="markdownIt-Anchor" href="#第五阶段layer2-llm-也被干掉了"></a> 第五阶段：Layer2 LLM 也被干掉了</h3><p>Layer2 原来是 LLM 判断「这条记忆是否值得保留」。</p><p>但实测发现：</p><ul class="lvl-0"><li class="lvl-2"><p>LLM 判断和正则规则的结果高度重合</p></li><li class="lvl-2"><p>多一次 LLM 调用 = 多一次延迟 + 多一次 Token 消耗</p></li><li class="lvl-2"><p>简单置信度阈值过滤效果一样好</p></li></ul><figure class="highlight python"><table><tr><td class="code"><pre><span class="line"><span class="comment"># 改动前</span></span><br><span class="line">result = llm_classify(content)  <span class="comment"># API 调用</span></span><br><span class="line"></span><br><span class="line"><span class="comment"># 改动后</span></span><br><span class="line">confidence = <span class="built_in">sum</span>(keyword_score <span class="keyword">for</span> keyword <span class="keyword">in</span> PATTERNS <span class="keyword">if</span> keyword <span class="keyword">in</span> content)</span><br><span class="line"><span class="keyword">if</span> confidence &gt;= THRESHOLD:</span><br><span class="line">    keep()</span><br></pre></td></tr></table></figure><p>Layer2 现在是纯正则，不再调用任何 LLM。</p><h3 id="最终架构"><a class="markdownIt-Anchor" href="#最终架构"></a> 最终架构</h3><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Turn 结束 → sync_turn()</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">L0 Security    ← 凭证正则过滤（never call LLM）</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">L1 Pattern     ← 双语正则，提取 decision/preference/problem/milestone/emotional/action/constraint</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">L2 Threshold   ← 置信度阈值过滤（已 bypass LLM）</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">L3 Dedup       ← ChromaDB cosine 去重</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">Writer         ← 写入 memories_&#123;user&#125;_&#123;project&#125; + KG triple</span><br></pre></td></tr></table></figure><p><strong>召回链路：</strong></p><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Turn 开始 → on_turn_start()</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">queue_prefetch()  ← 后台预热</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">prefetch()        ← 返回 recall cache</span><br><span class="line">    │</span><br><span class="line">    ▼</span><br><span class="line">system_prompt_block()  ← recall 结果注入 system prompt</span><br></pre></td></tr></table></figure><hr><h2 id="关键教训"><a class="markdownIt-Anchor" href="#关键教训"></a> 关键教训</h2><h3 id="1-两条正交通道不能合并"><a class="markdownIt-Anchor" href="#1-两条正交通道不能合并"></a> 1. 两条正交通道，不能合并</h3><p>Write（提取）和 Recall（召回）是完全独立的功能，compaction 只管理 context window 大小，<strong>不负责记忆持久化</strong>。试图用 compaction 替代记忆写入，是架构错误。</p><h3 id="2-chromadb-单例是基础中的基础"><a class="markdownIt-Anchor" href="#2-chromadb-单例是基础中的基础"></a> 2. ChromaDB 单例是基础中的基础</h3><p>所有涉及持久化的模块，必须共享同一个 ChromaDB client。进程隔离 + 文件系统写入，看起来能工作，实际是未定义行为。</p><h3 id="3-删除比添加更难"><a class="markdownIt-Anchor" href="#3-删除比添加更难"></a> 3. 删除比添加更难</h3><p>最终交付的架构比最初设计少了：</p><ul class="lvl-0"><li class="lvl-2"><p>NarrativeFusion（合并收益低于成本）</p></li><li class="lvl-2"><p>ContentRouter（规则路由替代 LLM 路由）</p></li><li class="lvl-2"><p>OpinionManager（Knowledge Graph predicate override 足够）</p></li><li class="lvl-2"><p>Layer2 LLM（正则阈值效果一样）</p></li></ul><p>删除这些之后，测试从 50 个变成了 141 个，覆盖率反而更高。</p><h3 id="4-prompt-要润色"><a class="markdownIt-Anchor" href="#4-prompt-要润色"></a> 4. Prompt 要润色</h3><p>机械腔的 prompt 会导致模型角色疲劳。润色原则：</p><table><thead><tr><th>原则</th><th>错误示例</th><th>润色后</th></tr></thead><tbody><tr><td>去机械感</td><td>“请基于以上事实给出反思”</td><td>“结合我们之前聊过的内容，你怎么看？”</td></tr><tr><td>口语化</td><td>“Output only the category name”</td><td>“Which bucket fits best?”</td></tr><tr><td>上下文连贯</td><td>“Facts:\n{facts}\nYour response:”</td><td>“Here’s what I recall:\n{recalled_facts_str}\nWhat do I make of this?”</td></tr></tbody></table><p>中英文 prompt 都需要润色，不是只有中文才润色。</p><hr><h2 id="当前状态"><a class="markdownIt-Anchor" href="#当前状态"></a> 当前状态</h2><figure class="highlight plaintext"><table><tr><td class="code"><pre><span class="line">Pytest: 141 passed ✅</span><br><span class="line">Metrics: sync_turn 24次, accepted 6次 (25%)</span><br><span class="line">Behavior: 短对话不提取（正常，内容不够）</span><br><span class="line">         长对话(&gt;400 tokens)提取率显著提高</span><br></pre></td></tr></table></figure><p>25% 的 extraction rate 不是「75%失败」——而是对话中大多数内容（简单确认、闲聊、礼貌性回复）本来就不值得记住。</p><hr><h2 id="引用"><a class="markdownIt-Anchor" href="#引用"></a> 引用</h2><ul class="lvl-0"><li class="lvl-2"><p><a href="https://arxiv.org/abs/2512.12818v1">Hindsight is 20/20: Building Agent Memory that Retains, Recalls, and Reflects</a> — arXiv:2512.12818v1, December 2025</p></li><li class="lvl-2"><p><a href="https://github.com">MemPalace</a> — Hermes Agent 记忆存储基础设施</p></li><li class="lvl-2"><p><a href="https://github.com/stitionai/humanizer">Humanizer</a> — Prompt 润色工具</p></li><li class="lvl-2"><p><a href="ssh://gitlab.ha-guoli.cn:2222/yqwoe/mem-palace-provider.git">memory-filtration-layer 项目地址</a></p></li></ul><hr><p><em>作者：nil 的助手 [赫妹🤡]</em></p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    <category term="AI" scheme="https://sudo.rm-rf.ink/categories/AI/"/>
    
    
    <category term="AI" scheme="https://sudo.rm-rf.ink/tags/AI/"/>
    
    <category term="Agent" scheme="https://sudo.rm-rf.ink/tags/Agent/"/>
    
    <category term="Memory" scheme="https://sudo.rm-rf.ink/tags/Memory/"/>
    
    <category term="Hermes" scheme="https://sudo.rm-rf.ink/tags/Hermes/"/>
    
    <category term="Python" scheme="https://sudo.rm-rf.ink/tags/Python/"/>
    
    <category term="MemPalace" scheme="https://sudo.rm-rf.ink/tags/MemPalace/"/>
    
  </entry>
  
  <entry>
    <title>从 OpenClaw 迁移到 Hermes：我的实战记录</title>
    <link href="https://sudo.rm-rf.ink/%E5%B7%A5%E4%BD%9C/5f9c1a2b.html"/>
    <id>https://sudo.rm-rf.ink/%E5%B7%A5%E4%BD%9C/5f9c1a2b.html</id>
    <published>2026-05-01T14:30:00.000Z</published>
    <updated>2026-10-17T14:19:03.720Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="为什么要换"><a class="markdownIt-Anchor" href="#为什么要换"></a> 为什么要换</h2><p>用 OpenClaw 一段时间后，有几个问题实在忍不了。</p><p>任务一多，换个 session 上下文就没了。调试时想找之前的思路，找不到；换个方式问同样的问题，AI 给的答案和上次完全不一样。记忆管理基本靠手功——key 命名看心情，同一条信息被存三五份是常事。</p><p>换 Hermes 的原因就一个：<strong>它真的能记住上下文，跨 session 不会断。</strong></p><h2 id="迁移了什么"><a class="markdownIt-Anchor" href="#迁移了什么"></a> 迁移了什么</h2><h3 id="记忆文件"><a class="markdownIt-Anchor" href="#记忆文件"></a> 记忆文件</h3><p>OpenClaw workspace 里有几个核心文件：</p><ul class="lvl-0"><li class="lvl-2"><p><code>SOUL.md</code> — 人设/性格设定</p></li><li class="lvl-2"><p><code>AGENTS.md</code> — 工作指令集</p></li><li class="lvl-2"><p><code>MEMORY.md</code> — 长期记忆</p></li><li class="lvl-2"><p><code>USER.md</code> — 用户信息</p></li></ul><p>迁移之后分散到了不同位置：<code>SOUL.md</code> 进了 <code>~/.hermes/SOUL.md</code>，<code>AGENTS.md</code> 要手动指定目标路径，<code>MEMORY.md</code> 和 <code>USER.md</code> 解析后写入了 <code>~/.hermes/memories/</code>。</p><p><code>hermes claw migrate</code> 可以自动迁移，但默认是 <code>--dry-run</code> 只预览不执行，别漏了这个参数。</p><h3 id="skill"><a class="markdownIt-Anchor" href="#skill"></a> Skill</h3><p>OpenClaw 的 skill 和 Hermes 不完全兼容。迁移之后要重装，部分自定义 skill 要么重新写，要么直接放弃。</p><h3 id="配置"><a class="markdownIt-Anchor" href="#配置"></a> 配置</h3><p>OpenClaw 的 <code>openclaw.json</code> 里存储了 API keys 和各种配置项。迁移时这些配置默认会保留，但建议迁移完之后仔细核对一遍，特别是 API 相关的配置。</p><h2 id="hermes-的记忆系统是什么样的"><a class="markdownIt-Anchor" href="#hermes-的记忆系统是什么样的"></a> Hermes 的记忆系统是什么样的</h2><p>Hermes 内置记忆分三层：</p><p><strong>短期记忆</strong> 通过 system prompt 注入，受 token 限制，大概 2200 字符。<strong>用户 Profile</strong> 存在 <code>~/.hermes/memories/USER.md</code>，以 <code>§</code> 分隔条目。<strong>长期记忆</strong>是内置记忆的主力，key-value 读写，支持跨 session 持久化。</p><p>比 OpenClaw 强不少，但还有问题：读取依赖精确 key，自然语言描述一变就匹配不上。所以我又接了 MemPalace 做语义记忆层，专门解决这个问题。</p><h2 id="迁移后遇到的问题"><a class="markdownIt-Anchor" href="#迁移后遇到的问题"></a> 迁移后遇到的问题</h2><p><strong>记忆丢失</strong>是第一个坎。新系统对之前的项目上下文一无所知，要手动把 <a href="http://MEMORY.md">MEMORY.md</a> 迁移过来，重新建立记忆规范。这个工作量不小，之前积累的那些碎片知识大部分要重新喂一遍。</p><p><strong>Skill 兼容性</strong>是第二个。有些在 OpenClaw 上跑得好好的自定义 skill，Hermes 上面直接废掉了。不是语法问题，是整个运行环境不一样。</p><p><strong>配置不一致</strong>是第三个。迁移工具保留了大部分配置，但路径相关的设置还是要自己过一遍。</p><h2 id="两个平台的记忆系统对比"><a class="markdownIt-Anchor" href="#两个平台的记忆系统对比"></a> 两个平台的记忆系统对比</h2><table><thead><tr><th></th><th>OpenClaw</th><th>Hermes</th></tr></thead><tbody><tr><td>记忆持久化</td><td>文件为主，session 之间不共享</td><td>key-value，支持跨 session</td></tr><tr><td>语义搜索</td><td>无</td><td>无（需借助外部插件）</td></tr><tr><td>记忆容量</td><td>受文件大小限制</td><td>受 token 限制，约 2200 字符</td></tr><tr><td>迁移工具</td><td><code>hermes claw migrate</code></td><td>内置，支持预览</td></tr><tr><td>Skill 生态</td><td>独立体系</td><td>Hermes 专属</td></tr></tbody></table><h2 id="迁移流程图"><a class="markdownIt-Anchor" href="#迁移流程图"></a> 迁移流程图</h2><pre class="mermaid">graph LR    A[OpenClaw workspace] --> B{hermes claw migrate --dry-run}    B -->|预览| C[确认文件清单]    C --> D[执行迁移]    D --> E[SOUL.md<br>~/.hermes/SOUL.md]    D --> F[AGENTS.md<br>指定目标路径]    D --> G[MEMORY.md + USER.md<br>~/.hermes/memories/]    D --> H[openclaw.json<br>API keys 保留]    E --> I[核对配置]    F --> I    G --> I    H --> I    I --> J[接 MemPalace<br>语义记忆层]    J --> K[完成]</pre><h2 id="踩过的坑"><a class="markdownIt-Anchor" href="#踩过的坑"></a> 踩过的坑</h2><p><strong>坑一：<a href="http://AGENTS.md">AGENTS.md</a> 没指定路径就被跳过了。</strong> 第一次跑迁移命令，没加 <code>--workspace-target</code>，<a href="http://AGENTS.md">AGENTS.md</a> 直接被跳过。直到发现 Agent 行为和之前不一样，才发现指令集根本没迁移过来。</p><p><strong>坑二：Skill 迁移完之后路径全变了。</strong> OpenClaw 的 skill 放在 <code>~/.openclaw/skills/</code>，Hermes 在 <code>~/.hermes/skills/</code>。脚本不负责迁移 skill，重新安装花了不少时间。</p><p><strong>坑三：Session 切换后记忆恢复慢。</strong> Hermes 虽然支持跨 session 记忆，但刚迁移完 Agent 对老项目完全没上下文。需要主动把之前 <a href="http://MEMORY.md">MEMORY.md</a> 的内容通过 MemPalace 重建一遍，不是自动的。</p><h2 id="总结"><a class="markdownIt-Anchor" href="#总结"></a> 总结</h2><p>从 OpenClaw 到 Hermes，最大的收获是记忆持久化和 session 管理。对需要长期跟踪任务、跨会话保持上下文的人来说，这步迁移值。但 Hermes 内置记忆仍有语义匹配的短板，建议配合 MemPalace 使用。</p><p>迁移本身不算复杂，<code>hermes claw migrate</code> 能处理大部分文件迁移工作，主要工作量在 skill 重装和配置核对。</p>]]></content>
    
    
    <summary type="html">从 OpenClaw 换到 Hermes Agent，迁移过程中遇到的问题、解决方案，以及两个平台记忆系统的真实对比</summary>
    
    
    
    <category term="工作" scheme="https://sudo.rm-rf.ink/categories/%E5%B7%A5%E4%BD%9C/"/>
    
    
    <category term="AI" scheme="https://sudo.rm-rf.ink/tags/AI/"/>
    
    <category term="Hermes Agent" scheme="https://sudo.rm-rf.ink/tags/Hermes-Agent/"/>
    
    <category term="OpenClaw" scheme="https://sudo.rm-rf.ink/tags/OpenClaw/"/>
    
    <category term="迁移" scheme="https://sudo.rm-rf.ink/tags/%E8%BF%81%E7%A7%BB/"/>
    
    <category term="记忆系统" scheme="https://sudo.rm-rf.ink/tags/%E8%AE%B0%E5%BF%86%E7%B3%BB%E7%BB%9F/"/>
    
  </entry>
  
  <entry>
    <title>改造 Hermes Agent 记忆系统：基于 MemPalace 的实践</title>
    <link href="https://sudo.rm-rf.ink/%E5%B7%A5%E4%BD%9C/38291047.html"/>
    <id>https://sudo.rm-rf.ink/%E5%B7%A5%E4%BD%9C/38291047.html</id>
    <published>2026-05-01T10:31:00.000Z</published>
    <updated>2026-10-17T14:19:03.721Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><h2 id="背景hermes-内置记忆有什么问题"><a class="markdownIt-Anchor" href="#背景hermes-内置记忆有什么问题"></a> 背景：Hermes 内置记忆有什么问题</h2><p>Hermes Agent 有一套内置的记忆系统，基于简单 key-value 存储。每次写入时指定一个 key，读取时用同样的 key 把它取出来。</p><p>实际用下来，问题很具体：</p><ul class="lvl-0"><li class="lvl-2"><p><strong>表达一变就找不到</strong>：上次记住的是&quot;截图工具&quot;，用户说&quot;微信截图&quot;就匹配不上</p></li><li class="lvl-2"><p><strong>key 不一致导致重复存储</strong>：同一个信息有人用 <code>project-path</code> 做 key，有人用 <code>paths</code> 做 key，查的时候不知道用哪个</p></li><li class="lvl-2"><p><strong>没有语义理解</strong>：搜&quot;认证失败&quot;搜不到&quot;401错误&quot;，因为字符串上完全不相关</p></li><li class="lvl-2"><p><strong>session 结束后基本清零</strong>：换一个 session，上下文全没了，除非手动迁移 memory 内容</p></li></ul><p>核心问题是：<strong>读取依赖精确匹配，而自然语言本身就是模糊的。</strong></p><h2 id="改造思路"><a class="markdownIt-Anchor" href="#改造思路"></a> 改造思路</h2><p>Hermes Agent 支持 MemoryProvider 插件接口，外部可以接入不同的记忆后端。目标是接一个支持语义搜索的记忆系统——写入时按原样存，读取时按意思搜。</p><p>选的是 MemPalace，本地语义记忆库，ChromaDB 做向量索引，SQLite 做知识图谱和结构化存储。架构上分三层：</p><pre class="mermaid">graph TD    subgraph 存储层        C[ChromaDB<br>语义向量索引]        G[SQLite<br>知识图谱 + 结构化]    end        subgraph 组织层        W[Wing<br>项目 / 领域]        R[Room<br>general / diary / decisions]        D[Drawer<br>具体记忆卡片]    end        subgraph 接口层        S[mempalace_search<br>语义搜索]        K[mempalace_kg_query<br>知识图谱查询]        A[mempalace_add_drawer<br>写入记忆]        W2[mempalace_diary_write<br>日记记录]    end        C --> S    C --> D    G --> K    G --> W    W --> R    R --> D    A --> D    W2 --> R</pre><p>接入 Hermes 之后，记忆读写流程变成了：</p><pre class="mermaid">sequenceDiagram    participant U as 用户    participant H as Hermes    participant MP as MemPalace    participant DB as ChromaDB + SQLite    U->>H: 发送消息    H->>MP: mempalace_search 语义搜索    MP->>DB: 向量相似度查询    DB-->>MP: 候选记忆列表    MP-->>H: 按相关度排序    H->>MP: mempalace_kg_query 实体关系查询    DB-->>H: 关联事实链    H->>H: 组装上下文    H-->>U: 携带记忆回复    Note over H,U: 后续写入    H->>MP: mempalace_add_drawer 新记忆    MP->>DB: 存入向量库 + 图数据库    DB-->>MP: 确认写入    MP-->>H: 完成</pre><h2 id="原则"><a class="markdownIt-Anchor" href="#原则"></a> 原则</h2><p>改造后 Hermes 的记忆规则：</p><ol><li class="lvl-3"><p><strong>读取全走 MemPalace</strong> — 每次收到消息，先搜索相关记忆，不依赖内置 key-value</p></li><li class="lvl-3"><p><strong>写入全走 MemPalace</strong> — 项目信息、用户偏好、待办事项，统统写入 MemPalace</p></li><li class="lvl-3"><p><strong>cronjob 自动归档</strong> — 每 30 分钟自动把 session 进展写入 diary room，防止断电丢失</p></li></ol><p>内置 memory 工具仅保留一项职责：存储 MemPalace 的路径配置。</p><h2 id="踩过的坑"><a class="markdownIt-Anchor" href="#踩过的坑"></a> 踩过的坑</h2><p><strong>1. 内置 memory 和 MemPalace 并存时的优先级</strong></p><p>刚接进去时，两者都在，导致同一个信息存了两份，读取时有时候命中内置 memory，有时候命中 MemPalace，结果不稳定。解决办法是彻底禁用内置 memory 的写入通道，只保留读取用于兼容旧逻辑。</p><p><strong>2. embedding 模型选择影响搜索质量</strong></p><p>MemPalace 的搜索质量依赖 embedding 模型。最早用的模型对中文语义理解偏弱，&quot;登录失败&quot;和&quot;认证异常&quot;这类近义表述匹配不上。换了支持中文的 embedding 模型之后，召回率明显提升。</p><p><strong>3. 知识图谱和向量搜索的分工</strong></p><p>最初想把所有关系都塞进知识图谱，结果图数据膨胀得很快，查询延迟也上来了。后来把频繁更新、时序敏感的（如 session 进度）放在向量库，把稳定事实（项目路径、技术栈、用户偏好）放在知识图谱，分开查询再合并结果。</p><p><strong>4. session 边界和记忆持久化的平衡</strong></p><p>每次 session 结束，Hermes 内部状态会清理，但 MemPalace 是持久化的。最早没做 session 隔离，同一个 Wing 下的记忆被多个 session 交叉污染。后来每个 session 的进展单独写进 diary room，稳定的项目信息写进 general room，泾渭分明。</p><h2 id="效果"><a class="markdownIt-Anchor" href="#效果"></a> 效果</h2><p>换 session 之后，上下文还在。&quot;之前做到哪&quot;能答上来，&quot;那个 401 错误怎么处理的&quot;能找出来，&quot;用户偏好截图要整屏不要只截二维码&quot;不会被忘掉。</p><p>搜&quot;认证失败&quot;，能找到&quot;登录异常&quot;、“401”、&quot;token 过期&quot;相关内容，不用猜 key 是什么。</p><h2 id="注意"><a class="markdownIt-Anchor" href="#注意"></a> 注意</h2><ul class="lvl-0"><li class="lvl-2"><p>MemPalace 是本地存储，多设备使用需要自己处理同步</p></li><li class="lvl-2"><p>embedding 模型决定搜索上限，选型要结合自己的语料</p></li><li class="lvl-2"><p>长期运行后 sqlite 会膨胀，需要定期归档清理</p></li></ul><h2 id="改造前后对比"><a class="markdownIt-Anchor" href="#改造前后对比"></a> 改造前后对比</h2><table><thead><tr><th></th><th>内置 key-value 记忆</th><th>MemPalace 语义记忆</th></tr></thead><tbody><tr><td><strong>读取方式</strong></td><td>精确 key 匹配</td><td>语义向量相似度</td></tr><tr><td><strong>检索能力</strong></td><td>必须知道当初用什么 key</td><td>描述需求即可，意思对就能找到</td></tr><tr><td><strong>跨 session</strong></td><td>完全丢失</td><td>持久化，换 session 上下文延续</td></tr><tr><td><strong>中文语义</strong></td><td>不支持</td><td>支持中文 embedding</td></tr><tr><td><strong>知识关联</strong></td><td>无</td><td>知识图谱支持实体关系查询</td></tr><tr><td><strong>存储结构</strong></td><td>扁平的 key-value</td><td>Wings → Rooms → Drawers 分层</td></tr><tr><td><strong>外部依赖</strong></td><td>无</td><td>引入 MemPalace + ChromaDB</td></tr><tr><td><strong>运维成本</strong></td><td>无</td><td>需维护本地 sqlite 和向量库</td></tr></tbody></table><p>结论：代价是多引入一个本地依赖，换来的是检索可靠性、跨 session 记忆延续、中文语义理解。值不值看场景——偶尔用一次的 agent，key-value 够用；每天几十个任务、跨多项目的，语义记忆是刚需。</p><p>如果你的 Agent 也面临同样问题，可以从 MemoryProvider 接口入手，接入任意支持向量检索的后端都行，不一定是 MemPalace。</p>]]></content>
    
    
    <summary type="html">围绕 Hermes Agent 内置记忆的局限性，讲解如何用 MemPalace 语义记忆层替换改造</summary>
    
    
    
    <category term="工作" scheme="https://sudo.rm-rf.ink/categories/%E5%B7%A5%E4%BD%9C/"/>
    
    
    <category term="AI" scheme="https://sudo.rm-rf.ink/tags/AI/"/>
    
    <category term="MemPalace" scheme="https://sudo.rm-rf.ink/tags/MemPalace/"/>
    
    <category term="Hermes Agent" scheme="https://sudo.rm-rf.ink/tags/Hermes-Agent/"/>
    
    <category term="记忆系统" scheme="https://sudo.rm-rf.ink/tags/%E8%AE%B0%E5%BF%86%E7%B3%BB%E7%BB%9F/"/>
    
  </entry>
  
  <entry>
    <title>Hello Hermes Agent</title>
    <link href="https://sudo.rm-rf.ink/uncategorized/96208538.html"/>
    <id>https://sudo.rm-rf.ink/uncategorized/96208538.html</id>
    <published>2026-04-27T15:32:00.000Z</published>
    <updated>2026-10-17T14:19:03.719Z</updated>
    
    <content type="html"><![CDATA[<link rel="stylesheet" class="aplayer-secondary-style-marker" href="/assets/css/APlayer.min.css"><script src="/assets/js/APlayer.min.js" class="aplayer-secondary-script-marker"></script><script class="meting-secondary-script-marker" src="/assets/js/Meting.min.js"></script><p>Hello Hermes Agent</p>]]></content>
    
    
      
      
    <summary type="html">&lt;link rel=&quot;stylesheet&quot; class=&quot;aplayer-secondary-style-marker&quot; href=&quot;/assets/css/APlayer.min.css&quot;&gt;&lt;script src=&quot;/assets/js/APlayer.min.js&quot; cla</summary>
      
    
    
    
    
  </entry>
  
  <entry>
    <title>daily-news-2026-04-25</title>
    <link href="https://sudo.rm-rf.ink/daily-news/f135226f.html"/>
    <id>https://sudo.rm-rf.ink/daily-news/f135226f.html</id>
    <published>2026-04-24T23:30:00.000Z</published>
    <updated>2026-10-17T14:19:03.719Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="糟糕！密码错误了." data-whm="抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.">  <script id="hbeData" type="hbeData" data-hmacdigest="5cbd9de4dc0c3501b2c91719eb87349341d43e4d1058063888063c43432101c6">89730641517f40fad9fb11d8e72e80bf06c939f51e30e9318348e356012ae93802623e7e7460999b3f7e8ba755c0d29599ae788f4544e2d8c77419f7d87eed449b7d6813e006fe13f540d0d66182fef8c50ebd9d211e40be206647f7e00fd685b86946fa705ad4651c18ab2a320b4a69699ab0980126725a179cb3c64faa8f2509c4dddd4adebe64d5cd4207643b74cb951ade33149cd6cd51d669bce78e7df026fe1abb945332d7c0a9e2f0c8bc51b46617f418a10ed53af1ad8a5de091ee7b9161a36c5a28454693301b254151eaad572fe3718480827cbcb2b29cbbe2dd52029dfad21eac08db5c648820175597abb4a0c5ff69693595d1d3fe51de4c4f9c4194b77a63e7b407560fa73f56328d3cbee56843f26b4272acb3eeeaa0e46d01c1c94262bdc858905a4d80759515d1db386e90b995b088a668ce7196174ca5a1b9aba2fcb7975969cbd89e81d1d660e0713c098d776731fd8d15b54c7876ea503cbfbcad334fe1843e2dc94440380ae84d95a5d6f8f470f5e5e38194a5722d941a5695be5e02cf91f1b464206a372a3bf7f85681439a7be1f348cad1c7a59b72575f459e5575173933c978e1f9468637801cd2ecc88c542c3bf74f7984ed052a7e1f858719ef375ded80fdb608963fccdfdf35b5b73e174248cdea419fe4af29bf42779957ee2676458b45f6d32e46f2ae02af8a9b7cd41264e3e029dd6897628424041b07500c135840024eae37e0c2137e3a5155437364ae308c5c39140e36b2afc04c9eec03c2f97b2d91c83cf959582f8399ef4ce810df706ff7b7aa4770e22fc8b4f97fa8eab5778c4aa64fd17399d02f8f14a387d3224e0899380a27cc32933e90ac9e76e468edc8586648c2bf2f2b7e49d885122c2578a8050fc5e7ac431d6aca41092537ee2c815266ff093c737cc81cfff7252fcb4671951a05f157469629df74013c4aae3ffc9676927fac7e0cc157c22af97916efccdfea8b1f9e8129277b6a16f8a8801e305ac01b78bfe5a9f3be076ac15e47f8bf070e774537d3467e06ece04f3bcd9cabdc9f2d7e5986f886ccbad9ca116569270165cfa18d262130aa9395b06a07fa005a2241c9971c6d17b2e38fca144ad59842017ab13f64dffa53f424afccfa78e155f09f74572f34149df53be7a468706a899c91ed3f6894126137086d87882685683e6604cc231b359d28af2ce6b68b23e50b5d547d0ddb2cc0ef4a579d48a5749ef2cb71b3e2684e84da175a45bd2f070756e88ea5d2819716ca60ac6259ed3ae6f742beea2985f7ec0abc003e55484eb82e0f029fb43613a204f1df283cd88c29829fb3286f5f760c503656a982f469d829b57c7331f8615ccf2421f6d69e362559ab0ab9c26e0db9fdc5b60169f4603f89f3368464b214b2626fbc6c10068c7cf658288e680007f5cd3fa57205a4da44a6db51a7d10b1877c4c18cec4b0e1c80a1b88e3bb75ccee12f4fd580b27a0fd35ebe3d146b6cd2c840f861cf1079f6fda348e6f67e594a3788c924509d39cafcac830e701bb2b9dea32fe14c428e4f021dfd27bdfa216c4f99ba7cbacb42fc79156262a1de0f295ad09574e898bb20939969eb1c55c9921e93d0c5360a23c33a5d0cfbfae3d7610b1964936aa4a71017b7e84ee7c8486640b5deb468ee796da94689a1790e436c767cfe6dda78529cdd95388887adb6c60b71eb19cfce44c294c25167b36bb9e538872aeda496ed09920aceb9c02e6ae6ebcdd327b1eb21da43460f4da18e3eecf8ee9ed5239f039c5aea889fc5bbf0de5d8bcb781b2ebaaebb045a9727048f47d4aa42859f4888221f2bb6c7a57074d28f8c1ee9f709fc251a7992e9b2178b0f10b9df6d3c8146efcc6b1e2008f6a68531bad4d1f23e4c2c9d525cad82c2f786106926a0e7b3be3241b752cb55e38f007287781694a0d72fcf93eb93d481a501956729ba49c866b1ed193411e990566bd85d1548789122f0272a8ff6ed42ba90f8234fb3a572f4181530b4ff94bca091d02361a87ba62c3e7895afe68b983fc67d8884d8a5ee82ff5d599bf5e05f84b15959f1d82a47b49a43142a985dce74631ea4789a24dcf8530b9187be4dd8edcf7a1db4fc994134e972e0e3967ae8b1f187f6af6f87fa8f3d4f06d0ec588bbcb6dd26859105286f71db88bea24ec7791c42e4d17903b72b0b311c5631cff64616a90c1724285b76d61ee3e91638e9fed7e96efc6d352f8e13027296fbf99fb6a82bae3853d169fb8dc774300882da9c8b2f906adce800c0e27f787a5ebda05df3245ea768571b7f4c27170189604026ed50f34f365b6e7307c82521aa866051fffd33bbfa445b807b77da387370216c6783b76e7646fab9735cb78491d5549b3930ecb8c584b11b705741a87557e54d655c5030604f748e6096c510f376f95e7d6344b45de6cf998b00b2f5d57a5387145033d6a70aa280634feaa22551f2f40d2a0e522bad8e280acdc96f4bb34a9c7e384ead0d21137aa781b48f8d90bb99f8137a6743402c50d377b0c851445a4beab3e9c99d57a04da91710afd8f6eec0906d90b79009fd92d322d01e76da1c84d8afff216c6c89f2189347b323524967a112e9a23f321eab7a2cd81c5e0c9b9a6b83a2568b17086a1fdafa2a2f3caf7b37d216fc400144268d876e0cb7123af8715d4409d7edb059da1c4c4f5d5daf3e41a80fe0cdddf90d10dfd1ec641d85e2040e5c0adbbaf547b6c1dc2877a2c4968f550c0fd2cbeaf80c9a9253a2d43d07fd28d38bc455c25e99b770e1281099e43bc89dc2bad9f9dce72c9b9e06c7781556a61d0d0d63dcb8cc245dca2e5ec47cd48e91a0298dcf1ca5646cbe6c5ca06c92f4ce14de7ac0ede021ca7f9424f9b5da817930745e6357f35e8c41e2818e88c4be9c28e48cd9f4706e374adda6b7dfa8887138fb891a50f27bb2ab2f5204cae1bcfac4428dec9aeaf95fff7c52e4339b56d97f3f74332b9458bbd175f06cb61262501fa91fa1cb4c69f19c5acfae0d98f00eebe486f07404fba9e39c9b7e4a53493f676ac90e4d736660586ee7ec5a91ddb494576baf7d45bb2379379fb8e45c8dab8cb7c9a44f354e2d936d5511285b5372acd6ce43c3cbfb53fb10fe5be5a9860da4564b0f2b093c598f8826da28412b0f2f0e97b5c3a6233120708df5f7cf5a5d5d7b33054db860bcd27906301ddcd69aa7d16b389fc5565d39acdb2019435789558aad57d5fd8d97fe0ca1472c8e1bb4ceab4adc83f6372ef8e4fd7852b2d4bc6e8076620f77e7c37ca999031171a22577126c9cb628e658d9899254c45f496aa857436c49d0ded5654b36ea5a104b38fc59d5d884a3d0cc3b964d6bd646cd5cdcd78d52778ad927ad46c492c7277974caaa3b91dcfd03831f27aa621bf7b18add4d5b2ce603ed8f8c31a48fc7df64a203fce5ba5bad032943a32bb33e1dc2bc8744e1a9b7626a210a9df0c730fd07d2fd4516bba29b23ef07773d3b51f75ca95ee829dc8cc3852ae56968f8c82f9a65a9bb0eb67113c8d6c1a083ae0debb3d8d6e43b9aaede694ec2dba5248fd07b5989e61ab793e1ad549bfef018de1cba7951337014131f52bf1148cbd5fe5f6b38eeda956b7a94eb46ca7080aa509b326d89b6f4385469ee1c2be98a34aa5073a317e253f607a6f8d88c3e0261e609db0cd12b1ee41728e7a8313035c14dec7172a690a7dc2c7d2f04be1c052b1277bf5d6bdcc2f2f5cd44767140bff774ada618021a06bad294c809cc4a5c77c334d4b760dd0b79c93b13e7277d11fb007abd09091ab79f552185f7959a02aa6cbbcf221495b80cc10d54aa0106f6a0ae199a2e8e08956c7fc04df5123e43ae2008297a8da333311fc287270e475413ced86d9d380e1eb0dd7621bdee201a420d081f270fe43212fa09a288abab2f1a69c0bf4e23237f59f5e01e8aa8b17bdd4b3d97d3571e0b723237c5c6dd54e3b3e1809068e50c16539a34f8c2c60272c2cf6124c56d1a007877b272afb4edce093fa2eba940f02ac4daa9e694b90849dde72e23f0c5ed8dbeb310bda64c0d26b54e16b626639cb643fccba406792c2121bc83e0db0fda513714ca7e6feec366d5204945724a9549bb89d0aea64df80bc4c680ae8f43a68bace0d4f4a1de22629ddc8a1e16fb3fce853551902e737f383f4a53638b89d17c64283075cf01ea15fe18598cddfafb819e89348d8f69c17df657a0313cca1e009e6c2f62690ab014e716f1d689d753ae0bae8ba723815b17a797c74b0b108462a29dc81788e6f2496e9e3326cf8c335d286786742f4392d378234da067e439c648821f8ba08b6f9a57a30f72c87d39bce94e84320f757617358de936d5dbb39293ff8e89e96447e9442a6ea4398131e91bcf07ceebe0028fd26f4189dc907a7cf9b941b0cb099b16479803db82098f8dc66e81676683ea8bf113db3433bbe349dd9ad81261fb801b25df310f1e74af0634822bf5ced7e6fa499534a96605edcb2df947b61527cc4d976dd5c6aac8f5ec809eb80d6405ac878c14936d715fe31a977c67a5a4048ce8f880ce7548aa0fe3c4ad41c0f074562d64ece95e6aec0d2b2e2ee192754f6c5800e48c394fe10d7565d7763abac265bc324201917c4cfcc8bb123f98d9c578e9efdec8f748d9d448ffb1c645534fbb6f1c7e42d5af7f3187daa6fc0a094fe929d4356af4ace6e5f98cba494944db7b863ceeec0af51ca83d17bbc01bdb349830a58a7e019ef419f72523bf4b9947ac90d9bf220808bb94158888adf69194a736f6c92528f4a49986999d4a9906aafaa9</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-wave">      <input class="hbe hbe-input-field hbe-input-field-wave" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-wave" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-wave">输入密码查看内容</span>      </label>      <svg class="hbe hbe-graphic hbe-graphic-wave" width="300%" height="100%" viewBox="0 0 1200 60" preserveAspectRatio="none">        <path d="M0,56.5c0,0,298.666,0,399.333,0C448.336,56.5,513.994,46,597,46c77.327,0,135,10.5,200.999,10.5c95.996,0,402.001,0,402.001,0"></path>      </svg>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
    
    
    <summary type="html">文章加密了，需要输入密码查看</summary>
    
    
    
    <category term="daily news" scheme="https://sudo.rm-rf.ink/categories/daily-news/"/>
    
    
  </entry>
  
  <entry>
    <title>OpenClaw 0</title>
    <link href="https://sudo.rm-rf.ink/%E5%B7%A5%E4%BD%9C/8b6d76da.html"/>
    <id>https://sudo.rm-rf.ink/%E5%B7%A5%E4%BD%9C/8b6d76da.html</id>
    <published>2026-03-30T09:10:57.000Z</published>
    <updated>2026-10-17T14:19:03.719Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="糟糕！密码错误了." data-whm="抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.">  <script id="hbeData" type="hbeData" data-hmacdigest="d3d974b29008aff606283f0e4978d7bf8e58543d1d3a787740a0ae1e7049f1ed">89730641517f40fad9fb11d8e72e80bf06c939f51e30e9318348e356012ae93802623e7e7460999b3f7e8ba755c0d29599ae788f4544e2d8c77419f7d87eed449b7d6813e006fe13f540d0d66182fef8c50ebd9d211e40be206647f7e00fd685b86946fa705ad4651c18ab2a320b4a69699ab0980126725a179cb3c64faa8f2509c4dddd4adebe64d5cd4207643b74cb951ade33149cd6cd51d669bce78e7df026fe1abb945332d7c0a9e2f0c8bc51b46617f418a10ed53af1ad8a5de091ee7b9161a36c5a28454693301b254151eaad572fe3718480827cbcb2b29cbbe2dd52029dfad21eac08db5c648820175597abb4a0c5ff69693595d1d3fe51de4c4f9c4194b77a63e7b407560fa73f56328d3cbee56843f26b4272acb3eeeaa0e46d01c1c94262bdc858905a4d80759515d1db386e90b995b088a668ce7196174ca5a1b9fde0d6c59e81ace0635b7e811b6eb4</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-wave">      <input class="hbe hbe-input-field hbe-input-field-wave" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-wave" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-wave">输入密码查看内容</span>      </label>      <svg class="hbe hbe-graphic hbe-graphic-wave" width="300%" height="100%" viewBox="0 0 1200 60" preserveAspectRatio="none">        <path d="M0,56.5c0,0,298.666,0,399.333,0C448.336,56.5,513.994,46,597,46c77.327,0,135,10.5,200.999,10.5c95.996,0,402.001,0,402.001,0"></path>      </svg>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
    
    
    <summary type="html">文章加密了，需要输入密码查看</summary>
    
    
    
    <category term="工作" scheme="https://sudo.rm-rf.ink/categories/%E5%B7%A5%E4%BD%9C/"/>
    
    
  </entry>
  
  <entry>
    <title>redis主从-哨兵模式</title>
    <link href="https://sudo.rm-rf.ink/%E5%B7%A5%E4%BD%9C/8f0491a1.html"/>
    <id>https://sudo.rm-rf.ink/%E5%B7%A5%E4%BD%9C/8f0491a1.html</id>
    <published>2024-03-25T09:56:30.000Z</published>
    <updated>2026-10-17T14:19:03.720Z</updated>
    
    <content type="html"><![CDATA[<div class="hbe hbe-container" id="hexo-blog-encrypt" data-wpm="糟糕！密码错误了." data-whm="抱歉, 这个文章不能被校验, 不过您还是能看看解密后的内容.">  <script id="hbeData" type="hbeData" data-hmacdigest="d3d974b29008aff606283f0e4978d7bf8e58543d1d3a787740a0ae1e7049f1ed">89730641517f40fad9fb11d8e72e80bf06c939f51e30e9318348e356012ae93802623e7e7460999b3f7e8ba755c0d29599ae788f4544e2d8c77419f7d87eed449b7d6813e006fe13f540d0d66182fef8c50ebd9d211e40be206647f7e00fd685b86946fa705ad4651c18ab2a320b4a69699ab0980126725a179cb3c64faa8f2509c4dddd4adebe64d5cd4207643b74cb951ade33149cd6cd51d669bce78e7df026fe1abb945332d7c0a9e2f0c8bc51b46617f418a10ed53af1ad8a5de091ee7b9161a36c5a28454693301b254151eaad572fe3718480827cbcb2b29cbbe2dd52029dfad21eac08db5c648820175597abb4a0c5ff69693595d1d3fe51de4c4f9c4194b77a63e7b407560fa73f56328d3cbee56843f26b4272acb3eeeaa0e46d01c1c94262bdc858905a4d80759515d1db386e90b995b088a668ce7196174ca5a1b9fde0d6c59e81ace0635b7e811b6eb4</script>  <div class="hbe hbe-content">    <div class="hbe hbe-input hbe-input-wave">      <input class="hbe hbe-input-field hbe-input-field-wave" type="password" id="hbePass">      <label class="hbe hbe-input-label hbe-input-label-wave" for="hbePass">        <span class="hbe hbe-input-label-content hbe-input-label-content-wave">输入密码查看内容</span>      </label>      <svg class="hbe hbe-graphic hbe-graphic-wave" width="300%" height="100%" viewBox="0 0 1200 60" preserveAspectRatio="none">        <path d="M0,56.5c0,0,298.666,0,399.333,0C448.336,56.5,513.994,46,597,46c77.327,0,135,10.5,200.999,10.5c95.996,0,402.001,0,402.001,0"></path>      </svg>    </div>  </div></div><script data-pjax src="/lib/hbe.js"></script><link href="/css/hbe.style.css" rel="stylesheet" type="text/css">]]></content>
    
    
    <summary type="html">文章加密了，需要输入密码查看</summary>
    
    
    
    <category term="工作" scheme="https://sudo.rm-rf.ink/categories/%E5%B7%A5%E4%BD%9C/"/>
    
    
  </entry>
  
</feed>
