用CUGA构建真实Agent应用:轻量框架上的24个可运行示例
Build real agentic apps using CUGA: two dozen working examples on a lightweight harness
IBM 发布开源 agent harness CUGA(Configurable Generalist Agent),通过 `pip install cuga` 安装。它处理规划、执行循环、工具调用和状态管道,开发者只需编写工具列表和 prompt。团队基于此构建了二十多个单文件应用(cuga-apps),每个均为包装单个 `CugaAgent` 的 FastAPI 文件,涵盖电影推荐、IBM Cloud 架构顾问等场景。应用运行在开放权重模型 `gpt-oss-120b` 上,在 AppWorld 和 WebArena 等 agent benchmark 上排名前列。CUGA 支持通过策略系统实现治理,并可在 IBM Sovereign Core 中部署。
](https://huggingface.co/anupamamurthi)
TL;DR——构建一个 agent 大部分是管道工作:工具、状态、护栏、从单个 agent 扩展到多个。CUGA(
pip install cuga),全称 Configurable Generalist Agent,即来自 IBM 的企业级 Agent Harness,负责处理这些工作,你只需编写一个工具列表和一个 prompt。我们构建了二十多个单文件应用来证明这一点。在此处从头到尾阅读一个应用,然后看看同一个 agent 如何在生产环境中以主权和受治理的方式运行,而无需重写。
大多数 agentic 应用在 agent 做任何有用的事情之前,都要先花一周时间做管道工作。你选择一个 framework,连接一个 model client,编写工具适配器,构建某种将状态流式传输到 UI 的方式,然后在这个过程中你还要决定 agent 的实际用途。有趣的部分总是最后才到来。
CUGA 颠覆了这一点。它是来自 IBM 的开源 agent harness,为你处理规划、执行循环、工具调用和状态管道。剩下的才是真正属于你的部分:agent 可以访问哪些工具,以及你告诉它做什么。为了展示这在实践中的感觉,我们构建了 cuga-apps:二十多个小巧、可工作的应用,每个都是一个包装了单个 CugaAgent 的 FastAPI 文件,从电影推荐器到 IBM Cloud 架构顾问。它们的存在是为了被阅读和复制。你可以点击浏览在线画廊。
本文详细讲解其中一个应用,指出 harness 为你省去了哪些工作,并展示当你需要为生产环境进行治理时,相同的代码会走向何方。无需先学习新的 framework。如果你写过 FastAPI 路由,你就能读懂每一行。
为什么是 harness,而不是 framework
对于这个领域的任何事物,一个合理的问题是:它让你免于编写什么?CUGA 的答案是:围绕一个 model 的编排工作,否则你每次都得重新构建。
它在行动之前进行规划,然后通过混合使用工具调用和生成的代码(CodeAct)来执行。对于一个运行二十步的长任务,大多数 agent 会失败的原因是丢失了中间结果的踪迹,并在下一轮中(通常是错误地)重新推导它们;CUGA 会持有该状态并运行一个反思步骤,该步骤可以捕获错误的调用并重新规划,而不是盲目地继续。正是这种机制使其在 AppWorld 和 WebArena 等 agent benchmark 上名列前茅,而不是靠手动调整的东西。
你还可以通过配置而非代码来设置成本/延迟权衡:快速、平衡和精确推理模式,代码执行在你信任的任何沙箱(本地、Docker/Podman 或 E2B 云)中进行。相同的 agent 定义,不同的旋钮。这个旋钮比听起来更重要。大多数 harness 假设底层有一个前沿模型,并依赖它在计划出错时进行恢复;CUGA 自己完成这项工作。规划、反思步骤、保持长运行过程不偏离轨道的变量追踪——这些都是 harness 承担了模型原本需要承担的负载,这使得一个较小的开放权重模型能够在通常无法支撑的地方站稳脚跟。这就是为什么托管应用运行在 gpt-oss-120b 上,而不是前沿 API 上。运行你能调用的最大模型是通常的赌注;CUGA 的赌注是,一个较小的开放模型就足够了。
没有任何一个单独的组件是 CUGA 独有的。不同之处在于它们被预先组装好了,所以你只需配置它们,而不是将它们连接在一起。你接触的 API 很小——用一个工具列表和一个 prompt 构建一个 CugaAgent,然后 await agent.invoke(...)。该行以下的所有内容都是 harness。
具体来说,这包括可互换的工具(OpenAPI、MCP 和 LangChain 函数都以相同方式绑定)、具有变量管理和自我纠正的长周期规划(这是 #1 on AppWorld from 07/25 - 02/26 和 WebArena from 02/25 - 09/25 背后的机制)、声明式护栏、通过 A2A 进行的多 agent 委派、由 Docling 驱动的 RAG,以及一个环境变量切换提供商(pip install cuga,然后是 OpenAI、watsonx、Ollama 等)——每一样都是你原本需要自己构建的。名称的第一个词说明了作用:Configurable(可配置);困难的部分已被处理,所以你的工作只是任务本身。
一个应用,从开始到结束
这是 IBM Cloud 顾问——一个为架构推荐真实 IBM Cloud 服务的 agent。整个东西放在一个文件中:一个包含 agent 工厂、工具和 prompt 的 main.py,外加一个小型 UI。
整个 agent 就是这样的:
def make_agent():
from cuga import CugaAgent
from _llm import create_llm
return CugaAgent(
model=create_llm(
provider=os.getenv("LLM_PROVIDER"),
model=os.getenv("LLM_MODEL"),
),
tools=_make_tools(),
special_instructions=_SYSTEM,
cuga_folder=str(_DIR / ".cuga"),
)
四个参数。模型来自一个小工厂(create_llm),它根据环境变量与 OpenAI、Anthropic、watsonx、LiteLLM 或 Ollama 通信。应用代码中没有任何东西知道背后是哪个模型。cuga_folder 是这个应用保存其状态和任何策略的地方。承载应用的两个参数是 tools 和 special_instructions。
工具混合了本地函数和托管函数:
def _make_tools():
from langchain_core.tools import tool
@tool
def search_ibm_catalog(query: str) -> str:
"""Search the IBM Cloud Global Catalog for real IBM Cloud services.
Always call this before recommending services to verify they exist."""
... # 调用 catalog API,返回 JSON
from _mcp_bridge import load_tools
web_tools = load_tools(["web"])
return [search_ibm_catalog, *web_tools]
这里有一个适用于所有应用的模式:MCP 工具和内联工具之间的分工。通用的、无状态的能力来自共享的 MCP 服务器;load_tools(["web"]) 拉入网络搜索,无需你托管任何东西。任何特定于此应用的内容都作为普通的 Python 函数内联定义,比如 search_ibm_catalog,其 docstring 是 agent 决定何时调用它的依据。你编写属于你自己的那个工具,然后借用其余的工具。
云顾问的 prompt 告诉 agent 在命名任何服务之前先搜索目录,推荐三到七个服务并说明每个服务在设计中的角色,并且绝不编造服务名称。最后这条规则至关重要:一个推荐不存在的 IBM Cloud 服务的 agent 比没有 agent 更糟糕,所以 prompt 强制每次推荐都先进行目录查找。以有序步骤编写并带有明确"不要编造"规则的 prompt 表现良好;以角色 persona 编写的 prompt 则会偏离方向。
这就是这个应用。一个工具,一个流程,四行构造函数。围绕它的 FastAPI 路由是普通的 Web 代码:浏览器向 /ask 发送问题,实时面板轮询 /session/{thread_id} 端点以获取状态。没有数据库;状态是一个按 thread_id 划分的 Python dict,只有 agent 通过其工具写入。当 agent 在运行过程中调用工具时,面板会立即重绘。UI 不是逻辑的第二个副本;它是 agent 所变更状态的一个视图。
承担繁重工作的约定
有一个细节很容易被忽略,但它实际上是关键所在:每个内联工具都返回相同的小信封。成功看起来像 {"ok": true, "data": {...}};失败看起来像 {"ok": false, "code": "...", "error": "..."}。
这看起来像是样板代码。其实不是。CUGA 的规划器能优雅地处理一个_声明了的_失败("地理编码没有返回任何内容,跳过该部分并继续"),但会在一个_未声明的_失败上卡住,此时原始堆栈跟踪会在规划过程中冒出来,导致运行脱轨。在所有应用中,那些可靠运行的应用,其工具从未向 agent 抛出过裸异常。一个乏味的约定,但却是 agent 能否恢复与直接摔跟头的区别。
上述分工之所以有效,只是因为通用部分已经在某处运行了。这些应用反复使用的能力——网络搜索、Wikipedia/arXiv、地理编码和天气、金融报价,以及更多——存在于 7 个公共 MCP 服务器(36 个工具) 中,托管在 IBM Code Engine 上,无需认证。一个小型桥接器自动解析它们的 URL,并且在线画廊附带了一个 MCP Tool Explorer,可以在你将工具接入 agent 之前,通过表单直接调用其中任何一个。
一个库,而非演示
有二十多个精良应用的原因比任何一个单独的应用都更重要:一旦你读懂了云顾问,你就读懂了它们全部。它们共享一个骨架——电影推荐器将 IBM 目录工具替换为 knowledge MCP 服务器,网络研究者几乎完全依赖 web——所以 cuga-apps 实际上是一个起点目录。你克隆仓库,找到最接近你想法那个应用,然后编辑它的工具列表和 prompt(HOW_TO_BUILD_AN_APP_FAST.md 和 ADDING_AN_APP.md 详细说明了这个过程)。有几个应用甚至是通过将一份规范文件和一个一行摘要交给编码助手生成的——足够规律以至于模型可以复现,意味着也足够规律让你学习。你可以在克隆任何东西之前,在在线画廊中点击浏览每一个应用。
它们还按类别展开,所以无论你在构建什么,总有一个应用已经用到了你需要的部分。有一个研究集群(Paper Scout 按引用数对 arXiv 论文进行排序;Wiki Dive 和 Web Researcher 进行引用综合),一个日常生产力套件(城市简报、旅行、食谱、步道),一个文档和媒体组,对 PDF、音频和视频进行 RAG,一个监控实时指标的运维角落,以及一个基于真实 IBM 产品文档的企业示例。Ouroboros 是一个七 agent 的线索生成系统;打开它以了解多 agent 形态。而 Meetup Finder 通过 Playwright 驱动无头 Chromium,从 Meetup、Luma 和 Eventbrite(它们都关闭了公共搜索 API)中提取结构化事件;打开它以了解浏览器自动化,这也是 CUGA 的起点及其强大 WebArena 成绩背后的支撑。
在克隆之前有两个注意事项。真正的目录位于内部的 cuga-apps/cuga-apps/apps/ 目录中,而不是外部的那个。而且并非每个应用都同样精良,所以 UI 将它们标记为可发布、待完善或探索性,并默认显示可发布的应用;从云顾问或电影推荐器开始,以获得一个可工作的基线。
将 agent 保持在边界内
一个搜索目录的演示 agent 风险很低。将同样的模式指向会写入文件、运行 shell 命令或接触生产环境的东西时,问题就变了:你如何阻止它做你会后悔的事情?
CUGA 在运行时中回答这个问题,而不是在你事后添加的包装器中。这个开源 agent 附带了一个策略系统,你将策略附加到同一个 agent 对象上:
await agent.policies.add_intent_guard(
name="Block force-push",
keywords=["--force", "--no-verify"],
response="Blocked: destructive git flags are not permitted.",
)
这是一个 Intent Guard,是六种策略类型之一,每种都回答了一个团队在让 agent 自由行动之前会问的问题:
- Intent Guard —— 它能直接拒绝一个请求吗?
- Tool Approval —— 它能在运行风险工具之前暂停等待人工批准吗?
- Tool Guide —— 我能否在不重写工具的情况下,指导某个特定工具的使用方式?
- Playbook —— 我能否为重复性任务固定一个已知良好的流程?
- Output Formatter —— 我能否强制最终响应符合要求的格式?
第六种类型 CustomPolicy 是当以上都不适用时的逃生舱。时间点值得注意,因为它并非都在同一阶段:Intent Guard 在 agent 选择工具之前检查请求,Tool Approval 在 agent 生成其代码_之后_运行,并检查该代码使用了哪些工具,而 Output Formatter 仅在最终消息存在时触发。触发器也不仅仅是关键词匹配:它们存储在一个 sqlite-vec 存储中,并进行语义匹配,因此策略会根据用户的_意图_触发,而不仅仅是精确的关键词。可以在语义相似性、agent 状态或特定工具触发时匹配。策略本身位于构造函数中的那个 .cuga 文件夹中,与代码一起版本化,而不是在单独的配置中漂移。
要查看一个工作示例,请打开 Ouroboros——一个七 agent 的线索生成应用,它为其 supervisor 附加了三个策略(一个 intent guard、一个 tool guide 和一个 output formatter),因此它是同一个文件中同时演示治理和多 agent 形态的应用。
超越单个 agent 的扩展
当一个应用超出单个聊天循环的范围时,有两个扩展很重要。当一个 agent 会在自己的上下文中淹没(工具太多,需要理清的证据太多)时,你就拆分工作。一个 CugaSupervisor 将任务委派给专门的 CugaAgent,每个 agent 都有自己的工具、prompt 和隔离的上下文,而 supervisor 只考虑将子任务交给哪个专家。无论底层有多少工具,它的规划面都很小,并且一个不稳定的工具只会导致一次委派失败,而不是整个运行失败。一个专家甚至不必是本地的;它可以是通过 A2A 访问的外部 agent,以相同的方式被委派。增加一个能力意味着增加一个专家,而不是重写协调器。
另一个扩展打包的是知识而非工具:Agent Skills,一个包含 SKILL.md 剧本的文件夹,只有当任务需要时,agent 才会将其拉入上下文,这样单个 prompt 就不必承载 agent 可能永远需要知道的一切。两者都使用相同的构建块(工具、prompt、状态、策略),只是组合方式高了一个层次。
之前的线索生成应用 Ouroboros 使这种模式具体化。它有一个 supervisor 管理七个专家(侦察员、站点审计员、客户之声、人员查找器、技术栈扫描器、收入估算器和综合撰写推销邮件的写手)。每个专家都是一个加载到 CugaAgent 中的技能,supervisor 通过一个自动生成的 delegate_to_<name> 工具来调用它。增加第八个专家是一行工厂代码,而不是重写协调器。如果你想从头到尾了解多 agent 形态,请阅读它的 main.py 和 ARCHITECTURE.md。
还有第三个扩展,它又指向了技能本身。借助 ALTK-Evolve,CUGA 的在职学习框架,一个 agent 从其自身的运行中完善一项技能,这样今天完成的任务会使明天的任务更快、更准确。专家加载的 SKILL.md 最终包含了 agent 在你编写的内容之上学到的东西。相同的构建块,只是现在使用一个技能会教会下一个技能。你不再需要为上周已经解决的问题重新编写 prompt。
通过构造实现治理
治理在技术栈中的位置决定了生产环境的故事如何展开。一个最小的 agent 库会给你好的原语,然后把治理(策略、审批、审计、身份)留给你自己去组装。CUGA 走了另一条路:策略、人机协同审批、.cuga 状态文件夹和自托管从一开始就是 harness 的一部分,而不是你后来添加的层。
当你将 agent 投入生产时,这改变了工作的方向。你不是在为为开放访问构建的东西加装控制;控制平面已经存在。受治理的路径是默认的,不受治理的捷径才是你需要选择加入的。所以剩下的工作很窄:收紧围绕少数几个真正接触外部世界的工具的沙箱,而不是发明围绕它们的治理。
同一个 agent 的最终归宿
这就是回报,也是这一切如此构建的原因。因为 harness 很小、开源、与模型无关,并且已经自我治理,你在笔记本电脑上编写的 agent 与在锁定部署中运行的 agent 是同一个。你不需要移植它。你重新部署它。
这就是 IBM Sovereign Core 所构建的基础,也是我们接下来将 CUGA 带向的方向。我们单独写了细节,但简短版本是:Sovereign Core 在我们称之为边界隔离(Boundary Isolation)的环境下运行 CUGA agent:数据、控制平面和执行引擎位于同一个逻辑边界内,agent 在租户自己的工作空间中运行于瞬态、隔离的容器中。模型也在那里运行。部署默认使用完全气隙隔离运行在你基础设施内的 gpt-oss-120b,工具只能访问私有 VNET,并且需要逐个工具批准。每个推理步骤都会将 OpenTelemetry 追踪发送到保持在租户内的 Grafana Tempo 后端,没有遥测数据回传。没有任何东西离开边界。
agent 定义不需要改变就能达到那里;围绕它的部署会改变。而这是可能的,原因在于以上所有内容——能力、策略和模型选择都存在于一个你可以阅读的运行时中。这是我们构建它时下的赌注:当 agent 的运行时是一个黑盒时,主权只是一个承诺,但当它是开放代码时,主权是你能够验证的东西。你克隆的应用和你编写的 agent 都是同一个开放运行时,这个主张就建立在此之上。
不过,开发者的收获本身是独立的。一个 agentic 应用可以是一个你能够完全理解的文件。工具和 prompt 是你真正需要编写的唯一部分。这些应用是一个可以学习的库,而不是一个封闭的演示。当风险增加时,治理已经在运行时中——你不需要为了安全而重建 agent。
下一步
克隆仓库并运行一个应用。托管的 MCP 服务器意味着你不需要第三方密钥,只需要一个 LLM 提供商。本文中的应用运行在开放权重的 gpt-oss-120b 上——与托管画廊和我们的 Sovereign Core 部署使用的模型相同——但由于模型是一行代码就能切换的(create_llm 读取单个环境变量),你可以将任何应用指向 OpenAI、Anthropic、watsonx 或本地 Ollama 模型而无需更改代码,并且使用本地模型完全没有 API 成本:
首先查看我们的快速入门指南 here。如果你想设置所有应用,请确保 Docker 正在运行,然后按照以下步骤操作。
git clone https://github.com/cuga-project/cuga-apps.git
cd build
cp .env.example .env # 设置你的 LLM 提供商 + 密钥;为使用它们的应用添加
# TAVILY_API_KEY / OPENTRIPMAP_API_KEY / ALPHA_VANTAGE_API_KEY
docker compose up --build # 首次构建较大(cuga + Chromium + MCP 依赖)
# 打开 http://localhost:8080
然后打开 apps/ibm_cloud_advisor/main.py 并从头到尾阅读——它是内联工具加 MCP 模式最清晰的例子。更改系统 prompt,添加一个工具,观察行为的变化。MCP Tool Explorer 列出了每个托管工具,并带有直接调用它的表单,这是在将工具接入 agent 之前快速检查管道的一种方式。
所以试试看。pip install cuga,克隆 cuga-apps,并运行一个应用——或者先点击浏览在线画廊。harness 位于 cuga-agent,项目主页是 cuga.dev。如果出现问题,应用行为异常,或者你有想法,我们很乐意倾听:提交 issue,提交 PR,放入你自己的应用,或者直接联系我们——这个仓库就是为了被扩展而构建的,我们会阅读所有收到的反馈。
资源
- cuga-apps —— 本文中的应用、MCP 服务器和 UI
- cuga-apps/apps —— 二十多个精良的单文件 agent 应用(内部目录;从这里克隆)
- cuga-apps/mcp_servers —— 应用借用的共享 MCP 服务器(web、knowledge、geo、finance、code、text 等)
- 在线应用画廊 + MCP Tool Explorer —— 每个应用都带有一个启动按钮,还有一个表单可以直接调用每个托管的 MCP 工具
- cuga-agent —— CUGA 运行时和策略系统
- cuga.dev —— CUGA 项目主页(
pip install cuga) - Open by Design: Generalist and Pre-Built Agents in the Sovereign Core —— IBM 社区关于 CUGA 如何在 Sovereign Core 内部运行的帖子(Srivastava, Marreed, Thomas, April 2026)
- IBM Sovereign Core —— 产品页面
