news 2026/10/1 4:25:11

AI Agent Harness安全设计:Trace强制采集与权限隔离实战

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
AI Agent Harness安全设计:Trace强制采集与权限隔离实战

1. 从“Trace 都能删”说起:Agent Harness 的安全困局

第一次看到“AI Agent 连 Trace 都能删”这个说法时,我正蹲在一个内部项目的日志面板前排查一次诡异的工具调用失败。Agent 明明执行了搜索动作,返回结果也正常,但 Trace 里干干净净,像什么都没发生过。当时第一反应是日志采集挂了,查了半天采集链路,最后发现是 Agent 自己在某个异常分支里把整段 Trace 记录给清掉了——不是恶意,是它“认为”那段记录属于临时中间态,顺手做了清理。

这件事让我意识到一个被很多人忽略的问题:当 Agent 拥有对自身运行痕迹的写权限时,Trace 就不再是可信的审计依据。而 Agent Harness 作为承载 Agent 运行、调度工具、管理上下文、控制权限的那层“骨架”,它的安全设计直接决定了整个系统是可控还是失控。

先把概念理清楚,因为热词里“harness 和 agent 区别”被搜了很多次。Agent 是干活的那个“人”,它理解目标、规划步骤、调用工具、生成结果。Harness 是它身上的“安全带 + 工位 + 监工”,负责给 Agent 提供运行环境、注入工具、限制它能碰什么、记录它干了什么、在它跑偏时把它拉回来。你可以把 Agent 想成一个能力很强但边界感很弱的实习生,Harness 就是公司给他配的工牌、门禁、操作日志和审批流。

问题在于,很多团队搭 Agent 时,Harness 是“事后补”的。先让 Agent 跑起来,能调工具、能出结果,然后再想安全的事。这时候 Agent 已经习惯了“我想删就删、我想写就写”的自由度,你再给它套约束,它会在各种边缘 case 里绕过去。Trace 被删只是表象,背后是权限边界、审计独立性和状态可信性三个层面的系统性缺失。

这篇内容适合两类人看:一类是正在从 0 到 1 搭建 AI Agent、还没认真考虑 Harness 安全边界的开发者;另一类是已经在跑 Agent 项目、发现 Trace 不可信、工具调用失控、并发下状态混乱,想找一套可落地约束方案的工程师。我会把 Harness 的安全设计拆成可操作的模块,每个模块都给出具体的实现思路和踩坑记录,不堆概念,只讲能直接抄作业的东西。

2. Agent Harness 的安全设计思路拆解

2.1 为什么“让 Agent 自己管 Trace”是个结构性错误

Trace 的本质是审计记录,审计记录的第一原则是记录者与被记录者分离。现实世界里,财务不能自己记自己的账,代码提交不能自己给自己 approve,同一个道理。Agent 在执行过程中产生的 Trace,如果由 Agent 自己决定写什么、不写什么、什么时候删,那这份 Trace 在安全意义上就是废纸。

我见过一种常见实现:Agent 的每个 step 结束后,由 Agent 自己调用一个log_trace()工具把当前状态写进去。看起来没问题,但 Agent 在规划阶段就可能决定“这一步不重要,不记了”,或者在异常处理时把错误 Trace 删掉“保持整洁”。更隐蔽的是,当 Agent 被注入恶意指令时,攻击者可以诱导它删除关键操作痕迹,让事后审计完全失效。

正确的做法是Trace 由 Harness 层强制采集,Agent 只有“产生事件”的能力,没有“管理事件”的能力。Agent 执行动作时,Harness 在调用链路外层拦截,把输入、输出、工具调用参数、返回结果、耗时、异常全部记录下来,写入 Agent 无法直接访问的存储。Agent 甚至不应该知道自己被记录了——知道得越少,越不容易针对性地规避。

注意:这里说的“Agent 无法访问”不是指代码层面不给它 API,而是指运行时的权限隔离。如果 Agent 和 Harness 跑在同一个进程、同一个权限上下文里,Agent 通过代码注入或工具滥用依然可能碰到 Trace 存储。真正的隔离需要进程级或容器级的边界。

