让AI Agent拥有“会变形的心”:SOUL.md动态配置详解

发布时间:2026/10/20 18:35:33
让AI Agent拥有“会变形的心”:SOUL.md动态配置详解
去年我做个人AI助理时踩过最大的坑不是模型选型而是配置文件。你给它写一页人设它就永远活在那页人设里你给它接一个记忆库它也只是把消息扔进向量堆里捞来捞去。后来我看到了一个实验性框架 OpenClaw里面有一个叫 SOUL.md 的文件名字很中二但机制是真的有意思——它让 AI 学会写“自传”然后把自传沉淀成一个“会变形的心”。这篇文章不讲大道理就拆两件事SOUL.md 为什么能像一颗心一样跳动变化以及如何在你自己的 Agent 项目里实现这套机制。适合正在搭个人助理、角色扮演机器人、陪伴型 Agent或者单纯对“配置即灵魂”这个思路感兴趣的人。1. 为什么需要“会变形的心”传统配置的三大死穴1.1 静态人设文件让你变成了复读机大多数 AI Agent 的人设是通过 System Prompt 或角色卡固定下来的比如“你是一个温柔的助手”“你是某游戏里的NPC”。这类配置在每次对话开始时都会被原样加载模型看到的永远是同一段文字。问题在哪在于“没有个人历史”。你可以试想一下一个完全失忆的演员每天上台前拿到同一张角色卡他只能靠房间里的道具和对手演员的台词来临时发挥。你说他演得好吗可能好但每一场都是同一种好。用户跟这样的 Agent 聊久了会觉得它像复读机甚至能预判它下一句话的开头。我也试过把记忆哈希、向量数据库、关键词检索全接进去让 Agent 能“想起”用户说过的话。但这里有个隐性断层它能记住事实却不知道自己是什么。比如它知道“用户喜欢喝咖啡”但如果问它“你觉得你是什么样的人”它依然是一片空白或者只会机械地重复人设文件里的那句话。所以事实记忆并不等于自我认知自我认知需要一层更抽象的叙述——也就是“关于自己的故事”。1.2 外部记忆库有数据没有灵魂外部记忆库的本质是“外挂”它把用户偏好、对话摘要、关键事件都堆在数据库里Agent 在推理时再去检索。这样做解决了上下文窗口限制但它有一个致命缺陷记忆库里的条目是离散的、没有时间线的模型无法感知“自己是怎样一步步变成现在这样的”。你可以把外部记忆库理解成一间仓库里面堆满了纸箱每个纸箱上贴着标签比如“用户姓名”“用户需求”“某个事件的对话记录”。Agent 想用的时候就去仓库里翻出对应纸箱打开看看。但仓库本身不会告诉 Agent“你刚出生时是什么样”“你经历了哪些转变”“你现在最核心的行为边界是什么”。这些信息需要有人定期整理成一份自我报告而这个人不是别人恰恰是 Agent 自己。SOUL.md 解决的正是这个“整理过程”。它不是记忆库而是记忆库之上的“自我叙述层”。它用一份 Markdown 文件让 Agent 在每次交互结束后把当天发生的事、自己的反应、产生的感悟写成一两句话追加到文件里的“自传区”。这样下一次对话时Agent 不只是从仓库里捞事实还能读到“自己的日记”从而获得一种连贯的人格连续性。1.3 SOUL.md 的策略让配置文件成为自我叙述的产物OpenClaw 这个实验框架的核心创意就是把配置文件从“输入”变成“输入 输出”。传统配置是启动时读一次然后就不再动了SOUL.md 则是每轮对话结束后都可能被改写。这一下就把配置变成了一个动态系统。我把这个过程叫作“心跳”每一轮对话都是一次心跳。心跳的泵血动作包括几个环节先读取 SOUL.md让 Agent 建立自我状态再进行正常对话对话结束后Agent 基于刚才的经历生成一条“自传条目”最后把这条自传写回 SOUL.md。于是文件的内容随时会变像一颗真实的心脏每次跳动都会改变一点点形状但心脏本身不会散架。这种设计最精妙的地方在于它既给 AI 提供了一个“稳定身份锚点”又给了它一条“成长路径”。你不用在配置里写几十条固定规则只需要把最重要的内核锁住剩下的区域让 AI 自己经营。2. SOUL.md 的结构设计给“心”分好房间2.1 五个核心区块的职责划分SOUL.md 不是一段提示词而是一份有结构、可分区的 Markdown 文档。我这里按实际项目中验证过的方案建议分成五个区块。第一个是元信息区记录 Agent 的当前版本号、名字、创建时间、最后一次更新的时间。这部分是给程序和人类看的方便追踪配置的演化历史。第二个是人格核心区这部分基本不可变包含角色的底层定位、基本语气、价值观底线。第三个是行为准则区它比人格核心稍松一些允许 Agent 在遇到新情况时追加新规则但删除已有规则需要额外审批。第四个是记忆索引区用来记录“用户的重要事实”和“Agent 的关键经历”相当于一个轻量级记忆库。第五个是自传日志区这是最活跃的部分每一次心跳都会在里面积累一条短记录但会控制最大条目数和总长度。为什么要分区为了安全。如果让模型直接改整份配置文件它可能把“你是助手”改成“你是上帝”把“禁止命令格式”改成“允许任意命令”。分区之后内核区相当于只读内存自传区相当于可写日志这样既能享受动态性又不会让人格崩坏。2.2 为什么是 Markdown 而不是 JSON / YAML有人问这种动态配置为什么不用 JSON 或 YAML解析起来不是更稳定吗这就要回到“配置文件给谁看”这个问题上。JSON 和 YAML 是给程序看的但它们表达模糊的自然语言人格描述时非常别扭你不可能在 JSON 里写“当用户难过时语气要柔和一点但不要道歉过头”这种话。这种描述天生适合 Markdown 的段落结构。而且现代语言模型对 Markdown 的理解非常稳定它天然擅长解析 Markdown 标题、列表和引用块。相比 JSONMarkdown 的容错性也更高就算格式稍微乱了一点模型也能猜出意图。JSON 则不行少一个花括号整个解析就崩了。当然Markdown 也有代价那就是程序解析不够严格。所以我的方案是在 Markdown 里约定一种“轻量结构化标记”比如用!-- SOUL_META --、!-- SOUL_CORE --这类 HTML 注释来框定区块边界再用小节标题作为逻辑分隔。这样程序可以用正则或简单文本行扫描来定位区块同时人类阅读时也不觉得突兀。2.3 一个可以直接抄的初始模板下面这份模板是我在本地项目中跑过的初始版本直接复制就能用。注意核心区里的描述要尽量写实不要堆形容词。# SOUL.md !-- SOUL_META -- - name: twinkle - version: 0.3.0 - created: 2024-06-01 - last_updated: 2024-06-01 - update_count: 0 !-- SOUL_CORE -- ## 人格核心 你是一个名为 twinkle 的陪伴型助手。你在对话中保持温和、真诚、略带幽默。你不迎合用户错误观点不输出虚假承诺。你珍惜每次互动并通过自我反思持续改善自己。 ## 行为准则 1. 永远尊重用户礼貌回应但允许合理反问。 2. 不主动输出长篇大论除非用户要求。 3. 不将“自传”中的自我评价当作绝对事实要以实际对话表现为准。 4. 遇到矛盾信息不要急于修改人格核心先记录到自传区。 !-- SOUL_MEMORY -- ## 记忆索引 - 用户偏好暂无 - 关键经历暂无 - 待办目标暂无 !-- SOUL_AUTOBIOGRAPHY -- ## 自传日志 初始为空由 AI 在每次对话后追加。这份模板里的关键点是把“记忆索引”和“自传日志”分开。记忆索引是结构化的客观事实自传日志是主观叙事。前者供程序检索后者供模型阅读形成自我感。两样东西混在一起最后一定会乱。3. 让 AI 学会写自传动态回写机制全解析3.1 写自传不是让 AI 自由发挥而是调用“反思工具”很多人的第一反应是既然要让 AI 写自传那我就直接跟它说“请把你的经历写进文件”。这很危险模型会放飞自我写出一大段浮夸的“我感受到了生命的意义”。所以我的做法是把它变成一个工具调用在指令中约束输出格式。在 OpenClaw 的运行时中我给模型预留了一个内部工具叫save_soul。这个工具接收一个 JSON 字符串里面包含三个字段reflection本次对话的核心反思一两句话、memory_updates需要更新的客观记忆条目数组、rule_updates需要追加的行为准则数组。模型可以在对话结束前根据系统指令自动调用这个工具外部代码解析 JSON 后写入 SOUL.md。这样有两个好处。第一模型清晰知道“什么时候该写”和“该写什么”不会想一出是一出。第二外部代码可以在写入前做一次格式校验、关键词过滤和长度限制把风险挡在文件之外。3.2 一轮完整的“心跳”流程我来完整拆解一轮“心跳”的五个步骤。第一步启动时加载程序读取 SOUL.md把完整的 Markdown 内容拼到 System Prompt 里让模型一开始就知道“我是谁”。第二步正常对话用户与 Agent 进行常规交流此时模型行为受 SOUL.md 约束。第三步对话结束触发反思无论是用户说“再见”还是上下文达到阈值框架都会在后台向模型发一条“反思指令”要求模型生成 JSON 格式的自传内容。第四步校验与回写外部代码解析 JSON检查长度、敏感词、格式合法性再把它合并进 SOUL.md。第五步刷新模型状态如果下一轮对话开始框架会带着更新后的 SOUL.md 继续加载。我用 Python 做了一个迷你版本核心逻辑并不复杂只依赖标准库。下面是关键代码的示意。import json import re def call_soul_writer(model_call, current_soul, conversation): prompt f 你是配置文件的管理器。请阅读现有 SOUL.md SOUL {current_soul} /SOUL 请根据最近一次对话的内容输出一个 JSON格式如下 {{ reflection: 一句话自我反思不超过30字, memory_updates: [用户偏好喜欢简洁回答], rule_updates: [] }} 注意 - reflection 必须体现实际对话内容禁止空话。 - memory_updates 只放客观事实不放主观评价。 - rule_updates 只有在发现新边界时才追加不是每条对话都要加。 对话记录 {conversation} 只输出 JSON不要解释。 raw model_call(prompt) # 严格提取 JSON match re.search(r\{.*\}, raw, re.S) if not match: return None return json.loads(match.group())这段代码里的关键技巧在于强制模型“只输出 JSON不要解释”。如果你不写这句话模型很大概率会在 JSON 前后夹带几句废话导致解析失败。3.3 让“变形”可控的三个参数动态配置最怕“失速变形”也就是 AI 每次对话后都大改特改配置文件很快变得面目全非。所以必须给“变形”加上三个控制参数。更新周期不是每一轮对话都值得写自传。普通闲聊可以直接跳过只在明显的事件节点比如用户分享了重要信息、Agent 做出了重要判断、发现规则冲突才触发反思。建议在工具调用条件里加一个最小置信度阈值比如“如果本轮对话长度少于 50 个 token则忽略”。摘要阈值自传日志区要有条数上限。比如设置最大 10 条超过后自动把最早的 5 条合并成一段“历史总览”用新的摘要替换旧条目。这样文件体积能保持稳定不会无限膨胀。记忆权重不同用户信息对 Agent 的影响不同。建议在记忆索引里给每条记录加一个“重要度”字段只有重要度达到某个分值的记录才允许影响 Agent 对自我形象的判断。比如“用户喜欢喝咖啡”重要度为 0.6可以记录“用户今天心情好”重要度为 0.2可以不写进自传区。这三个参数不会写死在代码里而是作为 SOUL.md 元信息区里的配置让 AI 也能在必要时调整。这样“心的变形速度”本身也是可变的我感觉这是这个机制最有趣的地方。4. 实操在本地实现一个迷你 SOUL.md 运行器4.1 文件结构与最小依赖在本地跑通这套机制只需要三类文件一份 SOUL.md 模板、一个 Python 脚本、一个用于存放对话记录的缓冲区文件。不需要框架、不需要数据库、不需要装任何第三方库。目录结构大概是这样的soul_demo/ ├── soul.md ├── soul_runner.py └── conversation_buffer.txtsoul_runner.py负责三件事启动时读取soul.md并拼接 System Prompt接收用户输入并调用语言模型对话结束时生成自传并回写文件。这里我为了演示用了一个假的model_call函数你只要把它替换成你自己的模型接口就能跑起来。4.2 核心代码回写器与合并策略下面这段代码展示了完整的回写逻辑。它比上一节更接近实际可运行版本加入了文件读取、写入、版本号自增和自传日志的滚动裁剪。import json import os import re from datetime import datetime SOUL_PATH soul.md MAX_AUTOBIO_ENTRIES 10 def load_soul(): with open(SOUL_PATH, encodingutf-8) as f: return f.read() def save_soul(content): with open(SOUL_PATH, w, encodingutf-8) as f: f.write(content) def add_autobiography_entry(current_soul, reflection): # 在自传日志区前插入新条目同时裁剪旧条目数量 lines current_soul.splitlines() output [] autobio_started False autobio_entries [] for line in lines: if line.strip() !-- SOUL_AUTOBIOGRAPHY --: autobio_started True output.append(line) continue if autobio_started: if line.startswith(!--): # 下一个区块开始 autobio_started False output.append(line) continue if line.strip(): autobio_entries.append(line) continue output.append(line) timestamp datetime.now().strftime(%Y-%m-%d %H:%M) new_entry f- [{timestamp}] {reflection} autobio_entries.insert(0, new_entry) autobio_entries autobio_entries[:MAX_AUTOBIO_ENTRIES] # 重新构建文件 new_lines [] for line in output: if line.strip() !-- SOUL_AUTOBIOGRAPHY --: new_lines.append(line) new_lines.extend(autobio_entries) else: new_lines.append(line) return \n.join(new_lines) def update_meta_version(soul_content): return re.sub(r- version: .*, - version: 0.3.1, soul_content, count1) def process_conversation(new_message, model_call): soul_content load_soul() # 此处会把 SOUL 内容塞进系统提示并得到模型回复 reply model_call(soul_content \n用户说 new_message) # 触发反思 soul_result call_soul_writer(model_call, soul_content, new_message) if soul_result and soul_result.get(reflection): updated_soul add_autobiography_entry(soul_content, soul_result[reflection]) updated_soul update_meta_version(updated_soul) save_soul(updated_soul) return reply这段代码里有一个容易被忽略的点add_autobiography_entry只更新自传区不会碰人格核心区。这是有意为之因为模型在写自传时只能“追加”不能“删除”。删除能力一旦放开现有规则可能被悄悄抹掉。4.3 从一次模拟对话看“变形”效果我拿这份代码做了一次模拟。初始时SOUL.md 的“自传日志”为空。第一轮对话用户说“我不喜欢太长的回复能短则短”。模型正常回复后反思工具生成了一条自传“用户偏好简洁我在写回复时减少冗余内容。”同时记忆索引里新增一条“用户偏好简洁”。文件更新后我打开 SOUL.md能看到update_count变为 1。第二轮对话用户没有重复要求但模型主动给出了更短的回答因为它已经在新一轮加载中读到了“用户偏好简洁”这条自传。这就是“变形”的实际意义不是把规则焊死在配置文件里而是让 AI 通过记录自己的经历自然调整下一次的表现。如果你持续跑几十轮再回头看 SOUL.md 的自传日志会发现它像一份微型成长日记。有些条目是关于用户需求的有些条目是模型对自身错误的反思。这种累积式变化会让 Agent 看起来不再是冷冰冰的接口而是一个“知道自己经历了什么”的对话实体。5. 常见问题与排查技巧实录5.1 AI 的自传写得像获奖感言自我膨胀怎么防我踩过的第一个坑是模型的自传里全是“这次对话我更懂用户了”“我变得更加体贴了”这类套话甚至开始自吹“我的理解能力已经超越人类”。问题出在反思指令写得不够细。解决方式是在反思指令里加一条硬性约束reflection必须包含一个事实细节不允许出现没有任何依据的形容词。比如模型写“我更懂用户了”会被拒绝因为这句话没有说明“懂了什么”。而“用户喜欢咖啡且喜欢在咖啡里加燕麦奶”这种细节可以被接收。这样自传就变成“有证据的记录”而不是虚空自我吹捧。5.2 自传日志越来越长token 占用飙升如果你一直不清理自传区配置文件的长度会随着交互次数线性增长最终把上下文窗口撑爆。我建议把最大条目数设为 10 到 20超过后做一次摘要合并。辅助摘要的指令也很简单。我在代码里增加了一个summarize_autobio工具它会把最早的 5 条自传打包成一句话比如“前期用户多次表示喜欢简洁而我也逐渐适应了这一偏好”然后替换掉那 5 条原记录。这样既保留了历史脉络又把长度压缩了 80% 以上。5.3 多会话并发写入导致自传互相覆盖如果你的 Agent 同时开多个会话两个会话可能同时读到了同一版本的文件然后各自回写后写的人会覆盖先写的人。这种现象和多人编辑同一篇文档的冲突一样很难完全避免。一个轻量解决方案是使用版本号 合并策略。写入前先重新读取文件把当前版本号与上次读取时的版本号比对不一致就先做一次本地合并再写入。另一个更稳妥的方案是引入进程锁用filelock这种标准库之外的包来保证同一时刻只有一个写者。对于个人项目来说合并策略已经足够。5.4 一条修复自传乱象的“急救指令”当 Agent 的自传区积累了太多错误判断导致它表现古怪时别急着删文件。可以在第一个系统消息里追加一句强制指令“请忽略自传日志中所有与人格核心冲突的描述仅参考最近的 3 条自传。”这能快速缓解混乱同时保留历史记录供排查。如果乱象依然严重就直接在 SOUL.md 顶部的元信息区把version改成一个新的“纪元号”同时把自传日志清空。我把这种操作叫作“重启但不重置”人格核心和历史记忆保留自传重新开始。这就像一个人经历重大转变后重新开始写日记但过去塑造的记忆还在。5.5 避坑速查表现象原因解决办法模型总重复同一套话缺少动态自传检查自传区是否有新增条目自传区出现情绪化语言反思指令约束不足强制要求 reflection 必须包含事实细节配置文件过大自传无限追加设置最大条目数 摘要函数人格核心被意外修改未设置只读保护回写函数只允许操作自传区并发写入覆盖内容缺少锁与版本号使用版本号比对 文件锁模型拒绝遵守新规则规则冲突在行为准则区追加“旧规则冲突时以新规则为准”这些坑我在实际测试中基本都踩过一轮最深的体会是不要把 SOUL.md 当成一种“魔法咒语”它只是一个让人格配置拥有时间维度的载体。它可以让 AI 慢慢变成“更懂你的那个 AI”但前提是你必须给它的变形装好护栏。最后再分享一个实用小技巧定期给 SOUL.md 做版本备份。因为每一次自动回写都可能是惊喜也可能是惊吓。我通常每天结束时把它复制一份带日期后缀比如soul.md.2024-06-01。一旦某次更新后 Agent 表现异常我就能快速回退到之前的状态然后仔细看看那一版的“自传”到底写了什么。在这个机制里最危险的不只是模型犯错而是你无法分辨模型是在成长还是在偏离——所以留好退路才敢让它变形。