
【免费下载链接】sandcastleOrchestrate sandboxed coding agents in TypeScript with sandcastle.run()项目地址https://gitcode.com/gh_mirrors/sandcastl/sandcastle点击查看免费下载导读本文解析 Sandcastle 的一项关键架构决策对 Agent ProviderClaude Code、Codex、Pi、OpenCode 等上报的限流rate limit、鉴权失败auth failure、配额超限quota error、网络超时等错误Sandcastle 一律不做重试而是快速失败fail fast并立即向用户呈现可操作的错误信息。该决策记录于仓库的 .out-of-scope/provider-error-retry.md属于官方明确划定的不在范围内的设计裁定。读完本文你将理解这一决策背后的四条理由、Sandcastle 在源码层面如何把不重试落到实处非零退出码 →AgentError→ 格式化输出 →exit(1)以及 provider 错误信息如何被完整地传递给用户。决策本身不重试就是设计Sandcastle 对 provider 错误的处理有一条明确且简单的规则不重试。无论错误来自限流、鉴权失败、配额超限还是网络超时Sandcastle 都不会自动重跑一次 agent 调用。这条规则以决策记录的形式固化在仓库中Decision:Sandcastle does not retry on provider errors (rate limits, auth failures, quota errors, network timeouts, etc.).需要注意的是这个决策文档位于仓库的.out-of-scope/目录——它明确表示provider 错误重试这一能力不在 Sandcastle 的职责范围内未来也不计划内置。这本身就是对边界的一次清晰界定哪些事该由 Sandcastle 做哪些事该由上层provider / harness 层做。为什么不做重试四条理由逐条拆解原文档给出了四条相互支撑的理由每一条都指向重试是一个错误的抽象层级这个结论。理由一Sandcastle 不拥有 API 连接与错误接口Sandcastle 与 agent 的交互方式是通过 shell 调用 provider 的命令行工具例如 Claude Code 的claude -p、Codex 的codex exec、Pi 的pi -p --mode json。从 src/AgentProvider.ts 的AgentProvider接口可以看到每个 provider 的核心就是buildPrintCommand(options)返回一条要执行的 shell 命令以及parseStreamLine(line)解析其 stdout 流。Sandcastle 拿到的只是命令的退出码 stdout stderr它并不直接持有与模型 API 之间的连接也接触不到 API 原始的错误响应结构。因此限流该等多久再重试鉴权失败是不是永久性的这类判断Sandcastle 无从准确作答——它缺少判定所需的上下文。理由二解析 provider 专属错误形状 为一个不受控的接口负责要判断这个错误是否可重试就必须解析每个 provider 各自的错误格式。比如 Pi 会在 stdout 上输出agent_error/error事件Codex 会输出error事件OpenCode 同样在 stdout 输出error事件见 src/AgentProvider.ts 中parsePiStreamLine、parseCodexStreamLine、parseOpenCodeStreamLine的注释。这些格式由各家 CLI 自行定义、随时可能改变Sandcastle 无法控制这些接口的演进一旦某个 provider 调整了错误事件的结构Sandcastle 的重试判定逻辑就会静默失效——要么漏判该重试的要么误判不该重试的为每个 provider 维护一份可重试错误特征库等于把不可控的外部接口变成了自己的长期维护负担。原文档的表述非常直接Parsing provider-specific error shapes to detect retryable conditions means taking responsibility for an interface we dont control and that could change at any time.解析 provider 专属错误形态以检测可重试条件意味着为一个我们无法控制且随时可能变化的接口承担责任。理由三盲目重试会掩盖真实错误并浪费资源如果 Sandcastle 简单地任何非零退出码就重试一次会出现两类严重问题掩盖真实错误坏提示词prompt 设计错误、鉴权失败密钥过期、配置问题模型名拼错、参数不合法这些错误并不会因为重试而消失。盲目重试会让用户看到又失败了一次的重复噪音而不是一次性暴露根因浪费时间和金钱agent 调用按 token 计费一次重试就是一次新的完整调用开销。对配额类错误重试还可能加重上游压力让限流更严重。所以盲目重试任何非零退出码被明确否决非零退出码只意味着这次调用失败了不代表重试能成功。理由四快速失败给用户即时、可行动的反愤不重试的正面价值是反馈速度。失败立即返回用户能立刻看到错误并做出选择升级计划配额不足时换更高级别的模型或提高预算等待限流可能稍纵即逝用户自己决定何时再跑切换 provider当前 provider 不可用改用别的 agent。这四种行动都要求错误尽快呈现在用户面前而这恰恰是快速失败提供的。原则错误处理属于 provider/harness 层原文档最后给出了贯穿全文的原则Principle:Error handling and retry logic belong in the provider/harness layer, not in Sandcastle. Sandcastle fails fast on provider errors.即重试逻辑的归属是 provider 或 harness 层。如果用户确实需要自动重试正确的做法是在调用 Sandcastle 的外层比如自己的 CI 脚本、工作流编排器基于退出码自行实现带退避backoff的重试策略Sandcastle 自身保持单一职责——执行一次、失败就报错。原文档还记录了这条决策的出处Rejected in: #246即 #246 中曾提出过在 Sandcastle 内做 provider 错误重试的方案最终被否决。源码层面Sandcastle 如何把不重试落到实处快速失败在实现上是一条清晰的调用链非零退出码 → 构造AgentError→ 格式化错误信息 → 进程以退出码 1 结束。第一步非零退出码被转换为AgentError在 src/Orchestrator.ts 中每次迭代执行完 provider 命令后如果execResult.exitCode ! 0编排器立即构造一个AgentError并Effect.failif (execResult.exitCode ! 0) { // Prefer stderr; fall back to resultText (from parsed stream events), // then to the tail of raw stdout (last 20 non-empty lines). let errorDetail execResult.stderr; if (!errorDetail.trim()) { errorDetail resultText; } if (!errorDetail.trim()) { const lines execResult.stdout.split(\n).filter((l) l.trim()); errorDetail lines.slice(-20).join(\n); } return yield* Effect.fail( new AgentError({ message: ${provider.name} exited with code ${execResult.exitCode}:\n${errorDetail}, }), ); }注意这里没有出现任何重试重试次数退避的逻辑——失败就是失败直接向上抛。AgentError的定义在 src/errors.ts并带有一个可选的preservedWorktreePath字段失败时若保留了 worktree会把宿主机路径一并带给用户。第二步错误被格式化并直接退出进程AgentError最终会经由 src/ErrorHandler.ts 的withFriendlyErrors处理它以Effect.catchTags捕获包括AgentError在内的全部SandboxError标签调用showErrorAndExit——先用Display服务以 error 级别打印格式化后的消息然后process.exit(1)src/ErrorHandler.tsconst showErrorAndExit (error: SandboxError) Effect.gen(function* () { const d yield* Display; yield* d.status(formatErrorMessage(error), error); return yield* Effect.sync(() process.exit(1) as never); });formatErrorMessage对AgentError的输出是Agent invocation failed: ${error.message}src/ErrorHandler.ts。整个流程没有任何循环重试的路径——一条错误路径一路到底退出码 1。第三步错误信息如何完整抵达用户为了让快速失败仍然信息完整Sandcastle 在收集错误详情时有三层回退fallback优先级为stderrprovider 命令写到 stderr 的内容优先保留resultText由parseStreamLine解析出的result事件文本stdout 尾部原始 stdout 的最后 20 个非空行。这个回退链在 src/Orchestrator.ts 中实现。而第三条回退的铺垫工作早在 provider 解析层就完成了Pi、Codex、OpenCode 都会把输出在 stdout 上的鉴权失败、限流、API 错误事件转换为result事件见 src/AgentProvider.ts、src/AgentProvider.ts、src/AgentProvider.ts 的注释这样当 stderr 为空时Orchestrator 的 stderr-empty fallback 依然能把Rate limit exceeded这类真实原因呈现给用户而不是只丢一个冷冰冰的退出码。测试如何验证不重试且不丢错误仓库的测试直接印证了这套行为。在 src/Orchestrator.test.ts 中有一个典型用例agent 以非零退出码结束、stderr 为空但结构化解析器把 stdout 上的result事件解析为Rate limit exceeded, please retry later——测试断言抛出的错误是AgentError实例错误消息包含Rate limit exceeded, please retry later换言之Sandcastle 既不重试也绝不吞掉这条错误而是原样交给用户。另一个用例src/Orchestrator.test.ts则验证 stderr 非空时优先保留 stderr不回退到 stdout——避免把无关输出混入错误信息。边界澄清哪些失败同样不重试provider 错误不重试并不是唯一的快速失败路径。从 src/errors.ts 定义的错误家族看Sandcastle 的整套错误处理都是单次失败语义AgentIdleTimeoutErroragent 超过空闲超时默认 600 秒无输出即失败src/Orchestrator.tsCompletionTimeoutError家族SyncInTimeoutError、HookTimeoutError、PromptExpansionTimeoutError、MergeToHostTimeoutError等任一环节超时都直接失败基础设施类DockerError、PodmanError、WorktreeError、SyncError等同样一次失败即报错src/errors.ts。这些错误统一汇入SandboxError联合类型src/errors.ts由withFriendlyErrors统一格式化后退出。也就是说错误即终止、交由用户决策是 Sandcastle 全链路的一致哲学provider 错误只是其中最典型的场景。对 provider 开发者与集成者的启示这套决策对两类人都有直接指导意义若你在为 Sandcastle 新增 agent provider请阅读 docs/agents/adding-an-agent-provider.md。它明确要求 provider 的 stdout 流必须能够被解析出助手文本、工具调用、最终结果、错误事件和session ID。其中错误一项特别指出需要确认 CLI 的错误是输出在 stdout 还是 stderr——Codex 和 Pi 将鉴权/限流错误作为 JSON 事件输出在 stdoutSandcastle 会把这些捕获为result事件以便呈现给用户。实现时遵循该文档的Patterns to followshell 转义所有插值、优先用 stdin 传提示词、防御式 JSON 解析、CLI 在 stdout 输出错误时将其转成result事件供 Orchestrator 的 stderr-empty fallback 使用。若你在 Sandcastle 之上构建工作流请把自动重试放在你自己的编排层监听进程退出码对可重试的错误类型自行实现带指数退避exponential backoff和抖动jitter的重试并设置重试上限——这正是原文档所说错误处理和重试逻辑属于 provider/harness 层的落点。小结Sandcastle 对 provider 错误一律不重试、一律快速失败并非能力缺失而是经过论证的架构边界它通过 shell 调用外部 CLI不拥有 API 连接与错误接口因而拒绝为不可控的 provider 错误格式承担维护责任盲目重试会掩盖坏提示词、鉴权失败等真实错误并浪费 token快速失败则让用户立即得到可行动的反馈升级计划、等待、切换 provider。这条决策在实现上体现为 src/Orchestrator.ts 中非零退出码 →AgentError→ src/ErrorHandler.ts 格式化输出 →exit(1)的单一失败路径并由 src/Orchestrator.test.ts 验证了不重试、不丢错误的行为。需要重试的集成方应当在 Sandcastle 之上的 harness 层自行实现。赞分享【免费下载链接】sandcastleOrchestrate sandboxed coding agents in TypeScript with sandcastle.run()项目地址https://gitcode.com/gh_mirrors/sandcastl/sandcastle点击查看免费下载相关推荐某语言错误处理机制为什么失败要安静地发生某语言错误处理机制为什么失败要安静地发生 在编程世界中错误处理机制Error Handling Mechanism通常被视为程序稳定性的基石。但某语编程语言编译器5 分钟看懂 AlphaFold 预测结果pLDDT 四档与 PAE 三个热点的完整避坑清单5 分钟看懂 AlphaFold 预测结果pLDDT 四档与 PAE 三个热点的完整避坑清单 打开 5 个 PDB 文件和两张热图的那一刻你面前的任务其实只人工智能深度学习生物信息学科学计算科研Open Generative AI 完整指南400模型的免费开源AI图像视频生成工作室Open Generative AI 完整指南400模型的免费开源AI图像视频生成工作室 Open Generative AI 是一个免费、开源的 AI 图AI 应用媒体生成大模型上一篇NetworkNightmare安全警示合法使用渗透测试工具的终极指南下一篇Cursor插件文档编写终极指南如何创建专业README.md与API参考的最佳实践创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考