我是如何在业务 Agent 项目中应用 Harness 的

背景

我们在做一个面向业务运营的 AI Agent 平台。Agent 可以调用各种工具帮用户完成任务------查数据、提单、提交审批、批量操作等等。

功能做起来很爽,但很快我们就意识到一件事:Agent 是一个自主执行体,它的"边界"在哪里?

如果用户说了一句模糊的话,Agent 是否会误解意图,去做一件用户没想到的事?

如果某个工具存在安全漏洞,Agent 会不会帮攻击者"顺手"利用?

如果一次批量操作涉及几千条记录,是否要让人先确认?

如果某个下游服务挂了,Agent 是否会无限重试把整个系统拖垮?

这些问题堆在一起,我们觉得需要一套专门针对 AI Agent 的安全防护框架,而不是简单地在业务代码里加几个 if。基于此,于是项目里面有了 Harness 层 。


什么是 Harness?

Harness,中文是"马具"------给马套上缰绳,让它跑得快但不失控。

我们用这个词来命名我们的 Agent 边界管控框架。它的核心思想是:

让 AI Agent 在可控的边界内自主执行,而不是无限制地"自由发挥"。

Harness 不是一个业务模块,它是一个横切所有工具调用链路的安全中间层,由四层 Guard 组成,采用纵深防御思路:在不同阶段分别拦截不同类型的风险。


四层防御体系

csharp 复制代码
用户输入
   │
   ▼
[L1] IntentGuard --- 意图安全防护(入口)
   │
   ▼
[L2] SkillGuard --- 技能包安全扫描
   │
   ▼
[L3] OperationGuard --- 操作风险控制(核心)
   │
   ▼
[L4] ResourceGuard --- 资源保护
   │
   ▼
工具执行

每一层都实现同一个接口:

typescript 复制代码
interface IGuardLayer {
  check(context: HarnessContext): Promise<HarnessDecision>;
}

// 决策结果只有三种
type Decision = 'ALLOW' | 'BLOCK' | 'HITL_WAIT';

这个设计让四层可以被统一调度,也很容易在某一层出问题时快速降级。


L1 --- IntentGuard:别让"坏话"进来

第一层在用户提交输入后立刻执行,目的是在 LLM 开始推理之前就拦截可疑请求

为什么要在 LLM 之前拦截?因为一旦把注入攻击或者越权指令喂给 LLM,Token 就消耗了,而且上下文被污染了,后续几乎无法清除。在入口处拦截的投资回报率最高。

IntentGuard 内部是一条四环检测流水线:

css 复制代码
输入文本
  → [归一化] Base64解码、Unicode还原(对抗混淆)
  → [模式匹配] 正则规则集匹配已知攻击模式
  → [上下文分析] 看历史消息,累积风险分(防分散投毒)
  → [语义分类] 仅当前三环未命中高威胁时,用 LLM 语义兜底
  → 综合最终威胁等级

设计上有几个有趣的取舍:

取舍一:语义检测是可选的。规则已经高置信度命中(HIGH/CRITICAL)时,跳过 LLM 调用------因为已经确定是攻击了,没必要再花钱确认一遍。

取舍二:整体 fail-open 。IntentGuard 出现异常或超时,默认放行,因为 L3 OperationGuard 会在工具执行前再做一次更精准的检查,两道防线之间有冗余。

取舍三:观察模式 。上线初期我们不敢直接拦截,开了 observe 模式------检测但不拦截,只记日志,先收集一段时间的误报数据,再调整规则,再开启拦截。


L2 --- SkillGuard:别让"坏工具"进来

我们平台允许接入第三方 Skill 包,这就带来了供应链安全风险------Skill 包里如果有危险 API 调用或者已知 CVE 漏洞,Agent 执行它等于帮攻击者做了事。

