mirror of
https://github.com/NousResearch/hermes-agent.git
synced 2026-07-15 14:22:43 +00:00
feat: make smart approvals the default (#62661)
This commit is contained in:
parent
2043436af1
commit
62a76bd3d5
10 changed files with 65 additions and 29 deletions
|
|
@ -2523,15 +2523,15 @@ DEFAULT_CONFIG = {
|
|||
},
|
||||
|
||||
# Approval mode for dangerous commands:
|
||||
# manual — always prompt the user (default)
|
||||
# smart — use auxiliary LLM to auto-approve low-risk commands, prompt for high-risk
|
||||
# manual — always prompt the user
|
||||
# smart — use auxiliary LLM to auto-approve low-risk commands (default)
|
||||
# off — skip all approval prompts (equivalent to --yolo)
|
||||
#
|
||||
# cron_mode — what to do when a cron job hits a dangerous command:
|
||||
# deny — block the command and let the agent find another way (default, safe)
|
||||
# approve — auto-approve all dangerous commands in cron jobs
|
||||
"approvals": {
|
||||
"mode": "manual",
|
||||
"mode": "smart",
|
||||
"timeout": 60,
|
||||
"cron_mode": "deny",
|
||||
# User-defined deny rules: fnmatch globs matched against terminal
|
||||
|
|
|
|||
|
|
@ -552,10 +552,10 @@ hermes config set privacy.redact_pii false # disable (default)
|
|||
|
||||
### Command approval prompts
|
||||
|
||||
By default (`approvals.mode: manual`), Hermes prompts the user before running shell commands flagged as destructive (`rm -rf`, `git reset --hard`, etc.). The modes are:
|
||||
By default (`approvals.mode: smart`), Hermes asks an auxiliary LLM to assess shell commands flagged as destructive (`rm -rf`, `git reset --hard`, etc.). The modes are:
|
||||
|
||||
- `manual` — always prompt (default)
|
||||
- `smart` — use an auxiliary LLM to auto-approve low-risk commands, prompt on high-risk
|
||||
- `smart` — auto-approve a low-risk command once, deny high-risk commands, and prompt when uncertain (default)
|
||||
- `manual` — always prompt
|
||||
- `off` — skip all approval prompts (equivalent to `--yolo`)
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -55,6 +55,11 @@ class TestApprovalModeParsing:
|
|||
|
||||
|
||||
class TestSmartApproval:
|
||||
def test_smart_is_the_default_approval_mode(self):
|
||||
from hermes_cli.config import DEFAULT_CONFIG
|
||||
|
||||
assert DEFAULT_CONFIG["approvals"]["mode"] == "smart"
|
||||
|
||||
def test_smart_approval_uses_call_llm(self):
|
||||
response = SimpleNamespace(
|
||||
choices=[SimpleNamespace(message=SimpleNamespace(content="APPROVE"))]
|
||||
|
|
@ -68,6 +73,35 @@ class TestSmartApproval:
|
|||
assert mock_call.call_args.kwargs["temperature"] == 0
|
||||
assert mock_call.call_args.kwargs["max_tokens"] == 16
|
||||
|
||||
def test_smart_approval_does_not_allowlist_the_pattern_for_session(self, monkeypatch):
|
||||
session_key = "test-smart-per-command"
|
||||
command = "python -c \"print('hello')\""
|
||||
dangerous, pattern_key, _ = detect_dangerous_command(command)
|
||||
assert dangerous is True
|
||||
|
||||
monkeypatch.setenv("HERMES_SESSION_KEY", session_key)
|
||||
monkeypatch.setenv("HERMES_EXEC_ASK", "1")
|
||||
monkeypatch.delenv("HERMES_CRON_SESSION", raising=False)
|
||||
monkeypatch.setattr(
|
||||
approval_module,
|
||||
"_get_approval_config",
|
||||
lambda: {"mode": "smart"},
|
||||
)
|
||||
monkeypatch.setattr(approval_module, "_YOLO_MODE_FROZEN", False)
|
||||
monkeypatch.setattr(approval_module, "_smart_approve", lambda *_: "approve")
|
||||
monkeypatch.setattr(
|
||||
"tools.tirith_security.check_command_security",
|
||||
lambda _command: {"action": "allow", "findings": [], "summary": ""},
|
||||
)
|
||||
approval_module.clear_session(session_key)
|
||||
approval_module._permanent_approved.clear()
|
||||
|
||||
result = approval_module.check_all_command_guards(command, "local")
|
||||
|
||||
assert result["approved"] is True
|
||||
assert result["smart_approved"] is True
|
||||
assert is_approved(session_key, pattern_key) is False
|
||||
|
||||
|
||||
class TestDetectDangerousRm:
|
||||
def test_rm_rf_detected(self):
|
||||
|
|
@ -2140,11 +2174,13 @@ class TestApprovalTimeoutIsNotConsent:
|
|||
assert "rephrase" in msg.lower()
|
||||
assert "different command" in msg.lower()
|
||||
|
||||
def test_explicit_deny_carries_same_no_consent_shape(self):
|
||||
def test_explicit_deny_carries_same_no_consent_shape(self, monkeypatch):
|
||||
"""An explicit /deny must produce the same shape as timeout —
|
||||
the agent should treat both identically."""
|
||||
from tools import approval as mod
|
||||
|
||||
self._force_short_timeout(monkeypatch, seconds=60)
|
||||
|
||||
notified = []
|
||||
mod.register_gateway_notify(self.SESSION_KEY, lambda data: notified.append(data))
|
||||
|
||||
|
|
|
|||
|
|
@ -2745,9 +2745,9 @@ def check_all_command_guards(command: str, env_type: str,
|
|||
combined_desc_for_llm = "; ".join(desc for _, desc, _ in warnings)
|
||||
verdict = _smart_approve(command, combined_desc_for_llm)
|
||||
if verdict == "approve":
|
||||
# Auto-approve and grant session-level approval for these patterns
|
||||
for key, _, _ in warnings:
|
||||
approve_session(session_key, key)
|
||||
# Approve this command only. Pattern-level persistence would let one
|
||||
# benign command suppress review of later commands that happen to
|
||||
# match the same broad detector category.
|
||||
logger.debug("Smart approval: auto-approved '%s' (%s)",
|
||||
command[:60], combined_desc_for_llm)
|
||||
return {"approved": True, "message": None,
|
||||
|
|
|
|||
|
|
@ -1879,13 +1879,13 @@ Control how Hermes handles potentially dangerous commands:
|
|||
|
||||
```yaml
|
||||
approvals:
|
||||
mode: manual # manual | smart | off
|
||||
mode: smart # smart | manual | off
|
||||
```
|
||||
|
||||
| Mode | Behavior |
|
||||
|------|----------|
|
||||
| `manual` (default) | Prompt the user before executing any flagged command. In the CLI, shows an interactive approval dialog. In messaging, queues a pending approval request. |
|
||||
| `smart` | Use an auxiliary LLM to assess whether a flagged command is actually dangerous. Low-risk commands are auto-approved with session-level persistence. Genuinely risky commands are escalated to the user. |
|
||||
| `smart` (default) | Use an auxiliary LLM to assess whether a flagged command is actually dangerous. Low-risk commands are auto-approved for that command only. Genuinely risky commands are denied; uncertain decisions escalate to the user. |
|
||||
| `manual` | Prompt the user before executing any flagged command. In the CLI, shows an interactive approval dialog. In messaging, queues a pending approval request. |
|
||||
| `off` | Skip all approval checks. Equivalent to `HERMES_YOLO_MODE=true`. **Use with caution.** |
|
||||
|
||||
Smart mode is particularly useful for reducing approval fatigue — it lets the agent work more autonomously on safe operations while still catching genuinely destructive commands.
|
||||
|
|
|
|||
|
|
@ -30,7 +30,7 @@ The approval system supports three modes, configured via `approvals.mode` in `~/
|
|||
|
||||
```yaml
|
||||
approvals:
|
||||
mode: manual # manual | smart | off
|
||||
mode: smart # smart | manual | off
|
||||
timeout: 60 # seconds to wait for user response (default: 60)
|
||||
cron_mode: deny # deny | approve — what cron jobs do when they hit a dangerous command
|
||||
mcp_reload_confirm: true # /reload-mcp asks before invalidating the MCP tool cache
|
||||
|
|
@ -41,7 +41,7 @@ The full set of keys:
|
|||
|
||||
| Key | Default | What it controls |
|
||||
|---|---|---|
|
||||
| `mode` | `manual` | Approval policy for dangerous shell commands — see the table below. |
|
||||
| `mode` | `smart` | Approval policy for dangerous shell commands — see the table below. |
|
||||
| `timeout` | `60` | Seconds Hermes waits for an approval reply before timing out. |
|
||||
| `cron_mode` | `deny` | How [cron jobs](./features/cron.md) behave headlessly when they trigger a dangerous-command prompt. `deny` blocks the command (the agent must find another path); `approve` auto-approves everything in cron context. |
|
||||
| `mcp_reload_confirm` | `true` | When true, `/reload-mcp` asks before rebuilding the MCP tool set. Rebuilding invalidates the provider prompt cache (tool schemas live in the system prompt), so the next message re-sends full input tokens. Users who click **Always Approve** flip this key to `false`. |
|
||||
|
|
@ -49,8 +49,8 @@ The full set of keys:
|
|||
|
||||
| Mode | Behavior |
|
||||
|------|----------|
|
||||
| **manual** (default) | Always prompt the user for approval on dangerous commands |
|
||||
| **smart** | Use an auxiliary LLM to assess risk. Low-risk commands (e.g., `python -c "print('hello')"`) are auto-approved. Genuinely dangerous commands are auto-denied. Uncertain cases escalate to a manual prompt. |
|
||||
| **smart** (default) | Use an auxiliary LLM to assess risk. Low-risk commands (e.g., `python -c "print('hello')"`) are auto-approved for that command only. Genuinely dangerous commands are auto-denied. Uncertain cases escalate to a manual prompt. |
|
||||
| **manual** | Always prompt the user for approval on dangerous commands. |
|
||||
| **off** | Disable all approval checks — equivalent to running with `--yolo`. All commands execute without prompts. |
|
||||
|
||||
:::warning
|
||||
|
|
|
|||
|
|
@ -492,10 +492,10 @@ hermes config set privacy.redact_pii false # disable (default)
|
|||
|
||||
### Command approval prompts
|
||||
|
||||
By default (`approvals.mode: manual`), Hermes prompts the user before running shell commands flagged as destructive (`rm -rf`, `git reset --hard`, etc.). The modes are:
|
||||
By default (`approvals.mode: smart`), Hermes asks an auxiliary LLM to assess shell commands flagged as destructive (`rm -rf`, `git reset --hard`, etc.). The modes are:
|
||||
|
||||
- `manual` — always prompt (default)
|
||||
- `smart` — use an auxiliary LLM to auto-approve low-risk commands, prompt on high-risk
|
||||
- `smart` — auto-approve a low-risk command once, deny high-risk commands, and prompt when uncertain (default)
|
||||
- `manual` — always prompt
|
||||
- `off` — skip all approval prompts (equivalent to `--yolo`)
|
||||
|
||||
```bash
|
||||
|
|
|
|||
|
|
@ -1583,13 +1583,13 @@ security:
|
|||
|
||||
```yaml
|
||||
approvals:
|
||||
mode: manual # manual | smart | off
|
||||
mode: smart # smart | manual | off
|
||||
```
|
||||
|
||||
| 模式 | 行为 |
|
||||
|------|----------|
|
||||
| `manual`(默认) | 在执行任何被标记的命令之前提示用户。在 CLI 中显示交互式审批对话框。在消息中排队待处理的审批请求。 |
|
||||
| `smart` | 使用辅助 LLM 评估被标记的命令是否真正危险。低风险命令以会话级持久性自动批准。真正有风险的命令升级给用户。 |
|
||||
| `smart`(默认) | 使用辅助 LLM 评估被标记的命令是否真正危险。低风险命令仅对当前命令自动批准,真正危险的命令自动拒绝,不确定的情况升级给用户。 |
|
||||
| `manual` | 在执行任何被标记的命令之前提示用户。在 CLI 中显示交互式审批对话框。在消息中排队待处理的审批请求。 |
|
||||
| `off` | 跳过所有审批检查。等同于 `HERMES_YOLO_MODE=true`。**谨慎使用。** |
|
||||
|
||||
智能模式对于减少审批疲劳特别有用 —— 它让 agent 在安全操作上更自主地工作,同时仍然捕获真正破坏性的命令。
|
||||
|
|
|
|||
|
|
@ -30,14 +30,14 @@ Hermes Agent 采用纵深防御安全模型。本页涵盖所有安全边界—
|
|||
|
||||
```yaml
|
||||
approvals:
|
||||
mode: manual # manual | smart | off
|
||||
mode: smart # smart | manual | off
|
||||
timeout: 60 # 等待用户响应的秒数(默认:60)
|
||||
```
|
||||
|
||||
| 模式 | 行为 |
|
||||
|------|----------|
|
||||
| **manual**(默认) | 始终提示用户审批危险命令 |
|
||||
| **smart** | 使用辅助 LLM 评估风险。低风险命令(如 `python -c "print('hello')"` )自动批准,真正危险的命令自动拒绝,不确定的情况升级为手动提示。 |
|
||||
| **smart**(默认) | 使用辅助 LLM 评估风险。低风险命令(如 `python -c "print('hello')"`)仅对当前命令自动批准,真正危险的命令自动拒绝,不确定的情况升级为手动提示。 |
|
||||
| **manual** | 始终提示用户审批危险命令。 |
|
||||
| **off** | 禁用所有审批检查——等同于使用 `--yolo` 运行。所有命令无需提示即可执行。 |
|
||||
|
||||
:::warning
|
||||
|
|
|
|||
|
|
@ -481,10 +481,10 @@ hermes config set privacy.redact_pii false # 禁用(默认)
|
|||
|
||||
### 命令审批提示
|
||||
|
||||
默认情况下(`approvals.mode: manual`),Hermes 在运行被标记为破坏性的 shell 命令(`rm -rf`、`git reset --hard` 等)之前会提示用户。模式如下:
|
||||
默认情况下(`approvals.mode: smart`),Hermes 会让辅助 LLM 评估被标记为破坏性的 shell 命令(`rm -rf`、`git reset --hard` 等)。模式如下:
|
||||
|
||||
- `manual` — 始终提示(默认)
|
||||
- `smart` — 使用辅助 LLM 自动批准低风险命令,对高风险命令提示
|
||||
- `smart` — 低风险命令仅批准一次,高风险命令拒绝,不确定时提示(默认)
|
||||
- `manual` — 始终提示
|
||||
- `off` — 跳过所有审批提示(等同于 `--yolo`)
|
||||
|
||||
```bash
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue