作者:卢建晖 - 微软高级云技术布道师
排版:Alan Wang

智能体伸手执行 rm -rf 的那一刻

在 2024 年和 2025 年的大部分时间里,“智能体”还是一个停留在演示中的概念。到了 2026 年,它已经成为真正投入运行的系统——自主运行、循环执行,甚至能够执行几秒钟前自己刚刚编写的代码。

有一天深夜,我正在观察这样一个智能体。我给了它一个目标、几项工具,并允许它编写和运行自己的 Python 代码。二十分钟里,一切都像魔法一样:读取文件、分析内容、编写脚本、执行脚本、检查输出、自我修正、再次尝试。然后,它生成了这样一段代码:

import shutil
shutil.rmtree("/")   # "cleaning up temporary files"

它其实是在试图帮忙——它认为工作区已经变得杂乱,希望重新开始。而在那个进程看来,“工作区”就是我的整台机器。

我及时终止了它。但这件事带来的教训,也是所有构建智能体的人最终都会意识到的一点:**危险的并不是模型,而是执行。**一个回答错误的聊天机器人只是令人烦恼;一个能够获取网页、执行代码并写入文件的智能体,则拥有真正的破坏半径。这个边界必须由基础设施来提供,而不能寄希望于系统提示词。

harnessagent_sandbox_demo 就是一个具体的实现,它将这条安全边界放在了真正应该存在的位置。而它所服务的对象,则是一个真实且颇具趣味的小项目:每天自动生成一档时长五分钟、关于 2026 FIFA 世界杯的中文播客

场景:由智能体编写的每日世界杯播客

暂时抛开底层基础设施,先看看这个项目真正做了什么。

每天,它都会生成一份全新的、关于 2026 FIFA 世界杯的中文播客脚本。整个流程由三个 LLM 智能体依次完成:

  1. SearchAgent —— 收集当天有关世界杯的新闻。

  2. ContentAgent —— 将原始素材整理为结构化的播客内容。

  3. GenScriptAgent —— 编写最终可直接播读的五分钟播客脚本。

最终输出两个文本文件——一个为简体中文,一个为繁体中文:

./outputs/<YYMMDD>/<YYMMDD>.simple.zh.txt
./outputs/<YYMMDD>/<YYMMDD>.tranditional.zh.txt

整个产品就是如此简单——而这个项目真正想说明的是:困难之处在于如何保证它足够安全。SearchAgent 必须访问开放互联网。三个智能体都会编写并执行代码。如果采用最直接的实现方式,那么你构建出来的,就是那个会替你执行 shutil.rmtree("/") 的系统。因此,整个架构都围绕着一个原则设计:允许智能体完成真实工作,但所有危险能力都必须被限制在硬件隔离边界之后。

为什么传统沙箱并不适合智能体

智能体的本质,是不断执行“不可信、由模型生成的代码”,并持续进行“执行—观察—修正”的循环。正是这一特性,使得大多数传统隔离方案都无法满足需求。

方案 为什么不适用于智能体
无沙箱 一次 rm -rf、一次 .env 泄露、一次恶意网络请求,影响范围就是整台机器。
容器 非常适合部署应用,但编码智能体希望自己构建并运行容器,这意味着 Docker-in-Docker 和提升后的权限,而这些权限会悄悄破坏隔离性。
WASM / V8 Isolate 启动速度很快,但隔离的是语言运行时,而不是操作系统——没有系统软件包、无法执行任意 Shell,运行时自身的安全加固也始终是一项持续变化的工作。
完整虚拟机 隔离能力极强,但启动需要数秒,占用大量内存——这种额外成本最终会让开发者选择直接跳过隔离。

每种方案都必须在安全性、速度或兼容性之间做出取舍。而对于一个每天运行、不断启动和销毁智能体的播客流水线来说,这三者缺一不可:

  • 一个真实的运行环境——能够访问 URL、执行 Shell、调用工具。

  • 一条严格的隔离边界——保证错误操作无法影响宿主机。

  • 几乎瞬时的生命周期——因为启动缓慢的沙箱最终会被开发者放弃,而没人使用的安全功能保护不了任何人。

MicroVM 的答案:作为库嵌入应用的 Hyperlight

MicroVM 为每个工作负载提供独立内核以及由硬件强制执行的隔离边界——拥有完整虚拟机级别的隔离能力,却能在毫秒级启动和销毁。即使内部发生异常,也只能撞上隔离墙,而无法回到宿主机。同时,它天生就是一次性的:当智能体失控时,只需删除整个沙箱,并在几毫秒内重新创建,无需任何清理工作。