SkillGuard 对每个 Skill 包做四级扫描:

  1. 结构校验:manifest 完整性、目录规范、package.json 合法性
  2. 静态分析:扫代码,找危险 API 调用(比如直接执行系统命令)、硬编码凭证
  3. 漏洞扫描:异步调 OSV API 查 CVE,不阻塞主链路
  4. 权限交叉比对:Skill 声明需要的权限,和代码里实际调用的权限,是否对得上

其中漏洞扫描是异步的------它不会卡住 Skill 注册流程,而是后台跑,跑完了更新 Skill 的审核状态。运行时 isSkillAllowed() 就是一次内存级白名单查询,几乎没有性能开销。


L3 --- OperationGuard:每次工具调用都要过一关

这是整个 Harness 框架里最核心的一层,也是设计最复杂的一层。

它的时机:工具调用真正执行的前一刻。Agent 已经决定要调用什么工具、传什么参数,OperationGuard 做最后审核。

它的决策:ALLOW(直接执行)、HITL(让人来确认)、BLOCK(拒绝)。

执行链

scss 复制代码
[1] PermissionChecker(六级硬保护)
    敏感路径 → 工具黑名单 → 工具白名单 → 自定义路径规则 → 命令deny模式 → 权限模式
    ↓(通过)
[1.5] RuleEngine(动态规则,可选)
    内置规则 + 数据库动态规则 → PASS / WARN / BLOCK / HITL
    ↓(PASS)
[2] RiskScorer(四维度加权评分 0-100)
    0-39 → ALLOW
    40-59 → HITL
    60-79 → 高优先级 HITL
    80+ → BLOCK
    ↓(HITL场景)
[3] HITLInterceptor
    生成一次性 Token → 写库 → 推通知 → 等待人工确认

权限检查的六级设计

为什么要六级?因为我们发现"权限"这件事有很多维度:

typescript 复制代码
// 优先级从高到低(更高优先级的判断覆盖低优先级)
// P1: 敏感路径(硬编码,不可绕过)
// P2: 工具黑名单
// P3: 工具白名单(在白名单里就放行,无需往下判断)
// P4: 路径规则(自定义 deny 路径)
// P5: 命令 deny 模式(正则匹配)
// P6: 权限模式(default/plan/full_auto)

default 模式下,"只读"类工具(get/list/query/search 等)直接放行,"变更"类工具(create/update/delete/batch 等)需要额外确认。这样大部分查询请求不会被打断,只有真正有副作用的操作才走确认流程。

HITL 双模式

HITL(Human-In-The-Loop)是我们整个框架里用户感知最强的机制。

我们做了两种模式:

simple 模式:适合"当前用户自己确认"的场景。比如 Agent 要提交一个审批单,弹出确认框,用户点"确认",WebSocket 实时通知 Agent 继续执行。

standard 模式:适合"需要特定审批人批准"的场景。比如某个高权限操作必须由负责人审批。这时 Harness 会向配置的审批人发送即时消息通知,审批人回调确认后,Agent 恢复执行。Token 有 TTL,超时自动拒绝。

typescript 复制代码
// 规则配置示例(伪代码)
{
  ruleId: 'high_risk_batch_op',
  name: '高风险批量操作需审批',
  condition: { toolName: 'batch_*' },
  action: {
    type: 'HITL',
    approvalType: 'standard',       // standard 模式
    approvers: ['mis_id_of_approver'],
    userMessage: '当前操作涉及批量变更,需要负责人审批',
    hitlTtlMs: 10 * 60 * 1000,      // 10分钟超时
  }
}

fail-closed 策略

OperationGuard 出现异常时,默认拒绝(而不是放行)。

这和 L1 的 fail-open 策略相反,是刻意设计的:L3 是工具执行前的最后一道关卡,如果这里出了问题还放行,工具可能已经产生了实际副作用(比如真的提了单、真的删了数据),这种风险远大于"多拒绝了一次"的代价。


L4 --- ResourceGuard:别把资源跑垮