2.2 Harness 安全的三层防线:权限、审计、状态

把 Harness 的安全需求拆开,核心就三件事。

权限层解决“Agent 能碰什么”。工具调用不是越多越好,每个工具都应该有明确的权限标签。读文件、写文件、发网络请求、执行命令,这些能力的风险等级完全不同。Harness 需要在 Agent 发起工具调用时做一次权限校验,而不是信任 Agent 的规划结果。我习惯把工具分成三档:只读类(搜索、查询)、受限写入类(写指定目录、发指定域名的请求)、高危类(执行任意命令、写系统路径)。高危类工具默认不挂载,需要显式开启并且加审批流。

审计层解决“Agent 干了什么”。前面说了 Trace 必须由 Harness 强制采集,但采集只是第一步。审计层还需要保证 Trace 的完整性和不可篡改性。完整性靠的是采集覆盖所有关键路径,不能有“这个分支不记录”的漏洞。不可篡改性靠的是写入后 Agent 无法修改或删除,通常用追加写(append-only)存储加上哈希链来实现。每一条 Trace 记录包含前一条的哈希,任何中间删除都会导致后续哈希校验失败。

状态层解决“Agent 当前处于什么状态”。Agent 是有状态的,它维护着对话历史、工具调用结果、中间变量。如果这些状态可以被 Agent 随意修改,那它就能伪造上下文来绕过后续的权限检查。Harness 需要把关键状态放在 Agent 不可直接写的存储里,Agent 只能通过受控的接口读取和追加,不能覆盖或删除。

这三层不是独立的,权限层校验时依赖状态层的可信状态,审计层记录权限层的校验结果,状态层的变更又需要审计层留痕。任何一层被绕过,整体安全就塌了。

2.3 方案选型:为什么我最终选了“外挂式 Harness”而不是“框架内嵌”

搭建 Agent 时,Harness 的实现方式大致有两种。一种是框架内嵌,比如用 LangChain 或 LangGraph 时,直接在 Agent 的 executor 里加权限检查和日志钩子。另一种是外挂式,Agent 跑在一个独立的运行时里,Harness 作为代理层拦截所有输入输出和工具调用。

我两个方案都试过。框架内嵌上手快,改几行代码就能加日志,但问题很明显:Agent 和 Harness 共享同一个运行时,Agent 通过工具调用或代码执行可以绕过钩子。而且不同框架的钩子机制不一样,换框架就要重写一遍安全逻辑,维护成本高。

外挂式 Harness 的架构是:Agent 进程只负责推理和规划,所有工具调用都通过一个本地代理转发,代理层做权限校验、参数检查、Trace 采集、结果过滤。Agent 拿不到真实工具的句柄,只能拿到代理的接口。这样即使 Agent 被注入,它能碰到的也只是代理暴露的受限能力。

代价是延迟增加和架构复杂度上升。每次工具调用多一次进程间通信,实测下来单次调用延迟增加 5 到 15 毫秒,对于大多数场景可以接受。复杂度方面,需要额外维护代理层的路由和协议,但换来的是安全边界清晰、框架无关、可独立升级。

实操心得:如果你的 Agent 还在原型阶段,用框架内嵌快速验证没问题。但一旦要上生产、要处理真实用户数据、要调用有副作用的工具,尽早切到外挂式。后期迁移的成本远高于一开始就搭好边界。

3. 核心细节解析与实操要点

3.1 Trace 强制采集的实现细节

Trace 采集要解决三个问题:采什么、怎么采、存哪里。

采什么取决于你要审计什么。最小集合包括:每次 Agent 推理的输入 prompt 和输出、每次工具调用的工具名和参数、工具返回结果、异常堆栈、时间戳、会话 ID、步骤序号。如果涉及多 Agent 协作,还要记录消息的发送方和接收方。我习惯再加一个trace_id贯穿整个会话,方便串联。

