ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

用 Think 编排容器化编码 Agent:Cloudflare Sandbox 与 Claude Code 无令牌协作实战

用 Think 编排容器化编码 Agent:Cloudflare Sandbox 与 Claude Code 无令牌协作实战 用 Think 编排容器化编码 AgentCloudflare Sandbox 与 Claude Code 无令牌协作实战【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agentsThink 作为编排层orchestrator把每个编码任务委派给运行在独立 Cloudflare Sandbox 容器中的 Claude Code 子 Agent并把子 Agent 的叙述、工具调用与最终 git diff 实时流式回传到聊天界面——这是 Cloudflare 原生实现 AI agent harness 模式的一次完整实战。读完本文你将掌握如何在本仓库的 sandbox-coding-agent 示例 中搭建一个多 Agent 编排 每任务独立容器 零密钥泄露的编码流水线并理解其背后的无令牌认证、并行 fan-out 与三层持久化恢复机制。从编码 Agent到编排编码 Agent 的层传统的 agent harness 模式是让一个编码 Agent 直接持有工具、编辑代码。本示例把它抬高一层Think 不再亲自写代码而是作为指挥者CodingOrchestrator把每个具体编码任务委派给一个独立的 Claude Code 子 Agent。每个子 Agent 以 agent-tool 运行单元facet的形式存在运行在属于自己的Cloudflare Sandbox 容器中彼此隔离。这个示例集中演示了六个关键能力Agents as tools / 子 Agent编排器通过agentTool(ClaudeCodeAgent, …)暴露委派工具规划循环在需要时触发它框架将子 Agent 作为 facet 生成把它的聊天分块以agent-tool-event帧转发给父 Agent并通过reportProgress在界面上渲染实时进度条——全部走同一条 WebSocket。每个任务一个容器子 Agent 作为 facet 运行时this.name即 agent-tool 的运行 id形如agent-tool:toolCallId源码中的sandboxIdFor(this.name)把它哈希成 DNS 安全的容器 idgetSandbox(env.Sandbox, …)由此为每个委派任务分配独立容器并检出仓库。并行 fan-outdelegate_parallel通过runAgentTool一次派发多个任务或同一任务的多个竞争尝试各跑各的容器最后比较 diff。Think 拥有规划循环Claude Code 拥有编码循环编排器的循环运行在 Workers AI 上每个子 Agent 在容器内以 headless 方式驱动 Claude Code CLI并把其stream-json输出映射为 AI SDK 的UIMessage分块。无令牌、无密钥容器内零凭据。Sandbox 拦截 Claude Code 到api.anthropic.com的出站流量经由env.AI.gateway()绑定转发——由你的 Cloudflare 账户完成认证。唯一配置是一个明文的 gateway id。示例配套演示仓库是threepointone/aywson——一个带测试的小型 JSONC 解析器非常适合观察添加 helper 添加测试 生成 diff这类任务。整体架构三层职责划分从浏览器到容器完整链路如下本仓库架构图中的数据流Browser (useAgentChat useAgentToolEvents) │ WebSocket: orchestrator stream forwarded agent-tool-event frames ▼ CodingOrchestrator (Think, Durable Object) ── planning loop on Workers AI │ getTools(): delegate_coding_task agentTool(ClaudeCodeAgent, …) │ delegate_parallel runAgentTool fan-out ▼ ClaudeCodeAgent (AIChatAgent facet, name runId) ── one per delegated task │ getSandbox(env.Sandbox, sandboxIdFor(this.name)) ▼ Sandbox container (cloudflare/sandbox) — claude -p against the aywson checkout │ egress to api.anthropic.com is intercepted → │ env.AI.gateway(GATEWAY_ID).run({ provider: anthropic, … }) ↑ reportProgress streamed chunks final diff ──▶ orchestrator UI三层各司其职编排器负责理解用户意图并拆解任务ClaudeCodeAgent一个很薄的AIChatAgent子类负责把一个任务翻译成容器内的claude -p调用并回收结果Sandbox 容器提供隔离的执行环境与 Claude Code CLI。用户只与编排器对话编码细节完全透明。前置条件与运行前置条件本地 Dockercloudflare/sandbox在本地开发时通过 Docker 运行容器若部署到线上Containers 需要付费 Workers 套餐。Cloudflare AI Gateway需要网关能不携带每次请求的 key访问 Anthropic——即启用 Unified Billing 能力或在网关内存储 Anthropic keyBYOK。仓库中不存放任何令牌。启动pnpm install pnpm run startpackage.json中start脚本为vite devpackage.json由cloudflare/vite-plugin驱动开发服务器并加载 Worker。部署脚本为vite build wrangler deploy。启动前把 wrangler.jsonc 中的GATEWAY_ID改成你的网关名默认default。然后打开开发服务器向编排器下达委派指令例如Add aclone(json)helper that deep-copies a JSONC string while preserving comments, with a test.或者并行 fan-outDo two things in parallel: (1) add aclone()helper with a test, and (2) add JSDoc to the public exports.wrangler.jsonc 关键配置配置文件 中几个关键块值得展开ai绑定{ binding: AI, remote: true }。编排器的规划循环走 Workers AISandbox 拦截的 Anthropic 出站流量也复用同一个绑定的env.AI.gateway()。这是零凭据架构的核心——容器本身没有密钥认证发生在 Worker 侧。vars.GATEWAY_IDAI Gateway 的明文名称。注释明确说明这是明文配置而非机密真正的计费由网关承载Unified Billing 或网关内存储的 key。durable_objects.bindings顶层只注册两个 DO——CodingOrchestrator用户对话的 Think Agent与Sandbox容器 DO。ClaudeCodeAgent不需要绑定它由编排器以 facet子 Agent方式动态生成但类本身必须从 Worker 入口导出。containers为SandboxDO 绑定./Dockerfile镜像、instance_type: standard-1、max_instances: 5与编排器maxConcurrentAgentTools 3共同约束并发上限。migrationsnew_sqlite_classes: [CodingOrchestrator, Sandbox]——两个 DO 均由 SQLite 支撑这正是后面持久化与恢复分析的基础。Dockerfile 基于docker.io/cloudflare/sandbox:0.12.1注意源码注释基础镜像必须与安装的cloudflare/sandbox版本匹配0.12.2 的镜像发布失败因此固定 0.12.1并执行npm install -g anthropic-ai/claude-code把 Claude Code CLI 装进镜像供每个子 Agent 在容器内驱动一轮 turn。无令牌认证Sandbox 如何劫持 Anthropic 出口流量整个示例最精巧的部分在no token技巧Claude Code 默认要连https://api.anthropic.com。与其往容器里塞 Anthropic key或 AI Gateway token不如让 Sandbox 子类拦截这条出站流量转交 AI Gateway 绑定处理// examples/sandbox-coding-agent/src/server.ts export class Sandbox extends BaseSandboxEnv { constructor(ctx, env) { super(ctx, env); this.interceptHttps true; // Anthropic 是 HTTPS需要 HTTPS 拦截 this.enableInternet true; // 放行 github.com 的 clone } } // 通过继承自基类的 setter 注册——不能用 static 字段否则会遮蔽 accessor Sandbox.outboundByHost { api.anthropic.com: async (req, env) { const endpoint new URL(req.url).pathname.replace(/^\//, ); // v1/messages return env.AI.gateway(env.GATEWAY_ID).run({ provider: anthropic, endpoint, headers: { content-type: application/json /* anthropic-version */ }, query: await req.json() }); } };实际实现中 anthropicViaGateway 还会透传anthropic-version与anthropic-beta两个请求头从原始请求读取、按需写入保证 Claude Code 对网关的请求语义完整。由于env.AI.gateway()由 Worker 的账户完成认证容器里没有cf-aigtoken、也没有 Anthropic key——计费交给网关。但 CLI 启动时仍要求存在某个 key所以运行时会传一个一次性占位值ANTHROPIC_API_KEYcf-aig-placeholder它会在拦截边界被丢弃见下文runClaudeCode的环境变量注入。拦截出站流量要求从 Worker 入口导出 SDK 的ContainerProxyctx.exports.ContainerProxy——server.ts 中的export { ContainerProxy } from cloudflare/sandbox正是为此。核心实现剖析CodingOrchestrator规划循环与委派工具编排器类 继承ThinkEnv把自己写代码这条路彻底关死只保留两个委派工具getTools(): ToolSet { return { delegate_coding_task: agentToolDelegateInput(ClaudeCodeAgent, { description: Delegate ONE self-contained coding task to a Claude Code agent running in its own container. Streams the agents work back and returns the files it changed., displayName: Claude Code, inputSchema: z.object({ task: z.string().min(5).describe(A clear, self-contained coding task…) }) }), delegate_parallel: tool({ /* … */ execute: async ({ tasks }, { toolCallId, abortSignal }) { const outcomes await Promise.allSettled( tasks.map((task, i) this.runAgentToolDelegateInput(ClaudeCodeAgent, { input: { task }, parentToolCallId: toolCallId, displayOrder: i, display: { name: Claude Code }, signal: abortSignal }) ) ); /* 把每个 outcome 折叠为 { task, runId, result|error } */ } }) }; }几个值得注意的设计决策单任务委派用agentTool(ClaudeCodeAgent, …)它是packages/agents/src/agent-tools.ts中agentTool函数agent-tools.ts提供的声明式 API——框架负责把子 Agent 以 facet 生成、转发其聊天分块、收集其getAgentToolOutput。并行 fan-out用runAgentTool手写execute配合Promise.allSettled保证一个任务失败不会拖垮其余任务parentToolCallId让多个子运行归并到同一次工具调用的面板下displayOrder控制展示顺序abortSignal联动取消。系统提示词明确约束行为getSystemPrompt编排器不亲自编辑代码、用delegate_coding_task处理单任务、用delegate_parallel并行或竞速、保持自身消息简短、委派 Agent 出错要如实报告。beforeTurn()限制可用工具Think 自带 workspace 工具read/list/find/edit等绑定到编排器自己空的文件系统由于本编排器没有本地仓库activeTools被收窄为[delegate_coding_task, delegate_parallel]防止规划器迷失在不存在的仓库里。模型选用cf/moonshotai/kimi-k2.7-codeWorkers AI 上的代码模型。onBeforeSubAgent做钻取门禁只允许ClaudeCodeAgent且必须是当前编排器发起的运行否则 404。ClaudeCodeAgent每个任务一个隔离容器ClaudeCodeAgent 是个很薄的AIChatAgent子类核心是onChatMessageasync onChatMessage(_onFinish, options) { const sandbox getSandbox(this.env.Sandbox, sandboxIdFor(this.name), { sleepAfter: 15m }); await this.ensureWorkspace(sandbox); return runClaudeCode({ sandbox, workDir: WORK_DIR, prompt: latestUserText(this.messages), reportProgress: (p) void this.reportProgress(p), onResult: (result) { this.lastResult result; } }); }容器归属facet 名是agent-tool:toolCallId可能超过沙箱 id 的 63 字符 DNS 安全上限所以 sandboxIdFor 用双 FNV 风格哈希把它压成task-hash——同名 → 同 id → 跨 turn 复用同一个温容器。ensureWorkspace幂等检出[ -d ${WORK_DIR}/.git ] || git clone --depth 1 …浅克隆一次性检出演示仓库。Claude 会话持久化onStart从 DO 存储读回claudeSessionId每轮结束后saveSessionId写回实现跨 turn 的--resume。对编排器只回传摘要getAgentToolOutput只返回filesChangedM src/foo.ts 形式与diffLineCount而不是整个 diff——完整 diff 太大会撑爆编排器的上下文。用户看到的是流式消息内联渲染的完整 diff。另外该类暴露了一个callable()的getWorkspaceDiff()用于从外部对指定子 Agent 的容器做实时 diff 钻取/调试。runClaudeCode驱动 CLI 并把 stream-json 投影为 UIMessagerunClaudeCode 负责一轮完整的 Claude Code turnCLI 在容器内以 headless 方式运行自己的 agentic 循环-pstream-json我们 tail 它的 stdout把按行分隔的 JSON 事件映射为 AI SDKUIMessage分块使编排器 UI以及任何钻取视图能像普通聊天一样渲染它。const command [ claude -p $PROMPT, --output-format stream-json, --verbose, --include-partial-messages, --permission-mode bypassPermissions, sessionFlag // --resume id 或 --session-id uuid ].join( ); const proc await sandbox.startProcess(command, { cwd: workDir, env: { PROMPT: prompt, ANTHROPIC_API_KEY: cf-aig-placeholder, // 启动所需但会被拦截边界丢弃 IS_SANDBOX: 1 // 容器以 root 运行需要此标志 } });三个关键实现点会话接续优先--resume 已有 sessionId否则生成 UUID 用--session-id开启新会话并立即保存。Claude 拥有原生会话因此每轮是短连接 resume而非常驻交互进程。IS_SANDBOX1的来历Sandbox 容器以 root 运行Claude Code 在 root 下会拒绝--permission-mode bypassPermissions除非它知道自己处于沙箱——该环境变量就是我已沙箱化的信号。错误兜底除了把 stdout 的 JSON 事件映射成 UI 分块代码还会收集 stderr 与退出码。若映射器检测到失败result事件的is_error、非 success 的subtype或整轮没有产生任何 assistant 文本或退出码非 0就会在消息里追加一段 Claude Code error 及 stderr 尾部 12 行——避免无声失败让人误以为只是没有改动。ClaudeStreamMapper事件到 UI 分块的翻译ClaudeStreamMapper 处理四类stream-json顶层事件system/result捕获session_id并回调保存result同时标记整轮失败。stream_eventtoken 级流式事件。content_block_start按index打开text→text-start或thinking→reasoning-start块content_block_delta追加text-delta/reasoning-deltacontent_block_stop关闭块。assistant只处理完整消息中的tool_use块写tool-input-start/tool-input-available——文本已经由 delta 流式渲染避免重复。user处理tool_result写tool-output-available输出经normalizeToolResult归一化为纯文本。flush()流意外中断时把遗留未关闭的块补上 end 事件。snapshotDiff捕获工作树快照diff.ts 的snapshotDiff先执行git add -A -N-N把未跟踪文件标记为 intent-to-add保证全新文件也能出现在git diff中再并行跑git status --porcelain与git diff返回{ files, diff }。parseStatus把 porcelain 输出解析成{ status, path }列表——这正是getAgentToolOutput里filesChanged的数据来源。运行结束时runClaudeCode把 diff 追加为消息中的内联 diff 分块diff …并在onResult回调中交给 Agent 保存。前端同一 WebSocket 上的实时委派面板client.tsx 用两个 hook 协作useAgentChat来自cloudflare/think/react驱动编排器的对话流useAgentToolEvents来自agents/react监听子 Agent 转发上来的agent-tool-event帧按源toolCallId分组。每个运行被折叠成DelegateState渲染为挂在对应工具 part 下的DelegatePanel状态徽章running/done/error实时进度条由子 Agent 的reportProgress驱动展示phase、message与fraction无比例时显示脉冲动画内容面板子 Agent 的叙述文本与 reasoning 用Streamdown渲染工具调用与结果以折叠卡片展示内联 diffrunClaudeCode写入消息的text-delta中的 diff 块直接在对应委派面板内渲染。顶栏的clearDelegatedRuns通过agent.call(clearDelegatedRuns)调用编排器的callable()方法清理历史运行状态。持久化与恢复三层独立生命周期本示例有三个相互独立的持久化生命周期各自拥有驱逐/休眠时钟恢复故事的优劣正是由它们的不对齐决定的CodingOrchestrator (Think DO) chat planning loop ← SQLite-backed └─ ClaudeCodeAgent (facet DO) one per delegated task ← SQLite-backed └─ Sandbox (container DO) claude -p the checkout ← disk is EPHEMERAL两个 Durable Object 由 SQLite 支撑、恢复良好容器是薄弱环节——其文件系统在休眠后不保留。事件DO 状态SQLite飞行中的流容器磁盘客户端断连关标签页、导航离开保留缓冲于 SQLite重连后回放不受影响DO 休眠空闲持久化onStart重新水合claudeSessionId不适用在sleepAfter内保持温热DO 中途驱逐部署/重启chatRecovery恢复该轮 turn尾部丢失turn 被重新发起继续运行成为孤儿容器休眠sleepAfter空闲 15m不受影响不适用消失——下次唤醒是全新磁盘逐条解读休眠没问题两个 Agent 都使用 WebSocket Hibernation API 与 always-on 的持久化聊天恢复。onStart持久化 Claude 的会话 idlastResult存放在 facet DO 上turn 之间的状态是持久的。中途驱逐只恢复一半子 Agent 的模型调用就是runClaudeCode——一个读取sandbox.streamProcessLogs(...)的循环。若 facet DO 在中途被驱逐容器里的claude -p进程会成为孤儿继续运行该轮尾部丢失恢复逻辑重新进入onChatMessage并启动新的claude -p --resume而不是重新附着到存活进程即框架的child runtime is not live-tailable场景。因此resume 发生在 turn 之间而非 turn 中途。容器磁盘易失是真正的缺口sleepAfter空闲 15 分钟后容器停止Cloudflare Containers 从镜像冷启动一个干净文件系统。磁盘上只存两样东西一是仓库 Claude 的改动——ensureWorkspace用幂等重 clone 糊弄过去但之前 turn 未提交的改动会丢失二是 Claude 的原生会话~/.claude/…——我们只持久化会话id并传--resume但会话数据在磁盘上休眠后--resume指向的会话对全新容器来说从未见过。结论在 15 分钟温热窗口内多轮可用真实--resume改动累积跨休眠后会话静默重置回干净检出。对单发式演示足够对长期多轮编码则是一个正确性缺口。升级路径当前延迟后续再议README 明确给出两条改进方向均未在本示例中落地用 backup/restore 持久化工作区cloudflare/sandbox暴露sandbox.createBackup({ directory })→ 可序列化的DirectoryBackupR2 中的 squashfs与sandbox.restoreBackup(backup)。干净的设计是每轮结束把WORK_DIR和~/.claude一起备份把句柄存放在 DO storage 里紧挨着claudeSessionId并让ensureWorkspace变成有备份就 restore否则 clone——把容器磁盘降级为缓存、让 DO 成为事实来源。示例为保持零配置会新增一个 R2 bucket 绑定而未落地。真正的跨进程中途接续需要跨进程边界挂起/恢复一个活动中的turn——正是 AI SDK Harness 的session.suspendTurn()/detach()模型被折叠进 harness 迁移工作参见 cloudflare/agents 的 issue 1829。限制与注意事项编排器只委派beforeTurn()把activeTools限制为两个委派工具防止规划器去访问不存在的本地仓库。diff 内联渲染每个子 Agent 把git diff追加进自己流式输出的消息中呈现在对应委派面板编排器模型只阅读紧凑的getAgentToolOutput改动文件列表保持自身上下文精简。Claude Code 以 root 运行沙箱容器是 rootCLI 除非知道自己在沙箱内IS_SANDBOX1否则拒绝--permission-mode bypassPermissions。若一轮 turn 无输出运行时会在委派面板中展示 CLI 的 stderr / 退出码而不是静默显示无改动。HTTPS 出站拦截的依赖把https://api.anthropic.com路由到绑定依赖容器平台对该主机的 TLS 终结interceptHttps在本地 Docker 下有效。若在你的环境中未触发回退方案是直接在容器上设置ANTHROPIC_BASE_URL/ANTHROPIC_API_KEY指向 AI Gateway URL。并发上限由编排器的maxConcurrentAgentTools源码中为 3与 wrangler 中容器的max_instances配置中为 5共同约束。相关示例在仓库中继续深入这个方向的示例agents-as-tools本示例依赖的子 Agent /agentTool机制但不含容器ai-chatAIChatAgent 工具 审批codemode在 Worker-loader 沙箱中运行模型编写的代码。其中 Think 平台本身位于 packages/thinkagentTool/runAgentTool等编排原语位于 packages/agents/src/agent-tools.ts。【免费下载链接】agentsBuild and deploy AI Agents on Cloudflare项目地址: https://gitcode.com/GitHub_Trending/agents1/agents创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表