行代碼到臨時Runtime:Agent云沙箱架構(gòu)實戰(zhàn))
1. 讓 Agent 從“會寫代碼”到“能管理一個臨時Runtime”1.1 Agent 過去那種“執(zhí)行代碼”方式到底缺什么我見過太多 Agent 項目demo 時一路綠燈真要讓它在業(yè)務(wù)環(huán)境里跑第一關(guān)往往就卡在“代碼跑不起來”。一開始大家都覺得 Agent 只要能生成代碼就夠了本地裝個 Python 跑一下就行可實操過一輪就會發(fā)現(xiàn)讓 Agent 在宿主機(jī)上直接執(zhí)行隨之而來是一串問題依賴缺失、解釋器版本沖突、權(quán)限不對、臟環(huán)境導(dǎo)致的隨機(jī)行為。典型就是今天模型決定用 pandas 處理 Excel但宿主機(jī)根本沒有 openpyxl明天它決定調(diào)用一個系統(tǒng)命令你卻不知道命令裝沒裝上更別說路徑在哪里。這些問題本質(zhì)上不是模型的錯而是給 Agent 準(zhǔn)備的環(huán)境不夠穩(wěn)定、不夠可預(yù)期。傳統(tǒng)做法是把“執(zhí)行代碼”當(dāng)成一個簡單的工具調(diào)用Agent 寫下腳本代碼執(zhí)行器拿到字符串丟給 shell 或python -c拿到 stdout 返回給模型然后就沒有下文。這套玩法處理獨立小腳本、算法片段是能跑的但也僅限于此。它把“代碼執(zhí)行”和“Runtime”割裂開了完全忽略了環(huán)境這個變量于是每一次執(zhí)行都充滿了不確定性。更要命的是如果 Agent 可以在宿主機(jī)上隨便跑任何一次模型誤判或提示詞注入都可能讓一段危險代碼直接暴露在真實系統(tǒng)里后果不可控。所以我才把“從執(zhí)行代碼到擁有臨時 Runtime”當(dāng)成一個質(zhì)的跨越來看。1.2 臨時 Runtime 解決了什么問題臨時 Runtime 的思路說白了就是一句話Agent 需要執(zhí)行時不要復(fù)用宿主機(jī)也不要共享遺留環(huán)境而是向云沙箱申請一個臨時、獨立、可銷毀的運行環(huán)境。這個環(huán)境是精簡的依賴是聲明過的資源限制是可見的生命周期是可控的。它只在任務(wù)期間存在任務(wù)結(jié)束立即銷毀不留任何現(xiàn)場。拿我自己的一個 Agent 項目舉例模型經(jīng)常需要生成圖表、做數(shù)據(jù)分析甚至臨時調(diào)用一個 OCR 模型。以前在本地跑幾次就把測試機(jī)環(huán)境搞臟不同任務(wù)之間的依賴互相打架還時不時留下幾個孤兒進(jìn)程。后來我把執(zhí)行鏈路全部切成云沙箱創(chuàng)建容器時直接指定python:3.12-slim把 pandas、matplotlib 這些常用庫在鏡像里固化容器分配固定的 CPU 配額和內(nèi)存上限Agent 提交代碼沙箱執(zhí)行最后把標(biāo)準(zhǔn)輸出和退出碼統(tǒng)一返回。任務(wù)完成要么手動刪容器要么等 TTL 自動回收現(xiàn)場干干凈凈。臨時 Runtime 的價值就在這它不追求長期存在它追求的是為一次任務(wù)提供完整、可預(yù)期、用后即焚的執(zhí)行環(huán)境。1.3 云沙箱在這里面承擔(dān)了什么云沙箱是臨時 Runtime 的載體。嚴(yán)格說臨時 Runtime 是一種抽象云沙箱是它的物理實現(xiàn)方式你用容器也好gVisor 也好輕量虛擬機(jī)也好關(guān)鍵是構(gòu)建出清晰的隔離邊界。Agent 要執(zhí)行的代碼無論如何都要當(dāng)成不可信程序來對待。所以云沙箱要解決的核心不是“代碼能不能跑”而是“代碼跑壞了、跑偏了、跑飛了代價能不能被輕松抹掉”?;谶@個目標(biāo)環(huán)境里的一切才可以被“臨時”化文件系統(tǒng)用完就刪網(wǎng)絡(luò)默認(rèn)關(guān)閉進(jìn)程數(shù)、內(nèi)存、CPU 全部收緊到最小實現(xiàn)任務(wù)的量級。如果一個 Agent 只能在單機(jī)上運行那它終究只是“個人腳本助手”。而給了它臨時運行環(huán)境它才真正有底氣去處理數(shù)據(jù)清洗、環(huán)境部署、問題定位這類重活也是標(biāo)題里“讓 Agent 從執(zhí)行代碼升級到擁有一個臨時 Runtime”的根本差別執(zhí)行代碼只是動作擁有 Runtime 才是能力。接下來我就把這一整套架構(gòu)拆開從選型、實現(xiàn)到踩坑一次性講透。2. 云沙箱技術(shù)選型與隔離模型2.1 三種隔離路線對比建設(shè)臨時 Runtime 的第一個決策是用什么做沙箱。目前主流不過三條路線容器、gVisor、輕量虛擬機(jī)。三者對“隔離”的理解和實現(xiàn)并不一樣實際落地之前必須盤清楚。容器Docker runc是最常用也最方便的一條路。它基于 Linux namespace 和 cgroup給進(jìn)程一個“看起來像獨立機(jī)器”的隔離環(huán)境創(chuàng)建速度毫秒級。Agent 代碼跑在里面訪問不到宿主機(jī)文件也看不到宿主機(jī)的全部進(jìn)程和網(wǎng)絡(luò)。問題是它只做資源隔離不做內(nèi)核隔離Agent 代碼和宿主機(jī)共享同一個內(nèi)核。真要遇到內(nèi)核級漏洞理論上還是能往上沖的。但對大部分 Agent 業(yè)務(wù)場景來說這個風(fēng)險可以接受是性價比最高的起點。gVisor 則更進(jìn)一層也是我越用越喜歡的一個方案。它通過一個用戶態(tài)內(nèi)核來截獲系統(tǒng)調(diào)用很多 syscall 根本不會進(jìn)入宿主內(nèi)核執(zhí)行而是讓 gVisor 在用戶態(tài)自行模擬。這樣一來即便 Agent 代碼里有惡意負(fù)載也基本找不到可利用的內(nèi)核接口逃逸路徑被大幅壓縮。代價是部分系統(tǒng)調(diào)用兼容性有問題涉及 GPU、特殊 ioctl、某些網(wǎng)絡(luò)能力時會報“Operation not permitted”之類的錯。它在 Agent 場景里足夠?qū)嵱梦ㄒ荒阈枰龅氖翘崆霸阽R像里規(guī)避那些不兼容的調(diào)用。Kata Containers 或其它輕量虛擬機(jī)則把每個沙箱變成一臺迷你虛擬機(jī)隔離最徹底可以大膽跑不信任代碼對應(yīng)的開銷也最大啟動速度慢、內(nèi)存占用高通常用于高安全需求的場景。我把三者做成一張表方便照著選型方案隔離邊界啟動速度系統(tǒng)調(diào)用兼容性資源開銷適用場景Docker runc 容器進(jìn)程/命名空間極快秒級高低日常代碼執(zhí)行、快速原型gVisorrunsc用戶態(tài)內(nèi)核 syscall 過濾快秒級中高特殊 syscall 受限中面向 Agent 的網(wǎng)絡(luò)任務(wù)、防逃逸優(yōu)先Kata Containers硬件虛擬化較慢10~30s高較高高安全要求、企業(yè)級任務(wù)做 Agent 沙箱時我比較推薦的組合是默認(rèn)用 runc 容器跑速度快的場景高敏任務(wù)或要執(zhí)行模型生成的復(fù)雜代碼時切到 gVisor。如果 Agent 時不時要加載模型、做 GPU 推理輕量虛擬機(jī)的 GPU 透傳會讓運維頭疼此時普通容器反而更省事。2.2 生命周期設(shè)計冷啟動、?;?、回收“臨時 Runtime”的“臨時”兩個字要落到生命周期設(shè)計上才算數(shù)。我一般只做三步。第一步是冷啟動。Agent 申請 Runtime 時沙箱服務(wù)先拉鏡像再創(chuàng)建容器最后等容器 ready。這里很容易踩一個坑剛創(chuàng)建完就立刻執(zhí)行容器內(nèi)進(jìn)程還沒完全就緒導(dǎo)致一堆莫名其妙的失敗。我建議創(chuàng)建后用sleep infinity?;顖?zhí)行請求到達(dá)時才用exec進(jìn)容器跑代碼跑完按需銷毀。之前我試過“每次請求都新建、執(zhí)行完立刻銷毀”的極端模式結(jié)果 Agent 需要多輪規(guī)劃時每輪都重新拉鏡像和加載依賴?yán)鋯訒r間比代碼執(zhí)行時間還長根本無法接受。第二步是?;詈屠m(xù)租。給每個 Runtime 一個 TTL比如默認(rèn) 10 分鐘。Agent 每次執(zhí)行前做一次心跳心跳成功就把租約續(xù)上。這樣可以避免 Runtime 在 Agent 長任務(wù)空閑期間被回收。心跳最好不要放在 Agent 生成的代碼內(nèi)部而是由 Agent harness 在工具調(diào)度層統(tǒng)一控制。租約到期后服務(wù)端必須強(qiáng)殺容器防止一個“臨時”環(huán)境最后變成常駐服務(wù)。這一點很容易被忽略不做的話過一晚上你機(jī)器上全是僵尸沙箱。第三步是回收?;厥詹荒苤慌躣ocker rm -f還要把這個 Runtime 關(guān)聯(lián)的日志、掛載目錄、臨時磁盤一并清理。服務(wù)端最好給每個 Runtime 打一組 label回收時全部按 label 過濾然后統(tǒng)一處理。長期不清理你會發(fā)現(xiàn)宿主機(jī)磁盤被一層層看不見的臨時數(shù)據(jù)填滿。臨時 Runtime 的核心承諾就是銷毀后不留痕。2.3 資源限制放在 Runtime 層面做才攔得住不受控代碼在沙箱設(shè)計里資源限制不是安全加固而是對 Agent 執(zhí)行的基本約束。三個參數(shù)我強(qiáng)烈建議創(chuàng)建每個 Runtime 都要設(shè)mem_limit、nano_cpus、pids_limit。我踩過一個大坑有個 Agent 任務(wù)里調(diào)用了subprocess.Popen不斷 fork結(jié)果把宿主機(jī)進(jìn)程表刷爆。當(dāng)時沒設(shè) PID 數(shù)量上限只限制了內(nèi)存和 CPU教訓(xùn)很深刻。網(wǎng)絡(luò)策略同樣要落到 Runtime 層。默認(rèn)關(guān)網(wǎng)能規(guī)避絕大多是誤操作確實需要聯(lián)網(wǎng)時用白名單控制只允許訪問必要域名和端口。Agent 在執(zhí)行中經(jīng)常嘗試下載代碼包如果你管不住它的外網(wǎng)出口建議給它配一個內(nèi)網(wǎng)鏡像源而不是讓它直連公共網(wǎng)絡(luò)。很多團(tuán)隊把 Agent 跑起來之后就忘了這層約束等收到一紙針對異常流量告警時才反應(yīng)過來悔之晚矣。這里再強(qiáng)調(diào)一次代碼層面的防護(hù)從來不是靠給模型“洗腦”實現(xiàn)的而是靠運行時環(huán)境兜底。模型每次輸出的不確定性只能通過確定性的沙箱邊界來約束。本地開發(fā)環(huán)境、生產(chǎn)執(zhí)行環(huán)境的差距往往就是一層“臨時 Runtime”的差距。3. 為 Agent 搭一個臨時 Runtime 服務(wù)3.1 調(diào)用鏈路先畫一遍鏈路Agent 框架或者自研 harness拿到用戶需求后先把任務(wù)拆成規(guī)劃當(dāng)它決定“跑這段代碼”時不直接調(diào)本地 Python而是調(diào)沙箱服務(wù)的 API。沙箱服務(wù)根據(jù)請求里的鏡像名和資源規(guī)格到容器運行時里創(chuàng)建沙箱然后返回一個帶sandbox_id的租約Agent 再拿這個 id 提交代碼執(zhí)行沙箱服務(wù)把 stdout、stderr、退出碼統(tǒng)一抓回來返回給 Agent。這層中間層很薄但是關(guān)鍵。在 LangGraph、CrewAI 或者自研 harness 里都可以把它封裝成一個普通工具比直接調(diào)本地 shell 反而更省心。不管 Agent 框架叫 harness 還是 workflow它本質(zhì)上都是“模型循環(huán) 工具調(diào)度”真正的執(zhí)行能力在 Runtime 服務(wù)這一側(cè)。我經(jīng)常提醒團(tuán)隊的話是不要把你的 Agent 流程綁死在某個框架上把執(zhí)行能力做成獨立服務(wù)框架可以隨便換Runtime 基礎(chǔ)設(shè)施穩(wěn)定即可。3.2 一個最小可用的沙箱服務(wù)用 FastAPI 加 Docker SDK幾十行代碼就能跑通最小鏈路。下面的版本不求生產(chǎn)級目的是把“創(chuàng)建—執(zhí)行—銷毀”的骨架講清楚from fastapi import FastAPI, HTTPException from pydantic import BaseModel from docker import DockerClient import base64 client DockerClient.from_env() app FastAPI() class SandboxSpec(BaseModel): image: str python:3.12-slim mem_limit: str 512m nano_cpus: int 1_000_000_000 network_enabled: bool False class ExecRequest(BaseModel): sandbox_id: str code_b64: str timeout: int 30 app.post(/sandbox) def create_sandbox(spec: SandboxSpec): container client.containers.create( imagespec.image, command[sleep, infinity], mem_limitspec.mem_limit, nano_cpusspec.nano_cpus, network_disablednot spec.network_enabled, labels{sandbox: demo, owner: agent}, ) container.start() return {sandbox_id: container.id, status: ready} app.delete(/sandbox/{sandbox_id}) def destroy_sandbox(sandbox_id: str): try: container client.containers.get(sandbox_id) container.remove(forceTrue) except Exception as exc: raise HTTPException(status_code404, detailstr(exc)) return {status: destroyed} app.post(/sandbox/exec) def exec_in_sandbox(req: ExecRequest): try: container client.containers.get(req.sandbox_id) except Exception: raise HTTPException(status_code404, detailsandbox not found) code base64.b64decode(req.code_b64).decode(utf-8) result container.exec_run( cmd[python, -c, code], demuxTrue, ) return { exit_code: result.exit_code, stdout: (result.output[0] or b).decode(utf-8, replace), stderr: (result.output[1] or b).decode(utf-8, replace), }這個版本刻意做了三件容易被新手忽略的事。第一代碼用 Base64 傳避免 JSON 傳輸時換行、引號、特殊字符把命令搞亂。第二demuxTrue分開拿 stdout 和 stderr否則混在一起模型沒法判斷到底哪段是正常輸出。第三exec_run里故意沒傳 timeout因為 Docker SDK 的 timeout 只是等待超時不會真正殺掉進(jìn)程正式環(huán)境必須自己做超時控制超時后調(diào)container.kill()。這版代碼只適合驗證鏈路真正上生產(chǎn)還需要補(bǔ)很多邊角。3.3 鏡像即運行時從裸 Python 到帶依賴的跑法臨時 Runtime 的靈魂很多時候藏在鏡像管理里。Agent 代碼經(jīng)常要依賴各種包和系統(tǒng)工具但你不能每次執(zhí)行前手動pip install必須在創(chuàng)建 Runtime 之前把所有依賴固化進(jìn)鏡像。這個思路理解成“把環(huán)境當(dāng)成代碼”就對了。實際操作中我建議把鏡像分層維護(hù)?!盎A(chǔ)鏡像”只裝解釋器和公共庫“任務(wù)鏡像”在基礎(chǔ)鏡像之上繼續(xù)裝專用依賴按任務(wù)類型分流。示例 Dockerfile FROM python:3.12-slim RUN apt-get update apt-get install -y --no-install-recommends \ git curl \ rm -rf /var/lib/apt/lists/* RUN pip install --no-cache-dir \ numpy pandas matplotlib openpyxl \ scikit-learn WORKDIR /workspace ENV PYTHONDONTWRITEBYTECODE1 \ PYTHONUNBUFFERED1 CMD [sleep, infinity]構(gòu)造這樣的鏡像有一點必須注意不要把公共 pip、apt 源作為運行時依賴。在團(tuán)隊內(nèi)網(wǎng)配好鏡像源構(gòu)建時把依賴版本鎖死。鏡像更新要有版本概念agent-py312:v3比latest靠譜得多。鏡像一變運行時行為就變了這樣“環(huán)境”可以被 hash 查證Agent 也能順著鏡像版本排查問題。我見過因為有人把latest標(biāo)簽覆蓋導(dǎo)致線上 Agent 行為漂移的情況排查了半天癥結(jié)居然是依賴版更新。3.4 執(zhí)行狀態(tài)機(jī)服務(wù)端最好用一個狀態(tài)字段管理 Runtime 的整個生命周期。我一般用四態(tài)PENDING等待創(chuàng)建、READY保活中、EXECUTING正在執(zhí)行代碼、DESTROYED已銷毀。狀態(tài)機(jī)的價值在于Agent 的執(zhí)行請求天然是異步的如果服務(wù)端不管理狀態(tài)很容易出現(xiàn)“容器還在冷啟動執(zhí)行請求已經(jīng)進(jìn)來了”的沖突。具體邏輯是創(chuàng)建接口返回READY之前所有exec請求應(yīng)該排隊或直接拒絕進(jìn)入EXECUTING后同一 Runtime 不再接受第二個執(zhí)行請求執(zhí)行完成回到READY可以繼續(xù)復(fù)用也可以由租約到期觸發(fā)DESTROYED。這個狀態(tài)機(jī)看著簡單實際價值很大它能避免 Agent 并發(fā)操作同一個運行時導(dǎo)致不可知結(jié)果。在多 Agent 協(xié)作任務(wù)里這個約束屬于硬要求Agent A 在跑代碼Agent B 不能同時往同一個沙箱里塞代碼否則整個上下文就亂了。4. 實操用一個真實沙箱跑通 Agent 執(zhí)行鏈路4.1 準(zhǔn)備與本機(jī)安裝先說環(huán)境Ubuntu 22.04/24.04 或 Debian 系都可以目標(biāo)是 Docker gVisor 的雙運行時配置。安裝 Dockersudo apt-get update sudo apt-get install -y docker.io sudo systemctl enable --now docker sudo usermod -aG docker $USER安裝 gVisorcurl -fsSL -o /usr/local/bin/runsc \ https://storage.googleapis.com/gvisor/releases/release/latest/runsc chmod x /usr/local/bin/runsc runsc --version兩個二進(jìn)制都就位了給 Docker 注冊 runsc 運行時cat EOF | sudo tee /etc/docker/daemon.json { runtimes: { runsc: { path: /usr/local/bin/runsc, runtimeArgs: [--platformptrace] } } } EOF sudo systemctl restart docker docker info | grep -A 3 runsc看到 runsc 出現(xiàn)在 runtimes 列表里說明配置生效。選擇 ptrace 平臺主要是內(nèi)容容易上手、兼容問題相對少想要更好性能可以換成 kvm 平臺但需要額外裝內(nèi)核模塊普通開發(fā)機(jī)不推薦。重新登錄終端之后當(dāng)前用戶才有 Docker 權(quán)限這一點很容易漏。4.2 用兩個運行時做對比實驗先跑一個普通容器docker run --rm --networknone python:3.12-slim \ python -c import os; print(runc:, os.getcwd())再用 runsc 跑同一個鏡像docker run --rm --runtimerunsc --networknone python:3.12-slim \ python -c import os; print(runsc:, os.getcwd())兩條命令都會輸出對應(yīng)前綴的結(jié)果說明兩種運行時都正常。接下來可以做一個很直觀的隔離測試默認(rèn)加--networknone容器內(nèi)請求外網(wǎng)必然失敗這正是 Agent 沙箱想要的效果。再把/workspace掛載成只讀目錄就能得到一個“能執(zhí)行任務(wù)但不能亂寫文件”的基礎(chǔ)沙箱。到這一步你已經(jīng)把大部分 Agent 代碼執(zhí)行的訴求覆蓋住了。4.3 接回 Agent 側(cè)沙箱跑通回到 Agent 那一步就非常清晰。定義一個execute_code工具輸入 Base64 代碼輸出結(jié)構(gòu)化結(jié)果大致如下import base64, requests API http://127.0.0.1:8000 def execute_code(code: str) - dict: spec { image: agent-py312:v3, mem_limit: 1g, network_enabled: False, } sandbox requests.post(f{API}/sandbox, jsonspec).json() try: resp requests.post( f{API}/sandbox/exec, json{ sandbox_id: sandbox[sandbox_id], code_b64: base64.b64encode(code.encode()).decode(), timeout: 30, }, ) return resp.json() finally: requests.delete(f{API}/sandbox/{sandbox[sandbox_id]})這段代碼就是 Agent 側(cè)的調(diào)用示范跟上面 FastAPI 服務(wù)正好呼應(yīng)。實操里我建議在create前后加一層“創(chuàng)建沙箱失敗自動重試 2 到 3 次”的邏輯而不是立刻把錯誤拋給模型。模型看到一大堆底層環(huán)境錯誤反而更容易開始瞎猜最終把問題帶偏。讓 Agent 看到穩(wěn)定、規(guī)整的執(zhí)行結(jié)果是保證它后續(xù)決策不跑偏的重要前提。5. 常見問題與排查技巧實錄5.1 速查表把做臨時 Runtime 時踩過的坑整理成一張表列在最前面的都是高頻問題現(xiàn)象常見原因排查方向container runtime is not runningDocker 服務(wù)沒起來或 daemon.json 配置錯誤systemctl status dockerdocker info查看 daemon.json 中 runsc 路徑unable to locate the codex cli binary or required runtime components鏡像里缺 CLIPATH 不對或安裝步驟沒固化到底層鏡像docker exec id which codex重做鏡像把 CLI 裝到/usr/local/bincould not find the webview2 runtimeWindows 工具缺少 Edge WebView2多見于桌面自動化構(gòu)建鏡像時安裝 WebView2 Runtime或先跑 bootstrap 再啟動程序執(zhí)行時報 runtime error 713Windows 下 COM 類未注冊常見于依賴舊組件的任務(wù)在鏡像構(gòu)建階段執(zhí)行regsvr32注冊 DLL不能僅靠 pip 解決一段代碼執(zhí)行超過 30s 沒返回exec_run(timeoutn)只等待不殺進(jìn)程自己實現(xiàn)超時線程超時后調(diào)container.kill()沙箱里普通命令報 Operation not permitted部分 syscall 在 gVisor 下不支持替換鏡像里的高危命令或改用 runc 執(zhí)行低敏任務(wù)Agent 拿回來的內(nèi)容全是亂碼輸出編碼不是 UTF-8執(zhí)行命令前設(shè)置PYTHONIOENCODINGutf-8或外層再包一層 base64容器一直卡在 Removing掛載目錄存活、IO 卡住docker rm -f清理掛載目錄查內(nèi)核 IO 狀態(tài)這些坑里前四條幾乎都發(fā)生在做本地 Agent 工具的時候。我的建議是開發(fā)階段就先把“鏡像自檢”跑起來把常見依賴是否存在用斷言寫進(jìn)容器啟動腳本里。否則等 Agent 跑到一半返回一堆看不懂的 runtime 錯誤你只會更痛苦地去調(diào)試。5.2 兩個印象最深的復(fù)盤第一個印象比較深的真事是 WebView2 Runtime 缺依賴。當(dāng)時做一個調(diào)用 Windows 桌面應(yīng)用的 Agent 任務(wù)程序反復(fù)報“could not find the webview2 runtime”。一開始我以為代碼里沒做好判斷后來才發(fā)現(xiàn)是鏡像里打包的是精簡版應(yīng)用根本沒預(yù)裝公共運行時。這類問題不能靠 Agent 自己解決因為它沒有權(quán)限改鏡像只能由你在構(gòu)建鏡像時把運行時一次性裝好。這件事讓我徹底明白Runtime 的“臨時”只是使用期依賴準(zhǔn)備永遠(yuǎn)是“長期”的工程。第二個案例是任務(wù)調(diào)度線程崩了之后產(chǎn)生的“僵尸沙箱”。Agent 框架曾經(jīng)頻繁創(chuàng)建沙箱但沒執(zhí)行銷毀因為finally沒跑到資源一直被占用。后來我在服務(wù)端加了一個定期掃描的兜底任務(wù)給所有 Runtime 設(shè)了絕對 TTL到期一律強(qiáng)殺。這次教訓(xùn)讓我養(yǎng)成一個習(xí)慣再好的優(yōu)雅退出機(jī)制都一定要配一個服務(wù)端的強(qiáng)制回收兜底。做臨時 Runtime寧可回收激進(jìn)一點也不能讓臨時環(huán)境堆積成永久垃圾。6. 臨時 Runtime 的幾個安全邊界6.1 隔離是兜底不是保險箱最忌諱的心態(tài)就是以為“代碼放進(jìn)沙箱就萬事大吉”。請時刻記住Agent 生成的代碼是不可信代碼運行時隔離只是最后一道兜底不是第一道防線更不是全部防線。前面該做的提示詞約束、工具白名單、數(shù)據(jù)最小權(quán)限一樣都不能少。一個非?,F(xiàn)實的攻擊路徑是Agent 正在讀取用戶上傳的文件攻擊者把惡意內(nèi)容藏在文件里再借助提示詞注入誘導(dǎo) Agent 去執(zhí)行它。Runtime 雖然能限制破壞范圍但數(shù)據(jù)可能已經(jīng)外泄了。因此云端沙箱的默認(rèn)姿態(tài)建議是網(wǎng)絡(luò)關(guān)閉、根文件系統(tǒng)只讀、臨時目錄用 tmpfs。Agent 需要讀取特定輸入文件就用只讀掛載方式放進(jìn)去確需寫文件時限定到專用目錄任務(wù)結(jié)束全部刪除。能配置為“只讀”就只讀直到業(yè)務(wù)明確需要寫再放開寫權(quán)限。6.2 給模型返回什么也要小心翼翼執(zhí)行完之后服務(wù)端要同時處理兩件事輸出和錯誤信息要帶得規(guī)整宿主層信息不要漏給模型。Agent 執(zhí)行系統(tǒng)命令失敗時不建議直接把它完整的 traceback 返回給模型因為里面往往有服務(wù)器路徑、IP、用戶名這些敏感字段也可能包含無關(guān)噪聲。更合理的做法是先過濾掉敏感信息再拼一個簡短錯誤摘要返回。輸出超長也很常見。不要直接把截斷后的文本拼回去否則模型根本不知道中間到底缺了什么。我的做法是保留 stdout 前 1KB 和后 1KB中間用[truncated ...]標(biāo)記并顯式告知模型“內(nèi)容過長已經(jīng)被截斷”。這樣模型至少知道自己看到的信息是不完整的決策時會更謹(jǐn)慎也更容易定位問題。6.3 我一直提醒自己的經(jīng)驗真正讓臨時 Runtime 發(fā)揮價值不只是執(zhí)行代碼而是讓 Agent 把所有臨場狀態(tài)都放進(jìn)沙箱里。Agent 沒亂來時宿主機(jī)上幾乎看不到它活動的痕跡Agent 出問題時一鍵銷毀就能抹掉所有現(xiàn)場。這種“用完即走”的能力重構(gòu)了一個很底層的語義環(huán)境不再是固定資產(chǎn)而是任務(wù)上下文的一部分。多 Agent 協(xié)作時不同 Agent 可以共用一份基礎(chǔ)鏡像但各自申請獨立 Runtime互不干擾任務(wù)結(jié)束再一起銷毀。我最近復(fù)盤時發(fā)現(xiàn)凡是能在生產(chǎn)環(huán)境穩(wěn)定跑的 Agent幾乎都做了同一個動作代碼不在本機(jī)跑。把隔離運行時當(dāng)作可編排的動作而不是一個固定服務(wù)整個 Agent 工程的形態(tài)會有一個質(zhì)的改變。這也是為什么我堅持把這項能力稱為“讓 Agent 擁有一個臨時 Runtime”而不是簡單地叫“遠(yuǎn)程執(zhí)行工具”。兩件事表面上區(qū)別不大但在架構(gòu)思維上差著整整一個層級。