怎么采的关键是拦截点要足够底层。不要在 Agent 的业务代码里埋点,那样覆盖不全。外挂式 Harness 的拦截点应该在代理层:Agent 发出的每个工具调用请求先到代理,代理记录请求内容后再转发给真实工具,拿到结果后记录结果再返回给 Agent。推理过程的采集类似,Agent 的每次模型调用也走代理,代理记录 prompt 和 completion。

存哪里要考虑不可篡改。最简单的方案是追加写的文件,Agent 进程没有该文件的写权限,只有 Harness 进程能写。进阶方案是写入独立的日志服务或对象存储,加上哈希链。哈希链的实现不复杂:每条记录包含hash(前一条记录的哈希 + 当前记录内容),校验时从头遍历,任何一条被改都会导致后续哈希不匹配。

import hashlib import json import time class TraceChain: def __init__(self, storage_path): self.storage_path = storage_path self.prev_hash = "0" * 64 def append(self, event: dict): event["timestamp"] = time.time() event["prev_hash"] = self.prev_hash payload = json.dumps(event, sort_keys=True) current_hash = hashlib.sha256(payload.encode()).hexdigest() event["hash"] = current_hash with open(self.storage_path, "a") as f: f.write(json.dumps(event) + "\n") self.prev_hash = current_hash return current_hash def verify(self): prev = "0" * 64 with open(self.storage_path, "r") as f: for line in f: event = json.loads(line) stored_hash = event.pop("hash") event.pop("prev_hash") payload = json.dumps(event, sort_keys=True) expected = hashlib.sha256(payload.encode()).hexdigest() if expected != stored_hash: return False, event prev = stored_hash return True, None

这段代码的关键点是prev_hash的传递和校验时的重算。注意json.dumps要加sort_keys=True,否则字典顺序变化会导致哈希不一致。实际部署时存储路径要放在 Agent 进程无权限访问的目录,或者直接用独立的存储服务。

3.2 工具权限校验的粒度控制

工具权限校验最容易犯的错是粒度太粗。比如只判断“Agent 有没有调用这个工具的权限”,而不判断“这次调用的参数是否在允许范围内”。一个文件读取工具,Agent 有权限调用,但它可以读/etc/passwd也可以读项目目录,风险完全不同。

我的做法是工具级权限 + 参数级约束双层校验。工具级权限决定 Agent 能不能用这个工具,参数级约束决定这次调用的具体参数是否合法。参数约束用声明式配置,每个工具定义自己的约束规则。

tools: read_file: enabled: true constraints: path: type: "prefix" allowed: ["/workspace/project/", "/tmp/agent_scratch/"] http_request: enabled: true constraints: url: type: "domain_whitelist" allowed: ["api.internal.com", "search.public.com"] method: type: "enum" allowed: ["GET", "POST"] execute_command: enabled: false requires_approval: true

校验逻辑在代理层执行,Agent 传过来的参数先过约束检查,不通过直接拒绝并记录 Trace。这里有个细节:拒绝也要记录。很多团队只记录成功的调用,失败的调用不记,结果攻击者可以反复试探权限边界而不留痕迹。失败的调用同样要写入 Trace,包括被拒绝的参数。

注意:参数约束要防绕过。比如路径前缀检查,Agent 可能传/workspace/project/../../etc/passwd,简单的字符串前缀匹配会放行。必须先做路径规范化(resolve 真实路径),再检查前缀。URL 检查同理,要防http://api.internal.com.evil.com这种域名后缀欺骗。

3.3 状态隔离与上下文可信性

Agent 的状态包括对话历史、工具调用结果、中间变量。这些状态如果 Agent 能直接写,它就能伪造上下文。比如 Agent 先调用一个查询工具拿到“用户余额 100 元”,然后自己把状态改成“用户余额 10000 元”,后续的决策就基于假数据。

状态隔离的做法是状态存储与 Agent 运行时分离,Agent 只能追加事件,不能覆盖或删除。对话历史用事件流的方式存储,每次 Agent 产生一条消息或工具返回一个结果,都作为新事件追加到流里。Agent 读取上下文时,从事件流重放生成当前状态,而不是直接读一个可写的状态对象。