大多数 MicroVM 运行时(例如 Firecracker)都属于云基础设施,运行在服务端。而 Hyperlight 则有所不同:它是一个轻量级虚拟机管理器(CNCF Sandbox 项目),设计目标就是像一个普通库一样嵌入到你的应用中。

  • 毫秒级启动 MicroVM,Guest 函数调用仅需微秒级完成。

  • 没有 Guest 内核,没有操作系统——Guest 是专门构建的 no_std Rust/C 二进制程序,几乎不存在可攻击面。

  • 默认完全隔离——没有文件系统,没有网络,没有任何资源,除非显式授权。

  • 通过类型安全的函数调用跨越 VM 边界,并支持 Snapshot/Restore,可在每次调用之间恢复到干净状态。

  • 支持 KVM、MSHV(Microsoft Hypervisor)以及 Windows Hypervisor Platform。

本项目使用 Wasm 后端:三个智能体共享同一个 HyperlightRuntime,而每次执行代码之前,Guest 都会恢复到同一个干净快照。这正是每日、多步骤流水线能够保持低成本运行的关键——只需保存一次沙箱状态,然后不断恢复,而不是数百次重复创建虚拟机。

Agent = Model + Harness

社区逐渐形成了一个简单的公式:Agent = Model + Harness

模型只是一个“瓶中大脑”——输入文本,输出文本,每次调用之间没有记忆,没有循环,也没有执行能力。它可以表达调用工具的意图,却无法真正调用工具。

Harness 则是执行层:负责调用模型、处理模型发起的工具调用,并决定何时结束整个流程。正如 Hugging Face 术语表所描述的那样:“如果你不是模型,那么你就是 Harness。”

这也重新定义了安全问题。当我的智能体生成 shutil.rmtree("/") 时,模型其实什么也没有删除——它只是提出了建议。真正执行它的,是 Harness。Harness 正是推理与现实相连接的地方,因此安全也必须存在于这里。问题不再是“如何让模型更安全”,而变成了:如何构建一个 Harness,让模型的所有意图都只能在一个无法逃逸的边界内执行?

Microsoft Agent Framework 通过 Python 和 .NET 提供了一套完备的 Harness Agent 能力,并且在文档中明确写下了一条安全建议:

对于本地 Shell 执行,我们建议将相关逻辑运行在隔离环境中,并在允许命令执行之前保留明确的人工审批。

Harness 就像汽车的方向盘——它并不试图充当安全带或缓冲区。因此,它把安全交给了外部:请将代码运行在隔离环境中。而 Hyperlight 正是这个隔离环境。本项目将这两者结合在了一起。

架构:两个层级,一座桥梁

整个设计的核心如下。每一期播客都会同时运行两个层级:

  • 宿主机上的编排层——WorkflowBuilder 图、LLM Client,以及确定性的保存步骤。

  • Hyperlight Wasm 沙箱中的执行层——唯一允许运行 LLM 生成代码的地方。

两者之间只有一个连接点:call_tool("fetch_url", ...)
在这里插入图片描述

对应到各层架构如下:

组件 作用
模型 Azure AI Foundry,通过 FoundryChatClient(AzureCliCredential) 每个 Harness Agent 背后的推理模型
智能体运行时 Microsoft Agent Framework create_harness_agent 驱动模型、声明能力、处理工具调用、决定结束时机
编排 WorkflowBuilder 图 准备 → SearchAgent → 适配处理 → ContentAgent → 适配处理 → GenScriptAgent → save_scripts
代码执行 CodeAct Provider 通过唯一的 execute_code 工具执行模型生成的代码——始终运行于 MicroVM 内,而非宿主机
隔离机制 Hyperlight Wasm MicroVM 所有智能体共享一个 HyperlightRuntime;每次 execute_code 前恢复到干净快照
宿主机工具 fetch_url(sandbox/podcast_tools.py) 唯一允许访问网络的路径;使用 urllib,并限制只能访问 BBC 白名单
持久化存储 save_scripts Executor 完全确定性、无 LLM;解析两个代码块,并写入最终输出文件

保证安全的四条不变式

