技术速递|Harness 驱动的智能体:在 Hyperlight MicroVM 沙箱中构建安全的播客流水线
作者:卢建晖 - 微软高级云技术布道师
排版:Alan Wang
智能体伸手执行 rm -rf 的那一刻
在 2024 年和 2025 年的大部分时间里,“智能体”还是一个停留在演示中的概念。到了 2026 年,它已经成为真正投入运行的系统——自主运行、循环执行,甚至能够执行几秒钟前自己刚刚编写的代码。
有一天深夜,我正在观察这样一个智能体。我给了它一个目标、几项工具,并允许它编写和运行自己的 Python 代码。二十分钟里,一切都像魔法一样:读取文件、分析内容、编写脚本、执行脚本、检查输出、自我修正、再次尝试。然后,它生成了这样一段代码:
import shutil
shutil.rmtree("/") # "cleaning up temporary files"
它其实是在试图帮忙——它认为工作区已经变得杂乱,希望重新开始。而在那个进程看来,“工作区”就是我的整台机器。
我及时终止了它。但这件事带来的教训,也是所有构建智能体的人最终都会意识到的一点:**危险的并不是模型,而是执行。**一个回答错误的聊天机器人只是令人烦恼;一个能够获取网页、执行代码并写入文件的智能体,则拥有真正的破坏半径。这个边界必须由基础设施来提供,而不能寄希望于系统提示词。
harnessagent_sandbox_demo 就是一个具体的实现,它将这条安全边界放在了真正应该存在的位置。而它所服务的对象,则是一个真实且颇具趣味的小项目:每天自动生成一档时长五分钟、关于 2026 FIFA 世界杯的中文播客。
场景:由智能体编写的每日世界杯播客
暂时抛开底层基础设施,先看看这个项目真正做了什么。
每天,它都会生成一份全新的、关于 2026 FIFA 世界杯的中文播客脚本。整个流程由三个 LLM 智能体依次完成:
-
SearchAgent —— 收集当天有关世界杯的新闻。
-
ContentAgent —— 将原始素材整理为结构化的播客内容。
-
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 明确说明了整个架构所保证的安全特性。这四条不变式,就是完整的安全论证。
-
模型永远无法直接访问网络。它唯一拥有的工具是
execute_code。网络访问只能发生在 Guest 内部执行call_tool("fetch_url", ...)时。模型无法直接连接互联网——它只能请求 Guest,而 Guest 又只能访问 BBC。 -
每次运行一个沙箱,每次调用恢复快照。三个智能体共享同一个 HyperlightRuntime。每次执行
execute_code前,Guest 都会恢复到同一个干净快照,因此任何一步产生的状态都不会泄漏到下一步,也无需重新创建虚拟机。 -
两条计数路径——以及为什么必须存在两条。
function_middleware(make_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"]。两个观察点,对应架构中两种真正不同的调用路径。 -
确定性的保存过程——持久化阶段没有 LLM 参与。
GenScriptAgent只负责输出文本;save_scriptsExecutor 从文本中解析出两个带围栏的代码块,并自行写入简体和繁体文件。当字节真正写入磁盘时,整个流程已经不存在任何模型参与,因此输出路径完全可预测。
现在来看真正的代码实现
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_agent 和 FoundryChatClient 构建。智能体本身只携带一段极简的 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_demo 是 Multi-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 负责隔离。给智能体一个允许失败的空间,然后让它尽情发挥。
参考资料
-
Hyperlight:hyperlight-dev/hyperlight · hyperlight-dev/hyperlight-sandbox
-
Agent Framework:Microsoft Agent Framework 中的 Agent Harness
-
背景资料:Why MicroVMs(Docker)· Harness vs. Scaffold 术语表(Hugging Face)
-
安装:
- Python:
pip install agent-framework-hyperlight --pre
- .NET:
dotnet add package Microsoft.Agents.AI.Hyperlight --prerelease
- 运行要求:Linux 需要 KVM,Windows 需要 WHP;暂不支持 macOS。
更多推荐



所有评论(0)