L4 关注的不是"做什么",而是"能不能做"------资源层面的边界。

月度 Token 配额:按团队维度统计本月已用 Token,超过配额则拦截。配额数据从 数据库 读取,加了 TTL 缓存避免每次都查库。

熔断器:每个工具都有一个独立的熔断器(三态:CLOSED/OPEN/HALF_OPEN),下游服务连续失败超阈值后熔断,在 OPEN 状态下直接快速失败,不再请求。恢复时走半开探测,指数退避重试。

上下文窗口保护:Session 历史如果无限增长,最终会超出 LLM 的上下文窗口。ResourceGuard 会估算当前 Session 的 Token 数,接近阈值时告警,超限时拦截。

沙箱租户配额:我们平台有代码执行沙箱,ResourceGuard 会按租户维度限制沙箱占用数,防止单个租户把全局资源耗尽(而影响其他租户)。


规则引擎:让安全策略变成数据

Harness 最初的权限规则都是硬编码的,调整起来需要发版。我们后来加了一个规则引擎,把规则从代码里抽出来,存到数据库里。

核心设计是双源合并

markdown 复制代码
L1 内置规则(代码硬编码,兜底保底)
       +
L2 数据库规则(运营配置,TTL 30s 缓存)
       ↓
去重合并(同 rule_id 时 DB 规则覆盖内置规则参数)
       ↓
按 priority 排序 → 逐条评估 → 首条非PASS即短路返回

内置了七种评估器:Shell 注入检测、预算控制、频率限制、内容关键词过滤、自定义正则、工具限制、通用条件表达式。

其中"通用条件表达式"评估器是最后加的,它让非研发人员也能写规则:

json 复制代码
// 通用规则示例(运营配置,无需改代码)
{
  "ruleId": "budget_check_001",
  "name": "预算超限审批",
  "condition": {
    "field": "budget",
    "op": "gt",
    "value": 50000
  },
  "action": {
    "type": "HITL",
    "userMessage": "申请金额较大,需要上级审批"
  }
}

热开关:生产出了问题怎么办

上线初期我们非常担心误报。如果 IntentGuard 误判了一个正常请求,用户会直接被拦截,体验非常差。

所以每个 Guard 层都有三种运行状态,通过配置中心实时切换,无需重启服务:

状态 行为
enabled 正常拦截 + 写审计日志
observe 检测但不拦截,只记日志(收集误报率)
disabled 完全跳过,不检测不记录(紧急回滚)

在灰度上线时,我们先把新规则设置为 observe 模式,跑一段时间后分析日志,确认没有误报再切换到 enabled。这个机制救了我们好几次。


审计日志:做了什么,为什么这么做

Harness 的每一个拦截决策、每一次 HITL 事件、每一次工具调用,都会写审计日志。但我们遇到了一个问题:日志太多

一个普通的 ALLOW 请求,四层 Guard 都通过了,如果每层都写一条日志,存储成本很高而且意义不大------"正常放行"的日志大部分时候没人看。

我们的解决方案是分层写入策略

arduino 复制代码
BLOCK 决策  → 只写触发拦截的那一层日志(精确定位原因)
ALLOW 决策  → 只写一条汇总日志("所有层通过",降噪)
HITL 事件   → 始终写完整事件日志(合规要求)
工具调用    → 始终写调用记录(操作留痕)

同时加了双队列机制:合规敏感日志(BLOCK/HITL)走高优先级队列,运营分析日志走普通队列。极端情况下数据库写入失败,合规日志会 fallback 写本地文件,普通日志则丢弃。


整体架构回顾

把上面所有东西放在一起,看起来是这样的:

scss 复制代码
用户请求
    │
    ▼
