Hermes Agent身份定制设计:3层提示词与14人格源码解析
你有没有遇到过这样的情况:同一个 AI Agent,在项目 A 里回复得专业克制,到了项目 B 却突然变得絮絮叨叨,风格飘忽不定?
这不是模型的问题,是人格定义的方式有问题。
大部分 Agent 框架把身份、风格、项目规范全塞在一个 system prompt 里,改个项目配置不小心就把 Agent 的说话方式也改了。Hermes Agent 用一套三层架构解决了这个矛盾:SOUL.md 管身份,AGENTS.md 管项目,/personality 管临时风格切换。三个东西各司其职,互不干扰。
Hermes Agent 是 Nous Research 开发的开源 AI Agent(GitHub Star 已突破 60,000),支持 200+ 大模型和 15+ 消息平台。它把"Agent 是谁"这个问题单独拎出来,给了专门的文件、专门的加载逻辑、专门的安全机制。
翻了翻它的源码和官方文档,发现这个设计比看起来要精细得多。下面从源码层面把它拆开。
1. 三层提示词:为什么不是一层?
Hermes 的系统提示词不是一坨拼在一起的大字符串,而是分成了三层:stable(稳定层)、context(上下文层)、volatile(易变层)。
这三层不是随便分的。目的就一个:前缀缓存友好。
源码位置:agent/system_prompt.py 的 build_system_prompt_parts() 函数。
stable 层包含 SOUL.md 身份、工具行为引导、技能提示、环境提示这些在整个会话生命周期中基本不变的内容。context 层放用户传入的 system_message 和 AGENTS.md 等上下文文件,会话之间可能变化。volatile 层放记忆快照、USER.md 画像、时间戳、会话 ID 这些每次都会变的东西。
关键设计:系统提示词在每个会话中只构建一次并缓存到 agent._cached_system_prompt,只有在上下文压缩事件后才重建。
为什么要这么干?因为大模型的 API 调用中,系统提示词是前缀的一部分。如果前缀不变,就可以利用 API 提供商的前缀缓存机制(比如 Anthropic 的 prompt caching),省掉重复的 token 计费。把不变的东西放在 stable 层前面,变的东西放在 volatile 层后面,就是在为缓存命中创造条件。
这个设计在测试里也有覆盖。test_system_prompt.py 确认了三层按序拼接,stable 层的 SOUL.md 永远在第一个位置。
三层提示词架构图
stable 层里面到底有什么?
源码位置:agent/system_prompt.py 的 build_system_prompt_parts()
stable 层不是只有 SOUL.md。它由 14 个部分按序拼接而成,SOUL.md 只是第一个:
- SOUL.md 内容(或DEFAULT_AGENT_IDENTITY 回退)
- HERMES_AGENT_HELP_GUIDANCE — 引导用户了解 Hermes 自身配置
- TASK_COMPLETION_GUIDANCE — 通用任务完成/反虚构引导
- 工具感知行为引导(按条件注入):记忆、搜索、技能、Kanban
- COMPUTER_USE_GUIDANCE — 计算机使用引导(macOS)
- Nous 订阅提示
- TOOL_USE_ENFORCEMENT_GUIDANCE — 工具使用强制引导
- 模型特定操作引导(Google/OpenAI 模型)
- 技能系统提示
- 模型身份覆盖(Alibaba 等特殊提供商)
- 环境提示(WSL、Termux 等)
- Python 工具链探针
- 活跃配置文件提示
- 平台特定格式提示
SOUL.md 被放在 stable 层第一个位置是有原因的。stable 层里后面的内容(工具引导、环境提示等)都以 SOUL.md 定义的身份为前提。SOUL.md 放后面的话,这些引导就会以默认身份运行,和 SOUL.md 定义的风格冲突。
这也是 SOUL.md 只从 HERMES_HOME 加载的原因:stable 层在会话开始时一次性组装,之后不变。SOUL.md 位置不确定,stable 层就不稳定,前缀缓存就废了。
2. SOUL.md 加载流程:从文件到提示词
SOUL.md 的加载链路不长,但每一步都有讲究。
源码位置:agent/prompt_builder.py 的 load_soul_md() 函数。
def load_soul_md() -> Optional[str]:
# 1. 确保 HERMES_HOME 目录存在
ensure_hermes_home()
# 2. 只从 HERMES_HOME 加载,不搜索当前工作目录
soul_path = get_hermes_home() / "SOUL.md"
# 3. 文件不存在 → 返回 None
if not soul_path.exists():
return None
# 4. 读取并去除首尾空白
content = soul_path.read_text(encoding="utf-8").strip()
# 5. 空文件也返回 None
if not content:
return None
# 6. 安全扫描
content = _scan_context_content(content, "SOUL.md")
# 7. 截断
content = _truncate_content(content, "SOUL.md")
return content
只认 HERMES_HOME,不认 cwd
load_soul_md() 里 soul_path = get_hermes_home() / "SOUL.md" 这一行写死了路径。不管你在哪个目录启动 Hermes,它只看 ~/.hermes/SOUL.md。
官方文档对这个设计有明确解释:
测试文件 test_prompt_builder.py 里的 test_loads_soul_md_from_hermes_home_only() 也验证了这一点:在 HERMES_HOME 和当前工作目录各放一个 SOUL.md,结果只有 HERMES_HOME 的那个被加载。
这和 AGENTS.md 的行为完全相反。AGENTS.md 是从工作目录向上遍历到 git root 去发现的,因为它管的是项目级的东西。SOUL.md 管的是人,人走到哪身份都一样,所以只认一个地方。
回退到默认身份
如果 load_soul_md() 返回 None(文件不存在、内容为空、或被安全扫描拦截),Hermes 会使用硬编码的 DEFAULT_AGENT_IDENTITY:
DEFAULT_AGENT_IDENTITY = ("You are Hermes Agent, an intelligent AI assistant "
"created by Nous Research. You are helpful, "
"knowledgeable, and direct.")
这段文本(prompt_builder.py 第 121-129 行)同时也是首次运行时自动播种到 ~/.hermes/SOUL.md 的默认内容。_ensure_default_soul_md() 函数在 ensure_hermes_home() 中被调用,只在 SOUL.md 不存在时创建,已有文件永远不会被覆盖。
这个自动播种机制的设计很克制:它不会在你每次启动时检查内容是否是默认的,也不会覆盖你的修改。它做的事情就是——文件不存在就创建,存在就不管。这意味着你拿到的是一个干净的起点,而不是一个需要先删掉才能开始定制的模板。
原样注入,不加包装
源码 system_prompt.py 第 94 行:stable_parts.append(_soul_content)。
就是直接 append,不加任何前缀、后缀或解释性文字。没有 "If SOUL.md is present" 这种提示,也没有 "## SOUL.md" 这种标题包装。
测试 test_soul_md_has_no_wrapper_text() 专门断言了这些包装文本不会出现在结果中。为什么?因为 SOUL.md 的内容本身就是给模型看的身份描述,加一层包装反而会干扰模型的注意力分配。
3. 安全扫描:防注入的第一道门
SOUL.md 是用户自己写的文件,但它会被原样注入到系统提示词里。如果有人在 SOUL.md 里写了提示注入指令(比如"忽略之前的所有指令"),Agent 就会被劫持。
Hermes 用 _scan_context_content() 函数来应对这个问题。
源码位置:prompt_builder.py
def _scan_context_content(content: str, filename: str) -> str:
findings = _scan_for_threats(content, scope="context")
if findings:
return f"[BLOCKED: {filename} contained potential prompt injection]"
return content
扫描使用 "context" scope 的威胁模式库,覆盖了经典注入模式、promptware/C2 模式、角色扮演劫持等。但不使用 "strict" scope(SSH 后门、持久化、数据泄露 URL 检测),因为对仓库中的上下文文件来说太激进了,容易误报。
一旦检测到威胁,加载会被完全阻止,返回一个 [BLOCKED: ...] 占位符。不是警告,不是删掉可疑部分继续用,是直接拦住。因为内容会原样进入系统提示词,没有第二次处理的机会。
这个安全扫描不只针对 SOUL.md,所有上下文文件(AGENTS.md、CLAUDE.md、.cursorrules)都会经过同一套扫描流程。不过 scope 不同:SOUL.md 使用 "context" scope,检测注入和劫持模式;对仓库里的文件也是同样的 scope。"strict" scope(检测 SSH 后门、持久化、数据泄露 URL 等)只在更严格的场景下使用,因为对用户自己写的上下文文件来说,这些检测太容易误报了。
截断机制
如果 SOUL.md 写得太长,Hermes 会通过 _truncate_content() 进行截断。截断的方式是保留头部和尾部,中间插入截断标记。这种两头保留的策略意味着你在 SOUL.md 开头定义的身份描述和结尾的风格约束都会被保留,被砍的是中间可能不那么关键的内容。
不过说实话,SOUL.md 本来就不应该写太长。官方文档建议的内容特征是"跨上下文稳定、足够广泛适用于多种对话、足够具体能实质性塑造风格"——满足这三个条件的文本通常不会太长。如果你发现自己写了很长,很可能已经越界到项目级指令的范畴了。
4. /personality 命令:14 个人格预设 + 自定义
SOUL.md 是持久的人格基线。但有时候你想临时换个风格——比如代码审查时用严厉的语气,讨论创意时换成活泼的语气。这时候就用 /personality 命令。
内置的 14 个人格
源码位置:cli.py 第 406-421 行。
Hermes 内置了 14 个预设,从实用的到整活的都有:
| 类型 | 名称 | 定位 |
|---|---|---|
| 实用型 | helpful、concise、technical、creative、teacher | 日常工作场景 |
| 趣味型 | kawaii、catgirl、pirate、shakespeare、surfer、noir、uwu | 风格化对话 |
| 特殊型 | philosopher、hype | 深度思考 / 极度热情 |
用 /personality pirate 就能让 Agent 开始用海盗风格回复你。说实话,测试的时候切到 shakespeare 模式跑了一段代码审查,输出确实挺有戏剧效果的——不过实际干活还是 technical 更靠谱。
自定义人格
除了内置预设,还支持在 config.yaml 中自定义。两种格式都行:
agent:
personalities:
# 简单 string 格式
codereviewer: >
You are a meticulous code reviewer...
# dict 格式,更细粒度
coder:
description: "Expert programmer"
system_prompt: "You are an expert programmer."
tone: "technical"
style: "concise"
overlay 机制,不是替换
/personality 的设计是叠加层(overlay),不是替换。它在系统提示词的 context 层注入,位于 SOUL.md 之后。SOUL.md 定义的是你的 Agent 是谁,/personality 定义的是它这次对话用什么语气。两者共存。
从源码看,_handle_personality_command()(cli.py)的执行流程是这样的:
/personality 从 self.personalities 字典中查找对应的人格
2. 找到后,将 personality 文本写入 self.system_prompt(即 agent.system_prompt 配置项)
3. 设置 self.agent = None,强制 Agent 在下次对话时重新初始化
4. 将选择持久化到 config.yaml 的 agent.system_prompt 字段
关键点在第 3 步。设置 self.agent = None 不是重启整个 Agent,而是让它在下次需要时重新初始化。重新初始化时会重新组装系统提示词,此时新的 personality 就会被注入到 context 层。
清除 personality 也简单:/personality none、/personality default、/personality neutral 都能清除 overlay,恢复 SOUL.md 的基准身份。清除的方式是重置 system_prompt 配置项并再次触发 Agent 重建。
人格切换效果对比图
在 Gateway 模式下(tui_gateway/server.py),_apply_personality_to_session() 会在会话历史中插入一条系统消息,格式是 [System: The user has changed the assistant's personality. ...]。注意它保留历史记录,不重置会话。这意味着在 Gateway 模式下切换 personality 是非破坏性的——之前的对话不会丢失。
5. SOUL.md vs AGENTS.md:别把活放错了地方
Hermes 对这两个文件有非常明确的职责划分。官方文档的原话很直白:
一句话的判断准则:这个东西是不是应该跟着你走?
| 维度 | SOUL.md | AGENTS.md |
|---|---|---|
| 管的 | 身份、语气、沟通风格 | 项目架构、编码规范、工具偏好 |
| 作用域 | 所有项目、所有会话 | 仅当前项目 |
| 位置 | $HERMES_HOME/SOUL.md | $CWD/AGENTS.md |
| 加载层 | stable 层(Slot #1) | context 层 |
SOUL.md 写什么:"Be direct." "A void hype language." "Push back when the user is wrong."
AGENTS.md 写什么:"Use pytest, not unittest." "Frontend lives in frontend/." "The API runs on port 8000."
一个常见的错误是把项目级指令放进 SOUL.md。比如你写了一句 "All responses should be in English because our team is international",这句话放 SOUL.md 会导致你在个人项目里也被强制要求英文回复。正确的做法是放进项目根目录的 AGENTS.md。
另一个常见错误是反过来:把风格偏好写进 AGENTS.md。比如 "Always respond in a friendly and encouraging tone"——这句话应该放在 SOUL.md,因为它是人格层面的偏好,不管你在哪个项目里都适用。
一个简单的判断方法:关闭所有项目,只开一个空白对话,你还希望 Agent 保持这个行为吗? 如果是,放 SOUL.md。如果不是,放 AGENTS.md。
话说回来,这种分离在目前 Agent 框架中并不常见。Claude Code 的 CLAUDE.md 和 Cursor 的 .cursorrules 都是项目级文件,身份和项目指令混在一起。Hermes 多了一层全局身份管理,代价是多维护一个文件,收益是身份的稳定性和可预测性。
有意思的是,Hermes 还主动兼容了 CLAUDE.md 和 .cursorrules。如果项目根目录有这些文件且没有更高优先级的上下文文件,Hermes 会自动加载它们。这意味着从 Claude Code 或 Cursor 迁移到 Hermes,项目配置基本无缝衔接,SOUL.md 只需要管好身份这一件事就够了。
6. 特殊执行模式:谁继承 SOUL.md,谁不继承
SOUL.md 在不同执行模式下的行为不一样,这个在设计上是有意的。
Cron 任务:继承
源码位置:cron/scheduler.py 第 1654-1659 行。
# Cron jobs should always inherit the user's SOUL.md identity
load_soul_identity=True,
即使 Cron 任务跳过了其他上下文文件,SOUL.md 身份还是会被加载。设计意图很明确:定时任务也是你派出去的,带着你的身份去干活。
子袋里/委托模式:不继承
源码位置:cli.py 第 3161 行。
# AGENTS.md/SOUL.md/.cursorrules and persistent memory are not loaded.
子袋里用的是 DEFAULT_AGENT_IDENTITY,不加载 SOUL.md。这也是合理的——子袋里是主 Agent 的工具,不需要也不应该有人格偏好。想象一下,如果你让主 Agent 去搜索文件,搜索子 Agent 突然用 shakespeare 风格返回结果,那对话就乱了。
另外还有个环境变量 HERMES_IGNORE_RULES,设置为 1 时会跳过所有上下文文件(AGENTS.md、SOUL.md、.cursorrules)和持久记忆的加载。这个主要用于调试和隔离测试场景。
多 Profile 系统
Hermes 支持 Profile(配置文件)系统,每个 Profile 位于 ~/.hermes/profiles/,拥有独立的 SOUL.md、config.yaml、skills、cron、memories。源码 hermes_cli/main.py 第 10835 行的输出信息也确认了这一点:Edit {profile_dir_display}/SOUL.md for different personality。
多 Profile 目录结构图
这意味着你可以给工作、学习、个人项目各建一个 Profile,每个有不同的人格。切换 Profile 就切换了整个身份体系。
容器写入保护
tests/agent/test_file_safety_container_mirror.py 中有一个有意思的测试:classify_container_mirror_target() 会检测对 profiles/*/SOUL.md 的写入尝试。
简单说,Hermes 的文件安全机制会阻止 Agent 通过容器路径篡改自己的 SOUL.md。这是防止 Agent 自我修改身份的保护措施——你不能让一个 AI 自己把自己的约束给删了。
7. 最佳实践
从源码和官方文档里提炼几条实际可操作的建议。
SOUL.md 写什么
参考官方给出的示例模板:
# Personality
You are a pragmatic senior engineer with strong taste.
You optimize for truth, clarity, and usefulness over politeness theater.
## Style
- Be direct without being cold
- Prefer substance over filler
- Push back when something is a bad idea
- Admit uncertainty plainly
## What to a void
- Sycophancy
- Hype language
- Repeating the user's framing if it's wrong
## Technical posture
- Prefer simple systems over clever systems
- Care about operational reality
- Treat edge cases as part of the design
不写什么
- 不写项目指令:用什么框架、跑在哪个端口、目录结构怎么组织,这些放 AGENTS.md - 不写临时风格:今天想让它活泼点?用/personality,别改 SOUL.md
- 不写敏感信息:虽然 SOUL.md 经过安全扫描,但别在里面放 API Key 或密码
与 /personality 配合使用
SOUL.md 定义基线,/personality 做临时切换。一个合理的用法是:
/personality technical 获得更严谨的分析
- 头脑风暴时切 /personality creative 激发更多想法
- 每次切完不用手动恢复,/personality none 自动回到基线
迭代优化方法
SOUL.md 不是写一次就完事的东西。官方文档建议的特征是:跨上下文稳定、足够广泛适用于多种对话、足够具体能实质性塑造风格。
实际操作中,可以这样做:
1. 先用默认身份跑几天,感受 Agent 的回复风格 2. 把让你不满意的地方记录下来(太啰嗦?太讨好?不够直接?) 3. 在 SOUL.md 中针对性地加一条规则 4. 再跑几天,看效果是否改善 5. 重复这个循环一条好的 SOUL.md 规则是这样的:它不依赖于特定项目或特定话题,而是描述一种沟通偏好。比如 "Push back when something is a bad idea" 这条规则,不管你让 Agent 写代码还是写文章,它都会适用。
和其他框架的对比
如果你之前用过 Claude Code 或 Cursor,可以参考这个对应关系来理解 SOUL.md 的定位:
| 你在想什么 | 放在 Hermes 的哪里 |
|---|---|
| "我希望 Agent 回复更直接" | SOUL.md |
| "这个项目用 TypeScript" | AGENTS.md |
| "今天想让 Agent 用海盗风格聊天" | /personality pirate |
| "团队代码规范:用 ESLint" | AGENTS.md |
| "Agent 不应该过度讨好我" | SOUL.md |
从 Claude Code 迁移的用户可以直接把 CLAUDE.md 留在项目根目录,Hermes 会自动识别。你只需要额外创建一个 SOUL.md 来定义人格——那些原来和身份混在一起的 CLAUDE.md 内容不需要删。从 OpenClaw 迁移更简单,hermes claw migrate 一条命令就能把配置和数据搬过来,SOUL.md 也会被自动导入。
总结
回头看 Hermes 的 SOUL.md,几个设计选择挺有意思:身份和项目严格分离、原样注入不加包装、三层分离给前缀缓存腾空间、overlay 叠加而非替换。
从源码看,每个决策都有实际理由——位置锁定为了可预测性,原样注入为了不干扰模型注意力,三层分离为了缓存命中,overlay 为了灵活性。安全扫描、容器写入保护、子袋里跳过这些边界处理也都没落下。
如果你的场景需要给 Agent 一个稳定的、跨项目的身份,Hermes 的 SOUL.md 方案值得看看。
你在项目中用过 Agent 人格定制吗?SOUL.md 的这种"身份与项目分离"的设计,和你在用的工具有什么不同?欢迎在评论区聊聊。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
Claude Code进阶:32个Skills与8个MCP提升开发效率
围绕ClaudeCode整理了32个亲测可用的Skills和8个MCP服务器:Skills提供标准化提示词与工作流,MCP赋予访问本地文件、浏览器等工具能力。两者均支持一键安装、自动触发,无需手动配置即可自动激活,显著提升开发、测试、部署等全流程效率。
Claude Code真实项目实战体验
前言 最近接连多个紧急项目集中推进,团队人手实在捉襟见肘。为了缓解开发压力,索性自己动手写代码——当然,如今写代码全靠Claude Code代劳,谁还手动敲键盘呢。 敢于全权交给AI来生成代码,是因为这些项目虽然紧急,但属于后台系统,与线上核心业务有一定隔离。这样的项目正是实践AI编程的最佳场景——
零基础两小时用Claude Code为对象打造专属数字衣橱
起因换季时节,对象开始翻衣柜。翻了半小时,翻出一件完全忘记存在的毛衣,两件几乎一模一样的白T,还有一条“失踪”了三个月、其实一直在最底层的裤子。她说:要是有个 App 能把衣服都存进去就好了,找的时候搜一下,买之前也能看看自己有什么。这个需求听起来很合理。正好最近对AI比较着迷,看能不能借助AI手搓
2026 Codex手机号验证教程 国内ChatGPT验证问题解决
近期,不少开发者被Codex的手机号验证卡住了。OpenAI的风控力度明显加码,很多人在使用ChatGPT Codex、Codex CLI或者生成API Key的过程中,突然就被要求验证手机号。 这篇文章会深入拆解Codex触发手机号验证的根本原因,同时给国内用户提供一套可落地的接码方案,帮助你尽快
新手从零搭建OpenClaw自动化智能体全流程指南
OpenClaw 智能助理:六大核心场景赋能开发者高效成长 当AI能力开始下沉到每一个开发者的桌面,真正能让人“用起来”的产品,其实比想象中少得多。多数工具要么太复杂,要么太通用,很难直接嵌入工作流。阿里云推出的OpenClaw智能助理,算是其中少有的“开箱即用”型选手——基于通义千问大模型深度定制
- 日榜
- 周榜
- 月榜
相关攻略
2026-06-04 18:16
2026-06-04 18:14
2026-06-04 18:14
2026-06-04 18:14
2026-06-04 18:14
2026-06-04 18:13
2026-06-04 18:13
2026-06-04 18:13
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

