首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >Hermes Agent SOUL.md:3 层提示词、14 个内置人格,从源码看身份定制的完整设计

Hermes Agent SOUL.md:3 层提示词、14 个内置人格,从源码看身份定制的完整设计

原创
作者头像
运维有术
发布2026-06-03 23:52:04
发布2026-06-03 23:52:04
940
举报
文章被收录于专栏:运维有术运维有术

🚩 2026 年「术哥无界」系列实战文档 X 篇原创计划 第 129 篇,Hermes Agent 最佳实战「2026」系列第 8

大家好,欢迎来到 术哥无界 | ShugeX | 运维有术

我是术哥,一名专注于 AI 编程、AI 智能体、Agent Skills、MCP、云原生、AIOps、Milvus 向量数据库的技术实践者与开源布道者

Talk is cheap, let's explore。无界探索,有术而行。

封面图
封面图

图 1:Hermes Agent SOUL.md 人格系统核心要点

说明:本文内容基于 Hermes Agent 源码(NousResearch/hermes-agent)和官方文档分析整理而成,源码分析基于笔者本地仓库版本。文中的配置模板和参数建议仅供参考,实际效果请以你的业务数据和环境测试结果为准。如果有实际使用经验,欢迎在评论区分享交流。

你有没有遇到过这样的情况:同一个 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 是谁"这个问题单独拎出来,给了专门的文件、专门的加载逻辑、专门的安全机制。

我翻了 Hermes 的源码和官方文档,发现这个设计比看起来要精细得多。下面从源码层面把它拆开。

1. 三层提示词:为什么不是一层?

Hermes 的系统提示词不是一坨拼在一起的大字符串,而是分成了三层:stable(稳定层)、context(上下文层)、volatile(易变层)。

这三层不是随便分的。目的就一个:前缀缓存友好

源码位置:agent/system_prompt.pybuild_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 永远在第一个位置。

三层提示词架构图
三层提示词架构图

图 2:Hermes Agent 三层提示词架构(stable / context / volatile)

stable 层里面到底有什么?

源码位置:agent/system_prompt.pybuild_system_prompt_parts()

stable 层不是只有 SOUL.md。它由 14 个部分按序拼接而成,SOUL.md 只是第一个:

  1. SOUL.md 内容(或 DEFAULT_AGENT_IDENTITY 回退)
  2. HERMES_AGENT_HELP_GUIDANCE — 引导用户了解 Hermes 自身配置
  3. TASK_COMPLETION_GUIDANCE — 通用任务完成/反虚构引导
  4. 工具感知行为引导(按条件注入):记忆、搜索、技能、Kanban
  5. COMPUTER_USE_GUIDANCE — 计算机使用引导(macOS)
  6. Nous 订阅提示
  7. TOOL_USE_ENFORCEMENT_GUIDANCE — 工具使用强制引导
  8. 模型特定操作引导(Google/OpenAI 模型)
  9. 技能系统提示
  10. 模型身份覆盖(Alibaba 等特殊提供商)
  11. 环境提示(WSL、Termux 等)
  12. Python 工具链探针
  13. 活跃配置文件提示
  14. 平台特定格式提示

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.pyload_soul_md() 函数。

代码语言:python
复制
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

官方文档对这个设计有明确解释:

If Hermes loaded SOUL.md from whatever directory you happened to launch it in, your personality could change unexpectedly between projects.

测试文件 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

代码语言:python
复制
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

代码语言:python
复制
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 中自定义。两种格式都行:

代码语言: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)的执行流程是这样的:

  1. /personality <name>self.personalities 字典中查找对应的人格
  2. 找到后,将 personality 文本写入 self.system_prompt(即 agent.system_prompt 配置项)
  3. 设置 self.agent = None,强制 Agent 在下次对话时重新初始化
  4. 将选择持久化到 config.yamlagent.system_prompt 字段

关键点在第 3 步。设置 self.agent = None 不是重启整个 Agent,而是让它在下次需要时重新初始化。重新初始化时会重新组装系统提示词,此时新的 personality 就会被注入到 context 层。

清除 personality 也简单:/personality none/personality default/personality neutral 都能清除 overlay,恢复 SOUL.md 的基准身份。清除的方式是重置 system_prompt 配置项并再次触发 Agent 重建。

人格切换效果对比图
人格切换效果对比图

图 3:/personality 命令 overlay 效果对比——叠加而非替换

在 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 对这两个文件有非常明确的职责划分。官方文档的原话很直白:

If it should follow you everywhere, it belongs in SOUL.md. If it belongs to a project, it belongs in AGENTS.md.

一句话的判断准则:这个东西是不是应该跟着你走?

维度

SOUL.md

AGENTS.md

身份、语气、沟通风格

项目架构、编码规范、工具偏好

作用域

所有项目、所有会话

仅当前项目

位置

$HERMES_HOME/SOUL.md

$CWD/AGENTS.md

加载层

stable 层(Slot #1)

context 层

SOUL.md 写什么:"Be direct." "Avoid 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 行。

代码语言:python
复制
# Cron jobs should always inherit the user's SOUL.md identity
load_soul_identity=True,

即使 Cron 任务跳过了其他上下文文件,SOUL.md 身份还是会被加载。设计意图很明确:定时任务也是你派出去的,带着你的身份去干活。

子代理/委托模式:不继承

源码位置:cli.py 第 3161 行。

代码语言:python
复制
# 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/<name>/,拥有独立的 SOUL.md、config.yaml、skills、cron、memories。源码 hermes_cli/main.py 第 10835 行的输出信息也确认了这一点:Edit {profile_dir_display}/SOUL.md for different personality

多 Profile 目录结构图
多 Profile 目录结构图

图 4:Hermes Agent 多 Profile 目录结构——每个 Profile 拥有独立的 SOUL.md

这意味着你可以给工作、学习、个人项目各建一个 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 写什么

参考官方给出的示例模板:

代码语言:markdown
复制
# 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 avoid
- 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 做临时切换。一个合理的用法是:

  1. SOUL.md 定义你的核心风格偏好(直接、不废话、技术导向)
  2. 日常对话用默认身份(走 SOUL.md)
  3. 代码审查时切 /personality technical 获得更严谨的分析
  4. 头脑风暴时切 /personality creative 激发更多想法
  5. 每次切完不用手动恢复,/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 的这种"身份与项目分离"的设计,和你在用的工具有什么不同?欢迎在评论区聊聊。

好啦,谢谢你观看我的文章,如果喜欢可以点赞转发给需要的朋友,我们下一期再见!敬请期待!

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 1. 三层提示词:为什么不是一层?
    • stable 层里面到底有什么?
  • 2. SOUL.md 加载流程:从文件到提示词
    • 只认 HERMES_HOME,不认 cwd
    • 回退到默认身份
    • 原样注入,不加包装
  • 3. 安全扫描:防注入的第一道门
    • 截断机制
  • 4. /personality 命令:14 个人格预设 + 自定义
    • 内置的 14 个人格
    • 自定义人格
    • overlay 机制,不是替换
  • 5. SOUL.md vs AGENTS.md:别把活放错了地方
  • 6. 特殊执行模式:谁继承 SOUL.md,谁不继承
    • Cron 任务:继承
    • 子代理/委托模式:不继承
    • 多 Profile 系统
    • 容器写入保护
  • 7. 最佳实践
    • SOUL.md 写什么
    • 不写什么
    • 与 /personality 配合使用
    • 迭代优化方法
    • 和其他框架的对比
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档