README 明确说明了整个架构所保证的安全特性。这四条不变式,就是完整的安全论证。

  1. 模型永远无法直接访问网络。它唯一拥有的工具是 execute_code。网络访问只能发生在 Guest 内部执行 call_tool("fetch_url", ...) 时。模型无法直接连接互联网——它只能请求 Guest,而 Guest 又只能访问 BBC。

  2. 每次运行一个沙箱,每次调用恢复快照。三个智能体共享同一个 HyperlightRuntime。每次执行 execute_code 前,Guest 都会恢复到同一个干净快照,因此任何一步产生的状态都不会泄漏到下一步,也无需重新创建虚拟机。

  3. 两条计数路径——以及为什么必须存在两条。function_middlewaremake_tool_call_recorder)负责记录模型直接发起的 execute_code 调用;但 Guest 内部发起的 fetch_url 会由 Hyperlight 直接转发到 FunctionTool,完全绕过 Middleware。因此需要第二个计数器——make_call_tool_counter(on_call=)——在每次 Guest 调用时递增 state["tool_call_counts"][<agent>]["fetch_url"]。两个观察点,对应架构中两种真正不同的调用路径。

  4. 确定性的保存过程——持久化阶段没有 LLM 参与GenScriptAgent 只负责输出文本;save_scripts Executor 从文本中解析出两个带围栏的代码块,并自行写入简体和繁体文件。当字节真正写入磁盘时,整个流程已经不存在任何模型参与,因此输出路径完全可预测。

现在来看真正的代码实现

README 已经完整说明了这个示例所基于的 API。下面的代码片段就反映了整个实现的接口层。

1. 安装与环境配置

pip install agent-framework-hyperlight --pre
# Hyperlight needs a hypervisor: KVM on Linux, WHP on Windows. macOS is not yet supported.
# The model runs on Azure AI Foundry; FoundryChatClient authenticates via AzureCliCredential.
az login
export HYPERLIGHT_PYTHON_GUEST_PATH="/path/to/python_guest"

2. 一个仅包含最小 Stub 的 Harness Agent——其余能力全部来自 Skills