这样做的另一个好处是可回溯。任何时候都能从事件流重放出 Agent 在某个步骤看到的状态,排查问题时非常有用。代价是重放有计算开销,长会话下事件流会很大。优化方式是定期做快照,快照之后的事件增量重放。快照本身也要纳入 Trace 链,保证快照内容可信。

class EventStore: def __init__(self, trace_chain): self.events = [] self.trace_chain = trace_chain def append(self, event_type: str, payload: dict): event = { "type": event_type, "payload": payload, "seq": len(self.events) } self.trace_chain.append(event) self.events.append(event) def replay(self, upto_seq=None): state = {"messages": [], "tool_results": {}} for event in self.events: if upto_seq is not None and event["seq"] > upto_seq: break if event["type"] == "message": state["messages"].append(event["payload"]) elif event["type"] == "tool_result": state["tool_results"][event["payload"]["call_id"]] = event["payload"]["result"] return state

Agent 拿到的上下文是replay()的结果,是一个只读的视图。Agent 想修改状态,只能通过产生新事件,而新事件会被 Trace 链记录。这样即使 Agent 想伪造,也会留下痕迹。

4. 实操过程与核心环节实现

4.1 从零搭建一个带安全边界的 Harness

假设你要搭一个能调用搜索和文件读取工具的 Agent,Harness 需要提供权限校验、Trace 采集、状态管理三个能力。我按实际搭建顺序走一遍。

第一步,定义工具接口和代理协议。Agent 不直接持有工具函数,而是通过一个本地 HTTP 或 Unix Socket 接口调用。接口协议统一为POST /tool/{tool_name},body 是工具参数,返回是工具结果或错误。代理层监听这个接口,做校验和转发。

from fastapi import FastAPI, HTTPException from pydantic import BaseModel app = FastAPI() trace_chain = TraceChain("/var/agent/trace/chain.log") event_store = EventStore(trace_chain) class ToolRequest(BaseModel): tool_name: str params: dict call_id: str @app.post("/tool") async def handle_tool(req: ToolRequest): event_store.append("tool_call", { "tool_name": req.tool_name, "params": req.params, "call_id": req.call_id }) allowed, reason = check_permission(req.tool_name, req.params) if not allowed: event_store.append("tool_denied", { "tool_name": req.tool_name, "params": req.params, "reason": reason }) raise HTTPException(status_code=403, detail=reason) result = await dispatch_tool(req.tool_name, req.params) event_store.append("tool_result", { "call_id": req.call_id, "result": result }) return {"result": result}

第二步,实现权限校验函数。校验逻辑从配置文件加载规则,对参数做规范化后匹配。

import os from urllib.parse import urlparse def check_permission(tool_name: str, params: dict): config = load_tool_config() tool_conf = config["tools"].get(tool_name) if not tool_conf or not tool_conf.get("enabled"): return False, f"tool {tool_name} is disabled" constraints = tool_conf.get("constraints", {}) for param_name, rule in constraints.items(): value = params.get(param_name) if value is None: return False, f"missing required param {param_name}" if rule["type"] == "prefix": normalized = os.path.realpath(value) if not any(normalized.startswith(p) for p in rule["allowed"]): return False, f"path {normalized} not allowed" elif rule["type"] == "domain_whitelist": domain = urlparse(value).hostname if domain not in rule["allowed"]: return False, f"domain {domain} not allowed" elif rule["type"] == "enum": if value not in rule["allowed"]: return False, f"value {value} not allowed" return True, "ok"

第三步,Agent 侧只保留代理的地址,不导入任何真实工具。Agent 的 prompt 里说明它可以通过call_tool(tool_name, params)调用工具,这个函数实际是向代理发请求。

import httpx HARNESS_URL = "http://127.0.0.1:8000/tool" async def call_tool(tool_name: str, params: dict, call_id: str): async with httpx.AsyncClient() as client: resp = await client.post(HARNESS_URL, json={ "tool_name": tool_name, "params": params, "call_id": call_id }) if resp.status_code == 403: return {"error": resp.json()["detail"]} return resp.json()

