Hermes Agent 门禁系统 + 本地审查模型:一键部署 点我 Hermes门禁系统一键部署.md
把下面这份指南原封不动复制,发给你的 Hermes Agent。Agent 会按步骤创建所有文件。
Windows / macOS / Linux 通用。不需要 API 密钥,不需要改代码。
发给 Hermes 的完整指令
请按以下步骤创建 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())" 代替。
步骤 1:安装 llama.cpp + 下载审查模型
安装 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
报告:模型是否下载成功,文件大小。
步骤 2:创建审查脚本
用 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")
报告:脚本是否创建成功,语法检查是否通过。
步骤 3:创建门禁插件(主门禁 + 备援门禁)
我们部署两道门禁——主门禁失效时备援顶上,防止单点故障。
文件 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("✅ 插件代码正确")
报告:两个文件是否创建成功。
步骤 4:启用插件
在 ~/.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 "跳过——重启桌面应用后插件自动生效"
报告:插件是否出现在启用列表中。
步骤 5:启动审查模型
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。
步骤 6:功能测试
测试 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。
以上全部完成后,报告:「门禁系统部署完成
」
Hermes门禁系统一键部署.md