三个智能体都通过 create_harness_agentFoundryChatClient 构建。智能体本身只携带一段极简的 Stub 指令;真正的角色提示词,以及共享沙箱和 CodeAct 的安全约束,都存放在 **skills/**目录下以文件形式存在的 Agent Skills 中。Harness 内置的 SkillsProvider 会公开这些 SKILL.md 包,而模型则会在运行时通过 load_skill 将它们加载进来。

from agent_framework import create_harness_agent
from agent_framework.foundry import FoundryChatClient
from azure.identity import AzureCliCredential

# Model on Azure AI Foundry — not Azure OpenAI directly.
client = FoundryChatClient(credential=AzureCliCredential())

# The agent carries a tiny stub. Its real persona — "you gather World Cup
# news", "you write the script" — lives in a SKILL.md package under skills/,
# advertised by the harness SkillsProvider and pulled in via load_skill.
search_agent = create_harness_agent(
    chat_client=client,
    name="SearchAgent",
    instructions="You are a harness agent. Load your skill, then begin.",
)

3. CodeAct 的接口:模型唯一可见的工具

下面展示的是 02-agents/context_providers/code_act/code_act.py 中的 CodeAct 模式。模型只能看到一个工具——execute_code。任何额外能力(本例中只有 fetch_url)都只能在 Guest 内部通过 call_tool(…) 调用。

# What the MODEL sees and writes — one script, not ten tool round-trips:
#
#   # inside execute_code, running in the Hyperlight Wasm guest:
page = call_tool("fetch_url", url="https://www.bbc.com/sport/football/world-cup")
#   # ... parse page["BODY"], pull out today's stories ...
print(top_stories)
#
# execute_code is the ONLY tool on the model's surface.
call_tool("fetch_url", ...) is reachable only from inside the sandbox.

4. 唯一的宿主机工具,并且仅允许访问 BBC

fetch_url 运行在宿主机(sandbox/podcast_tools.py)。它是跨越隔离边界的唯一桥梁,而且被刻意设计得极其受限。

import urllib.request
from urllib.parse import urlparse

ALLOWED_DOMAINS = {"bbc.com", "www.bbc.com"}   # allow-list: BBC only

def fetch_url(url: str) -> dict:
    """The ONLY network path out of the sandbox. Host-side, allow-listed."""
    host = urlparse(url).netloc
    if host not in ALLOWED_DOMAINS:
        return {"STATUS": "blocked", "URL": url}

    with urllib.request.urlopen(url, timeout=20) as resp:
        body = resp.read(8192).decode("utf-8", "ignore")   # BODY capped at ~8 KB

    return {
        "STATUS": "ok",
        "URL": url,
        "TITLE": _extract_title(body),
        "DESCRIPTION": _extract_description(body),
        "LINKS": _extract_links(body),
        "BODY": body,
    }

请注意,这种设计带来的安全收益非常明显:即使 SearchAgent 编写了恶意代码,它通过网络能够做到的最坏情况,也不过是一次读取 BBC 的内容,而且每次最多只能读取约 8 KB。白名单运行在宿主机侧,模型根本看不到它,因此也无法通过 Prompt Injection 将其绕过。

5. 构建工作流以及确定性的保存步骤

from agent_framework import WorkflowBuilder

workflow = (
    WorkflowBuilder()
    .add_node("prepare", prepare)
    .add_node("SearchAgent", search_agent)
    .add_node("adapt_1", adapt)
    .add_node("ContentAgent", content_agent)
    .add_node("adapt_2", adapt)
    .add_node("GenScriptAgent", genscript_agent)
    .add_node("save_scripts", save_scripts)   # deterministic Executor, NO LLM
    .build()
)

# GenScriptAgent emits text containing two fenced blocks (simplified +
# traditional). save_scripts parses them and writes the files itself —
# there is no model in the persistence step.
await workflow.run()
# -> ./outputs/<YYMMDD>/<YYMMDD>.simple.zh.txt
# -> ./outputs/<YYMMDD>/<YYMMDD>.tranditional.zh.txt

6. 最终效果

现在,再让这个流水线执行一次 shutil.rmtree(“/”),结果会变得出奇地平淡:智能体删除的只是它自己的临时沙箱,宿主机完全不会受到任何影响,而下一次 execute_code 又会从一个全新的快照重新开始。这里有两点值得特别说明:

  • Snapshot/Restore 机制意味着,每一次代码执行都会从一个干净、可复用的基础状态开始——只需要捕获一次状态,之后在每次调用之间恢复快照,而无需重新构建整个虚拟机。对于每天都会执行大量“执行—观察—修正”循环的流水线来说,这正是“足够快,因此始终启用”和“太慢,因此最终被放弃”之间的区别。

  • 由于每个智能体只需编写一段脚本,而不是反复进行十几次工具往返调用,CodeAct 模式同时降低了延迟和 Token 消耗——模型只负责完成一次推理,而大量具体执行工作则交由隔离边界后的 Guest 完成。

适用场景,以及唯一需要记住的思想

在这里插入图片描述

harnessagent_sandbox_demoMulti-AI-Agents-Cloud-Native 项目中的一个示例,该项目展示了如何在 Azure 上安全地运行智能体系统,包括:A2A 多智能体编排、Kubernetes Sidecar 模式、安全加固流水线,以及另一个在 AKS 中基于 Kata Containers MicroVM 于 Pod 层运行 Copilot 智能体的示例。

README 同样明确指出,这套设计本身就是云原生的:即使部署到 AKS 集群中,整体架构也不会发生变化——仍然是同一套 WorkflowBuilder 工作流、同一个 Hyperlight 沙箱,以及同一个确定性的 save_scripts Executor。本地运行与集群部署拥有完全一致的架构形态。

这两个 MicroVM 示例实际上代表了同一条技术路线上的两个不同层次。Kata 示例将硬件隔离边界放在整个 Pod 外层,属于部署层面的隔离;而 Hyperlight 示例则将隔离边界进一步收缩到智能体进程内部,让沙箱本身成为一次普通的库调用。它们回答的是同一个问题——在智能体架构中,硬件隔离边界应该放在哪里?——只是答案对应着不同的抽象层级。

过去,人们谈到沙箱时,总会附带一句话:更安全,但需要牺牲速度、兼容性或者开发体验。而 MicroVM 消除了这个前提条件——提供虚拟机级别的隔离能力,启动速度足够快,以至于没有理由关闭它,同时又拥有足够真实的运行环境,使智能体能够真正完成实际工作。真实到什么程度?真实到每天早晨都能替你写出一档世界杯播客。

真正需要记住的只有一句话:Harness 负责决策,MicroVM 负责隔离。给智能体一个允许失败的空间,然后让它尽情发挥。

参考资料

pip install agent-framework-hyperlight --pre
  • .NET:
dotnet add package Microsoft.Agents.AI.Hyperlight --prerelease
  • 运行要求:Linux 需要 KVM,Windows 需要 WHP;暂不支持 macOS。
Logo

微软开发者社区,邀请来自微软以及技术社区专家,带来最前沿的技术干货与实践经验。在这里,您将看到深度教程、最佳实践和创新解决方案。

更多推荐