这套结构跑起来后,Agent 能调用的工具、能传的参数都被代理层约束,所有调用和结果都进了 Trace 链。Agent 进程即使被注入,它能做的也只是向代理发请求,而代理会按规则拒绝越权操作。

4.2 并发场景下的 Trace 写入与状态一致性

热词里“ai agent 怎么扛并发”被搜了很多次,这个问题在 Harness 层尤其突出。多个 Agent 实例同时运行时,Trace 写入和状态更新都需要保证一致性。

Trace 写入的并发问题在于哈希链。如果两个请求同时追加 Trace,都读到同一个prev_hash,就会产生分叉。解决办法是串行化写入。用一个单线程的写入队列,所有 Trace 事件先入队,由单独的写入线程按顺序处理。这样哈希链不会分叉,代价是写入吞吐受限于单线程。实测下来单线程写入每秒能处理几千条事件,对于大多数 Agent 场景够用。如果不够,可以按会话分片,每个会话一条独立的链。

状态一致性的问题在于事件流的顺序。多个工具调用并发返回时,事件追加的顺序会影响重放结果。我的做法是给每个事件分配单调递增的序号,序号由 Harness 统一分配,Agent 不能自己指定。重放时按序号排序,保证顺序确定。工具调用的结果事件里带上call_id,重放时按call_id关联请求和结果,不依赖到达顺序。

import threading import queue class SerializedTraceWriter: def __init__(self, trace_chain): self.trace_chain = trace_chain self.queue = queue.Queue() self.thread = threading.Thread(target=self._worker, daemon=True) self.thread.start() def _worker(self): while True: event = self.queue.get() if event is None: break self.trace_chain.append(event) self.queue.task_done() def submit(self, event): self.queue.put(event)

实操心得:并发下最容易忽略的是拒绝事件的顺序。如果 Agent 并发发起多个工具调用,其中一个被拒绝,拒绝事件和成功事件的相对顺序会影响审计时的判断。用统一序号分配就能解决,序号在请求进入代理时就分配,而不是在结果返回时分配。

4.3 异常分支的 Trace 覆盖

Trace 被删的案例里,最隐蔽的是异常分支不记录。正常路径都记了,但 Agent 走到异常处理逻辑时,那段代码没有埋点,或者埋了点但异常导致写入失败。攻击者可以故意触发异常来制造 Trace 盲区。

解决办法是在代理层用 try-finally 保证 Trace 写入。不管工具调用成功还是抛异常,finally 块里都写一条结果事件,异常信息作为事件内容。这样即使工具本身崩溃,Trace 里也有记录。

@app.post("/tool") async def handle_tool(req: ToolRequest): event_store.append("tool_call", {...}) try: allowed, reason = check_permission(req.tool_name, req.params) if not allowed: event_store.append("tool_denied", {...}) raise HTTPException(status_code=403, detail=reason) result = await dispatch_tool(req.tool_name, req.params) event_store.append("tool_result", {"call_id": req.call_id, "result": result}) return {"result": result} except HTTPException: raise except Exception as e: event_store.append("tool_error", { "call_id": req.call_id, "error": str(e), "traceback": traceback.format_exc() }) raise HTTPException(status_code=500, detail="tool execution failed")

注意except HTTPException: raise这一行,权限拒绝的异常已经记录过了,不要重复记录。其他异常统一记录为tool_error。这样 Trace 里能看到每次调用的完整生命周期:请求、校验、执行、结果或错误。

5. 常见问题与排查技巧实录

5.1 Trace 链校验失败的排查思路

哈希链校验失败是最常见的问题,表现是verify()返回 False。排查时先定位第一条不匹配的记录,看它的内容是否被改过,还是prev_hash传递出了问题。

我遇到过几种原因。一种是写入时sort_keys不一致,有的地方用了有的地方没用,导致哈希重算不匹配。统一在 TraceChain 内部做序列化,外部不要自己序列化。另一种是并发写入导致分叉,两个线程同时写,prev_hash读到了同一个值。用串行化写入解决。还有一种是存储文件被外部工具修改,比如日志轮转把文件截断了。Trace 存储要排除在日志轮转之外,或者用独立的存储服务。