HarnessOrchestrator(编排器)
    │
    ├── [L1+L2 并行] IntentGuard + SkillGuard
    │         │
    │         └── BLOCK/HITL → 短路,跳过后续层
    │
    ├── [L3 串行] OperationGuard
    │     ├── PermissionChecker(硬保护)
    │     ├── RuleEngine(动态规则)
    │     ├── RiskScorer(风险评分)
    │     └── HITLInterceptor(人工确认)
    │
    └── [L4 串行] ResourceGuard
          ├── BudgetInterceptor
          ├── CircuitBreakerRegistry
          ├── ContextEfficiencyOptimizer
          └── SandboxQuotaCheck
    │
    ▼
工具执行
    │
    ▼
AuditLogger(异步写审计日志)

L1 和 L2 并行执行(互相不依赖),L3 和 L4 串行执行(L3 要先出决策,L4 才检查资源)。任意一层 BLOCK 或 HITL_WAIT,后续层跳过------这是短路求值。


一些经验总结

做完这套框架,有几点感受比较深:

1. fail-open 和 fail-closed 是两个哲学,要根据层级选择

L1 意图检测失败 → fail-open(放行):因为 L3 还有兜底,误拦截的代价更高。

L3 操作权限检查失败 → fail-closed(拒绝):因为这是最后一关,放行可能产生实际副作用。

L4 资源检查失败 → fail-closed:资源不确定时放行可能触发雪崩。

2. 可观测性比拦截能力更重要(初期)

上线前三个月,observe 模式的价值远大于 enabled 模式。看到哪些请求会被命中规则,才能判断规则是否合理,才敢真正开启拦截。急着上 enabled 只会积累大量投诉。

3. HITL 要设计得让人愿意点

HITL 的用户体验非常关键。如果审批流程太繁琐,用户会绕着走(比如把批量操作拆成多次小操作)。我们花了很多时间打磨通知的文案和确认界面,让"需要确认"这件事本身不成为负担。

4. 规则数据化是必要的,但要留好兜底

把规则放数据库里,运营团队可以实时调整,非常灵活。但一定要保留代码层面的内置规则作为兜底------数据库出问题、缓存失效,都不应该导致安全规则完全失效。


结语

Harness 从一个"临时加的安全校验"演化成今天这个四层防护框架,大概花了三个月时间,经历了好几轮重构。

最大的收获不是框架本身,而是在做这件事的过程中,把"AI Agent 的边界在哪里"这个问题想清楚了

Agent 不应该是无限制的自动化执行器,它应该是一个在清晰边界内工作的可信助手------知道什么能做、什么要确认、什么绝对不能做。Harness 就是这套边界的技术实现。

如果你们团队也在做 Agent 平台,希望这篇文章对你有所启发。欢迎交流。

相关推荐
xinlianyq2 小时前
TikTok短视频生成工具哪家好?跨境出海如何用 AI 实现爆款视频复刻
人工智能·aigc
guyoung2 小时前
BoxAgnts 运行时(7)——沙箱执行,重塑 Agent 基础设施
agent·ai编程
就玩一会_2 小时前
AI应用开发(Java方向)---实习\校招进度
agent
葫芦和十三2 小时前
执行拓扑|Agent 不只是会什么,还要怎么跑
架构·agent·ai编程
装不满的克莱因瓶3 小时前
学习 LLM 的函数回调及格式化输出,让 LLM 拥有更强的能力
人工智能·ai·大模型·llm·agent·智能体
手写码匠3 小时前
手写 DeepSeek 推理引擎优化:从 FP16 到 INT4 的量化加速实战
人工智能·深度学习·算法·aigc
倔强的石头_4 小时前
保姆级教程:如何0成本调用千问3.6大模型?讯飞星辰MaaS平台上手指南
aigc
用户5191495848454 小时前
WordPress File Upload 插件路径遍历漏洞利用工具 (CVE-2024-9047)
人工智能·aigc
CoderJia程序员甲4 小时前
GitHub 热榜项目 - 周榜(2026-06-06)
ai·大模型·llm·github