拓冰建站拓冰建站
首页 / 资讯中心 / 正文

WeKnora 基于 CubeSandbox 的 Agent 持久化运行环境建设

作者腾讯技术专家、WeKnora项目Maintainer · 陈洋、赵海龙编者按WeKnora 是腾讯开源的企业级 LLM 知识平台自 2025 年 8 月开源以来热度持续攀升目前已收获 24.4k stars。它把企业的文档资产转化为 RAG 问答、ReAct 智能体和自维护 Wiki。其沙箱层同时支持了 Cube Sandbox、E2B、Docker 三种后端其中 Cube Sandbox 已经跑通了会话绑定、技能快照、暂停恢复、模板管理和网络策略的完整链路。这篇文章记录了WeKnora 对Cube Sandbox的设计与建设过程也回答一个更根本的问题沙箱在 Agent 平台里到底该扮演什么角色。◆一、Cube Sandbox在 WeKnora 里的应用全景◆WeKnora 最新发布的 v0.8.0核心特性是技能沙箱运行时会话级常驻的Docker / E2B / Cube沙箱后端按空间配置网络策略。沙箱在 WeKnora 里并不是跑一次性命令的附属执行件而是 Agent 技能的运行环境。Docker / E2B含 E2B Cloud 和任意 E2B 兼容控制面/ Cube三种后端并列。应用层不按品牌分支走同一套RemoteSandboxClient 会话绑定。部署方按隔离强度和运维形态选。在 CubeSandbox 这条线上WeKnora 的用法主要包括以下七项用法Cube 机制解决什么会话级持久沙箱Create metadata 会话绑定一个会话绑定一个沙箱上一轮装的包、创建的文件、跑起来的服务下一轮还在技能快照化CreateSnapshot快照 ID 即 TemplateID装好技能的沙箱打成快照新会话直接从快照创建技能秒级就位暂停恢复onTimeoutpause autoResume空闲自动暂停下一轮 Connect 自动恢复内存态不丢会话文件系统envd Files API附件、产物、跨 tool call 的工作区全部收进 /workspace命令执行Commands API stdin 注入Agent 在会话内跑脚本执行结果可追踪模板管理模板 CRUD 独立镜像变体weknora-sandbox:*-cube 镜像携带 envd 数据面网络策略出站/入站双开关 L7 规则按空间控制沙箱能不能出网、能不能被公网访问WeKnora 沙箱层中的三种后端可以实现上层业务代码一行不改的条件下互换。支撑这个约束的是两层设计◆ 第一层是中立接口 RemoteSandboxClientCreate、Connect、Get、List、Delete、Exec 六个生命周期方法加上文件、快照、模板、入站令牌四个辅助接口。Cube 特有的类型、错误码和 HTTP 语义全部被翻译成中立的 DTO 和带稳定 Kind 的 RemoteError不向外泄漏一行。◆ 第二层是能力广告应用层不写 if cube而是在运行时查询能力访问器会话内命令执行、会话文件系统、轮次标记、安装命令。访问器返回 nil就表示当前配置下这个能力不可用应用层据此决定注册哪些功能。图1WeKnora x Cube Sandbox 集成架构示意图CubeRemoteClient 承载了 Cube 后端的全部适配代码收敛在单个文件中它同时实现四个接口所有调用划分为两个平面平面调用内容传输路径控制面Create / Connect / List / 模板 CRUD / 快照 CRUD经共享网关连接池与 E2B 后端复用同一个 transport数据面envdFiles 操作Write/Read/List/MakeDir/Remove/Stat Commands 执行保留 SDK 的代理拨号重写ProxyNodeIP/Port/Scheme经独立 transport 包裹目前WeKnora 主要以三种形态接入 Cube裸金属、PVMK8s目前还在 preview 阶段。◆二、沙箱角色的转变从“跑完即毁”到“持久化运行环境”◆用法全景里的这些能力设计和架构设计是在经历了几次实际问题之后才逐步演化成型的。最初的后端是 Docker实现是每次 docker run --rm跑完即毁。它暴露出三个问题1会话状态缺失上一轮装的包下一轮就丢失了2shell 执行、附件暂存、产物收集这些能力在能力矩阵里注册不上3超时杀的是客户端进程容器还在后台跑。后来改成“一会话一长驻容器”行为才和Cube / E2B对齐。但Docker 的天花板也很明显它实现不了我们的三个底层需求跨主机调度、内核级隔离、内存态快照。对运行模型生成代码的场景共享内核的隔离边界也不够。这三样缺失把我们的选型推向了远程沙箱服务。接入远程沙箱WeKnora 选择走 E2B 协议。但是转折发生在技能持久化这个需求出现的时候。技能环境要构建、要打成镜像、要管理版本这些是控制面能力。E2B 协议同样覆盖模板构建与网络控制两者的差异在于粒度对运行中的沙箱直接打快照、快照 ID 作为模板使用、allowOut/denyOut延伸至 L7 规则的出站控制。这些正是技能镜像方案所需要的能力Cube 的控制面 API 恰好一一对应。这也是为什么 WeKnora 在 E2B 适配器之外单独维护了一个 Cube 适配器。WeKnora 技能持久化的最初设计是“一 skill 一卷”即每个技能一个 volume 挂载进沙箱。因为 E2B Volumes 当时还处在 private beta 阶段我们的首发改用了 Cube并在动手实现前把整个方案从卷挂载换成了快照。不过这次被迫的改道后来被证明是一条更好的路。演进到这里沙箱的角色已经发生了根本变化。它不再是“拿沙箱跑个命令”的执行容器而是 Agent 技能的持久化运行环境技能预装在里面会话在里面发生文件也“长“在里面。◆三、技能持久化快照做成发行版◆技能安装的成本模型决定了它不能摊到每个会话上。装一个技能要做什么解析SKILL.md、装系统包和Python / Node依赖、跑安装验证。这本身就是一次长达数分钟的 agent 对话。如果用户发起会话时才现场装太难等每个会话各装一份又太浪费。因此技能环境必须冻结成不可变的产物会话直接这份产物启动。那为什么选择快照而不是 volume两者代表的是两条相反的模型。volume 是共享可写的适合数据集技能环境要的是“已验证、不可变装完后 skills 目录归 root 所有、只读会话里临时 pip install 只能落到/workspace的 overlay不能污染镜像。而且对依赖系统包的技能volume 方式难以支持还存在装好的技能被 agent 改掉的风险。这条路能走通依托的是 Cube 快照的四个特性。◆快照 ID 可以直接当 CreateOptions 的 TemplateID会话侧根本不知道“技能”两个字它只是换了一张模板。◆可以对运行中的沙箱打快照安装过程就是普通的 exec 加文件写入不需要另搭一套镜像构建流水线。◆ XFSreflink / CoW让“底层模板 一层已装依赖”的存储密度和增量成本合理。◆ 快照与沙箱保活共享同一套生命周期语义配套齐全。完整的安装链路是这样的从基础模板创建沙箱 → 安装器执行安装 → 校验通过 → ledger 先落库、再调 CreateSnapshot → 记录 SnapshotID。之后所有新会话用快照 ID 代替模板 ID 创建技能秒级就位。快照的“所有权”由指纹机制守门。指纹是 SHA-256(provider APIKey APIURL)——标识快照所在的提供商账户。安装路径、快照生效判断、配置解析三方从相同输入计算指纹保证判定一致。凭据轮换后指纹不匹配旧快照静默失效会话自动回退基础模板而不是拿着一个已经不存在的镜像 ID 去启动。指纹为空时安装流程会直接拒绝记录快照没有 owner 的指针会在会话启动时被丢弃。选择快照方案同样需要付出代价主要是两条。Cube 把快照和普通模板放在同一个列表接口里返回WeKnora 的设置页必须把 snap- 前缀和快照列表减掉否则管理员会把技能镜像当成底模选上去。另外技能装进快照后随安装次数累积镜像会慢慢膨胀。◆四、会话持久化保活与 /workspace◆Agent 会话是分钟到小时级的中间夹着思考、等用户、等检索。如果 TTL 一到就 kill用户下一句话就要经历一次冷启动/workspace里的中间文件、overlay 包、未交的产物全部消失。所以会话沙箱创建时就是 onTimeoutpause autoResume空闲把 MicroVM 冻住省计算、保内存态下一轮 Connect 自动唤醒。对用户来说体感就是对话还在环境还在。Docker 后端的处理方式是一个有意思的对比。Docker 的 pause 内存还占着宿主机冻住等于不回收。所以 Docker 后端空闲是 kill由绑定层在下次使用时重建它对齐的语义是“没有了就当可重建”不是保活。同一个抽象接口下不同后端各自选择语义这正是中立接口层的价值。环境的另一半是文件系统。envd 的读写如果只给安装器用Agent 在会话里就是个“瞎子“。WeKnora 把 Files API 收成会话文件系统解决了四件事◆附件对象存储才是源会话开始时 restore 到/workspace/input只读约定技能通过环境变量拿到这个目录◆产物脚本写/workspace/output回合结束按同一棵树收集下载不经过模型“读文件再贴聊天”也不占用上下文◆模型写文件生成的脚本直接落盘不必塞进 shell 命令的 eredoc◆跨 tool call 的工作区同一会话多次执行看到同一棵树。这才是「持久化运行环境」对模型可见的部分。技能镜像是只读的“发行版”/workspace是可写的“这一轮电脑”。两套路径、两套权限文件 API 是后者的正式入口。◆五、身份持久化Redis 绑定层与孤儿回收◆Cube 的 TTL 加 AutoPause 管的是 MicroVM 自己的寿命空闲了就 pause下次 Connect 就 resume。它不知道 WeKnora 的会话、租户、副本、技能代际。这些应用语义全压在 Redis 绑定层上Cube 有的Redis 补的沙箱 ID、TTL、pause会话 → 沙箱的权威绑定多副本 WeKnora 必须共享内存绑定只适合单进程开发单次 Create生命周期锁create/recover/replace/delete 跨进程串行避免同一会话起两台 VMmetadata我们会打上 tenant/session/config绑定丢失后靠 metadata 认领认领不到才新建。TTL/pause 救不回“绑定写丢了”沙箱自己过期绑定永不过期SET NXTTL0。会话还在、沙箱被 pause 了绑定必须在才能 resume 而不是再买一台无“镜像已换”概念StaleAt 标记 轮次租约见下pause 后仍占快照存储和钱孤儿回收绑定被覆盖或 Redis 丢了之后paused 沙箱会占据磁盘。Cube 不会按「WeKnora 还认不认」去删这层设计要面对的最典型问题是计费。会话沙箱用 onTimeoutpause绑定一旦丢失Cube 侧就是一台 paused 的 VM会一直占用存储空间。所以必须有一层 reaper按 tenant metadata 定期对账把未绑定的实例包括 paused 的删掉。这两层的分工因此非常清晰Cube 把沙箱管活Redis 把“这台沙箱属于哪个会话、能不能拆、该不该换镜像”管住。◆六、轮次租约管理员装技能用户正在对话◆持久环境带来了一个独有的冲突管理员在装技能用户正在对话意味着他们在抢同台VM。这个冲突的具体场景是这样Agent 一轮对话要多次解析沙箱暂存附件、若干次命令执行、跑技能脚本、收产物——全都假定/workspace和进程还在。而技能安装常常就发生在对话中间——第一次工具调用之后管理员装完了第二次工具调用就把 VM 拆了这轮的草稿、已装进overlay 的包、在跑的 exec 全部消失。模型侧的表现就是“刚才还在的文件没了。”不拆也不行。技能安装成功后如果已有会话不做标记用户刚装的技能在当前对话里永远看不见——指针切换只影响新建沙箱。WeKnora 的解法是把“声明”和“动手”分开◆StaleAt 是声明镜像已经换了但不动手。◆BeginTurn 时 rebuild1本轮第一次 resolve 允许拆掉重建ConsumeTurnRebuild 立刻置零。同轮后续 resolve 即使仍是 stale也继续用当前沙箱。◆ 没有租约没有 AgentQA 在飞时stale 仍然立即重建——后台任务、空闲会话没必要拖。◆ Redis 读租约失败时 当有一轮在飞不拆。“只重建一次”防的是另一种情况一轮之内多次解析、中间又来一次安装反复拆 VM。票消费掉之后本轮不再重建。进程崩溃泄漏的轮次标记由 30 分钟 TTL 兜底过期此时重建 stale 镜像是预期行为。◆七、预埋的、踩出来的和给后来者的清单◆在上述这些设计里有一部分是架构设计阶段预先埋好的◆ 后端无关的RemoteSandboxClient能力用 capability 广告不用if cube。◆ 会话级沙箱 Redis 绑定 生命周期锁。◆ 租户可控 URL 的 SSRF保存时校验 拨号时再校验防 DNS rebinding即便允许私网也拦 link-local / 云 metadata。◆ 技能装进快照、会话从快照启动ledger 先写再 CreateSnapshot。◆ 脚本和非 root useruid 1000执行安装才走 root 的独立接口。◆ 工作目录锁在/workspace技能树在/opt/weknora/tenant/skills快照前清 scratch。预埋设计里网络策略的处理值得单独说明。Cube 的网络控制有两个开关分别管两个正交的维度allowInternetAccess 管出站——沙箱能不能主动出公网curl/pip通不通由它叠加allowOut/denyOut/L7规则决定allowPublicTraffic 管入站——沙箱的公网 URL 是否公开可达关掉后所有入站必须带 traffic token否则 403。WeKnora 在创建每个沙箱时把两个开关都显式定值防的是默认漂移——配置里省略不写服务端就回落模板默认模板一变线上行为跟着变。其中 allowPublicTraffic 显式 true 是刚需WeKnora 的文件、执行、终端全走 CubeProxy 数据面 URL关掉它自家访问先被 403 挡住。现状是出站默认全放行——这是有意识的起点选择技能安装要拉包先保证通细粒度的按空间/会话出站白名单还在收紧的路上。另一部分则是运行过程中我们踩过坑沉淀出来的经验◆ Cube 模板必须带 envd否则: 49983/health探活 connection refused——于是有独立的 -cube 镜像变体。◆ 快照混进模板列表设置页要滤掉 snap-前缀。◆ ListSnapshots 分页 token 循环重复会失败技能孤儿清理曾因此卡死。◆ 通用 E2B 数据面兼容envd 要 Basic auth、要补 X-User-ID 头上传要 multipart 而 SDK 发裸 octet-stream——E2B Cloud 宽容其它实现直接 401/500。◆ 设置页保存曾把 SkillImage 指针冲掉技能「列表里在、会话里没有」——更新接口现在不允许客户端碰快照字段和端点。◆ 一轮中途拆 VM 毁/workspace——轮次租约。◆ 命名配置字段级继承曾悄悄拨到 127.0.0.1——改为命名配置自包含不继承部署基线的 endpoint。◆ 命令黑名单连 pip install 的恢复建议一起拦——改成技能装完即只读、内核拒写失败后再提示走 overlay。对于同样在做 Agent 平台的团队我们建议先验证以下八件事按“技能持久化”这条路径而不是“能 exec 就行”1、快照 ID 能否直接当模板创建新沙箱、快照是否混在模板列表里2、对运行中实例打快照打完原沙箱还能不能用、pause 期间打快照稳不稳3、pause 加 Connect 自动 resume文件系统和内存还在不在、paused 实例是否仍计费、怎么 List 到4、Create 时的 metadata 能否按租户/会话 List 回来——没有这条多副本和崩溃恢复只能靠自己的绑定库碰运气5、出网双开关是否真如文档所说正交生效、私网里 DNS 是否可用6、数据面 envd 契约——Basic auth、multipart 上传、非 root 账号不要只测官方网关它更宽容7、镜像里有没有 envd没有会在健康检查上直接失败8、分页、删除幂等、名字回显——技能镜像是账单资源进程死在“已创建、未落库”窗口时必须能按名字认领或按 List 对账。在架构层面WeKnora 目前仍然缺少的能力包括Create 时挂 volumeCube 的 CreateOptions 还没有 volume-mount 字段共享数据集、热更新大文件目前没有这条路快照与模板分目录混在一起污染“选底模”的产品体验也增加误删风险pause/超时的应用层回调现在只能自己扫 ListTTL 到了 WeKnora 不会被通知孤儿只能周期性对账以及按会话身份的一等公民——Cube 管 VM不管“这是哪个 session 的”绑定、租约、stale、孤儿全在应用侧。如果控制面能原生认 metadata 所有权、按策略回收未认领实例Redis 这层可以瘦很多。Weknora 项目地址https://github.com/Tencent/WeKnoraCube 实战笔记专栏介绍本系列旨在记录真实团队和项目在 Cube Sandbox 上的工程实践——架构选型、部署落地、踩坑复盘每一篇都来自一线上手经验。既有跑通的方案也有走过的弯路给正在评估和使用 Cube 的人一份可参照的路径。如果你也在 Cube 上实践、探索或有对于问题的解决方案欢迎投稿或与我们联系让经验被更多用户看见。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门