Sapience-AI/openclaw-middleware-suite
GitHub: Sapience-AI/openclaw-middleware-suite
面向 OpenClaw AI 代理平台的六合一进程内中间件套件,通过 HITL 审批、提示注入防护、PII 脱敏、工具调用预算、上下文压缩和智能模型路由,在零遥测、全本地状态下解决代理自主运行中的安全、成本和上下文退化三大问题。
Stars: 5 | Forks: 0
## 痛点
一次 OpenClaw 会话就可以读取您的代码库、运行任意的 shell 命令、管理您的 Google Drive、代您发送电子邮件,并将代码推送到生产环境——所有这些都是自主完成的。在这个过程中,每一次交互都会消耗 token、选择模型并积累上下文。
如今,在“代理决定做某事”与“某事发生”之间没有任何隔离层。这一空白隐藏了三个截然不同的问题——而且它们很少能共用同一种解决方案:
- **安全漏洞。** 沙盒可以阻止代理逃离其容器。但在该容器内部,它仍然掌握着核心权限:API token、文件系统访问权、出站网络、电子邮件凭据。一次产生幻觉的 `rm -rf`,一次隐藏在获取文档中的提示注入,一次工具参数中泄露的 SSN——您面临的将是真实的破坏。
- **失控的成本。** 每次交互都会重新发送完整的上下文窗口。每个请求都会选择预先接入的任何模型,无论任务是否需要。在尖端模型上进行长时间的编码会话可能会在几分钟内烧掉数美元,其中大部分开销是以高昂的费率重新读取陈旧的历史记录。
- **上下文退化。** 随着会话的增长,代理会丢失其最早的指令,忘记关键决策,并开始自相矛盾。简单的截断会丢弃重要内容;而置之不理则会超出预算。
**这就是本套件存在的意义。**
## 解决方案
Sapience AI 作为 **OpenClaw 的控制平面**。六个中间件——每个都解决一个特定的故障模式——在一个管道中协同工作。
其中四个负责**治理和保护**操作面:
- **HITL** — 针对危险操作的 Human 审批机制
- **Guardrail** — 阻断提示注入、数据泄露和破坏性命令
- **PII Sanitizer** — 在敏感数据离开前进行检测与脱敏
- **Tool Call Limit** — 强制执行会话和请求预算
另外两个负责**优化请求本身:**
- **Context Editing** — 智能上下文窗口压缩,保留重要内容
- **Model Routing** — 将请求路由至适合该任务的模型
它们共同确保每一次交互都是安全、可观测且具有成本效益的。
中间件
| # | 中间件 | 功能描述 |
|:-:|---|---|
| 1 | :shield: **HITL** | 针对危险操作的 Human 审批机制 |
| 2 | :brain: **Context Editing** | 智能上下文窗口压缩 |
| 3 | :zap: **Model Routing** | 将请求路由至适合的模型与提供商 |
| 4 | :lock: **Guardrail** | 阻断提示注入、数据泄露和破坏性命令 |
| 5 | :detective: **PII Sanitizer** | 检测并脱敏个人身份信息 |
| 6 | :bar_chart: **Tool Call Limit** | 强制执行每个工具的会话和请求预算 |
本套件提供**两种使用模式**,本 README 的其余部分将围绕此划分展开:
- **[插件](#plugin)** — 一次安装,通过仪表盘和 `sai` CLI 进行管理。零代码。OpenClaw 网关会为您加载中间件并连接所有钩子。
- **[编程式用法](#programmatic-usage)** — `import { ... } from '@sapience-ai-corporation/openclaw-middleware-suite/
'`。将任意子集直接嵌入到 Node 应用中,引入您自己的管道,自由组合。
两种模式共享相同的磁盘配置存储,因此您可以从插件开始,然后无缝过渡到编程式嵌入,而无需重新学习任何内容。
插件
插件是零代码路径:安装 npm 包,使用 `sai init` 进行一次配置,OpenClaw 网关就会加载全部六个中间件并连接每个钩子。所有的运维操作——开启/关闭中间件、编辑策略、查看审计日志、同步模型目录——都通过仪表盘或 `sai` CLI 完成。
### 快速入门
#### 1. 安装
```
# 从 npm(已发布插件)
openclaw plugins install @sapience-ai-corporation/openclaw-middleware-suite
# 将 `sai` CLI 暴露到你的 PATH(Windows 用户 — 需要管理员权限 / Developer Mode)
mkdir -p ~/.local/bin
chmod +x ~/.openclaw/extensions/sapience-ai-suite/dist/index.js
ln -sf ~/.openclaw/extensions/sapience-ai-suite/dist/index.js ~/.local/bin/sai
# 或者从源码
git clone https://github.com/Sapience-AI/openclaw-middleware-suite.git
cd openclaw-middleware-suite
npm install && npm run build
openclaw plugins install --link .
```
#### 2. 配置
```
# 交互式向导 — 引导你完成安全级别、modules 和 policies 的设置
sai init
# 启动 gateway — 控制面板将在 http://localhost:9000/dashboard 提供
openclaw gateway start
```
至此完成。接下来,每个中间件都可以通过 [仪表盘](#dashboard) 或其专用的 `sai …` CLI 子命令(链接位于下方每个中间件部分中)进行开关切换、配置和检查。
### 插件工作原理
作为插件加载后,所有六个中间件都会遵循一些共同的事实——在调整任何内容之前值得了解:
- **唯一的权威磁盘存储。** 所有六个中间件共享同一个配置文件:`~/.openclaw/sapience-ai-suite/sapience-ai-suite.json`。仪表盘、`sai` CLI 以及中间件本身都在读写同一个 JSON 文件。文件系统中不存在散落的每个中间件单独的配置文件。
- **插件级开关。** 每个中间件在该文件的 `plugin_config.middlewares[name]` 处都有一个布尔值开关(通过仪表盘的概览页或 `sai init` 管理)。当该标志为 `false` 时,插件运行时会短路相应的钩子——中间件代码不会被执行,因此禁用是零成本的。
- **运行配置位于每个中间件的子树下。** `hitl`、`context_editing`、`model_routing`、`guardrail`、`pii_sanitizer`、`tool_call_limit`——每个都拥有自己的配置块。(HITL 将其用户配置嵌套在 `hitl.policy` 下;Context Editing 在 `context_editing.configOverrides` 下。)这些就是仪表盘编辑器和 `sai ` CLI 子命令所操作的内容。
- **插件运行时总是在 `initialize()` 时传递 `{}`。** 它完全依赖磁盘覆盖层进行配置。唯一的例外是 **Context Editing**,它接收 `{ pluginApi }`,以便其 ICC LLM 提取可以通过 OpenClaw 的插件 API 进行调度。这使得插件行为具有完全的可重现性:默认值 + `sapience-ai-suite.json` 中的任何内容,没有其他。
### 中间件
快速链接:[HITL](#hitl) · [Context Editing](#context-editing) · [Model Routing](#model-routing) · [Guardrail](#guardrail) · [PII Sanitizer](#pii-sanitizer) · [Tool Call Limit](#tool-call-limit)。
#### :shield: Human-in-the-Loop (HITL)
##### 为什么需要它
AI 代理会犯错。它们会产生文件路径幻觉、误解指令,有时还会尝试做在生产环境中将是灾难性的事情。HITL 确保由 Human 在高风险操作发生前(而不是发生后)进行审查。
##### 工作原理
```
Tool Call Arrives
│
├─ Policy Lookup ─── ALLOW ──→ Execute immediately
│
├─ Policy Lookup ─── DENY ───→ Block with reason
│
└─ Policy Lookup ─── ASK ────→ Risk Assessment
│
├─ Destructive classifier
├─ Irreversibility scorer (0-100)
└─ Memory risk forecaster
│
▼
Approval Queue
│
├─ /approve ──→ Execute
├─ /approve ──→ Execute (MFA verified)
└─ /deny ──→ Block
```
##### 功能特性 — vs. ClawReins
[ClawReins](https://github.com/pegasi-ai/reins) 是 OpenClaw 最接近的同类 HITL 层。两者共享一个共同的基础——三项决策策略、不可逆性评分、破坏性命令检测以及 WhatsApp/Telegram 审批通道。Sapience HITL 在最重要的两个方面扩展了该基础:**更广泛的受保护工具集,以及代理无法绕过的审批机制。**
**图例:** ✅ 支持 · ❌ 无
| 功能 | ClawReins | Sapience HITL |
| ----------------------------------------------------------------------------------------------------------------- | :-------------------------------: | :--------------------------: |
| **策略模型** — 每个模块和方法的 `ALLOW` / `DENY` / `ASK`,带有 `allowPaths` / `denyPaths` glob 匹配 | ✅ | ✅ |
| **风险评分** — 不可逆性 (0–100),破坏性分类器 (`HIGH` / `CATASTROPHIC`),轨迹预测 | ✅ | ✅ |
| **审批队列** — 异步 WhatsApp/Telegram + TTY,带有 TTL 过期和信任速率限制 | ✅ | ✅ |
| **不可变审计轨迹** — 每次决策附带完整风险评分的只追加 JSONL | ✅ | ✅ |
| **灾难性操作确认** | 屏幕上的 `CONFIRM-XXXX` token | **TOTP 2FA** (RFC 6238) |
| **代理无法读取审批码**(防止自我审批) | ❌ token 出现在终端/聊天中 | ✅ 代码在设备外生成 |
| **重试时的 ArgsHash 强制校验**(防止参数替换) | ❌ 仅记录日志 | ✅ 在审批时验证 |
| 保护 FileSystem、Shell、Browser、Network、Gateway | ✅ | ✅ |
| **Gmail** (list, send, draft, delete) | ❌ | ✅ |
| **GoogleDrive** (list, upload, download, delete, share, move) | ❌ | ✅ |
| **Memory** (search, add, delete) | ❌ | ✅ |
| **Process** (list, poll, log, write, kill, clear, remove) | ❌ | ✅ |
| **Shell 子命令路由** (`gog`, `gdrive`, `rclone` → Gmail/Drive policy) | ❌ | ✅ |
| **Gateway endpoint 重分类** (`gateway.maton.ai/*` auto-mapped) | ❌ | ✅ |
| **MCP tool name 映射** (`mcp__google_workspace__*`) | ❌ | ✅ |
任何未映射的工具都会回退到 `defaultAction` (ASK)。
##### CLI
```
sai hitl policy # View/manage security policies
sai hitl stats # View approval statistics
sai hitl audit # View decision audit trail
sai hitl reset # Reset statistics
```
#### :brain: Context Editing
##### 为什么需要它
LLM 上下文窗口是有限的——而且成本高昂。上下文窗口中的每个 token 在每次请求时都会产生费用。随着会话的增长,您需要付费重新读取数千个代理不再需要的陈旧对话历史 token。一个 120K token 的会话,如果每次交互都命中 GPT-4,可能会在几分钟内烧掉数美元,其中大部分花费在模型几乎不使用的上下文上。
这不仅仅是成本问题。在长时间的编码会话中,代理会逐渐丢失其最早的指令,忘记关键决策,并随着关键上下文被噪音挤出而开始自相矛盾。简单的截断会不加选择地丢弃重要上下文。
Context Editing 解决了这两个问题:它使用 LLM 驱动的提取 (ICC) 来压缩旧消息,保持 token 数量——以及成本——在控制之下,同时保留真正重要的上下文。
##### 工作原理
```
Turn Completes
│
└─ agent_end hook ──→ Evaluate triggers
│
├─ Token count > threshold?
├─ Message count > threshold?
└─ Adaptive rules?
│
▼
Schedule compaction (if triggered)
Next Turn Begins
│
└─ before_agent_start hook ──→ Compact
│
├─ Run ICC extraction
│ ├─ Priority Preservation
│ ├─ Conflict Resolution
│ └─ Entity Locks
│
▼
Replace old messages with dense summary
(before SessionManager opens the JSONL)
```
两阶段设计确保压缩在 SessionManager 打开 JSONL 文件之前发生,从而防止竞态条件。
##### ICC Pipeline(智能上下文压缩)
| 阶段 | 提取内容 |
| ------------------------- | --------------------------------------------------------------- |
| **优先级保留** | 代理绝不能忘记的关键目标和指令 |
| **冲突解决** | 检测并解决记录中的矛盾 |
| **实体锁定** | 关键值(名称、路径、配置值)被逐字保留 |
##### 功能特性 — vs. OpenClaw 内置压缩
OpenClaw 已经内置了一个强大的压缩管道:当提示即将溢出上下文窗口时,它会将记录拆分为多个块,对每个块进行摘要,然后合并各部分——这是一个专为应对超大型工具输出、工具调用配对约束和瞬态 API 故障而构建的批量摘要器。Sapience Context Editing **并不取代该管道——而是在其之上添加了一条更便宜、可控制的快速路径。**
快速路径是一个 **单次 LLM 调用**,其输出(ICC 提取:实体、冲突、优先级)_就是_ 压缩摘要。单次调用意味着提示可以由用户控制,模型可以替换,并且压缩可以根据您自己的阈值提前触发,而无需等待溢出。如果 ICC 调用失败——,对于提取模型窗口而言记录过大——中间件只会跳过该轮交互,而 OpenClaw 原生的溢出触发压缩会在下一个提示时完全按照原安装的方式处理它。您永远不会失去覆盖范围;您只是在溢出边缘情况下失去了控制权。
**图例:** ✅ 支持 · ❌ 无
| 功能 | OpenClaw 内置 | Sapience Context Editing |
| -------------------------------------------------------------------------------------------------------------- | :------------------------: | :--------------------------------------: |
| **压缩管道** — 将旧消息摘要为密集的摘要 | ✅ 两阶段 分块 + 合并 | ✅ 作为摘要的单次 ICC 调用 |
| **溢出触发压缩** (在下一个提示超出上下文窗口时触发) | ✅ | ✅ |
| **手动 `/compact` 命令** | ✅ | ✅ |
| **标识符保留** — UUID、哈希、URL、文件名原样保留 | ✅ | ✅ |
| **达到上下文限制前的早期 / 主动压缩** | ❌ 仅响应式 | ✅ 阈值触发 |
| **早期压缩的 Token 计数阈值** (默认 80k,可配置) | ❌ | ✅ |
| **早期压缩的消息计数阈值** (默认 50,可配置) | ❌ | ✅ |
| **触发模式选择器** — `token` / `message` / `both` | ❌ | ✅ |
| **在压缩截断前逐字保留 N 条最新消息** | ❌ 固定块比率 | ✅ 用户可配置 |
| **在压缩截断前逐字保留 N 个最新 token** | ❌ 仅内部控制 | ✅ 用户可配置 |
| **自定义压缩提示** — 用户提供的指令指导摘要保留的内容 | ❌ 固定合并提示 | ✅ 注入到单次 ICC 调用中 |
| **类型化实体锁定** — API endpoint、文件路径、变量、常量、模型名称、代码标识符 | ❌ | ✅ |
| **冲突解决** — 检测指令覆盖 ("use X instead of Y") 并锁定解析后的值 | ❌ | ✅ |
| **优先级保留** — `TODO` / `FIXME` / `REQUIREMENT` / `MUST` 片段被标记为原样保留 | ❌ | ✅ |
| **自定义压缩模型** — 在与代理不同的模型上运行摘要 | ➕ 原生 `openclaw.json` 键 | ✅ 通过 `sai ctx model --set` 一等公民支持 |
| **会话修剪开关** (空闲上下文的 cache-TTL) | ➕ 原生 `openclaw.json` 键 | ✅ 通过 `sai ctx pruning` 一等公民支持 |
| **交互式向导** 用于上述所有配置 | ❌ | ✅ `sai init` |
| **每次压缩的审计轨迹** (JSONL: 实体、冲突、优先级、指令哈希) | ❌ | ✅ |
| **每次会话累积的 token 节省统计** | ❌ | ✅ |
##### CLI
```
sai ctx stats # Compaction state, token savings, and compaction history
sai ctx reset # Clear compaction state
```
#### :zap: Model Routing
##### 为什么需要它
并非每个请求都需要 GPT-4 或 Claude Opus。一个简单的“列出此目录中的文件”不需要 $0.015/1K token 的模型——但在没有路由的情况下,它就会被分配这样的模型。Model Routing 实时评估请求的复杂性并路由到最优的模型层级,在不牺牲关键质量的情况下削减高达 70% 的成本。
##### 工作原理
```
Incoming Request
│
├─ Complexity Scorer
│ ├─ Message length
│ ├─ Instruction complexity
│ ├─ Tool usage patterns
│ └─ Reasoning depth signals
│
├─ Tier Assignment ──→ simple | standard | complex | reasoning
│
├─ Model Selection ──→ Primary model for tier
│ └─ Fallback chain if primary fails
│
└─ Provider Routing ──→ Route to correct API endpoint
├─ OpenAI format
├─ Anthropic format
└─ Google format
```
##### 层级系统
| 层级 | 用例 | 示例模型 |
| ------------- | ------------------------------------------ | -------------------------- |
| **Simple** | 文件读取、列表、基本 Q&A | GPT-4o-mini, Claude Haiku |
| **Standard** | 代码生成、适度推理 | GPT-4o, Claude Sonnet |
| **Complex** | 架构决策、复杂重构 | GPT-4, Claude Opus |
| **Reasoning** | 多步规划、新颖问题解决 | o1, Claude Opus (extended) |
##### 功能特性 — vs. Manifest
[Manifest](https://github.com/mnfst/manifest) 是最接近的基于复杂度的模型路由器——两者共享源自同一谱系的相同 23 维评分核心。两者的目标部署模型不同:Manifest 作为 Docker 服务提供,带有多租户 Web 仪表盘和 Postgres 后端;Sapience Model Routing 是一个 OpenClaw 插件,在开发人员的机器上运行,带有本地 JSON 配置和 CLI。下表侧重于 Sapience 在共享评分基础之上增加的内容——特别是**每次会话的模型固定**和**自动 prompt-cache 标记注入**,这两者结合在一起,可以在固定会话的每次交互中保持提供商缓存前缀的热度。
**图例:** ✅ 支持 · ❌ 无
| 功能 | Manifest | Sapience Model Routing |
| ---------------------------------------------------------------------------------------------------------------------------------------------------------------- | :----------------------------------------------------: | :-----------------------------------------------------------------------------------: |
| **23 维评分器** — Aho-Corasick trie,密度聚类,带 0.45 歧义阈值的 sigmoid (k=8),四层边界 | ✅ 对等 | ✅ 对等 |
| **会话动量** — 最近 5 次层级决策的长度加权混合 | ✅ | ✅ |
| **请求去重** — 并发重试/双击共享一次上游调用(30 秒窗口) | ✅ 基于 trace-id / token | ✅ SHA-256 进行中 + 已完成 |
| **经过能力过滤的回退链** — 根据工具支持、视觉、上下文窗口、排除项进行过滤 | ✅ | ✅ 每层最多 5 个 |
| **原生提供商适配器** — 带有 SSE 流转换的 OpenAI / Anthropic / Google | ✅ (+ ChatGPT Codex subscription) | ✅ |
| **硬性覆盖** — reasoning 关键字、短消息、工具下限、大上下文 | ✅ 4 项覆盖 | ✅ 6 项覆盖 (+ 结构化输出下限 + 会话启动 `/new` `/reset` → SIMPLE) |
| **多语言关键字** — 9 种语言,横跨所有 14 个关键字维度的 1,500+ 个关键字 | ❌ 仅限英语 | ✅ |
| **路由配置文件** — `auto` / `eco` / `premium` / `agentic` 在一个设置中切换整个回退链 | ❌ 单一确定性映射 | ✅ |
| **模型固定** — 同一会话在每次交互中保持相同的模型,并在复杂性升级时自动释放 | ❌ 仅层级动量 | ✅ 每次会话的高水位标记 |
| **三次升级** — 用户重试相同请求 3 次 → 自动提升至下一层级 | ❌ | ✅ |
| **自动缓存标记注入** — 最后一个系统块和最后一个工具上的 Anthropic `cache_control`,Google `cachedContent` token 透传 | ✅ 仅限 Anthropic | ✅ Anthropic + Google |
| **会话固定的 Prompt 缓存** — 固定和注入结合,使提供商的缓存前缀在交互中得以存活(在 Anthropic 上最高可节省 90% 的缓存输入成本) | ❌ 无固定 → 前缀在模型漂移时丢失 | ✅ |
| **确定性响应缓存** — 针对 `temperature=0` 非流式请求的 LRU(在重复相同提示时完全绕过提供商) | ❌ | ✅ 选择加入,200 条目 / 10 分钟 |
| **每日成本警报** — 在 90 天分类账之上每天触发一次的警告/严重阈值 | ❌ 存在通知,无预算上限 | ✅ `$5` 警告 / `$20` 严重 (可配置) |
| **每步审计日志** — 请求 ID 作用域内的每次路由决策 JSONL 跟踪 | Postgres `agent_message` 行 | ✅ 本地 JSONL |
| **配置热重载** (`sapience-ai-suite.json`) | ❌ 需要重启 | ✅ `fs.watchFile`,2 秒防抖 |
| **插件钩子系统** — `onBeforeScore` / `onAfterScore` / `onBeforeForward` / `onAfterForward` | ❌ | ✅ 4 个钩子 |
| **完整 CLI** — `sai router stats / config / tiers / test / exclude / models / reset` | ❌ | ✅ |
| **交互式设置向导** — 配置文件 + 层级自定义 + 端口 + 实时目录拉取 | Web 仪表盘注册 | ✅ `sai init` |
| **特异性路由** — 任务类型类别 (coding / vision / trading / …) 覆盖复杂度层级 | ✅ 9 个类别 | ❌ |
| **订阅 OAuth** — ChatGPT Plus, Claude Max, MiniMax, GitHub Copilot | ✅ | ❌ 仅限 API 密钥 |
##### CLI
```
sai router stats # Daily cost ledger + per-model breakdown + alerts
sai router config # View / edit profile, thresholds, exclusions
sai router tiers # Inspect tier-to-model assignments
sai router models # Sync live model catalog (LiteLLM, 24h cache)
sai router test # Score a sample request without forwarding
sai router reset # Reset cost history / session state
```
#### :lock: Guardrail
##### 为什么需要它
提示注入是针对 AI 代理的头号攻击媒介。隐藏在文档、电子邮件或网页中的单个恶意指令就能劫持代理的行为——使其泄露机密、删除数据或执行任意命令。Guardrail 在多个检测层捕获这些攻击,防止它们造成危害。
##### 检测层
| 层级 | 技术 | 捕获内容 |
| ---------------------- | ---------------------- | ---------------------------------------------- |
| **Regex Scanner** | 模式匹配 | 已知注入模式、角色覆盖 |
| **Prefix Scanner** | 已知前缀检测 | 常见注入前缀和转义序列 |
| **Heuristic Scanner** | 行为分析 | 异常请求模式、多步攻击 |
| **Entropy Analyzer** | 随机性检测 | 编码载荷、混淆的数据泄露 |
| **Unicode Normalizer** | 规范化 | Unicode 转义攻击、同形字替换 |
| **OpenAI Moderation API** | ML 内容分类器(外部) | 暴力、仇恨、色情、自残、非法内容——结果在 `before_agent_start` 处缓存,在 `before_message_write` 处执行基于严重级别的强制处理(默认:在 `HIGH` + `CRITICAL` 时重写;`MEDIUM` 仅记录审计) |
##### 防护模块
| 防护 | 保护内容 |
| ------------------------ | ---------------------------------------------------------------- |
| **Sensitive Paths** | 阻止对 `~/.ssh`、`~/.aws`、`/etc/passwd`、`.env` 文件的访问 |
| **Egress Control** | 防止向外部端点的未授权数据传输 |
| **Destructive Commands** | 捕获 `rm -rf`、`DROP TABLE`、`kill -9` 及类似模式 |
| **Content Moderation** | 对传入提示进行 OpenAI Moderation API 检查——标记暴力、仇恨、色情、自残、非法内容(异步 → 同步缓存桥接;通过 `moderation.rewriteThreshold` 设定严重级别,默认为 `HIGH`) |
| **Role Impersonation** | 检测伪装成系统/管理员角色的尝试 |
| **Canary Tracker** | 如果暴露在输出中则触发警报的蜜罐 token |
| **Output Scrubber** | 从代理响应中移除中间件元数据 |
##### 关键特性
- **输入 + 输出扫描** — 覆盖提示注入和数据泄露
- **可配置的操作** — 每条规则的 `BLOCK`、`WARN`、`REDACT`
- **置信度过滤** — 可调节敏感度以减少误报
- **试运行模式** — 仅记录检测而不进行阻断(用于调优)
- **自定义模式** — 为特定领域的威胁添加您自己的正则表达式规则
##### 功能特性 — vs. OpenClaw Shield &Guardrails
最接近的两个同类产品是 [**OpenClaw Shield**](https://github.com/knostic/openclaw-shield) (Knostic)——一个轻量级的 OpenClaw 插件,带有五个独立切换的策略层,包括一个依赖代理遵守注入指令的建议性“security gate”工具——以及 [**OpenGuardrails / MoltGuard**](https://github.com/OpenGuardrails)——一个全栈平台,具有一个与托管 Core 服务通信的代理端插件,该服务在工具调用序列上运行 10 扫描器内容模型和行为规则引擎。Sapience Guardrail 采取了与两者不同的立场:**一切都在 OpenClaw 原生钩子的进程内运行**,并且 `before_message_write` 钩子**实际重写了持久化的记录**,使得 LLM 永远无法看到预脱敏的内容——不仅在下一轮交互中如此,在当前轮也是如此。下表的行侧重于实际不同的能力;省略了共有的基础功能(正则表达式扫描、OpenClaw 插件集成、PII 脱敏)。
**图例:** ✅ 支持 · ⚠️ 部分 · ❌ 无
| 功能 | OpenClaw Shield | OpenGuardrails | Sapience Guardrail |
| ------------------------------------------------------------------------------------------------------------------ | :---------------: | :-----------------------: | :----------------------------------------------------------------------------: |
| **提示注入正则表达式 / 模式扫描器** | ✅ | ✅ (托管 Core S01) | ✅ (21 种模式) |
| **针对混淆载荷的启发式 / 香农熵检测器** | ❌ | ❌ | ✅ (在 20+ 字符的 token 上 ≥ 4.0) |
| **扫描前的 Unicode NFKC 规范化** — 同形字 + 零宽 + 软连字符 | ❌ | ❌ | ✅ |
| **外部审核 API 集成** (OpenAI Moderation, Perspective, …) | ❌ | ⚠️ 仅网关清理器 | ✅ OpenAI Moderation,异步 → 同步缓存桥接 |
| **敏感文件路径阻断** (`.ssh`, `.env`, `.aws/credentials`, …) | ✅ 18 种模式 | ❌ | ✅ 52 种模式 + 符号链接解析 |
| **出站域名白名单** (默认拒绝) | ❌ | ❌ | ✅ 25 个允许 (npm, PyPI, GitHub, AWS, Cloudflare…) |
| **私有 IP / 元数据端点 SSRF 阻断** (`169.254.169.254`, RFC 1918, IPv6 ULA, mapped `::ffff:`) | ❌ | ❌ | ✅ |
| **破坏性 shell 命令阻断** (`rm -rf`, `DROP TABLE`, `git push --force main`, fork 炸弹, `chmod 777`…) | ✅ 6 种模式 | ❌ (仅行为) | ✅ 22 种内置 + 自定义正则表达式 |
| **角色冒充 / ChatML / 伪造 `[SYSTEM]` 中和** | ❌ | ❌ | ✅ 16 种模式,包括 Llama 标记和工具输出标签 |
| **Canary / 泄露重脱敏** (重新检测之前脱敏的内容) | ❌ | ❌ | ✅ SHA-256 环形缓冲区,空白规范化 |
| **持久化到记录的实际消息重写**(对比仅记录日志或持久化后脱敏) | ❌ | ❌ | ✅ `before_message_write` 根据 OpenClaw 2026.4.x 契约返回 `{ message }` |
| **异步 → 同步缓存桥接** — `before_agent_start` 中的外部 API 检查,同步 `before_message_write` 中基于严重级别的重写(可配置阈值) | ❌ | ❌ | ✅ |
| **工具调用序列上的行为规则引擎** (例如,文件读取 → 外部写入) | ❌ | ✅ 托管 Core | ❌ |
| **代理被提示调用的建议性“security gate”LLM 策略工具** | ✅ L5 | ❌ | ❌ |
| **试运行 / 影子模式** (记录而不阻断) | ✅ | ⚠️ 不明确 | ✅ |
| **每次决策的 JSONL 审计日志** — 时间戳、模块、严重性、sessionKey、agentId | ❌ | ✅ (托管 DB) | ✅ 本地只追加 |
| **用于运行时配置的完整 CLI 接口** | ❌ 仅 JSON 编辑 | ✅ `/og_*` 斜杠命令 | ✅ `sai guardrail …` |
| **Web 仪表盘** | ❌ | ✅ localhost:53668 | ❌ (计划中) |
| **完全在进程内运行** (无外部服务依赖) | ✅ | ❌ 需要 Core API | ✅ |
| **带有计费/配额的多租户托管服务** | ❌ | ✅ | ❌ |
**各自真正独特之处**
- **OpenClaw Shield** — 逐层切换 (L1–L5),以便在主机缺少特定钩子时能发布降级配置;建议性的 L5 关卡依赖代理遵守注入的策略,而非主机强制执行。
- **OpenGuardrails / MoltGuard** — 托管行为规则引擎,可捕获多轮攻击模式(凭据读取 → 网络写入),涵盖 NSFW / MCP 中毒 / 偏离主题漂移的 10 扫描器内容模型,以及带配额系统的托管仪表盘。
- **Sapience Guardrail** — 同步记录重写,使 LLM 永远无法看到预脱敏的内容;扫描前的 Unicode NFKC + 同形字 + 零宽规范化;基于信息熵的混淆检测;全深度 L2 堆栈(敏感路径 + 出站白名单 + 私有 IP SSRF + 破坏性命令)在任何工具执行前触发;置信度过滤匹配以抑制重写的误报。
##### 我们从各自采纳的内容
Sapience Guardrail 并非凭空出现。两个同类产品都贡献了我们赖以构建的基础理念——我们在下面明确列出了它们。
| 功能 | OpenGuardrails | OpenClaw Shield | Sapience Guardrail |
|---|:-:|:-:|:-:|
| **带有类别分类法的正则表达式规则引擎** (注入 / PII / 可疑) | 采纳自 | — | 扩展:3 个引擎上的 53 条规则 (正则表达式 + 前缀 + 启发式) |
| **置信度层级** (HIGH / MEDIUM / LOW) | 采纳自 | — | 扩展:MEDIUM 需要跨类别*和*同类别的多重匹配 |
| **通过 `before_tool_call` 钩子进行 L2 工具调用拦截** | — | 采纳自 | 扩展:6 个防护 (敏感路径、出站、破坏性、shell 间接、预读取、参数扫描) |
| **文件路径黑名单** 概念 | — | 采纳自 | 扩展:52 种模式 + 白名单覆盖 + 符号链接解析 |
| **通过 `before_message_write` 进行 L3 记录扫描** | — | — | 原创 — 弥补了 Shield 留下的空白(工具结果、文件内容在执行*之后*进入) |
| **将 L1 提示防护策略注入**到系统提示中 | — | — | 原创 — 代理知道受保护的*是什么*,而不是*如何*受保护 |
| **出站 / SSRF 防护** (域名白名单,IPv4+IPv6 私有范围,`169.254.169.254`) | — | — | 原创 |
| **Canary 跟踪 / 泄露重脱敏** (SHA-256 环形缓冲区) | — | — | 原创 |
| **角色冒充** (ChatML, Llama, 伪造 `[SYSTEM]`,工具输出标签注入 — 16 种模式) | — | — | 原创 |
| **代理审讯防御** (防御枚举检测) | — | — | 原创 |
| **带有异步 → 同步缓存桥接的 OpenAI Moderation API 集成** | — | — | 原创 |
| **CLI 管理界面** (`sai guardrail …`) | — | — | 原创 |
**两个同类产品都没有做到的一点:** 将执行前拦截与执行后记录扫描结合起来。OpenGuardrails 扫描文本但无法阻断工具。OpenClaw Shield 阻断工具但无法扫描记录。Sapience 在同一管道中完成了这两项操作。
##### 配置
```
{
"dryRunMode": false,
"entropyThreshold": 4.0,
"sensitivePaths": { "enabled": true, "action": "BLOCK" },
"egressControl": { "enabled": true, "blockDataSending": true },
"destructiveCommands": { "enabled": true, "action": "BLOCK" },
"moderation": { "rewriteThreshold": "HIGH" }
}
```
中间件的开关是插件级标志(`sapience-ai-suite.json` 中的 `plugin_config.middlewares.guardrail`,通过仪表盘或 `sai init` 管理),而不是此配置中的字段。上面的子功能 `enabled` 标志(sensitivePaths、egressControl、destructiveCommands)在 guardrail 中间件已经运行时,会切换其内部的*各个*防护。
`moderation.rewriteThreshold` 控制异步 → 同步缓存桥接的严重性门槛。接受 `MEDIUM`、`HIGH` 或 `CRITICAL`——默认值为 `HIGH`。达到或超过阈值的标记会在 `before_message_write` 中触发记录重写(硬阻断);低于阈值的标记仅记录审计日志并放行,以便 LLM 自身的安全层可以在没有合成 `[GUARDRAIL]` 标记替换用户提示的情况下处理灰色地带。设置为 `MEDIUM` 以获得最大严格度,或设置为 `CRITICAL` 以仅对最严重的类别进行硬阻断。
##### CLI
```
sai guardrail status # Show guardrail state
sai guardrail toggle dry-run # Toggle dry-run mode on/off
sai guardrail list [category] # List rules (optionally filtered)
sai guardrail rule-add # Add a custom regex rule
sai guardrail rule-toggle # Enable/disable a rule
sai guardrail paths block # Block a sensitive path
sai guardrail egress allow # Whitelist an egress domain
sai guardrail egress data-sending # Toggle outbound body blocking
sai guardrail destructive list # List blocked command patterns
sai guardrail destructive add # Add a custom destructive pattern
sai guardrail config # Print resolved config
sai guardrail reset # Reset to defaults
```
#### :detective: PII Sanitizer
##### 为什么需要它
AI 代理处理其上下文中的所有内容——包括用户粘贴到对话中的敏感数据。如果没有 PII 层,代理可能会无意中将 SSN、API 密钥或电子邮件地址传递给外部 API、将其记录到文件中,或将其包含在 shell 命令中。PII Sanitizer 拦截工具调用并在执行前应用数据丢失防护策略。
##### 检测模式
| 类别 | 示例 | 默认严重性 |
| --------------------------- | ------------------------ | ---------------- |
| **电子邮件地址** | `user@example.com` | LOW |
| **电话号码** | `+1-555-0123` | MEDIUM |
| **社会保障号** | `123-45-6789` | CRITICAL |
| **信用卡号** | `4111-1111-1111-1111` | CRITICAL |
| **API 密钥和 token** | `sk-proj-...`, `ghp_...` | CRITICAL |
| **IP 地址** | `192.168.1.1` | LOW |
##### DLP 操作
| 操作 | 行为 |
| ---------- | ---------------------------------------------- |
| `ALLOW` | 透传 (仅验证) |
| `REDACT` | 用 `[REDACTED_]` 占位符替换 PII |
| `ESCALATE` | 强制要求 HITL 审批才能继续 |
| `BLOCK` | 完全阻断工具调用 |
##### 关键特性
- **递归深度扫描** — 遍历嵌套对象、数组和字符串化的 JSON
- **Shell 参数解析** — 从 shell 命令中提取并扫描字面量
- **字段级策略** — 针对不同的 PII 类型和严重性执行不同操作
- **严重性分类** — `LOW`、`MEDIUM`、`HIGH`、`CRITICAL`
- **与 HITL 集成** — `ESCALATE` 操作路由至 Human 审批
##### CLI
```
sai dlp info # Show DLP status, toggles, rules, tool mappings
sai dlp toggle # Toggle DLP settings
sai dlp rule-add [options] # Add or update a PII scanning rule
sai dlp rule-rm # Remove a PII scanning rule by name
sai dlp policy-set # Set scanning policy for a tool field
```
#### :bar_chart: Tool Call Limit
##### 为什么需要它
陷入循环的代理可以在单个会话中数百次调用同一工具——耗尽 API 配额、累积高昂成本并产生垃圾输出。Tool Call Limits 对每个工具可被调用的次数强制执行硬性边界,涵盖会话和请求两个级别。
##### 强制执行模型
```
Tool Call Arrives
│
├─ Check session counter ─── Under limit ──→ PASS
│ └── Soft limit ───→ WARN + PASS
│ └── Hard limit ───→ BLOCK
│
└─ Check request counter ─── Under limit ──→ PASS
└── Soft limit ───→ WARN + PASS
└── Hard limit ───→ BLOCK
```
##### 默认预算
| 范围 | 全局 | Gmail/Drive 操作 |
| ------------- | --------- | --------------- |
| 会限制 | 100 次调用 | 50 次调用 |
| 请求限制 | 20 次调用 | 10-20 次调用 |
##### 关键特性
- **双重强制执行范围** — 会话级和请求级预算
- **软限制 + 硬限制** — 在阻断前发出警告
- **按方法粒度** — `FileSystem.read` 和 `Gmail.send` 有不同的限制
- **滑动窗口** — 24 小时可配置窗口用于计数器重置
- **会话跟踪** — 将虚拟会话 ID 映射到真实会话密钥
##### 与 OpenClaw `tools.loopDetection` 对比
OpenClaw 核心内置了一个 `tools.loopDetection` 防护,用于检测**退化调用模式**——使用相同参数重复调用同一工具、无状态更改的已知轮询、乒乓交替——在一个滑动窗口内。它是**基于模式的**并且默认禁用。Sapience Tool Call Limits 是**基于预算的**:它将累积调用与数字配额进行比较。两者解决不同的故障模式,旨在协同运行。
| 故障模式 | OpenClaw 循环检测器 | Sapience Limits |
| ------------------------------------------------------------------- | :--------------------: | :-------------: |
| `Gmail.read` 使用相同参数被轮询 50 次 | ✅ | ✅ |
| `Gmail.send` 在一个会话中发送给 50 个不同的收件人 (垃圾邮件/数据窃取) | ❌ 参数不同 | ✅ |
| 代理使用不同的游标进行 100 次合法分页 | ⚠️ 可能误报 | ✅ 有界限 |
| 乒乓 `Read → Write → Read → Write` | ✅ | ❌ |
| 成本激增:1000 次看似廉价的调用,均未重复 | ❌ 无模式 | ✅ |
| 请求级失控(单轮 20 次以上调用,均不相同) | ❌ | ✅ |
**基于模式与基于预算之外的区别:**
- **双重强制执行范围** — 每次调用都会评估会话(生命周期)*和*请求(单次交互)预算
- **每个模块 × 每个方法的粒度** — `Gmail.send` 与 `FileSystem.read` 有不同的预算
- **软限制 + 硬限制层级** — 在阻断前发出警告,以便操作员看到接近限制的情况
- **24 小时滚动窗口** — 计数器自动重置,无需手动干预
- **内置可观测性** — `sai limits status` 和仪表盘页面显示实时计数器;OpenClaw 的检测器只在触发时记录日志
**推荐设置:** 两者都启用。OpenClaw 的检测器以低廉的成本捕获退化的*形状*;Sapience Limits 捕获模式检测器无法看到的预算超支(分布式循环、成本激增、请求级失控)。
##### CLI
```
sai limits stats # View current usage
sai limits show # View limit policies
sai limits reset # Reset counters
```
仪表盘
仪表盘是一个由 OpenClaw 网关提供的 **Preact 单页应用**。它为套件中的每个中间件提供实时配置、状态监控和日志流。
**页面:**
| 页面 | 您可以做什么 |
| -------------------- | ------------------------------------------------------------------ |
| **概览** | 开启/关闭中间件,查看系统范围的健康统计信息 |
| **HITL** | 查看待处理的审批、决策审计轨迹、策略可视化 |
| **Context Editing** | 会话历史、压缩统计信息、实体锁定 |
| **Model Routing** | 路由指标、成本趋势、层级配置 |
| **Guardrail** | 威胁检测日志、规则配置、出站控制 |
| **PII Sanitizer** | 检测模式、DLP 策略编辑器 |
| **Tool Call Limits** | 使用跟踪、限制配置 |
**技术栈:** Preact + preact-router,@preact/signals 用于状态管理,uPlot 用于图表,SSE 用于实时流。
### 插件清单
这是 OpenClaw 的插件加载器在注册套件时看到的内容:
```
{
"id": "sapience-ai-suite",
"entry": "dist/plugin/index.js",
"hooks": [
"before_tool_call",
"before_prompt_build",
"before_agent_start",
"before_message_write",
"agent_end",
"llm_output"
]
}
```
入口导出 `SapienceMiddlewarePlugin`(一个符合 `SapienceMiddlewareManifest` 的默认导出对象)。加载器注册每个声明的钩子,并分派到进程内管道运行器 (`MiddlewareRegistry`)。
### 卸载
分三步反转安装过程。顺序很重要:首先删除 CLI shim,以便 `$PATH` 上不会有悬空的内容,然后从网关注销插件,最后重新启动,以便运行时停止加载它。
```
# 1. 移除 `sai` CLI 符号链接(如果你之前使用的是 `npm install -g`,请跳过此步)
rm ~/.local/bin/sai
# 2. 取消注册插件,并从 ~/.openclaw/extensions/ 中删除其运行时文件
openclaw plugins uninstall sapience-ai-suite
# 3. 重新加载 gateway,以便插件不再在进程内被加载
openclaw gateway restart
```
**配置和审计数据默认保留。** 位于 `~/.openclaw/sapience-ai-suite/` 的磁盘配置存储(HITL 策略、模型路由、Guardrail 规则、审计日志 JSONL、MFA 密钥)在卸载后依然存在——稍后重新安装会自动恢复它们。要彻底删除:
```
rm -rf ~/.openclaw/sapience-ai-suite
```
编程式用法
该套件作为单个 npm 包发布,**每个中间件有一个子路径**。导入 HITL 不会引入路由器或 Guardrail 代码,并且每个子路径都可以单独进行摇树优化。您可以直接构建中间件,可选择将其交给 `MiddlewareRegistry`,然后自己调用生命周期方法。
### 快速入门
```
npm install @sapience-ai-corporation/openclaw-middleware-suite
```
```
import { MiddlewareRegistry } from '@sapience-ai-corporation/openclaw-middleware-suite';
import { HitlMiddleware } from '@sapience-ai-corporation/openclaw-middleware-suite/hitl';
import { GuardrailMiddleware } from '@sapience-ai-corporation/openclaw-middleware-suite/guardrail';
const hitl = new HitlMiddleware();
const guard = new GuardrailMiddleware();
await hitl.initialize({ defaultAction: 'ASK' });
await guard.initialize({ dryRunMode: false });
const registry = new MiddlewareRegistry();
registry.register(hitl);
registry.register(guard);
// In your tool-call dispatcher:
const result = await registry.runBeforeToolCall(ctx);
if (result.block) throw new Error(result.reason);
```
这就是完整的嵌入形式:导入 → 构建 → `initialize()` → 注册 → 调用生命周期方法(`runBeforeToolCall`、`runBeforeAgentStart` 等)。下面的所有其他内容都是关于契约、配置路径以及每个中间件编程接口的详细信息。
### 根接口
根包仅暴露跨领域的框架关注点——管道运行器、基础契约、插件生命周期。中间件类位于它们自己的子路径下。
```
import {
// Plugin lifecycle
registerPlugin,
unregisterPlugin,
isPluginRegistered,
// Pipeline runner
MiddlewareRegistry,
// Base pipeline contract
Middleware,
MiddlewareContext,
MiddlewareResult,
// Plugin entry (what OpenClaw's loader imports)
SapienceMiddlewarePlugin,
SapienceMiddlewareManifest,
} from '@sapience-ai-corporation/openclaw-middleware-suite';
```
基础管道契约:
```
interface Middleware {
readonly name: string;
readonly version: string;
initialize(config: Record): Promise;
// Tool-call pipeline
beforeToolCall?(context: MiddlewareContext): Promise;
afterToolCall?(context: MiddlewareContext, result: unknown): Promise;
// OpenClaw lifecycle events (implement only the surfaces you need)
beforeAgentStart?(context: AgentStartContext): Promise;
beforePromptBuild?(context: PromptBuildContext): Promise;
beforeMessageWrite?(
context: MessageWriteContext
): MessageWriteResult | undefined | Promise;
agentEnd?(context: AgentEndContext): Promise;
llmOutput?(context: LlmOutputContext): Promise;
// Lifecycle / reporting
getStatus(): { enabled: boolean; stats?: Record };
shutdown?(): Promise;
}
interface MiddlewareResult {
block: boolean;
reason?: string;
modifiedParams?: Record;
/** First-class "force human approval" signal — guardrail WARN and PII
* ESCALATE both surface here so orchestrators read one consistent field. */
escalate?: boolean;
escalateReason?: string;
metadata?: Record;
}
```
每个生命周期上下文(`MiddlewareContext`、`AgentStartContext`、`PromptBuildContext`、`MessageWriteContext`、`AgentEndContext`、`LlmOutputContext`)都扩展了共享的 `LifecycleContext` 基类,因此无论触发哪个事件,会话作用域字段(`sessionKey`、`agentId`、`runId`、`metadata`)都位于同一位置。
### 中间件子路径
| 中间件 | 子路径 | 主要类 |
|---|---|---|
| [HITL](#programmatic-hitl) | `@sapience-ai-corporation/openclaw-middleware-suite/hitl` | `HitlMiddleware` |
| [Context Editing](#programmatic-context-editing) | `@sapience-ai-corporation/openclaw-middleware-suite/context-editing` | `ContextEditingMiddleware` |
| [Model Routing](#programmatic-model-routing) | `@sapience-ai-corporation/openclaw-middleware-suite/model-routing` | `ModelRoutingMiddleware` |
| [Guardrail](#programmatic-guardrail) | `@sapience-ai-corporation/openclaw-middleware-suite/guardrail` | `GuardrailMiddleware` |
| [PII Sanitizer](#programmatic-pii-sanitizer) | `@sapience-ai-corporation/openclaw-middleware-suite/pii-sanitizer` | `PiiSanitizerMiddleware` |
| [Tool Call Limit](#programmatic-tool-call-limit) | `@sapience-ai-corporation/openclaw-middleware-suite/tool-call-limit` | `ToolCallLimitMiddleware` |
### `initialize(config)` 的优先级
套件中的每个中间件都以相同的方式解析配置:
```
DEFAULTS < inline config < sapience-ai-suite.json disk overlay
```
关于这对于嵌入式消费者有何影响,需要了解三件事:
1. **磁盘覆盖层是可选的,但在存在时总是占主导地位。** 如果 `~/.openclaw/sapience-ai-suite/sapience-ai-suite.json` 存在(因为用户运行了 `sai init`,或者仪表盘写入了它,或者另一个实例之前保存过),它的值将覆盖您内联传递的任何内容。希望获得完全可重现配置的应用程序应确保磁盘上不存在该文件(参见下文的 *密闭嵌入*)。
2. **插件运行时总是传递 `{}`**(对于 Context Editing 是 `{ pluginApi }`)。这就是为什么插件行为是“默认值 + 磁盘,没有其他”——没有内联层与仪表盘的写入竞争。
3. **三种配置路径。** 每个中间件都支持相同的设置配置的三种方式:
- **在 `initialize(config)` 时内联** — 传递中间件配置类型的部分内容。在默认值之上应用,在磁盘覆盖层之下。
- **进程内 `updateConfig(partial)`** — 在内存中修补正在运行的实例;**无磁盘 I/O**,无跨进程可见性。最适合不想触及 `sapience-ai-suite.json` 的嵌入式应用。
- **磁盘支持的 `Store.update()` / `Store.save()` + reload** — 持久化到 `sapience-ai-suite.json`,在重启后依然存在,传播到监视同一文件的其他插件实例。
### 最佳实践
以下五个注意事项适用于所有六个中间件;它们是嵌入式消费者最常遇到的陷阱。在此之后的各个中间件部分仅指出*偏离*这些默认值的细节。
#### 密闭嵌入
磁盘覆盖层是让仪表盘和 `sai` CLI 透明地管理插件的关键——但对于提供自己配置的嵌入式应用来说,这是一个隐患:留在部署主机上的 `sapience-ai-suite.json` 会默默地覆盖您内联的 `initialize()` 值。两种解决方案:
**选项 1 — 磁盘上无文件。** 如果部署机器上不存在 `sapience-ai-suite.json`,则没有覆盖层 → 内联完全占主导:
```
await mw.initialize({ dryRunMode: true }); // applies; nothing shadowing it
```
**选项 2 — 初始化后使用 `updateConfig`。** 在内存中修补解析后的配置,无论文件是否存在都绕过磁盘覆盖层:
```
await mw.initialize({}); // pulls disk if any
mw.updateConfig({ dryRunMode: true }); // overrides disk in memory
```
如果您不知道部署机器上是否存在 `sapience-ai-suite.json`,请选择选项 2——它在两种模式下都有效。MR 代理端口和其他一些“在启动时构建”的字段是特殊情况;请参阅每个中间件的注释。
#### 用于进程内修补的 `updateConfig`
`mw.updateConfig(partial)` 是主要的进程内调节器。它直接修补 `this.config`,没有 I/O,也没有文件监视器事件。当您希望**仅**当前进程看到运行时更改时使用它——通常是因为要么(a)您是嵌入式的,不想触及磁盘,要么(b)您希望仅针对此实例覆盖来自磁盘的值。
#### 浅合并
`Store.update()` 和 `mw.updateConfig(partial)` 都**仅在顶层**进行浅合并。传递像 `{ modules: { ... } }` 这样的部分内容会替换整个 `modules` 映射——擦除同级项。对于嵌套修补,请读取当前值并自行展开子映射:
```
const current = mw.getConfig();
mw.updateConfig({ modules: { ...current.modules, FileSystem: { /* … */ } } });
```
下面的每个中间件部分指出了哪些顶层字段是值得注意的嵌套对象。**一个例外:** `ce.updateConfig` 深度合并其三个已知子树(`icc` / `pruning` / `compaction`),以匹配其 `initialize` 约定。
#### 重载语义
`reloadPolicy()` (HITL, PII) 和 `reloadConfig()` (CE, MR, Guardrail, TCL) 重新读取磁盘覆盖层,并将其重新合并到当前内存状态之上。进程内的 `updateConfig()` 修补在磁盘未设置的字段的重载后依然存在——但磁盘设置的字段会覆盖它们(优先级与 `initialize()` 相同)。要在重载后重新声明覆盖,请再次调用 `updateConfig()`。
#### 简单委托模式
一旦调用 `initialize()`,每个生命周期方法就会直接运行——没有内部启用的门。**在您自己的管道中禁用中间件只需简单地不调用其方法**;插件运行时通过 `Store.isPluginEnabled()` 在上游对每个中间件进行门控。一旦初始化,`getStatus().enabled` 会被硬编码为 `true`。
**MR 是一个例外。** 其 `enabled` 字段跟踪嵌入式 HTTP 代理是否绑定到端口,因此 `mr.initialize({ enabled: false })` 是一种有意义的“构建配置但不启动代理”模式(用于进程内仅 `pickTier` 的嵌入),并且 `mr.getStatus().enabled` 反映了实际的代理状态。
### 中间件
每个中间件都在其自己的子路径下提供了一个独立的编程接口。这些模式刻意保持一致——构建 → `await initialize(config?)` → 直接调用生命周期方法或将实例交给 `MiddlewareRegistry.register()`。阻断/升级/放行使用一种形式:`MiddlewareResult.block`、`escalate`、`escalateReason`、`modifiedParams`。参见上面的[基础契约](#root-surface)。
下面的每个中间件还记录了一个**独立原语**子部分——纯函数或单类导出,允许您跳过完整的中间件实例,用于测试、一次性分类或构建您自己的管道形式。
快速链接:[HITL](#programmatic-hitl) ·Context Editing](#programmatic-context-editing) · [Model Routing](#programmatic-model-routing) · [Guardrail](#programmatic-guardrail) · [PII Sanitizer](#programmatic-pii-sanitizer) · [Tool Call Limit](#programmatic-tool-call-limit).
#### HITL
```
import {
HitlMiddleware,
Interceptor,
createToolCallHook,
classifyDestructiveAction,
scoreIrreversibility,
MemoryRiskForecaster,
trustRateLimiter,
DEFAULT_POLICY,
// types
Decision,
SecurityPolicy,
SecurityRule,
SystemThresholds,
BeforeToolCallEvent,
ToolContext,
BeforeToolCallResult,
} from '@sapience-ai-corporation/openclaw-middleware-suite/hitl';
```
| 运行时 | 类型 |
|---|---|
| `HitlMiddleware`, `Interceptor`, `Arbitrator`, `approvalQueue`, `createToolCallHook`, `getToolMapping`, `getProtectedModules`, `classifyDestructiveAction`, `hashArgs`, `scoreIrreversibility`, `MemoryRiskForecaster`, `trustRateLimiter`, `TrustRateLimiter`, `detectBrowserChallenge`, `PolicyStore`, `DecisionLog`, `StatsTracker`, `BrowserSessionStore`, `DEFAULT_POLICY` | `Decision`, `SecurityPolicy`, `SecurityRule`, `SystemThresholds`, `ExecutionContext`, `InterventionMetadata`, `DestructiveClassification`, `DestructiveSeverity`, `IrreversibilityAssessment`, `MemoryRiskAssessment`, `SimulatedPath`, `BrowserChallengeSignal`, `EscalationLevel`, `TrustRateLimiterState`, `PersistedPolicy`, `DecisionRecord`, `Stats`, `SessionInjectionResult`, `BeforeToolCallEvent`, `ToolContext`, `BeforeToolCallResult` |
| 方法 | OpenClaw 事件 | 作用 |
|---|---|---|
| `beforeToolCall(ctx: MiddlewareContext)` | `before_tool_call` | 通过底层的 `Interceptor` 根据加载的 `SecurityPolicy` 评估调用——破坏性操作分类、不可逆性评分、内存风险预测、浏览器挑战检测、信任速率限制和基于规则的 ALLOW/DENY/ASK 匹配。在 DENY 或失败的 ASK 时返回 `{ block: true, reason }`,在 ALLOW 时返回 `{ block: false }`。 |
磁盘覆盖层位于 `sapience-ai-suite.json[hitl.policy]`。插件门控:`_hitl.isPluginEnabled()`(通过 `HitlMiddleware` 代理到底层的 Interceptor)。请注意嵌套对象字段上的浅合并整体替换:`modules`、`systemThresholds`。
```
import { MiddlewareRegistry } from '@sapience-ai-corporation/openclaw-middleware-suite';
import { HitlMiddleware, PolicyStore } from '@sapience-ai-corporation/openclaw-middleware-suite/hitl';
const hitl = new HitlMiddleware();
// Path 1: Inline at initialize
await hitl.initialize({
defaultAction: 'DENY',
modules: {
Shell: { bash: { action: 'ASK' } },
FileSystem: { write: { action: 'ASK', allowPaths: ['/tmp/**'] } },
},
});
new MiddlewareRegistry().register(hitl);
// Path 2: In-process patch
hitl.updateConfig({ defaultAction: 'ALLOW' });
// Path 3: Disk-backed
await PolicyStore.update({ defaultAction: 'DENY' });
hitl.reloadPolicy();
```
##### 独立原语
如果您根本不想要 `HitlMiddleware` 实例,风险评估的构建块将作为自由函数和小型类导出——与中间件内部使用的返回形式相同,但可在您自己的管道中组合:
| 导出 | 提供的功能 |
|---|---|
| `Interceptor` | 完整的 ALLOW/DENY/ASK 决策引擎,无需 `Middleware`-接口包装器。使用内联策略构建并调用 `evaluate(event, context)`——这是 `HitlMiddleware` 内部封装的内容。 |
| `createToolCallHook(interceptor)` | 将 `Interceptor` 直接接入 OpenClaw 的 `before_tool_call` 钩子契约(返回 `BeforeToolCallResult`),绕过 `MiddlewareRegistry`。 |
| `classifyDestructiveAction(toolName, params)` | 返回 `{ severity: 'HIGH' \| 'CATASTROPHIC' \| null, reason }`。适用于无需强制执行的报告/日志记录。 |
| `scoreIrreversibility(toolName, params)` | 返回一个 0-100 的不可逆性分数。ASK 仲裁器用于门控 TOTP 要求的评分器。 |
| `MemoryRiskForecaster` | 模拟候选写入的下游内存污染的类。ASK 预过滤使用的预测器。 |
| `trustRateLimiter` | 跨审批跟踪信任衰减状态的单例。`getLevel()` / `recordDenial()` 镜像了 `Interceptor` 的调用。 |
| `DEFAULT_POLICY` | 附带的基线 `SecurityPolicy`——在编写内联覆盖时从这里开始。 |
```
import { Interceptor, createToolCallHook } from '@sapience-ai-corporation/openclaw-middleware-suite/hitl';
const interceptor = new Interceptor({
defaultAction: 'DENY',
modules: {
FileSystem: {
read: { action: 'ALLOW' },
write: { action: 'ASK', allowPaths: ['/tmp/**'], denyPaths: ['**/.ssh/**'] },
},
Shell: { bash: { action: 'ASK' } },
},
});
const hook = createToolCallHook(interceptor);
const result = await hook(
{ toolName: 'write', params: { path: '/tmp/out.txt', content: 'data' } },
{ toolName: 'write', sessionKey: 'session-1' }
);
// allow path → undefined (void)
// block path → { block: true, blockReason: '...' }
```
#### Context Editing
```
import {
ContextEditingMiddleware,
DEFAULT_CONTEXT_EDITING_CONFIG,
ContextEditingPolicyStore,
// types
ContextEditingConfig,
ContextEditingPolicyData,
CompactionTrigger,
CompactionResult,
SessionBuffer,
EntityLock,
ConflictResolution,
} from '@sapience-ai-corporation/openclaw-middleware-suite/context-editing';
```
| 运行时 | 类型 |
|---|---|
| `ContextEditingMiddleware`, `ContextEditingPolicyStore`, `DEFAULT_CONTEXT_EDITING_CONFIG` | `ContextEditingConfig`, `ContextEditingPolicyData`, `CompactionTrigger`, `CompactionResult`, `SessionBuffer`, `EntityLock`, `ConflictResolution` |
| 方法 | OpenClaw 事件 | 作用 |
|---|---|---|
| `beforeToolCall(ctx)` | `before_tool_call` | 透传 (对工具调用为空操作;触发评估在 agent_end 进行) |
| `afterToolCall(ctx, result)` | `after_tool_call` | 使用工具输出的 token 估算细化会话缓冲区 |
| `beforeAgentStart(ctx)` | `before_agent_start` | 执行计划中的压缩 (SessionManager 前——无 DAG 分支) |
| `beforePromptBuild(ctx)` | `before_prompt_build` | 同步会话统计;跟踪主代理的 runId 以供 `llmOutput` 使用 |
| `agentEnd(ctx)` | `agent_end` | 自适应触发评估;为下一轮交互安排压缩 |
| `llmOutput(ctx)` | `llm_output` | 记录助手的 token 使用量以计算节省量 |
磁盘覆盖层位于 `sapience-ai-suite.json[context_editing][configOverrides]`。插件门控:`ContextEditingPolicyStore.isPluginEnabled()`。`config` 参数有一个特定于 CE 的目的:在插件内运行时在此处传递 OpenClaw 的 `pluginApi`,以便 ICC 的 LLM 提取可以通过它进行调度。**CE 深度合并三个嵌套子树** `icc` / `pruning` / `compaction`(在 `initialize`、`updateConfig` 和 `reloadConfig` 中),因此像 `{ icc: { messagesKeptBeforeCompaction: 5 } }` 这样的部分内容会保持 `icc.customPrompt`、`icc.customSchema` 等不变——顶层字段像套件中的其他地方一样浅合并。
```
import { MiddlewareRegistry } from '@sapience-ai-corporation/openclaw-middleware-suite';
import {
ContextEditingMiddleware,
ContextEditingPolicyStore,
DEFAULT_CONTEXT_EDITING_CONFIG,
} from '@sapience-ai-corporation/openclaw-middleware-suite/context-editing';
const ce = new ContextEditingMiddleware();
// Path 1: Inline at initialize
await ce.initialize({
triggerMode: 'both', // 'token' | 'message' | 'both'
tokenThreshold: 80_000, // early compaction when transcript > 80k tokens
messageThreshold: 50, // or > 50 messages
icc: {
...DEFAULT_CONTEXT_EDITING_CONFIG.icc,
messagesKeptBeforeCompaction: 3,
},
// pluginApi: // omit when embedding standalone —
// // ICC LLM calls will be skipped.
});
new MiddlewareRegistry().register(ce);
// Path 2: In-process patch (deep-merges icc/pruning/compaction).
ce.updateConfig({ triggerMode: 'token' });
ce.updateConfig({ icc: { messagesKeptBeforeCompaction: 5 } }); // keeps customPrompt etc.
// Path 3: Disk-backed
await ContextEditingPolicyStore.update({ triggerMode: 'token' });
ce.reloadConfig();
```
##### 独立原语
Context Editing 是六个中间件中最受中间件驱动的——其触发评估、调度和 ICC 调度仅作为长期存在的管道参与者才有意义。仍有两个导出是独立有用的:
| 导出 | 提供的功能 |
|---|---|
| `ContextEditingPolicyStore` | 对磁盘 `ContextEditingPolicyData` 形式的直接读写访问。用于从设置脚本编写磁盘覆盖层,而无需启动中间件。 |
| `DEFAULT_CONTEXT_EDITING_CONFIG` | 完整的嵌套默认值对象——内联配置的起点(特别是 `icc` 子树,它有许多字段)。 |
如果您需要在中间件之外进行 ICC 提取(例如,用于离线记录分析),调度路径是私有的——请提出描述您用例的 issue,我们将考虑将其公开。
#### Model Routing
```
import {
ModelRoutingMiddleware,
scoreRequest,
RequestDeduplicator,
ResponseCache,
CostTracker,
PluginRegistry,
PROFILE_CONFIGS,
VALID_PROFILES,
isValidProfile,
// types
RouterPlugin,
Tier,
RoutingDecision,
RoutingStats,
ModelRoutingConfig,
} from '@sapience-ai-corporation/openclaw-middleware-suite/model-routing';
```
| 运行时 | 类型 |
|---|---|
| `ModelRoutingMiddleware`, `scoreRequest`, `classifyWithLLM`, `RequestDeduplicator`, `ResponseCache`, `discoverAllModels`, `resolveProvider`, `autoAssignTiers`, `MomentumTracker`, `SessionStore`, `CostTracker`, `PluginRegistry`, `PROFILE_CONFIGS`, `VALID_PROFILES`, `isValidProfile`, `DEFAULT_MODEL_ROUTING_CONFIG`, `DEFAULT_SCORING_CONFIG`, `DEFAULT_CLASSIFIER_CONFIG`, `DEFAULT_DEDUP_CONFIG` | `ModelRoutingConfig`, `ResponseCacheConfig`, `MomentumConfig`, `SessionStoreConfig`, `SessionEntry`, `PinningDecision`, `RoutingProfile`, `CostAlertConfig`, `CostEvent`, `CostSummary`, `RouterPlugin`, `AfterForwardEvent`, `Tier`, `ScoringResult`, `RoutingDecision`, `RoutingStats`, `DimensionScore`, `DiscoveredModel`, `ProviderConfig`, `ClassifierConfig`, `DedupConfig`, `ModelCapabilities`, `FallbackAttempt` |
**两种消费模式。** Model Routing 可以根据您调用 LLM 的方式以两种模式嵌入到您的应用中:
1. **嵌入式 HTTP 代理** *(默认)* — `initialize()` 构建配置并在 `port` 上启动 localhost 代理。将您现有的 OpenAI/Anthropic SDK 指向 `http://127.0.0.1:{port}/v1/chat/completions`,路由将在代理内部透明进行。
2. **进程内评分** — 直接调用 `mr.pickTier(input)` 以获取选定的层级 + 置信度 + 维度细分,而无需绑定端口。当您希望转发到自己的提供商客户端时很有用。
磁盘覆盖层位于 `sapience-ai-suite.json[model_routing]`。请注意嵌套对象字段上的浅合并整体替换:`tiers`、`tierOverrides`、`weightOverrides`、`boundaryOverrides`、`providerConfigs`。**API 密钥回退** — 当内联配置和磁盘覆盖层都未指定时,`targetApiKey` 和每个提供商的密钥会回退到环境变量(`OPENAI_API_KEY`、`ANTHROPIC_API_KEY`、`GOOGLE_API_KEY`)。
```
import { MiddlewareRegistry } from '@sapience-ai-corporation/openclaw-middleware-suite';
import {
ModelRoutingMiddleware,
ModelRoutingPolicyStore,
} from '@sapience-ai-corporation/openclaw-middleware-suite/model-routing';
const mr = new ModelRoutingMiddleware();
// Path 1a — Mode 1 (embedded proxy, default): inline at initialize.
await mr.initialize({
enabled: true, // auto-start proxy (default)
port: 9000,
defaultProfile: 'auto', // 'eco' | 'auto' | 'premium' | 'agentic'
targetApiKey: process.env.OPENAI_API_KEY,
// tiers, providers, classifier, dedup, session, momentum, responseCache,
// costAlerts — all optional, all merged over DEFAULT_MODEL_ROUTING_CONFIG
});
// Route your LLM client at http://127.0.0.1:9000/v1/chat/completions
// Path 1b — Mode 2 (in-process scoring): pass `enabled: false` to skip the proxy.
await mr.initialize({ enabled: false });
const result = mr.pickTier({
body: {
messages: [{ role: 'user', content: 'Refactor this code…' }],
tools: [{ type: 'function', function: { name: 'edit_file', /* … */ } }],
},
});
// result = { tier: 'COMPLEX', score: 47.2, confidence: 0.83, dimensions: [...] }
const tierConfig = mr.getConfig().tiers[result.tier];
// tierConfig = { primary: 'gpt-5', fallbacks: ['claude-opus-4-7'] }
// Path 2: In-process patch. Shallow-merges into this.config; if the proxy is
// running, propagates via proxy.updateConfig() so live HTTP traffic uses
// the new values.
mr.updateConfig({ defaultProfile: 'cost' });
new MiddlewareRegistry().register(mr);
// Path 3: Disk-backed. CLI (`sai router …`) and dashboard use the same pair.
await ModelRoutingPolicyStore.update({ defaultProfile: 'premium' });
mr.reloadConfig();
```
**代理的运行时控制。** 无需重新初始化即可切换:
```
await mr.start(); // bind the proxy port (idempotent — no-op if running)
await mr.stop(); // tear down the proxy + cleanup; instance stays usable
await mr.start(); // re-bind without rebuilding config
await mr.shutdown(); // permanent teardown (Middleware-interface contract)
```
`start()` 要求首先调用 `initialize()` 以便填充 `this.config`。`initialize({ enabled: false })` 是一种构建配置但不自动启动代理的方法(用于进程内仅 `pickTier` 的嵌入)。
##### 独立原语
Model Routing 的内部异常可组合——管道的每个阶段都作为自由函数或小型类导出,因此您可以构建自己的路由器,而无需实例化 `ModelRoutingMiddleware`:
| 导出 | 提供的功能 |
|---|---|
| `scoreRequest(input, scoringConfig)` | 作为自由函数的 23 维评分器。与 `mr.pickTier(input)` 的返回形式相同——`{ tier, score, confidence, dimensions }`。适用于测试和一次性分类。 |
| `classifyWithLLM(input, classifierConfig)` | 可选的基于 LLM 的回退分类器(在评分置信度低于歧义阈值时使用)。 |
| `RequestDeduplicator` | 具有 30 秒窗口的 SHA-256 键控进行中 + 已完成的去重缓存。放入您自己的 HTTP 客户端以合并并发的相同请求。 |
| `ResponseCache` | 针对 `temperature=0` 非流式请求的 LRU 响应缓存。默认 200 条目 / 10 分钟。 |
| `CostTracker` | 带有每日警告/严重警报的 90 天滚动成本分类账。 |
| `discoverAllModels()` / `resolveProvider()` / `autoAssignTiers()` | 实时 LiteLLM 目录拉取、提供商格式检测和层级自动分配——`sai router models` 在内部使用的构建块。 |
| `MomentumTracker`, `SessionStore` | 会话动量和固定状态。在运行您自己的代理时用于在交互中保持相同的模型热度。 |
| `PluginRegistry`, `RouterPlugin` | 用于 `onBeforeScore` / `onAfterScore` / `onBeforeForward` / `onAfterForward` 的挂钩点系统。与 `ModelRoutingMiddleware` 组合或独立使用。 |
| `PROFILE_CONFIGS`, `VALID_PROFILES`, `isValidProfile` | 四个内置配置文件预设(`eco` / `auto` / `premium` / `agentic`)和验证器。 |
| `DEFAULT_*_CONFIG` | 路由/评分/分类器/去重的完整默认配置对象。展开到内联覆盖中。 |
```
import { scoreRequest, DEFAULT_SCORING_CONFIG } from '@sapience-ai-corporation/openclaw-middleware-suite/model-routing';
const result = scoreRequest(
{ body: { messages: [{ role: 'user', content: 'Refactor this code…' }] } },
DEFAULT_SCORING_CONFIG,
);
// { tier: 'COMPLEX', score: 47.2, confidence: 0.83, dimensions: [...] }
```
#### Guardrail
```
import {
GuardrailMiddleware,
GuardrailScanner,
executeGuardrailScan,
createWriteScannerHook,
createPromptGuardHook,
createModerationGuardHook,
consumeModerationResult,
scrubMetadata,
getPatternCount,
GuardrailConfigStore,
GuardrailDecisionLog,
// types
GuardrailConfig,
GuardrailDetection,
DetectionRule,
OutputScrubberConfig,
ScrubResult,
ModerationCacheEntry,
} from '@sapience-ai-corporation/openclaw-middleware-suite/guardrail';
```
| 运行时 | 类型 |
|---|---|
| `GuardrailMiddleware`, `GuardrailScanner`, `executeGuardrailScan`, `createWriteScannerHook`, `createPromptGuardHook`, `createModerationGuardHook`, `consumeModerationResult`, `scrubMetadata`, `getPatternCount`, `GuardrailConfigStore`, `GuardrailDecisionLog` | `GuardrailConfig`, `GuardrailDetection`, `DetectionRule`, `OutputScrubberConfig`, `ScrubResult`, `ModerationCacheEntry` |
**单一对象访问所有 guardrail。** `GuardrailMiddleware` 实现了 guardrail 关心的所有 `Middleware` 接口生命周期方法。每个方法都接收一个扩展自 `LifecycleContext`(来自 `@sapience-ai-corporation/openclaw-middleware-suite`)的标准化上下文,因此调用约定与 `beforeToolCall` 保持一致:
| 方法 | OpenClaw 事件 | 涵盖的防护 |
|---|---|---|
| `beforeToolCall(ctx: MiddlewareContext)` | `before_tool_call` | 敏感路径、出口控制、破坏性命令、Shell 间接调用、预读扫描、参数模式扫描 (regex + prefix + heuristic + Unicode NFKC + 置信度过滤器) |
| `beforeAgentStart(ctx: AgentStartContext)` | `before_agent_start` | Prompt-guard 策略注入,异步 OpenAI Moderation API(为 `beforeMessageWrite` 缓存结果) |
| `beforeMessageWrite(ctx: MessageWriteContext)` | `before_message_write` | 角色冒充、Agent 审查、金丝雀追踪器、转录本 regex/prefix/heuristic 扫描、审核缓存强制执行 |
**阻断 / 升级 / 放行 — 整个套件采用相同的结构。** `beforeToolCall` 返回标准的 `MiddlewareResult`。硬性 BLOCK 检测会设置 `block: true`;WARN 检测(需要 HITL 批准而非硬性拒绝)则设置一等公民的 `escalate: true` + `escalateReason` 字段 — 与 `PiiSanitizerMiddleware` 用于其 ESCALATE 严重级别 DLP 规则的通道相同,因此编排器在所有 middleware 中读取的都是一个一致的字段:
```
const result = await gr.beforeToolCall(ctx);
if (result.block) reject(result.reason);
if (result.escalate) requestApproval(result.escalateReason); // route to your own HITL
if (result.modifiedParams) ctx.params = result.modifiedParams;
```
磁盘覆写位于 `sapience-ai-suite.json[guardrail]`。插件门控:`GuardrailConfigStore.isPluginEnabled()`。注意嵌套对象字段上的浅合并整体替换:`rules`、`sensitivePaths`、`egressControl`、`destructiveCommands`、`outputScrubber`、`moderation`。
```
import { MiddlewareRegistry } from '@sapience-ai-corporation/openclaw-middleware-suite';
import {
GuardrailMiddleware,
GuardrailConfigStore,
} from '@sapience-ai-corporation/openclaw-middleware-suite/guardrail';
const gr = new GuardrailMiddleware();
// Path 1: Inline at initialize
await gr.initialize({
dryRunMode: true,
entropyThreshold: 5.0,
// rules, sensitivePaths, egressControl, destructiveCommands, outputScrubber,
// moderation — all optional partials, all merged over DEFAULT_GUARDRAIL_CONFIG
});
new MiddlewareRegistry().register(gr);
// Path 2: In-process patch
gr.updateConfig({ dryRunMode: false });
// Path 3: Disk-backed
await GuardrailConfigStore.update({ dryRunMode: true });
gr.reloadConfig();
```
`GuardrailMiddleware` 涵盖了所有三个生命周期切面(`before_tool_call`、`before_agent_start`、`before_message_write`) — OpenClaw 插件运行时使用此类作为其唯一的 guardrail 入口点,镜像了 HITL 使用的 `Interceptor` 模式。
##### 独立原语
Guardrail 合并前的架构将每个防护公开为 Hook 工厂或纯函数。全套功能仍可供希望自行按需组合防护的消费者使用:
| 导出 | 提供的功能 |
|---|---|
| `GuardrailScanner` | 底层的 regex / prefix / heuristic 扫描器。使用内联的 `GuardrailConfig` 构造,并调用 `.scan(text): GuardrailDetection[]` 进行临时文本扫描。注意:这**仅**涵盖纯模式引擎 — 敏感路径、出口、破坏性命令、角色冒充、金丝雀追踪器和审核仍从 `GuardrailConfigStore.getCached()` 读取,因此这不是一个完整的内联管道。 |
| `executeGuardrailScan(toolName, moduleName, methodName, params, sessionKey?, agentId?, configOverride?)` | 对工具调用的参数(内部从字符串字段中提取文本)运行完整的 regex+prefix+heuristic 堆栈的一次性扫描。返回 `GuardrailScanResult` — `{ block, escalate, detections }`。传递 `configOverride` 以避免对缓存存储的依赖。对于不通过工具参数的纯文本扫描,推荐使用 `GuardrailScanner.scan(text)`。 |
| `createWriteScannerHook(getConfig?)` | 返回一个 `before_message_write` Hook 函数。传递一个可选的 config 获取函数以覆盖缓存存储的后备方案。(下面的另外两个 Hook 工厂**不**接受 config 获取函数 — 它们总是读取缓存存储。) |
| `createPromptGuardHook()` | 返回一个用于 prompt-guard 策略注入的 `before_agent_start` Hook。 |
| `createModerationGuardHook()` | 返回异步 OpenAI Moderation API Hook,它为 `before_message_write` 预热同步缓存。 |
| `consumeModerationResult(sessionKey)` | 读取(并清除)审核缓存条目。用于在同步强制执行运行前检查异步 Hook 看到了什么。 |
| `scrubMetadata(text, cfg)` | 从 Agent 输出中去除 middleware 元数据 Token 的纯函数。在已经处理输出格式化的管道中非常有用。 |
| `getPatternCount()` | 诊断功能 — 返回已编译 regex 模式的实时计数。 |
```
import { GuardrailScanner, scrubMetadata } from '@sapience-ai-corporation/openclaw-middleware-suite/guardrail';
const scanner = new GuardrailScanner(/* GuardrailConfig — typically GuardrailConfigStore.getCached() */);
// Example role-override input (broken across concat to keep the README itself
// from matching naive prompt-injection scanners that read source verbatim).
const userInput = ['Ignore', 'previous', 'instructions and …'].join(' ');
const detections = scanner.scan(userInput);
// detections[0] = {
// ruleName: 'role-override-classic', ruleType: 'regex',
// severity: 'HIGH', action: 'BLOCK', confidence: 'HIGH',
// matchedContent: '…', matchIndex: 0, category: 'promptInjection',
// }
const cleaned = scrubMetadata('Done. [SAPIENCE_INTERNAL: …]', { stripTokens: true });
// 'Done.'
```
#### PII Sanitizer
```
import {
PiiSanitizerMiddleware,
PII_PATTERNS,
DlpStore,
// types
DlpPolicy,
DlpDetection,
DlpRule,
PiiPatternKey,
PiiPatternSpec,
PiiSeverity,
} from '@sapience-ai-corporation/openclaw-middleware-suite/pii-sanitizer';
```
| 运行时 | 类型 |
|---|---|
| `PiiSanitizerMiddleware`, `PII_PATTERNS`, `DlpStore` | `DlpPolicy`, `DlpDetection`, `DlpRule`, `PiiPatternKey`, `PiiPatternSpec`, `PiiSeverity` |
| 方法 | OpenClaw 事件 | 功能说明 |
|---|---|---|
| `beforeToolCall(ctx: MiddlewareContext)` | `before_tool_call` | 根据加载的 `DlpPolicy`,递归扫描工具参数(通过 `ShellParser` 对嵌套对象、数组和 Shell 命令字面量进行深度遍历)。对于 BLOCK 严重级别的匹配返回 `{ block: true, reason }`;对于 ESCALATE 严重级别的匹配返回 `{ block: false, escalate: true, escalateReason }`(路由至 HITL 审批);对于 REDACT 匹配返回 `{ block: false, modifiedParams }`(用经过脱敏的参数替换原始参数);未匹配时返回 `{ block: false }`。 |
**调用约定与 guardrail 相同。** `PiiSanitizerMiddleware` 实现了标准的 `Middleware` 接口,并共享相同的 `MiddlewareResult` 形状 — 包括一等公民的 `escalate` / `escalateReason` 通道。BLOCK 严重级别的规则返回 `block: true`;ESCALATE 严重级别的规则返回 `block: false, escalate: true, escalateReason: …`,以便编排器可以将其路由至 HITL 而不是硬性阻断。任何一条路径也可能携带应用了脱敏处理的 `modifiedParams`。
```
const result = await pii.beforeToolCall(ctx);
if (result.block) reject(result.reason); // BLOCK rule
if (result.escalate) requestApproval(result.escalateReason); // ESCALATE rule
if (result.modifiedParams) ctx.params = result.modifiedParams; // REDACT rule
```
磁盘覆写位于 `sapience-ai-suite.json[pii_sanitizer]`。插件门控:`DlpStore.isPluginEnabled()`。注意 `toolPolicies`(按模块映射)和 `globalRules`(规则数组)上的浅合并整体替换。
```
import { MiddlewareRegistry } from '@sapience-ai-corporation/openclaw-middleware-suite';
import { PiiSanitizerMiddleware, DlpStore } from '@sapience-ai-corporation/openclaw-middleware-suite/pii-sanitizer';
const pii = new PiiSanitizerMiddleware();
// Path 1: Inline at initialize
await pii.initialize({
dryRunMode: true,
// globalRules, toolPolicies — optional partials, all merged over DEFAULT_DLP_POLICY
});
new MiddlewareRegistry().register(pii);
// Path 2: In-process patch
pii.updateConfig({ dryRunMode: false });
// Path 3: Disk-backed
await DlpStore.update({ dryRunMode: true });
pii.reloadPolicy();
```
##### 独立原语
| 导出 | 提供的功能 |
|---|---|
| `PII_PATTERNS` | 完整的模式字典,以 `PiiPatternKey` 为键(`EMAIL`, `SSN`, `CREDIT_CARD`, `OPENAI_KEY`, `GITHUB_TOKEN`, `IP_ADDRESS`, …)。每个条目公开了 `type` (`'regex' \| 'prefix' \| 'heuristic'`)、`pattern` (字符串 — 需自行编译)、`severity` 和 `description`。可直接放入您自己的扫描器中,而无需承担 `PiiSanitizerMiddleware` 的运行时依赖。 |
| `DlpStore` | 直接读写磁盘上的 DLP 策略。用于通过安装脚本编写磁盘覆写。 |
```
import { PII_PATTERNS } from '@sapience-ai-corporation/openclaw-middleware-suite/pii-sanitizer';
const text = 'Send the report to user@example.com';
for (const [key, spec] of Object.entries(PII_PATTERNS)) {
if (spec.type !== 'regex') continue;
if (new RegExp(spec.pattern).test(text)) console.log(`Found ${key} (${spec.severity})`);
}
```
#### Tool Call Limit
```
import {
ToolCallLimitMiddleware,
LimitPolicyStore,
// types
LimitPolicy,
LimitRule,
EnforcementStatus,
} from '@sapience-ai-corporation/openclaw-middleware-suite/tool-call-limit';
```
| 运行时 | 类型 |
|---|---|
| `ToolCallLimitMiddleware`, `LimitPolicyStore` | `LimitPolicy`, `LimitRule`, `EnforcementStatus` |
| 方法 | OpenClaw 事件 | 功能说明 |
|---|---|---|
| `beforeToolCall(ctx: MiddlewareContext)` | `before_tool_call` | 递增每会话和每请求的计数器,然后根据加载的 `LimitPolicy`(全局 + 每工具规则)进行检查。当达到 HARD 限制时返回 `{ block: true, reason }`;当跨越 SOFT 限制时(仅记录日志,不阻断)返回 `{ block: false, metadata: { softLimitTriggered: true, scope } }`;否则返回 `{ block: false }`。通过策略的 `resetAt` 标记检测 `sai limits reset`,并在其更改时清除内存中的追踪器。 |
磁盘覆写位于 `sapience-ai-suite.json[tool_call_limit]`。插件门控:`LimitPolicyStore.isPluginEnabled()`。注意顶层 `modules` 映射上的浅合并整体替换。
```
import { MiddlewareRegistry } from '@sapience-ai-corporation/openclaw-middleware-suite';
import { ToolCallLimitMiddleware, LimitPolicyStore } from '@sapience-ai-corporation/openclaw-middleware-suite/tool-call-limit';
const limits = new ToolCallLimitMiddleware();
// Path 1: Inline at initialize
await limits.initialize({
globalSessionCallLimit: 100,
globalRequestCallLimit: 20,
// modules: per-tool rules — optional partial, merged over DEFAULT_LIMIT_POLICY
});
new MiddlewareRegistry().register(limits);
// Path 2: In-process patch
limits.updateConfig({ globalSessionCallLimit: 50 });
// Path 3: Disk-backed
await LimitPolicyStore.update({ globalSessionCallLimit: 200 });
limits.reloadConfig();
```
##### 独立原语
| 导出 | 提供的功能 |
|---|---|
| `LimitPolicyStore` | 直接读写磁盘上的 `LimitPolicy`。用于通过安装脚本编写磁盘覆写,或者通过写入新的 `resetAt` 标记来发出进程外重置(与 `sai limits reset` 使用的机制相同)。 |
计数追踪器(`SessionTracker` / `RequestTracker`)被有意设为私有 — 它们与 `before_tool_call` 生命周期和重置语义紧密耦合。如果您需要在 middleware 之外进行会话预算强制执行,请提交一个描述您使用场景的 issue。
## Hook 管道
每个工具调用按顺序通过 middleware 管道:
```
before_tool_call
│
├─ 1. Guardrail scan → Block injection, exfiltration, destructive commands
├─ 2. PII DLP scan → Block or redact PII in parameters
├─ 3. Tool call limit → Enforce session / request budgets
└─ 4. HITL evaluation → Apply policy; ASK → approval queue
│
▼
before_message_write
│
├─ 5. Write scanner → Scan outgoing content (L3 guards)
└─ 6. Output scrubber → Strip middleware tokens from agent responses
│
▼
before_prompt_build
│
└─ 7. Context editing → Inject ICC directives, sync session stats
│
▼
before_agent_start
│
└─ 8. Context compaction → Execute scheduled compaction before session opens
│
▼
agent_end
│
└─ 9. Trigger evaluation → Check if compaction is needed for next turn
│
▼
llm_output
│
└─ 10. Token tracking → Record assistant token usage for savings
```
## 环境变量
| 变量 | 描述 | 默认值 |
| ----------------- | -------------------------------------------- | ------------------------------ |
| `OPENCLAW_HOME` | OpenClaw 配置目录 | `~/.openclaw` |
| `OPENCLAW_CONFIG` | `openclaw.json` 的路径 | `$OPENCLAW_HOME/openclaw.json` |
| `LOG_LEVEL` | 日志级别 (`error`, `warn`, `info`, `debug`) | `info` |
## 数据存储
所有运行时数据存储在 `~/.openclaw/sapience-ai-suite/` 中。配置存在于一个统一的 JSON 存储中;每个 middleware 都有自己用于日志和运行时状态的目录:
```
~/.openclaw/sapience-ai-suite/
├── sapience-ai-suite.json # Unified config store (all middleware config)
├── sapience-ai-suite.log # Main application log
├── hitl/
│ ├── decisions.jsonl # Approval decision audit trail
│ ├── stats.json # Approval statistics
│ ├── browser-sessions.json # Encrypted browser automation state
│ └── totp.json # TOTP authenticator secret
├── context-editing/
│ ├── audit.jsonl # Compaction event audit trail
│ ├── stats.json # Compaction statistics
│ └── diagnostic.log # Verbose diagnostic output
├── model-routing/
│ ├── routing-audit.jsonl # Routing decision audit trail
│ ├── proxy-audit.log # Step-by-step request traces
│ ├── cost-tracker.json # Real-time cost tracking
│ └── litellm-model-catalog.json # Cached model pricing catalog
├── guardrail/
│ └── audit.jsonl # Guardrail detection audit trail
├── pii-sanitizer/
│ └── audit.jsonl # PII detection audit trail
└── tool-call-limit/
├── sessions.json # Session call trackers
├── requests.json # Request call trackers
└── last_request.txt # Virtual request ID tracking
```
## 安全保证
- **零信任** — 每个操作都根据策略进行评估,无隐式信任
- **同步阻断** — Agent 在执行前等待批准
- **无法绕过** — 插件 Hook 在框架级别拦截所有工具调用
- **不可变审计** — 符合合规要求的 JSON Lines 仅追加格式
- **安全失败** — 未知操作默认设为 ASK
- **ArgsHash 验证** — 批准的参数在重试时会进行哈希校验,以防止替换攻击
- **深度防御** — 多个独立的检测层 (guardrail, PII, HITL)
## 开发
```
git clone https://github.com/Sapience-AI/openclaw-middleware-suite.git
cd openclaw-middleware-suite
npm install
npm run build # Compile TypeScript + Vite dashboard
npm run dev:dashboard # Dev server on port 5173
npm test # 237 tests, 0 failures
npm run lint # ESLint
npm run format # Prettier
```
贡献
欢迎提交 PR!有关设置、测试命令、分支命名和代码风格的信息,请参见 [CONTRIBUTING.md](CONTRIBUTING.md)。
## 参与其中
我们非常乐意了解您如何使用 Sapience AI Middleware Suite。
|
### :rocket: 加入开发者计划
抢先体验托管的 guardrails、路由 API 和企业级功能。
注册候补名单 →
|
### :speech_balloon: 社区与反馈
欢迎提出问题、报告 Bug 和请求功能。
讨论区 ·
报告 Bug ·
请求功能
|
## 许可证
Apache License, Version 2.0 — 参见 [LICENSE](LICENSE)。
`src/middlewares/hitl/` 中的 Human-in-the-Loop middleware 衍生自 Pegasi 的 [Reins](https://github.com/pegasi-ai/reins) 项目,在 Apache License 2.0 下使用。`src/middlewares/model-routing/` 中的 Model Routing middleware 包含来自 MNFST, Inc. 的 [Manifest](https://github.com/mnfst/manifest) 项目和 BlockRun 的 [ClawRouter](https://github.com/BlockRunAI/ClawRouter) 项目的代码,两者均在 MIT License 下使用。`src/middlewares/guardrail/` 中的 Guardrail middleware 包含来自 Knostic 的 [OpenClaw Shield](https://github.com/knostic/openclaw-shield) 项目和 [OpenGuardrails](https://github.com/openguardrails/openguardrails) 项目的代码、扫描器逻辑和检测模式,两者均在 Apache License 2.0 下使用(OpenGuardrails 在租户 SaaS 使用和前端品牌归属方面带有额外的上游条件 — 本产品仅将其作为后端/SDK 风格的组件使用,并未嵌入其前端或基于其源码运营多租户 SaaS)。完整的归属声明和衍生文件列表请参见 [NOTICE](NOTICE)。根据 Apache 2.0 §4(b) 的规定,逐文件头信息标识了出处和修改。
Built by Sapience AI for a safer AI future.
If your agent can do it, the middleware sees it first.
标签:Agent中间件, AI安全, AI治理, API网关, Chat Copilot, CISA项目, DLL 劫持, GNU通用公共许可证, HITL, LLM, Node.js, NPM包, OpenClaw, OSV-Scalibr, PII脱敏, Token预算, TypeScript, Unmanaged PE, 上下文压缩, 人工智能, 人机协同, 大语言模型, 安全插件, 安全网关, 提示注入防护, 暗色界面, 本地部署, 模型路由, 用户模式Hook绕过, 网络安全, 自动化攻击, 越狱防护, 隐私保护, 零遥测