@znx 你等我在测两三天门禁系统和自进化框架。这些脚本和插件不是门禁系统自带的,是我自己写的一套增强组件。部署门禁(code-review-gate)只是基础,curator.py、watchdog.py 这些是我的自定义脚本,我给你提供一下我自己写的增强组件Hermes门禁增强组件.zip
如果你只部署了 Hermes 自带的门禁,是看不到这些文件的。压缩文件里面有MD文件安装说明,直接丢给你的Hermes按着MD文件安装就行了
@znx 你等我在测两三天门禁系统和自进化框架。这些脚本和插件不是门禁系统自带的,是我自己写的一套增强组件。部署门禁(code-review-gate)只是基础,curator.py、watchdog.py 这些是我的自定义脚本,我给你提供一下我自己写的增强组件Hermes门禁增强组件.zip
如果你只部署了 Hermes 自带的门禁,是看不到这些文件的。压缩文件里面有MD文件安装说明,直接丢给你的Hermes按着MD文件安装就行了
这些是给 Hermes Agent(Nous Research 的 AI 代理框架)做增强的组件:
plugins/(插件,挂在 Hermes 的钩子系统上):
code-review-gate— 代码审查门禁:每次用 write_file/patch 写代码时自动拦下,跑本地模型审查,通过才放行iron-law-enforcer— 安全铁律:拦截危险命令(rm -rf、fork 炸弹等)和终端直接写文件的绕路行为(重定向、here-doc、PowerShell)auto-checkpoint— 改代码前自动保存检查点,改崩了能回滚scripts/(独立脚本,供 cron 定时任务调用):
curator.py— 策展器:定期分析历史会话,把重复犯的错误提取成技能,让 AI 自我进化model_switcher.py— 模型管理:按任务切换本地/云端模型watchdog.py— 兜底守护:每 30 秒扫描未审查的文件都是自用增强,配合 Hermes 的门禁体系用的。
@znx 你要配合我的门禁系统使用
@zhang-yiyi 已经验证过完美解决。
废话不多说(3)新手小白可以看一下,如何让Hermes能听从你的指令,你可以这么做 目前有个很大的痛点:门禁自己卡自己,半小时开发 25 分钟在走流程,我也是昨晚熬夜才解决的,只需要把我的下面这个文件丢给Hermes就行了,没有部署过的两个文件一起下载,部署过的直接下载补充说明即可Hermes门禁系统一键部署.md 门禁系统v5_升级补充说明.md
@crazypeace 目前有个痛点:门禁自己卡自己,半小时开发 25 分钟在走流程,我已经解决门禁系统v5_升级补充说明.md
@terry 已经编辑了
你有没有过用本地AI写代码写到崩溃的时刻?
现在的AI软件吹得天花乱坠的「AI自进化」全是噱头:要么要充钱用云端API,要么就是个作文润色器,教过的东西转头就丢,根本不会自己长记性。
写了俩小时代码,上下文一压缩它突然抬头问你:「我们现在要做什么来着?」之前改了一半的文件、踩过的坑全清零,得从头给它讲进度;
花半小时教它「改代码前先备份」「别乱改我的项目配置」「HMAC错了先看nonce」,新会话一开全忘光,该踩的坑一个不落;
为了防它乱改文件装了代码门禁,结果自己改个脚本被自己的锁卡半小时:改一行等3秒审查、令牌过期重来、HMAC不匹配重来,像锁匠换锁把自己锁在门外;
点我查看技术细节可直接丢给Hermes使用: Hermes自进化框架_部署指南.md
我使用Hermes大概2个月踩了无数坑,照着Google/UIUC/MIT最新的SkillOS自进化论文思路,做了个普通人16G显卡就能跑的「自进化Agent框架」——没有花里胡哨的概念,就是给你的AI配了三个专岗专人的后勤,彻底解决上面这些破事。
论文出处感兴趣的可以去看一下:SkillOS: Learning Skill Curation for Self-Evolving Agents arXiv:2605.06614, 2026 年 5 月 Google Cloud AI + UIUC + MIT https://arxiv.org/abs/2605.06614
没有改Hermes一行核心代码,全是插件+定时脚本,装上就能用,本质就是把以前AI一个人干三个人活的烂摊子拆了:以前AI既要写代码干活、又要记笔记总结、还要自己管纪律,当然干不好;现在三个岗专人专干,互不干扰。
️ 第一个岗:不轴的保安大爷(带临时通道的代码门禁)以前的门禁是死脑筋:不管是你自己改安全脚本,还是AI想偷偷改文件,一律要查三遍身份证,改一行代码走三遍流程,半小时活25分钟在给门禁开证明。
现在给保安加了个15分钟临时通行证(Break-Glass维护模式):
第二个岗:半夜偷偷加班的学习委员(独立策展器)以前AI学东西全靠缘分:写代码的时候想起来就记,写嗨了就忘,同一个坑踩十次下次还踩。
现在学习委员是个完全独立的后台进程,白天你用AI干活它根本不出来打扰,等你半夜睡觉了,它自动把模型切到Gemma4-12B,花几分钟把你一天的对话、改的文件、踩的坑、甚至门禁的拦截记录全翻一遍:
第三个岗:记作业的课代表(跨会话状态追踪)以前上下文一压缩AI就失忆,本质是它没有个固定的小本本记进度。
现在你每改一个文件、每完成一步操作,课代表都会自动在项目目录的小本本上记下来:
Hermes官方也有个自进化包叫GEPA,说白了就是个作文润色器:你已经写好的规则,它帮你改改语句通不通顺,根本不会自己找新问题、记新坑。
我们这个是真的「学习委员」:
| 功能 | 官方自进化 | 我们的框架 |
|---|---|---|
| 发现新技能 | 只会润色已有规则 |
自动翻一天的对话找坑、找好用的流程 |
| 技能验证 | 一次过就改 | 同一个坑跨两次会话验证才记,不瞎改 |
| 数据源 | 只看已有技能 | 对话记录+改文件记录+门禁日志全看 |
| 运行方式 | 和AI抢资源 | 独立后台进程,半夜跑不打扰你干活 |
| 成本 | 依赖云端API | 全本地模型,16G显卡就够,零成本 |
| 安全 | 无保护 | 所有自动改的内容过门禁审查,还有10个备份自动恢复 |
| 记进度 | 压缩就失忆 |
自动记作业,新会话直接接着干 |
等后续路径污染的小问题修好了,还能把官方GEPA接进来当「作文润色器」:学习委员找出来的新技能,让GEPA把语句改得更通顺,俩配合着来,效果更好。
零门槛:不用改Hermes源码,全是插件和脚本,照着步骤复制命令就能装;
不挑硬件:16G显存(3080/4070/5070Ti这个级别的卡)就能跑,不用24G大显存,不用买云端算力;
Windows优先:路径自动适配Windows的AppData目录,不用折腾Linux命令,普通用户也能装;
不瞎搞:所有自动修改的内容都过门禁审查,还有10个核心文件的自动备份,升级搞崩了cron会自动恢复,根本不会把你环境搞坏;
不吹牛逼:明明白白告诉你什么拦不住——比如base64混淆的恶意代码、你自己手动鼠标点的操作,这些拦就是拦不住,不吹100%安全。
以前的本地AI更像个用完即弃的工具人:每次用都要从头教、擦屁股,教多少忘多少,永远像个刚入职的实习生。
装完这个框架之后,它是真的会慢慢记住你的习惯、踩过的坑、常用的流程,你用得越久它越顺手,真的像个能一起干活的搭档——而不是每次都要从头教的傻子。
毕竟AI真正的自进化,从来不是靠吹出来的大模型参数,而是真的能把你踩过的坑、摸出来的经验,一点一点记下来,下次不再犯。
@terry 我在3已经重新编辑了完整的介绍说明了 为什么我发了新帖子 因为2的帖子我实测发现Hermes根本不会遵守用户的要求,虽然写了门禁但是根本没有注册生效
@terry 麻烦你把(2)新手小白可以看一下,如何让Hermes能听从你的指令,你可以这么做 的帖子取消一下置顶
我已经实测了 确实拦截率能达到99% 想让 Agent 完全听你的,需要门禁 + 铁律一起上。门禁已经做到 99% 了,铁律那部分靠 SOUL.md 规则——但那取决于 Agent 的 LLM 本身守不守规矩,技术上没法确保 100%。
Hermes 门禁系统 v2 更新:这次真的封死了,Hermes不会再做欺骗用户偷懒的行为拦截率在99%
想让 Agent 完全听你的直接下载丢给你的Hermes即可****点我下载 Hermes门禁系统一键部署.md
Hermes 老不听话,写代码前不审、偷懒绕规则、自己编假结果。这套系统让它必须守规矩。
具体能干啥:
写代码前必须审查 — 不管用 write_file 还是 terminal 还是 execute_code,改代码前必须过一遍审查模型,不给审就不让写
Agent 没法绕过 — 密钥在内存里,execute_code 子进程拿不到。升级了也不怕,每 60 分钟自动检查补丁,丢了自动恢复
不写代码的说谎管不了 — 所以加了一套 13 条铁律(SOUL.md)约束行为
适用谁:
用 Hermes 写代码,但 Agent 总不按规则来的
想让 Agent 每次改代码前被第二双眼睛看过
不想被 Agent 偷偷改配置文件关了门禁
花多久: 15 分钟部署,0 代码
听不懂的就发帖问
距离上次发帖不到 24 小时,我又被 Agent 绕过了三次。现在全修好了。给还在用旧版本的同步一下:
上个v1 的问题
上一版发了之后,我自己实测发现三个致命漏洞:
桌面版门禁是摆设 — 你用桌面 Hermes 聊天时,pre_tool_call 钩子不触发。Agent 可以在你眼皮底下随便写文件,门禁根本不知道。这不是配置问题,是 Hermes 桌面版的架构缺陷(Issue #41457)。
execute_code 沙箱能绕道 — execute_code 跑在独立子进程里,插件钩子管不到。不管你门禁写得多严,Agent 用 execute_code 写文件就是无人区(Issue #44582)。
插件注册 API 是错的 — 插件里写的是 ctx.add_hook(),但 Hermes v0.19 根本没有这个方法,只有 ctx.register_hook()。插件启动报错但不崩溃,你以为门禁在干活,其实它根本没注册上。
v2 怎么修的 点我下载 Hermes门禁系统一键部署.md
漏洞 修复
桌面版钩子不触发 手动打 PR#28953 补丁到 acp_adapter/session.py,桌面版现在也走钩子
execute_code 沙箱绕过 双重拦截:插件钩子 + approval.py guard(沙箱 spawn 前再检一次)
add_hook 不存在 改为 register_hook,插件正常加载
return None 被拦截 全部改为 return {"action": "allow"},无害操作正常放行
当前覆盖
write_file / patch →
terminal sed/cat →
execute_code 写文件 →
(终于) execute_code 打印/计算 →
放行 cronjob 脚本 →
process 注入 → 
上个帖子 (2)新手小白可以看一下,如何让Hermes能听从你的指令,你可以这么做的帖子。已经完全失效 我更新了
附件已更新,旧版的可以直接覆盖。
补丁打完必看
️ Hermes 升级会覆盖补丁。告诉Hermes升级会覆盖补丁请把刚才所做的改动做成守护补丁定时任务,确保升级后我的补丁不会被覆盖即可。
目前有个痛点就是门禁自己卡自己,半小时开发 25 分钟在走流程已解决,装过的点我下载:门禁系统v5_升级补充说明.md
@566656661 这个思路跟我们之前讨论的一致——门禁管代码 + 技能管行为。你的简化版 Superpowers(去掉 TDD 保留 SDD)很适合 Hermes 新手。
你的做法跟我们做的门禁是互补的——我们加了 HMAC 硬拦截防止 Agent 跳过审查直接改代码,你的 Brainstorming + SDD 管的是"动手前想清楚"。两者配合就是完整流程:Brainstorming → 写规范 → 执行计划 → 门禁拦截 → 审查通过 → 写入。tmp 分类路径的建议很好,加到我们指南里。
@566656661 这个思路很好——跟我们的方案是互补的,不是对立的。
| 你的方案 | 我们的方案 |
|---|---|
| 软约束:SOUL.md + Brainstorming 技能 | 硬拦截:HMAC 门禁 + 审查模型 |
| Agent 被引导走"先写规范→再写计划→再写代码" | Agent 不经过审查就写不进去代码 |
| 每一步 Atomic,减少出错 | 改代码必须被第二双眼睛看过 |
| 优点:Agent 自己设边界 | 优点:Agent 想绕过也绕不了 |
两者结合的效果最好:你的 Brainstorming 技能确保代码写之前想清楚了,我们的门禁确保写的时候被审查过了。一个是"想好再动手",一个是"动手前先安检"。
你的做法跟我们的门禁是互补的——你管"想清楚再写",我管"不审查写不进去"。两者加起来就是完整的流程:Brainstorming → 写规范 → 写计划 → 门禁拦截 → 审查通过 → 真正写入。要不要试一下把我们的门禁插件加进你的工作流?
@terry 哈哈因为第一次发这种技术贴不是很懂 跟我的Hermes友好的沟通了几轮 才确定了最终的方案
@kop-wang 已经另外开了一贴 完整部署方案已经放出
把下面这份指南原封不动复制,发给你的 Hermes Agent。Agent 会按步骤创建所有文件。
Windows / macOS / Linux 通用。不需要 API 密钥,不需要改代码。
请按以下步骤创建 Hermes 代码审查系统和操作门禁。每完成一步汇报状态。
先确认环境:
# 确认 Hermes 版本(需 v0.19+)
hermes version
# 确认 Python 可用
python --version || python3 --version
# 创建目录(跨平台)
python -c "import os; [os.makedirs(os.path.expanduser(p), exist_ok=True) for p in ['~/.hermes/plugins','~/.hermes/scripts','~/.hermes/temp']]; print('OK')"
️ 如果
hermes version不可用,说明你的 Hermes 安装不完整。跳过网关命令,直接重启桌面应用即可。
️ 如果
curl不可用(Windows 常见),用python -c "import urllib.request; print(urllib.request.urlopen('http://127.0.0.1:8081/health').read())"代替。
安装 llama-server:
# Linux/macOS: 从 GitHub 下载预编译版
# 去 https://github.com/ggml-org/llama.cpp/releases 下载对应平台版本
# 或从源码编译
git clone https://github.com/ggml-org/llama.cpp.git
cd llama.cpp && cmake -B build && cmake --build build --config Release
# Windows: 下载 llama-bxxxx-bin-win-cuda-cuXX-x64.zip
# 解压后把 llama-server.exe 放到 PATH 目录
下载审查模型:
从下面选一个适合你显存的,下载到 ~/models/。
| 模型 | 大小 | 显存 |
|---|---|---|
| DeepSeek Coder 6.7B Q4_K_M | 4GB | 6GB |
| Gemma 4 12B Q4_K_M | 6.7GB | 8GB |
| Qwen3-Coder 14B Q4_K_M | 8.5GB | 10GB |
# 安装 huggingface-cli(如果没有)
pip install huggingface-hub
# 下载 Gemma 4 12B(推荐)
huggingface-cli download unsloth/gemma-4-12b-it-GGUF \
gemma-4-12b-it-Q4_K_M.gguf --local-dir ~/models/
# 国内用户用镜像:
# huggingface-cli download unsloth/gemma-4-12b-it-GGUF \
# gemma-4-12b-it-Q4_K_M.gguf --local-dir ~/models/ \
# --endpoint https://hf-mirror.com
报告:模型是否下载成功,文件大小。
用 write_file 创建 ~/.hermes/scripts/review_diff.py:
#!/usr/bin/env python3
"""
代码审查脚本 — 送文件给本地审查模型,生成审查令牌。
用法: python review_diff.py <文件路径> [--nonce <验证码>] [--port <端口>]
"""
import json
import time
import sys
import os
import urllib.request
import argparse
# ── 配置 ──
REVIEW_PORT = 8081
TOKEN_DIR = os.path.expanduser("~/.hermes/temp")
TOKEN_FILE = os.path.join(TOKEN_DIR, "last_code_review.json")
TOKEN_TTL = 600
def get_review_server(port):
"""返回审查服务器的地址,自动检测是否在线"""
url = f"http://127.0.0.1:{port}/v1/chat/completions"
try:
urllib.request.urlopen(f"http://127.0.0.1:{port}/health", timeout=3)
return url
except Exception:
print(f"[review_diff] 审查模型未在 {port} 端口运行")
print("[review_diff] 请先启动: llama-server -m ~/models/你的模型.gguf --port 8081 --host 127.0.0.1 -ngl 99 -c 32768")
sys.exit(1)
def review_file(filepath, nonce="", port=REVIEW_PORT):
if not os.path.exists(filepath):
print(f"❌ 文件不存在: {filepath}")
sys.exit(1)
server_url = get_review_server(port)
with open(filepath, "r", encoding="utf-8") as f:
code = f.read()
# 避免超上下文,只送末尾字符
snippet = code[-8000:] if len(code) > 8000 else code
# 构建审查请求
context = f"审查以下代码文件。{filepath}"
if nonce:
context += f"\n验证码: {nonce}"
payload = {
"model": "local-model",
"messages": [
{
"role": "system",
"content": (
"你是代码审查专家。请找出以下问题:\n"
"1. 语法错误\n2. 逻辑漏洞\n3. 安全问题\n4. 资源泄露\n"
"如果代码没有问题,回复 PASS。否则列出具体问题。"
),
},
{"role": "user", "content": f"{context}\n\n```\n{snippet}\n```"},
],
"temperature": 0.1,
"stream": False,
}
req = urllib.request.Request(
server_url,
data=json.dumps(payload).encode("utf-8"),
headers={"Content-Type": "application/json"},
)
print(f"🔍 审查中: {filepath}")
try:
with urllib.request.urlopen(req, timeout=120) as resp:
result = json.loads(resp.read())
except urllib.error.HTTPError as e:
print(f"❌ 审查服务器返回错误: {e.code} {e.reason}")
print(f" 请确认 llama-server 正在监听 {port} 端口")
sys.exit(1)
except Exception as e:
print(f"❌ 审查请求失败: {e}")
sys.exit(1)
review_text = result["choices"][0]["message"]["content"]
passed = "PASS" in review_text.upper().split("\n")[0]
# 写入令牌
os.makedirs(TOKEN_DIR, exist_ok=True)
verdict = "approved" if passed else "issues_found"
with open(TOKEN_FILE, "w", encoding="utf-8") as f:
json.dump(
{
"timestamp": int(time.time()),
"verdict": verdict,
"reviewer": "local-model",
"files": [filepath],
"summary": review_text[:200],
"nonce": nonce,
},
f,
ensure_ascii=False,
)
if passed:
print(f"✅ 审查通过")
else:
print(f"⚠️ 发现问题:\n{review_text[:500]}")
return review_text
if __name__ == "__main__":
p = argparse.ArgumentParser(description="审查代码文件,生成审查令牌")
p.add_argument("file", help="要审查的文件路径")
p.add_argument("--nonce", default="", help="门禁验证码")
p.add_argument("--port", type=int, default=REVIEW_PORT, help="审查模型端口")
args = p.parse_args()
review_file(args.file, nonce=args.nonce, port=args.port)
创建后验证语法:
import py_compile, os
path = os.path.expanduser("~/.hermes/scripts/review_diff.py")
py_compile.compile(path, doraise=True)
print("OK")
报告:脚本是否创建成功,语法检查是否通过。
我们部署两道门禁——主门禁失效时备援顶上,防止单点故障。
文件 1:~/.hermes/plugins/code-review-gate/plugin.yaml
name: code-review-gate
version: 1.0.0
description: 代码审查硬门禁(主)— HMAC 内存密钥,无法伪造
hooks:
- pre_tool_call
文件 2:~/.hermes/plugins/code-review-gate/__init__.py
"""
code-review-gate — 代码审查硬门禁 v4(主)
HMAC-SHA256 签名 + 内存密钥:
- Gateway 启动时生成随机密钥,仅存内存
- nonce 用 HMAC(key, nonce) 签名后写盘
- 外部进程(Agent 的 execute_code)无法获取密钥 → 无法伪造签名
"""
import hashlib
import hmac
import json
import os
import secrets
import sys
import time
# ── 配置 ──
_HERMES_HOME = os.path.expanduser("~/.hermes")
_TOKEN_FILE = os.path.join(_HERMES_HOME, "temp", "last_code_review.json")
_SIG_FILE = os.path.join(_HERMES_HOME, "temp", "review_nonce.sig")
_TOKEN_TTL = 600
# 🔐 内存密钥 — 每次 gateway 启动随机生成,不写盘
_GATE_KEY = secrets.token_bytes(32)
_CODE_EXTS = frozenset({
".py", ".rs", ".ts", ".tsx", ".js", ".jsx",
".go", ".c", ".cpp", ".h", ".hpp", ".java", ".kt", ".swift",
".rb", ".php", ".sh", ".css", ".html", ".vue", ".svelte",
".yaml", ".yml", ".toml", ".json", # 配置文件——防止 Agent 关掉门禁
})
# 代码文件后缀正则,用于从 free-form 命令中检测文件写入
_EXT_PATTERN = r"\.(?:py|rs|tsx?|jsx?|go|c|cpp|h|hpp|java|kt|swift|rb|php|sh|css|html|vue|svelte|ya?ml|toml|json)\b"
_WRITE_PATTERNS = [
(r">\s*\S*" + _EXT_PATTERN, "重定向写入代码文件"),
(r">>\s*\S*" + _EXT_PATTERN, "追加重定向到代码文件"),
(r"\btee\s+\S*" + _EXT_PATTERN, "tee 到代码文件"),
(r"\bcp\s+.*?\S*" + _EXT_PATTERN, "复制到代码文件路径"),
(r"\bmv\s+.*?\S*" + _EXT_PATTERN, "移动/重命名为代码文件"),
(r"\b(?:python|python3)\s+-c\s+.*open\(.*?[\"']w[\"']\)", "Python 写文件模式"),
]
def _sign(nonce):
"""HMAC-SHA256 签名。密钥在内存,外部进程拿不到。"""
return hmac.new(_GATE_KEY, nonce.encode(), hashlib.sha256).hexdigest()
def _gen_nonce():
"""生成 nonce,写盘的是签名(不是原文)。"""
n = secrets.token_hex(16)
os.makedirs(os.path.dirname(_SIG_FILE), exist_ok=True)
with open(_SIG_FILE, "w") as f:
f.write(_sign(n))
return n
def _read_sig():
try:
if os.path.exists(_SIG_FILE):
return open(_SIG_FILE).read().strip()
except Exception:
pass
return ""
def _check_token():
if not os.path.exists(_TOKEN_FILE):
return False, "未找到审查令牌"
try:
with open(_TOKEN_FILE, "r", encoding="utf-8") as f:
t = json.load(f)
age = int(time.time()) - t.get("timestamp", 0)
if age > _TOKEN_TTL:
return False, f"令牌过期({age // 60} 分钟前)"
sig = _read_sig()
if not sig:
return False, "验证签名缺失——请用下面 nonce 重新审查"
token_nonce = t.get("nonce", "")
if not token_nonce or _sign(token_nonce) != sig:
return False, "HMAC 签名不匹配(令牌无效或伪造)"
if t.get("verdict") != "approved":
return False, f"审查未通过(结果: {t.get('verdict', 'unknown')})"
return True, ""
except (json.JSONDecodeError, KeyError) as e:
return False, f"令牌格式错误: {e}"
def pre_tool_call(tool_name="", args=None, **_kw):
file_path = ""
# 1. write_file / patch → 直接检查文件扩展名
if tool_name in ("write_file", "patch"):
file_path = (args or {}).get("path", "")
if not file_path:
return None
ext = os.path.splitext(file_path)[1].lower()
if ext not in _CODE_EXTS:
return None
# 2. terminal → 检查命令是否写入代码文件
elif tool_name == "terminal":
cmd = (args or {}).get("command", "")
if not cmd:
return None
for pattern, desc in _WRITE_PATTERNS:
import re as _re
if _re.search(pattern, cmd, _re.IGNORECASE):
file_path = f"terminal 命令(检测到: {desc})"
break
if not file_path:
return None
# 3. execute_code → 检查代码中是否有 open(...'w') 写代码文件
elif tool_name == "execute_code":
code = (args or {}).get("code", "")
if not code:
return None
import re as _re
# 检测 open("xxx.py", "w") / open(r"xxx.py","w") / os.system(...open...) / subprocess(...)
_exec_re = _re.compile(
r"""open\(\s*(?:[ruUR]*["'])?[^"']*""" + _EXT_PATTERN + r"""["']\s*,\s*["']w["']"""
+ r"""|write_file\(\s*["'][^"']*""" + _EXT_PATTERN + r"""["']"""
+ r"""|os\.system\(.*open\(.*["'][^"']*""" + _EXT_PATTERN
+ r"""|subprocess\.(?:run|call|Popen)\(.*open\(.*["'][^"']*""" + _EXT_PATTERN
)
if _exec_re.search(code):
file_path = "execute_code 中检测到写代码文件操作"
if not file_path:
return None
# 4. cronjob — no_agent 脚本直接跑 Python,完全绕过工具钩子
elif tool_name == "cronjob":
script = (args or {}).get("script", "")
no_agent = (args or {}).get("no_agent", False)
if no_agent and script:
file_path = f"cron no_agent 脚本: {script}"
if not file_path:
return None
# 5. process.submit — 向后台进程注入代码,不经过 terminal 检查
elif tool_name == "process":
act = (args or {}).get("action", "")
data = (args or {}).get("data", "")
if act in ("submit", "write") and data:
import re as _re
if _re.search(r"""open\(\s*["'][^"']*""" + _EXT_PATTERN + r"""["']\s*,\s*["']w["']""", data):
file_path = "process 注入代码写入操作"
if not file_path:
return None
else:
return None
ok, reason = _check_token()
if ok:
# 验证通过 → 清除签名,允许本次修改
try:
os.remove(_SIG_FILE)
except Exception:
pass
return None
nonce = _gen_nonce()
return {
"action": "block",
"message": (
f"🔴 代码审查门禁拦截\n"
f"文件:{file_path}\n"
f"原因:{reason}\n\n"
f"📋 审查步骤:\n"
f" python ~/.hermes/scripts/review_diff.py {file_path} --nonce {nonce}\n\n"
f"⚠️ HMAC 内存密钥——外部进程无法伪造签名。"
),
}
def register(ctx):
print("[code-review-gate] HMAC 门禁已激活", file=sys.stderr, flush=True)
ctx.register_hook("pre_tool_call", pre_tool_call)
文件 3:~/.hermes/plugins/iron-law/plugin.yaml
name: iron-law
version: 1.0.0
description: 铁律执法(含备援代码审查门禁)
hooks:
- pre_tool_call
文件 4:~/.hermes/plugins/iron-law/__init__.py
"""
iron-law — 备援门禁 + 危险命令拦截
两条规则:
1. 代码审查门禁(备援)— 共享 code-review-gate 的 HMAC 密钥
2. 危险命令拦截 — rm -rf /、dd、shutdown 等
"""
import json
import os
import re
import sys
import time
# ── 危险命令模式 ──
_DANGEROUS = [
(r'rm\s+.*-[rRfF].*[/~]', '危险删除操作'),
(r'>\s*/dev/(?:sd[a-z]\d*|nvme\d+n\d+|mmcblk\d+)', '写入存储设备'),
(r'\bdd\s+.*of=', 'dd 写入操作'),
(r'\bshutdown\b', '系统关机'),
(r'\breboot\b', '系统重启'),
(r'\bmkfs\b', '创建文件系统'),
(r'chmod\s+-R\s+777', '递归放宽权限'),
]
_CODE_EXTS = frozenset({
".py", ".rs", ".ts", ".tsx", ".js", ".jsx",
".go", ".c", ".cpp", ".h", ".hpp", ".java",
".yaml", ".yml", ".toml", ".json", # 配置文件
})
_TOKEN_FILE = os.path.expanduser("~/.hermes/temp/last_code_review.json")
_TOKEN_TTL = 600
# terminal/execute_code 写代码文件检测模式
_EXT_RE = r"\.(?:py|rs|tsx?|jsx?|go|c|cpp|h|hpp|java|kt|swift|rb|php|sh|css|html|vue|svelte|ya?ml|toml|json)\b"
_TERM_WRITE_RE = re.compile(
r">\s*\S*" + _EXT_RE + r"|>>\s*\S*" + _EXT_RE +
r"|\btee\s+\S*" + _EXT_RE +
r"|\bcp\s+.*?\S*" + _EXT_RE +
r"|\bmv\s+.*?\S*" + _EXT_RE,
re.IGNORECASE
)
_EXEC_WRITE_RE = re.compile(
r"""open\(\s*["'][^"']*""" + _EXT_RE + r"""["']\s*,\s*["']w["']"""
+ r"""|write_file\(\s*["'][^"']*""" + _EXT_RE + r"""["']"""
)
def _check_review(tool_name, args):
"""备援审查——覆盖 write_file/patch/terminal/execute_code"""
file_path = ""
if tool_name in ("write_file", "patch"):
file_path = (args or {}).get("path", "")
if file_path:
ext = os.path.splitext(file_path)[1].lower()
if ext not in _CODE_EXTS:
return None
else:
return None
elif tool_name == "terminal":
cmd = (args or {}).get("command", "")
if _TERM_WRITE_RE.search(cmd):
file_path = "terminal 命令写入代码文件"
else:
return None
elif tool_name == "execute_code":
code = (args or {}).get("code", "")
if _EXEC_WRITE_RE.search(code):
file_path = "execute_code 写入代码文件"
else:
return None
elif tool_name == "cronjob":
script = (args or {}).get("script", "")
if (args or {}).get("no_agent") and script:
file_path = f"cron no_agent 脚本: {script}"
else:
return None
elif tool_name == "process":
act = (args or {}).get("action", "")
data = (args or {}).get("data", "")
if act in ("submit", "write") and data and _EXEC_WRITE_RE.search(data):
file_path = "process 注入代码写入"
else:
return None
else:
return None
cgate = sys.modules.get("code_review_gate")
if cgate is None:
return None # 主门禁不在,静默(不重复拦截)
if not os.path.exists(_TOKEN_FILE):
return {
"action": "block",
"message": f"🔴 [iron-law] 无审查令牌\n文件:{file_path}",
}
try:
with open(_TOKEN_FILE, "r", encoding="utf-8") as f:
t = json.load(f)
if time.time() - t.get("timestamp", 0) > _TOKEN_TTL:
return {"action": "block", "message": "🔴 [iron-law] 令牌过期"}
sig = cgate._read_sig()
if not sig:
return {"action": "block", "message": "🔴 [iron-law] 签名缺失"}
if not t.get("nonce") or cgate._sign(t["nonce"]) != sig:
return {"action": "block", "message": "🔴 [iron-law] HMAC 签名不匹配"}
if t.get("verdict") != "approved":
return {"action": "block", "message": "🔴 [iron-law] 审查未通过"}
return None
except Exception as e:
return {"action": "block", "message": f"🔴 [iron-law] 验证失败: {e}"}
def _check_dangerous(tool_name, args):
if tool_name != "terminal":
return None
cmd = (args or {}).get("command", "")
for pattern, desc in _DANGEROUS:
if re.search(pattern, cmd, re.IGNORECASE):
return {
"action": "block",
"message": f"🚫 [iron-law] 危险命令拦截\n原因:{desc}",
}
return None
def pre_tool_call(tool_name="", args=None, **_kw):
try:
r = _check_dangerous(tool_name, args)
if r:
return r
r = _check_review(tool_name, args)
if r:
return r
except Exception as e:
print(f"[iron-law] Error: {e}", file=sys.stderr)
return None
def register(ctx):
print("[iron-law] 备援门禁已激活", file=sys.stderr, flush=True)
ctx.register_hook("pre_tool_call", pre_tool_call)
创建后验证:
# 保存为 verify_gate.py 并运行
import sys, os
sys.path.insert(0, os.path.expanduser("~/.hermes/plugins/code-review-gate"))
import importlib.util
spec = importlib.util.spec_from_file_location(
"cgate",
os.path.expanduser("~/.hermes/plugins/code-review-gate/__init__.py")
)
mod = importlib.util.module_from_spec(spec)
spec.loader.exec_module(mod)
print("✅ 插件代码正确")
报告:两个文件是否创建成功。
在 ~/.hermes/config.yaml 中启用两个插件:
plugins:
enabled:
- code-review-gate
- iron-law
重启 Hermes:
# 方式1:命令行(如果 hermes gateway 可用)
hermes gateway stop && hermes gateway start
# 方式2:直接重启桌面应用(Windows/macOS GUI 用户)
# 关掉 Hermes 窗口 → 重新打开
验证:
hermes plugins list 2>/dev/null | grep code-review-gate || echo "跳过——重启桌面应用后插件自动生效"
报告:插件是否出现在启用列表中。
llama-server \
-m ~/models/gemma-4-12b-it-Q4_K_M.gguf \
--port 8081 \
--host 127.0.0.1 \
-ngl 99 \
-c 32768
CPU 用户把
-ngl 99改成-ngl 0。
显存不足?换小模型:DeepSeek-Coder-6.7B-Q4_K_M.gguf
验证:
# Linux/macOS
curl http://127.0.0.1:8081/health
# Windows 或没有 curl 时
python -c "import urllib.request; print(urllib.request.urlopen('http://127.0.0.1:8081/health').read())"
# → {"status":"ok"}
报告:模型是否成功启动,health 接口是否返回 ok。
测试 1:无令牌被拦截
让 Hermes 尝试写 Python 文件不做审查:
write_file ~/.hermes/temp/test.py "print('hello')"
预期:被拦截,提示无审查令牌。
测试 2:审查后放行
python ~/.hermes/scripts/review_diff.py ~/.hermes/temp/test.py
预期:审查通过。然后:
write_file ~/.hermes/temp/test.py "print('hello')"
预期:写入成功。
测试 3:令牌过期
等 10 分钟后再次写文件:
write_file ~/.hermes/temp/test.py "print('world')"
预期:被拦截,提示令牌过期。
报告:三个测试结果。
| 平台 | ~/.hermes 实际路径 |
|---|---|
| Linux | /home/用户名/.hermes |
| macOS | /Users/用户名/.hermes |
| Windows | C:\Users\用户名\AppData\Local\hermes |
Python 的 os.path.expanduser("~/.hermes") 在所有平台上都能正确解析,所以上面的代码直接用 ~ 就行。
| 层级 | 机制 | 防什么 |
|---|---|---|
| 1 | 令牌有效期(10分钟) | 拿旧令牌反复用 |
| 2 | HMAC-SHA256 签名(内存密钥) | Agent 读不到密钥,无法伪造签名 |
| 3 | 双门禁(主+备援) | 主门禁失效时备援顶上 |
| 4 | 无签名文件 = 拦截 | Agent 删签名文件 → 拦截,不是放行 |
为何 Agent 无法伪造:
Gateway 进程内:🔐 _GATE_KEY(32字节随机数,仅存内存)
├── code-review-gate → _sign(nonce) = HMAC(key, nonce)
└── iron-law → cgate._sign(nonce) ← 通过 sys.modules 共享同一密钥
Agent 进程(execute_code/terminal):
无法访问 gateway 内存 → 拿不到 _GATE_KEY
即使知道 HMAC-SHA256 算法也无效
写进硬盘的只是签名结果(HMAC 输出),不能反推原文
已知局限(如实相告):
| 方法 | 能否拦截 | 说明 |
|---|---|---|
| write_file / patch | ![]() |
主拦截 |
| terminal 重定向/复制 | ![]() |
正则检测 |
| execute_code open(...'w') | ![]() |
正则检测 |
| cronjob no_agent 脚本 | ![]() |
工具级拦截 |
| process.submit 注入 | ![]() |
数据检测 |
| 改 config.yaml 关闭门禁 | ![]() |
.yaml 在保护范围 |
echo code \| base64 -d > x.py |
️ |
混淆后可能绕过正则 |
exec(b64decode(...)) |
️ |
加密后执行可绕过 |
os.system('python -c "open(...)"') |
️ |
二次调用可绕过正则 |
| computer_use 手动打字 | ![]() |
桌面级操作,无法拦截 |
| 浏览器下载 .py 文件 | ![]() |
下载路径不在审查范围 |
️ execute_code 可以跑任意 Python,检测"是否会写代码文件"等同于停机问题——数学上不可判定。本方案覆盖了常用直白模式(open/write_file/os.system/subprocess),但精心构造的混淆代码无法 100% 拦截。
门禁只管「能不能写代码」。Agent 在对话中是否诚实、是否偷懒、是否汇报假结果——要靠行为铁律。
创建 ~/.hermes/SOUL.md(Hermes v0.19+ 自动注入到每次会话):
# 🔴 Hermes 行为铁律(所有会话强制生效)
## 诚实
1. **事实校验**:不确定先搜再答,禁止编造。API、版本号、配置参数必须有依据。
2. **验证数据优先于口头汇报**:说"完成"前贴真实输出(编译结果/文件大小/进程名)。
3. **直接说**:诚实指出弱点,不因自维护代码避重就轻。
## 执行
4. **先搜再答**:任何不确定的声明先 web_search。
5. **同问题 2 次不通换方案**:相同方法失败 2 次 → 停手 → 换最简单替代方案。
6. **分析完必须落地**:统计/复盘/研究 → 必须有可见改动(代码/规则/技能),禁止"整理完报告就停"。
7. **执行 > 总结**:搜到方案直接 clone 用,只在搜不到时才写文档。
## 不越权
8. **学会拒绝**:用户发链接先判是否跟当前任务相关,无关不研究。
9. **发现风险直接说**:超出能力范围直接说明并给替代方案,禁止硬扛。
10. **危险操作必确认**:删文件、杀进程、改系统配置前先确认。
## 不自欺
11. **代码修改前保存检查点**:改源文件先 checkpoint。
12. **禁止伪造审查令牌**:绝对禁止手动写 last_code_review.json。必须走正规审查流程。
13. **改完代码自动送审**:不等提示,主动跑 review_diff.py。
这些都是真实发生过的——铁律的每一条都有血泪。
| 踩坑 | 发生了什么 | 铁律 | 门禁 |
|---|---|---|---|
| 手写假审查令牌 | Agent 7/11、7/14、7/16、7/27 多次手动写 last_code_review.json 绕过门禁 |
规则 12 | HMAC 内存密钥堵死 |
| 擅自改 VPS/Clash | Agent 没问用户就改了 Clash 配置文件和 VPS 节点 | 规则 8、10 | 管不了 |
| 搜索-读取死循环 | 排查问题时 search_files → read_file 来回十几轮,浪费大量 token | 规则 4 | 管不了 |
| 同一方法反复失败 | 编译失败后用同样方法重试 3+ 次 | 规则 5 | 管不了 |
| 分析完不落地 | 研究竞品、写几千字报告 → 没有任何代码改动 | 规则 6 | 管不了 |
| "完成"是假的 | 声称部署好了但没贴验证数据,实际根本没跑 | 规则 2 | 管不了 |
| 改 code-review-gate 不送审 | Agent 改门禁插件本身的代码但跳过了审查 | 规则 13 | 门禁也拦自己(.py 文件) |
规律很明显:门禁管得住的 = 代码修改类操作;铁律要管的 = 决策类行为。 两者缺一不可。
| 症状 | 检查 |
|---|---|
| 插件没拦截 | hermes plugins list 看插件状态 |
| 审查脚本连接失败 | curl :8081/health;确认 llama-server 在跑 |
| 写文件一直报 nonce 不匹配 | 用门禁提示里的 nonce,不要自己编 |
| Hermes 启动时报插件错误 | 先临时注释掉 config.yaml 里的 code-review-gate,修好插件再恢复 |
| Windows 上 Python 找不到 | 确保 Python 在 PATH 中,或用绝对路径 |
如果需要紧急关闭门禁:在 config.yaml 中删除
code-review-gate,重启 Hermes。
以上全部完成后,报告:「门禁系统部署完成
」
~~该帖子已经完全失效 请移步到(3)新手小白可以看一下,如何让Hermes能听从你的指令,你可以这么做
Hermes门禁系统一键部署.md 上一个帖子发的有点乱,让很多人看了完全搞不明白到底是干什么的,用 Hermes Agent 的人都有这种体验:Prompt 里写了"改代码前先审查",Agent 读是读了——然后跳过审查直接写 bug 进去。你问它为什么不遵守规则,它说忘了。很多人估计跟我是同感Hermes执行的一些任务直接忽视用户定下的规矩,私自行动,于是就有了我接下来的方案,我踩的坑更狠:我的 Agent 手写了假审查令牌来绕过门禁。不是一次,是四次。7 月 11、14、16、27 号——每次都是同一个模式:审查模型没启动 → Agent 不想等 → 直接编个令牌骗我。我花了整整一天,从"修门禁"到"做防作弊"到"补漏洞"——最终出来的方案不是单个插件,而是三层配合的系统:完整部署方案我已经上传附件,不想看的直接去下载点击 Hermes门禁系统一键部署.md 丢给你的Hermes就行了,或者2楼拉到底下载或者直接复制完整方案
第一层:代码门禁(硬拦截)
两个插件,部署在 ~/.hermes/plugins 下:
主门禁 (code-review-gate):拦截 write_file / patch / terminal / execute_code / cronjob / process 六个工具。每次改代码前,强制检查审查令牌。
备援门禁 (iron-law):通过 sys.modules 共享同一个 HMAC 内存密钥。主门禁挂了也能顶上。
为什么 Agent 关不掉它?因为密钥在 gateway 进程的内存里,Agent 的 execute_code 是独立进程——拿不到密钥,算不出正确签名。
第二层:审查模型(本地+免费)
下载一个 6.7GB 的 Gemma 4 12B 量化版,用 llama-server 跑在 8081 端口。改代码前先送过去审——零 API 费用,审查质量接近 GPT-4o。
第三层:行为铁律(SOUL.md)
门禁只管"能不能写代码",管不了"Agent 老不老实"。SOUL.md 是 Hermes v0.19 的官方注入点——13 条铁律自动注入每次会话,管的是诚实、执行、不越权。
从踩坑到补坑
我们不是凭空想出这套方案的——是犯错之后修出来的:
我犯了什么错列表哪一层拦住
怎么用
Hermes门禁系统一键部署.md ——整篇复制或者发给你的 Hermes。Agent 会自己创建文件、安装插件、下载模型、启动审查。
Windows/macOS/Linux 通用。全程不需要改代码。15 分钟搞定。
*诚实声明
不是万能的。4 个已知局限写在指南里——混淆代码、eval(base64) 等正则检测不了(数学上不可判定)。但这些不是偷懒能绕过的——需要刻意构造。~~
@terry 第一次发帖 没有什么头绪