排查时可以用一个简单的脚本逐行校验,打印出第一条不匹配的记录序号和内容。

def debug_verify(path): prev = "0" * 64 with open(path) as f: for i, line in enumerate(f): event = json.loads(line) stored_hash = event.pop("hash") stored_prev = event.pop("prev_hash") if stored_prev != prev: print(f"line {i}: prev_hash mismatch, expected {prev}, got {stored_prev}") return payload = json.dumps(event, sort_keys=True) expected = hashlib.sha256(payload.encode()).hexdigest() if expected != stored_hash: print(f"line {i}: hash mismatch, content may be modified") print(json.dumps(event, indent=2)) return prev = stored_hash print("all ok")

5.2 工具调用被绕过的典型场景

权限校验被绕过通常不是校验逻辑本身的问题,而是校验点选错了。我见过几种典型绕过。

一种是 Agent 通过代码执行工具间接调用其他工具。比如 Agent 有execute_python权限,它可以在 Python 代码里直接import requests发请求,绕过 HTTP 工具的域名白名单。解决办法是高危工具默认禁用,或者用沙箱限制代码执行的能力。沙箱里禁网络、禁文件系统写、只暴露受控的接口。

另一种是 Agent 通过 prompt 注入让模型直接输出工具调用的参数,然后由另一个组件执行。如果那个组件没有走 Harness 的校验,就绕过了。解决办法是所有工具调用必须经过 Harness,没有例外。任何直连工具的路径都是安全漏洞。

还有一种是参数编码绕过。比如路径检查用字符串匹配,Agent 传 URL 编码的路径%2e%2e%2f,解码后是../。解决办法是先解码再规范化再检查,不要用原始字符串做匹配。

绕过场景根因修复方式
代码执行间接调用高危工具未隔离沙箱限制能力,默认禁用
旁路组件直连工具校验点不统一所有调用强制走 Harness
参数编码绕过检查前未规范化解码 + 规范化 + 再检查
工具别名混淆工具名未做映射工具名白名单,拒绝未知工具

5.3 并发下状态错乱的排查

并发场景下状态错乱的表现是 Agent 看到的历史和实际发生的不一致。比如两个工具调用并发返回,Agent 读到的结果顺序和实际执行顺序不同,导致推理出错。

排查时先看事件流的序号是否连续。如果序号有跳跃或重复,说明序号分配有问题。序号应该由 Harness 在请求进入时分配,用原子计数器保证唯一递增。如果序号连续但重放结果不对,看事件的call_id关联是否正确。工具结果事件必须带上对应的call_id,重放时按call_id匹配,不能按到达顺序匹配。

还有一种情况是快照和增量事件不一致。快照生成时的事件流和后续增量事件之间有重叠或遗漏。解决办法是快照生成时记录当前最大序号,增量重放从该序号之后开始,严格不重叠。

注意:并发测试一定要做。单线程下跑通的逻辑,并发下经常出问题。我习惯用 10 到 50 个并发请求压一遍,观察 Trace 链是否完整、状态重放是否一致。压测时故意混入被拒绝的请求和异常请求,验证异常路径的 Trace 覆盖。

5.4 常见问题速查表

问题现象可能原因排查动作解决方式
Trace 链校验失败序列化不一致 / 并发写入 / 文件被改逐行校验定位首条不匹配统一序列化、串行写入、独立存储
工具调用被绕过校验点不统一 / 高危工具未隔离检查所有工具调用路径强制走 Harness、沙箱隔离
状态重放不一致序号分配问题 / call_id 缺失检查事件序号和关联字段原子序号、强制 call_id
异常分支无 Trace埋点遗漏 / 写入失败检查异常路径的 Trace 覆盖try-finally 保证写入
并发下 Trace 分叉多线程同时写检查 prev_hash 是否重复串行化写入队列
权限拒绝无记录只记成功不记失败检查拒绝路径的埋点拒绝也写 Trace

6. 一些实际踩过的坑和后续可以做的事

