作式云端畫布如何重塑人機(jī)協(xié)作)
最近在調(diào)研多智能體協(xié)作和 AI 編程工作流時注意到一個挺有意思的項(xiàng)目Murmell。它把自己定義為 Collaborative cloud canvas for coding agents也就是“面向編碼代理的協(xié)作式云端畫布”。這個定位很有意思它不像傳統(tǒng) IDE 那樣強(qiáng)調(diào)本地編輯也不像聊天窗口那樣只有對話流而是把人和 AI 編碼代理的工作空間抽象成一塊共享畫布所有任務(wù)、變更、審查意見都在上面流動。這篇文章會從一個相對系統(tǒng)的角度來拆解這類工具它到底解決什么問題、核心能力有哪些、比較通用化的架構(gòu)和實(shí)現(xiàn)思路是什么以及如果要接入 Claude Code、Codex 這類編碼代理有哪些值得注意的集成方式和坑點(diǎn)。1. 背景與核心概念1.1 什么是 Collaborative Cloud Canvas先解決概念問題。Collaborative Cloud Canvas翻譯過來是“協(xié)作式云端畫布”。它本質(zhì)上是一個運(yùn)行在云端的、支持多端同步的共享可視化空間這個空間里的所有元素——節(jié)點(diǎn)、連線、卡片、評論、狀態(tài)標(biāo)記——都會實(shí)時同步給所有參與者。和傳統(tǒng)的“白板類”協(xié)作工具不同Murmell 這類畫布的核心參與對象不只是人還包括編碼代理Coding Agent。也就是說它打破了“只能人畫給人看”的限制讓 AI 代理也能在這個畫布上創(chuàng)建節(jié)點(diǎn)、推送進(jìn)度、請求審批甚至留下中間決策的理由。1.2 Coding Agents 是什么Coding Agents 是指能夠自主完成代碼修改、文件讀寫、命令執(zhí)行、測試運(yùn)行等任務(wù)的 AI 智能體。和普通的代碼補(bǔ)全工具不同Agent 擁有“規(guī)劃 - 執(zhí)行 - 觀察結(jié)果 - 再規(guī)劃”的循環(huán)能力。比如Claude Code在終端中以會話方式執(zhí)行編碼任務(wù)。OpenAI Codex云端沙箱里的編碼代理可以操作倉庫、運(yùn)行命令。Copilot Workspace以任務(wù)為中心生成計(jì)劃并提交 PR。這類工具的問題是人和 Agent 交互的界面很割裂。Agent 的輸出通常是日志流人看到的是一串文字Agent 做了什么、改了什么文件、為什么要這么改很難一眼看清。Murmell 想解決的正是這個問題。1.3 它解決什么問題用一個開發(fā)場景來理解假設(shè)你讓一個編碼 Agent 幫你實(shí)現(xiàn)“用戶登錄接口”。Agent 會做這些事情讀取代碼、創(chuàng)建文件、修改路由、寫測試、跑測試。如果所有動作都只出現(xiàn)在終端里作為人類開發(fā)者你很難在過程中及時判斷方向?qū)Σ粚?。而?Murmell 這類畫布中Agent 的每個關(guān)鍵動作都會變成一個可視節(jié)點(diǎn)“讀取了 src/auth/login.ts”“創(chuàng)建了 interfaces/login-payload.ts”“修改了路由配置”“測試通過等待審批”每個節(jié)點(diǎn)旁邊可以掛上代碼片段預(yù)覽、執(zhí)行日志、審批按鈕。人工開發(fā)者只需要像看一張項(xiàng)目管理圖一樣就能理解 Agent 的完整動作鏈。所以Murmell 解決的問題可以總結(jié)為三點(diǎn)讓 AI 編碼過程可視化。讓人類可以低成本地介入、審批和糾偏。讓多個 Agent 或人與 Agent 的協(xié)作有一個統(tǒng)一的共享空間。2. 核心能力拆解作為一類產(chǎn)品面向編碼代理的協(xié)作畫布通常會包含下面這些核心能力模塊。了解這些模塊有助于理解 Murmell 的設(shè)計(jì)思路也有助于后續(xù)做二次開發(fā)或者自建類似系統(tǒng)。2.1 實(shí)時畫布渲染畫布不只是“一塊白板”它需要支持多種節(jié)點(diǎn)類型、分組、連線甚至支持縮放平移Pan Zoom。節(jié)點(diǎn)通常包括任務(wù)節(jié)點(diǎn)描述一個待辦目標(biāo)。文件節(jié)點(diǎn)展示某個文件或某個代碼片段。審批節(jié)點(diǎn)人工審查后才能繼續(xù)。狀態(tài)節(jié)點(diǎn)標(biāo)記當(dāng)前 Agent 的執(zhí)行狀態(tài)。評論節(jié)點(diǎn)人類或 Agent 留下的備注。2.2 Agent 狀態(tài)可視化一個長期運(yùn)行的編碼任務(wù)可能有多個階段規(guī)劃中 - 讀取文件 - 生成代碼 - 運(yùn)行測試 - 等待評審 - 完成畫布需要將這些狀態(tài)以可視化方式呈現(xiàn)同時把關(guān)鍵動作序列記錄下來。這種“動作流”回放能力對調(diào)試 Agent 的行為、定位錯誤決策點(diǎn)非常有價值。2.3 人工介入與指令通道人類開發(fā)者需要能在畫布上直接給 Agent 下達(dá)指令暫停當(dāng)前任務(wù)。修改某個文件節(jié)點(diǎn)的內(nèi)容。駁回當(dāng)前的實(shí)現(xiàn)方案。追加一條新的約束說明。這就是“Human-in-the-loop”機(jī)制。畫布不只被動展示還要能反向控制 Agent。2.4 多會話與歷史回放同一個項(xiàng)目可能同時跑多個 Agent 會話。每個會話在畫布上表現(xiàn)為一組獨(dú)立的“圖層”或者“泳道”。同時每次操作都需要有歷史記錄支持回放和審計(jì)。2.5 權(quán)限與協(xié)作邊界當(dāng)畫布上了云端權(quán)限就變得很重要。誰可以看誰可以編輯誰能審批這些都需要通過角色來區(qū)分。比如角色查看編輯節(jié)點(diǎn)審批控制 Agent開發(fā)者????技術(shù)負(fù)責(zé)人????訪客????Agent????3. 架構(gòu)設(shè)計(jì)原理如果你想真正跑通一個類似 Murmell 的最小系統(tǒng)核心不是畫布 UI而是“如何把 Agent 的事件流同步到畫布上”。3.1 事件驅(qū)動架構(gòu)畫布上的每一個變化本質(zhì)上都是一條事件。常見的事件類型{ type: node.create, payload: { id: node_123, kind: task, title: 實(shí)現(xiàn)登錄接口, position: { x: 120, y: 200 } } }再比如 Agent 推送一條狀態(tài)變更{ type: agent.status.update, payload: { agentId: agent_001, status: running, currentAction: 正在修改 src/auth/login.ts } }這種“事件 畫布對象”的做法可以讓前端只負(fù)責(zé)渲染代理端只負(fù)責(zé)產(chǎn)生事件雙方解耦。3.2 狀態(tài)同步模型畫布狀態(tài)同步有幾種常見方案各有優(yōu)劣方案優(yōu)點(diǎn)缺點(diǎn)適用場景全量快照輪詢實(shí)現(xiàn)簡單流量大、延遲高低并發(fā)小團(tuán)隊(duì)WebSocket 增量事件實(shí)時性好需要處理亂序與重連主流實(shí)時協(xié)作場景CRDT無沖突復(fù)制數(shù)據(jù)類型離線合并且無沖突實(shí)現(xiàn)復(fù)雜、學(xué)習(xí)成本高多人同時編輯同一節(jié)點(diǎn)Operation Transform經(jīng)典協(xié)作算法需要中心服務(wù)器Google Docs 類產(chǎn)品對 Murmell 這類場景實(shí)際應(yīng)用中更推薦“WebSocket 增量事件 服務(wù)端存快照”的方案。畫布上很多節(jié)點(diǎn)是 Agent 自動產(chǎn)生的人擠人的高頻沖突場景并不多CRDT 反而會讓系統(tǒng)復(fù)雜度上升。3.3 Agent 適配層這是理解這類工具最關(guān)鍵的模塊。編碼 Agent 通常都有自己的工具調(diào)用機(jī)制比如Tool Use協(xié)議Function CallingMCPModel Context Protocol畫布系統(tǒng)需要把 Agent 的工具調(diào)用轉(zhuǎn)換成畫布事件。舉個例子如果 Agent 調(diào)用了read_file工具Agent 調(diào)用 read_file(src/auth/login.ts)適配層可以把它轉(zhuǎn)換成{ type: node.create, payload: { id: file_node_001, kind: file, title: 讀取文件 src/auth/login.ts, content: export async function login() {}, parentId: task_node_001 } }這樣Agent 執(zhí)行過程中訪問過的每一個關(guān)鍵文件都會在畫布上形成一條可視鏈路。3.4 認(rèn)證與審計(jì)因?yàn)樵贫水嫴紩休d項(xiàng)目源代碼片段和決策過程認(rèn)證和審計(jì)是必須考慮的安全邊界。推薦的安全基線使用 OIDC 或云廠商統(tǒng)一的身份提供商。Agent 接入使用獨(dú)立的 API Key最小權(quán)限范圍。所有 Agent 操作寫入不可篡改的審計(jì)日志。畫布中的代碼內(nèi)容加密存儲。對敏感倉庫禁止將完整代碼內(nèi)容同步到畫布只允許同步文件路徑或摘要。4. 最小原型實(shí)現(xiàn)方案上面聊了很多概念但空談沒有意義。這一節(jié)我提供一個“最小可運(yùn)行的云端畫布 Agent 事件源”原型實(shí)現(xiàn)思路。這里要說明一下下面的實(shí)現(xiàn)不是 Murmell 的源碼而是為了幫助你理解同類產(chǎn)品的實(shí)現(xiàn)思路用 Node.js WebSocket 寫一個能跑的最小示例。如果你只是使用 Murmell 產(chǎn)品可以跳過這一節(jié)如果你打算研究它的架構(gòu)或者自建類似工具這部分就非常關(guān)鍵。4.1 創(chuàng)建項(xiàng)目結(jié)構(gòu)murmell-demo/ ├── package.json ├── server.js ├── agent-worker.js └── public/ └── index.html4.2 初始化項(xiàng)目mkdir murmell-demo cd murmell-demo npm init -y npm install express ws uuid4.3 WebSocket 服務(wù)端實(shí)現(xiàn)服務(wù)端負(fù)責(zé)兩件事維護(hù)畫布狀態(tài)把 Agent 產(chǎn)生的事件廣播給所有前端畫布。// 文件server.js const express require(express); const http require(http); const { WebSocketServer } require(ws); const { v4: uuidv4 } require(uuid); const app express(); const server http.createServer(app); const wss new WebSocketServer({ server }); app.use(express.static(public)); // 保存畫布中的節(jié)點(diǎn)對象 const canvasState new Map(); function broadcast(message) { const data JSON.stringify(message); wss.clients.forEach(client { if (client.readyState client.OPEN) { client.send(data); } }); } wss.on(connection, (ws) { console.log(畫布客戶端已連接); ws.send(JSON.stringify({ type: canvas.snapshot, payload: Array.from(canvasState.values()), })); ws.on(message, (raw) { try { const msg JSON.parse(raw.toString()); if (msg.type node.create) { const node { id: msg.payload.id || uuidv4(), kind: msg.payload.kind || task, title: msg.payload.title || , content: msg.payload.content || , position: msg.payload.position || { x: 0, y: 0 }, createdAt: Date.now(), }; canvasState.set(node.id, node); broadcast({ type: node.create, payload: node }); } } catch (err) { console.error(消息解析失敗, err.message); } }); }); server.listen(3000, () { console.log(murmell-demo 運(yùn)行在 http://localhost:3000); });4.4 模擬編碼代理的推送腳本Agent 并不需要知道 WebSocket 細(xì)節(jié)它只需要向本地端口發(fā)送事件。我們可以用一個腳本模擬一個 Agent 先后讀取文件、生成代碼、修改狀態(tài)的動作。// 文件agent-worker.js const WebSocket require(ws); const ws new WebSocket(ws://localhost:3000); const sleep (ms) new Promise(resolve setTimeout(resolve, ms)); ws.on(open, async () { console.log(Agent 已連接到畫布服務(wù)); await sleep(500); ws.send(JSON.stringify({ type: node.create, payload: { kind: task, title: 實(shí)現(xiàn)用戶登錄接口, content: 目標(biāo)新增 POST /api/login 接口, position: { x: 100, y: 100 }, }, })); await sleep(1000); ws.send(JSON.stringify({ type: node.create, payload: { kind: file, title: 讀取 src/auth/login.ts, content: export async function login() { /* auth 邏輯 */ }, position: { x: 300, y: 200 }, }, })); await sleep(1000); ws.send(JSON.stringify({ type: node.create, payload: { kind: status, title: 測試通過等待人工審批, content: npm test 全部通過共 12 個用例, position: { x: 500, y: 300 }, }, })); console.log(Agent 事件推送完成); });4.5 前端畫布頁面前端渲染層不需要多復(fù)雜核心是監(jiān)聽 WebSocket 事件把它們渲染成畫布上的卡片。!-- 文件public/index.html -- !DOCTYPE html html langzh-CN head meta charsetUTF-8 / meta nameviewport contentwidthdevice-width, initial-scale1.0 / titleMurmell Demo Canvas/title style body { margin: 0; background: #1e1f22; color: #d4d4d4; font-family: sans-serif; } #canvas { position: relative; width: 100vw; height: 100vh; overflow: hidden; } .node { position: absolute; min-width: 180px; max-width: 260px; background: #2d2d30; border: 1px solid #4c4c4e; border-radius: 8px; padding: 12px; font-size: 13px; box-shadow: 0 4px 12px rgba(0,0,0,0.3); } .node h3 { margin: 0 0 6px 0; color: #8dc5e3; font-size: 14px; } .node pre { white-space: pre-wrap; color: #a8a8a8; margin: 4px 0 0 0; } .tag { display: inline-block; margin-top: 8px; padding: 2px 8px; border-radius: 10px; font-size: 11px; color: #fff; } .tag.task { background: #7a5b1f; } .tag.file { background: #4d6b5a; } .tag.status { background: #34567a; } /style /head body div idcanvas/div script const canvas document.getElementById(canvas); function addNode(node) { const div document.createElement(div); div.className node; div.style.left node.position.x px; div.style.top node.position.y px; div.innerHTML h3${node.title}/h3 pre${node.content || }/pre span classtag ${node.kind}${node.kind}/span ; canvas.appendChild(div); } const ws new WebSocket(ws://localhost:3000); ws.onmessage (event) { const msg JSON.parse(event.data); if (msg.type canvas.snapshot) { msg.payload.forEach(addNode); } if (msg.type node.create) { addNode(msg.payload); } }; /script /body /html4.6 運(yùn)行與驗(yàn)證開啟兩個終端# 終端 1 node server.js # 終端 2 node agent-worker.js打開瀏覽器訪問http://localhost:3000你會在畫布上看到 Agent 依次創(chuàng)建的三個節(jié)點(diǎn)。如果同時打開多個瀏覽器窗口它們會保持實(shí)時同步。這個原型雖然簡單但它已經(jīng)具備了 Murmell 這類工具的核心鏈路Agent 產(chǎn)生事件 - WebSocket 同步 - 畫布實(shí)時渲染 - 多端可見。5. 與真實(shí) Coding Agents 的集成思路真實(shí)項(xiàng)目里你不會只用一個模擬腳本來推送事件。Murmell 的真正價值是接入真實(shí)的編碼代理。這一節(jié)討論常見的集成模式。5.1 工具調(diào)用攔截模式大多數(shù)編碼 Agent 都支持工具調(diào)用Tool Use。你可以在 Agent 的工具執(zhí)行層加一層“事件旁路”也就是在調(diào)用read_file、write_file、run_command等工具時額外向 Murmell 畫布推送一條事件。偽代碼思路def on_tool_call(tool_name, payload): if tool_name write_file: canvas.push_event({ type: node.create, kind: file, title: f寫入文件 {payload[path]}, content: payload[content][:200], # 只取摘要避免畫布過載 }) elif tool_name run_command: canvas.push_event({ type: node.create, kind: status, title: f執(zhí)行命令 {payload[command]}, })這種模式的好處是侵入性低不用改 Agent 的核心邏輯。5.2 審批攔截模式對于需要人工確認(rèn)的高風(fēng)險操作比如刪除文件、修改生產(chǎn)配置、批量遷移數(shù)據(jù)可以通過畫布實(shí)現(xiàn)“攔截式審批”。實(shí)現(xiàn)思路Agent 在準(zhǔn)備執(zhí)行高風(fēng)險操作前先向畫布發(fā)起一個審批請求節(jié)點(diǎn)。畫布顯示“等待審批”狀態(tài)。人類開發(fā)者點(diǎn)擊“通過”或“駁回”。結(jié)果回傳給 AgentAgent 再繼續(xù)執(zhí)行。這種模式在真實(shí)場景中很關(guān)鍵。比如在自動化代碼遷移或者大規(guī)模重構(gòu)時人工審批點(diǎn)可以顯著降低誤操作風(fēng)險。5.3 倉庫級會話關(guān)聯(lián)模式每個畫布會話可以關(guān)聯(lián)一個 Git 倉庫、一個分支甚至一個具體的 PR。這樣 Agent 產(chǎn)生的每個節(jié)點(diǎn)都可以關(guān)聯(lián)到具體的代碼提交。在這類產(chǎn)品中比較實(shí)用的設(shè)計(jì)是讓每個畫布節(jié)點(diǎn)攜帶 Git 元信息{ id: node_456, kind: file, title: 修改 login.ts, repo: code-platform/user-service, branch: feat/login-api, commit: a3f4d5e6f7a8b9c0 }這樣方便回溯這個畫布節(jié)點(diǎn)是在哪一次提交、由哪個 Agent 產(chǎn)生的是否經(jīng)過人工批準(zhǔn)。6. 常見問題與排查思路這類云端畫布工具在使用和自建過程中經(jīng)常會遇到下面這些問題。這里整理成表格方便查閱。問題現(xiàn)象常見原因解決思路畫布一片空白Agent 端沒有連接到服務(wù)檢查 WebSocket 連接地址和 Agent 日志節(jié)點(diǎn)出現(xiàn)重復(fù)Agent 重發(fā)同一事件事件寫入采用冪等方式按事件 ID 去重畫布更新有明顯延遲WebSocket 無法連接前端輪詢兜底檢查網(wǎng)絡(luò)代理、防火墻確認(rèn) WebSocket 路徑Agent 推送大量節(jié)點(diǎn)畫布卡頓單次同步節(jié)點(diǎn)過多做節(jié)點(diǎn)壓縮、分頁加載只渲染可視區(qū)域兩個 Agent 同時編輯同一節(jié)點(diǎn)內(nèi)容互相覆蓋客戶端無沖突處理策略增加節(jié)點(diǎn)級鎖或改用 CRDT 算法前端顯示狀態(tài)和實(shí)際 Agent 狀態(tài)不一致狀態(tài)事件丟失或亂序服務(wù)端維護(hù)有序事件流前端按 sequence 排序打開多個瀏覽器標(biāo)簽頁部分頁面不同步重連后未重新拉取全量快照連接建立后先推送 canvas.snapshot6.1 事件丟失問題在 WebSocket 連接不穩(wěn)定的場景下事件可能丟失。緩解方式服務(wù)端為每條事件分配自增序號??蛻舳擞涗涀詈笠粭l序號重連后從斷點(diǎn)處補(bǔ)齊。對敏感操作審批、修改文件使用“事件 確認(rèn)”機(jī)制。6.2 節(jié)點(diǎn)內(nèi)容過大Agent 讀取一個大型文件時如果直接把全文推到畫布會導(dǎo)致網(wǎng)絡(luò)開銷大、渲染卡頓。比較穩(wěn)妥的辦法節(jié)點(diǎn)只保存文件路徑。開發(fā)者在畫布中點(diǎn)擊節(jié)點(diǎn)時再按需拉取文件內(nèi)容?;蛘咧煌扑臀募_頭 N 字節(jié) 摘要。6.3 權(quán)限邊界模糊如果畫布已經(jīng)接入了真實(shí)代碼倉庫權(quán)限失控會直接導(dǎo)致源碼泄露。務(wù)必做到訪問畫布必須先通過統(tǒng)一身份認(rèn)證。Agent 推送的節(jié)點(diǎn)遵循“最小內(nèi)容”原則。內(nèi)網(wǎng)部署時通過防火墻限制畫布服務(wù)端口。所有畫布請求記錄操作者 ID 與 IP。7. 最佳實(shí)踐與工程建議7.1 事件模型先于 UI 設(shè)計(jì)在搭建 Murmell 這類工具時一定要先定義清楚事件模型再寫 UI。建議用 JSON Schema 約束每一種事件類型。事件類型越規(guī)范后續(xù)接入不同 Coding Agent 時成本越低。7.2 冪等寫入是基本要求Agent 網(wǎng)絡(luò)重試、斷線重連很容易導(dǎo)致同一事件被發(fā)送兩次。服務(wù)端在寫入畫布節(jié)點(diǎn)時必須做冪等。常見做法是使用eventId作為唯一鍵重復(fù)寫入時直接忽略。7.3 畫布與代碼倉庫分離存儲不要把畫布節(jié)點(diǎn)直接存進(jìn)代碼倉庫。畫布有自己獨(dú)立的數(shù)據(jù)庫代碼倉庫只存儲源碼。這樣可以避免畫布操作污染 Git 歷史也能避免非開發(fā)人員誤操作倉庫。7.4 審計(jì)日志必須獨(dú)立所有 Agent 產(chǎn)生的畫布操作都應(yīng)該有獨(dú)立審計(jì)日志。尤其在高權(quán)限操作場景比如“允許 Agent 推送代碼到 release 分支”必須能回答四個問題誰哪個 Agent在什么時候、通過哪個會話、做了什么事情。7.5 灰度發(fā)布與安全回滾如果你在自建一個面向團(tuán)隊(duì)的工具建議采用“會話級別灰度”方式先讓一個項(xiàng)目組試用再逐步推廣。每個畫布版本都要支持快照回滾避免一次錯誤的狀態(tài)變更影響了多個正在運(yùn)行的 Agent 任務(wù)。7.6 對接 Agent 時的協(xié)議選擇如果 Agent 已經(jīng)支持 MCPModel Context Protocol優(yōu)先通過 MCP 接入畫布。這樣可以避免直接修改 Agent 源碼用一套統(tǒng)一的工具協(xié)議把 Agent 能力“暴露”給畫布。目前主流的編碼 Agent 都在往 MCP 方向演進(jìn)。Murmell 這類 canvas 工具如果做成 MCP Server就可以低成本接入大量支持 MCP 的 Agent這個方向值得重點(diǎn)觀察。8. 對使用者的建議8.1 關(guān)注畫布的“人工審批點(diǎn)”在使用 Murmell 時建議把畫布審批點(diǎn)當(dāng)作任務(wù)推進(jìn)的閥門。不要讓 Agent 連續(xù)執(zhí)行超過 5 個步驟才創(chuàng)建一次審批點(diǎn)。步驟越短、審批越頻繁越容易在早期發(fā)現(xiàn)方向性錯誤。8.2 注意畫布中代碼片段的敏感程度畫布可能會保存 Agent 讀取過的代碼片段。務(wù)必確認(rèn)你的代碼倉庫里沒有把明文密碼、密鑰、Token 寫入源碼。如果有接這種畫布工具會加劇敏感信息擴(kuò)散風(fēng)險。建議先在團(tuán)隊(duì)里執(zhí)行一輪密鑰掃描。8.3 小團(tuán)隊(duì)先用輕量模式如果團(tuán)隊(duì)只有 3 到 5 人不太需要一上來就部署一整套權(quán)限系統(tǒng)和審計(jì)平臺??梢韵茸尞嫴脊ぷ髟凇爸蛔x 審批”模式Agent 產(chǎn)生的節(jié)點(diǎn)不能被普通成員編輯所有寫操作統(tǒng)一由負(fù)責(zé)人完成。這個模式對系統(tǒng)性能的要求低也更容易被團(tuán)隊(duì)接受。編碼代理和人類開發(fā)者的協(xié)作界面正在從“純終端日志”走向“可視化畫布”。Murmell 這個名字雖然還比較新但它代表的“Collaborative Cloud Canvas”方向很可能就是未來 AI 編程工具鏈條上重要的一環(huán)。如果你正在做 Agent 相關(guān)工作花點(diǎn)時間研究這類畫布工具的設(shè)計(jì)思路一定會對如何構(gòu)建更可控的 AI 編碼流程有新的啟發(fā)。