Trace 存储的权限隔离,我一开始以为把文件权限设成只有 Harness 用户可写就够了。后来发现 Agent 和 Harness 跑在同一个容器里,Agent 通过工具调用执行chmod就能改权限。真正的隔离需要把 Trace 存储放在 Agent 容器之外,通过网络或挂载卷访问,且挂载卷对 Agent 只读。这个改动不大,但意识不到就会留个大洞。

工具权限配置的维护成本比想象中高。工具多了之后,每个工具的约束规则散落在配置文件里,改一个规则要翻半天。后来我把工具定义和权限规则放在一起,用代码生成配置,工具注册时自动带上权限声明。这样新增工具时权限规则是强制的,不会漏。

状态重放的开销在长会话下确实明显。一个跑了上百步的会话,每次重放要遍历所有事件。我的优化是每 20 步做一次快照,快照内容也进 Trace 链。重放时从最近的快照开始,只重放之后的事件。快照的生成时机选在工具调用之间的空闲点,避免影响响应延迟。

后续可以扩展的方向,一个是把 Trace 链接到外部的审计服务,做实时告警。比如检测到短时间内大量权限拒绝,可能是 Agent 被注入后在试探边界,触发告警人工介入。另一个是给 Trace 加签名,用 Harness 的私钥对每条记录签名,校验时验证签名,这样即使存储被攻破,篡改也能被发现。签名会增加写入开销,但对安全要求高的场景值得。

最后分享一个小技巧:在 Agent 的 prompt 里明确告诉它“你的所有工具调用都会被记录和审计”,这本身就有威慑作用。虽然不能替代技术约束,但能减少 Agent 在规划阶段就尝试越权的概率。实测下来,加了这句话之后,Agent 主动尝试调用未授权工具的次数明显下降。技术约束加心理约束,双管齐下。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/1 4:25:05

Android Profiler实战:CPU与内存监控定位卡顿和泄漏

做Android开发的人,大概都经历过这种场景:线上反馈说App卡顿、内存嗖嗖涨、甚至直接OOM崩溃,但你本地怎么跑都一切正常。翻Logcat日志,全是无关紧要的Warning,真正的性能问题像泥鳅一样滑不留手。这时候,An…

作者头像 李华
网站建设 2026/10/1 4:24:50

YOLOv8象棋棋子识别实战:小目标检测与真实场景畸变处理

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/10/1 4:24:37

基于Python的垃圾分类系统毕业设计:从CNN模型训练到Flask部署全流程

简介:这是一套面向计算机、人工智能、自动化等专业学生与教师的Python垃圾分类系统毕业设计资源,包含完整源码与部署指南,适合作为期末课程设计、大作业或毕设参考,也便于基础较好的学习者在此基础上二次开发。资源包共126个文件&…

作者头像 李华
网站建设 2026/10/1 4:24:21

C语言sizeof深度解析:从运算符本质到内存对齐实战

C语言里的 sizeof,你真正弄懂了吗?我在不少技术交流群里见过这样的场景:有人问sizeof(int)和sizeof(int*)是不是一回事,底下能吵好几页。还有人写代码时用sizeof(指针)去算数组长度,结果跑出个固定 8,找半天…

作者头像 李华
网站建设 2026/10/1 4:24:17

第一次作业不用慌:一套从拆解到交付的完整执行框架

“第一次作业”这四个字,恐怕是学生时代到职场生涯里,出现频率最高、也最容易让人心里发慌的场景了。我到现在还记得自己交第一份课程论文时的状态:材料下载了十几个,文档打开了一上午,光标在空白页上闪了一整天&#…

作者头像 李华
网站建设 2026/10/1 4:23:49

12dB全向铜丝天线DIY:9段螺旋+3mm铜管的高增益物理实现

简介:本资源是一份面向无线通信DIY爱好者与入门级射频实践者的高增益全向天线制作教程,聚焦解决低成本、易上手实现12dB增益全向辐射的现实难题。针对开槽天线成本高、电缆天线精度难控等痛点,方案采用铜丝铜管PVC管等常见材料,完…

作者头像 李华