【免费下载链接】sandcastle
Orchestrate sandboxed coding agents in TypeScript with sandcastle.run()
导读
implement-prompt.md是 Sandcastle 项目中“实现型工作流”(implement agent workflow)的提示词模板,它把一个 GitHub Issue 变成一次可交付的编码任务:拉取 Issue 上下文、在指定分支上通过红-绿-重构循环编写代码、运行类型检查与测试、最后以规范格式提交。本文基于该文档,结合 .sandcastle/agent-workflows/implement/implement.ts 等仓库源码,讲解该提示词的结构、占位符机制、内置 shell 块、执行约束与配套工作流,让你能直接复现并定制属于自己的“Issue 自动实现”Agent。
一、文档定位:Sandcastle 的“实现”环节
在 Sandcastle 的编排体系中,一条完整的自动化开发流水线通常包含四个阶段(可参见 .sandcastle/run.ts 中的三阶段编排):
- Plan(计划):读取开放 Issue,分析依赖关系,产出可并行执行的任务清单(.sandcastle/plan-prompt.md);
- Implement(实现):针对每个 Issue,在独立分支上写代码、跑测试、提交(本文主角 .sandcastle/implement-prompt.md);
- Review(评审):对实现结果做代码审查并直接优化(.sandcastle/review-prompt.md);
- Merge(合并):把各分支合并回主干并关闭 Issue(.sandcastle/merge-prompt.md)。
implement-prompt.md是整个流水线中最核心的“执行单元”,它把“如何完成一个 Issue”这件事完整地委托给 Agent,并用严格的规则约束其行为边界。
二、提示词结构逐段拆解
原文档由六个相互衔接的部分组成:TASK、CONTEXT、EXPLORATION、EXECUTION、FEEDBACK LOOPS、COMMIT,最后以THE ISSUE与FINAL RULES收尾。下面逐一说明每段的职责与可替换变量。
1. TASK:明确任务入口
# TASK Fix issue #{{ISSUE_NUMBER}}: {{ISSUE_TITLE}} Pull in the issue using `gh issue view`, with comments. If it has a parent PRD, pull that in too. Only work on the issue specified. Work on branch {{BRANCH}}. Make commits, run tests, and close the issue when done.这一段的要点:
- 任务原子性:
Only work on the issue specified明确限定 Agent 只能处理指定 Issue,禁止发散到其他任务——这是FINAL RULES中 “ONLY WORK ON A SINGLE TASK” 的呼应。 - 上下文获取:通过 GitHub CLI 的
gh issue view(带--comments)拉取 Issue 详情;如果该 Issue 关联了父级 PRD(产品需求文档),也要一并读取,保证实现者理解需求全貌。
注意:该模板中写着 “close the issue when done”,但文档后半部分
THE ISSUE段明确要求 “Do not close the issue - this will be done later”。这是模板演进留下的一个矛盾点——在配套的 .sandcastle/agent-workflows/implement/prompt.md 中,这一规则被统一为“不 push、不关 Issue、不编辑标签、不创建 PR”。实践时应以后者为准,把“关闭 Issue”的职责留给合并阶段。
2. CONTEXT:注入最近提交历史
# CONTEXT Here are the last 10 commits: <recent-commits> !`git log -n 10 --format="%H%n%ad%n%B---" --date=short` </recent-commits>这里展示了 Sandcastle 提示词的两大动态机制:
!反引号 shell 块:以!开头包裹在反引号中的内容会被 Sandcastle 在提示词预处理阶段原地执行,并把命令输出嵌入提示词(见 src/PromptPreprocessor.ts)。这里的git log -n 10会把最近 10 条提交的哈希、日期、正文注入<recent-commits>标签,让 Agent 在动手前了解仓库最近的演变。{{KEY}}占位符:{{ISSUE_NUMBER}}、{{ISSUE_TITLE}}、{{BRANCH}}会在运行时被promptArgs中的实际值替换(机制详见 src/PromptArgumentSubstitution.ts)。
git log的参数解读:
--format="%H%n%ad%n%B---":%H为完整提交哈希,%n为换行,%ad为按--date=short格式化的日期(YYYY-MM-DD),%B为提交正文;---作为提交之间的分隔符;-n 10只取最近 10 条,控制注入上下文的体量。
3. EXPLORATION:先探索再动手
# EXPLORATION Explore the repo and fill your context window with relevant information that will allow you to complete the task. Pay extra attention to test files that touch the relevant parts of the code.这段没有硬性命令,但它设定了关键的行为准则:实现前必须探索仓库、填充上下文窗口,并特别强调“优先阅读与改动点相关的测试文件”。这正是仓库 .sandcastle/CODING_STANDARDS.md 中“通过公共接口验证行为,而非实现细节”测试理念的前置——Agent 只有先看懂现有测试,才能写出符合项目风格的测试。
4. EXECUTION:红-绿-重构循环
# EXECUTION If applicable, use RGR to complete the task. 1. RED: write one test 2. GREEN: write the implementation to pass that test 3. REPEAT until done 4. REFACTOR the code这里定义了 TDD(测试驱动开发)的完整循环,RGR 即 RED-GREEN-REFACTOR:
- RED:先写一个会失败的测试;
- GREEN:写最小实现让它通过;
- REPEAT:重复直到任务完成;
- REFACTOR:最后整理代码。
与 .sandcastle/CODING_STANDARDS.md 的 “TDD Workflow: Vertical Slices” 完全一致:“Do NOT write all tests first, then all implementation”,而应“一个测试、一个实现、循环往复”,每个测试都回应上一轮循环中学到的东西,且“Never refactor while RED”。
在配套的 .sandcastle/agent-workflows/implement/prompt.md 中还有一个重要补充:不要擅自发明新的测试接缝(比如为了单独测试而抽取函数),这会“创造意大利面条式测试”;只有在已存在测试接缝时才做红-绿-重构。
5. FEEDBACK LOOPS:提交前的质量闸门
# FEEDBACK LOOPS Before committing, run `npm run typecheck` and `npm run test` to ensure the tests pass.这是硬性质量门禁:每次提交前必须运行npm run typecheck和npm run test。本仓库的 package.json 中定义了两条对应脚本,前者做全量类型检查,后者运行完整的 vitest 测试套件(见 vitest.config.ts)。任何实现只有在通过这两道闸门后才有资格进入提交环节。
6. COMMIT:规范化的提交消息
# COMMIT Make a git commit. The commit message must: 1. Start with `RALPH:` prefix 2. Include task completed + PRD reference 3. Key decisions made 4. Files changed 5. Blockers or notes for next iteration Keep it concise.提交消息有严格的五要素结构:
- 以
RALPH:前缀开头(这是本流水线的统一提交标识,评审阶段的 .sandcastle/review-prompt.md 也要求提交以RALPH: Review -开头); - 包含“任务已完成”的说明与 PRD 引用;
- 记录关键决策(为什么这样实现);
- 列出变更文件;
- 记录阻塞项或留给下一轮迭代的备注。
同时要求“Keep it concise”——结构完整但文字精炼。要注意的是,配套的 implement prompt 采用的是 conventional commits(约定式提交)风格,二者可根据流水线版本选择,核心是让提交消息可被下游评审与合并环节读取。
7. THE ISSUE 与 FINAL RULES:收尾规则
# THE ISSUE If the task is not complete, leave a comment on the GitHub issue with what was done. Do not close the issue - this will be done later. Once complete, output <promise>COMPLETE</promise>. # FINAL RULES ONLY WORK ON A SINGLE TASK.收尾部分定义了三个关键行为:
- 未完成时:在 GitHub Issue 上留言说明已做的工作,而不是悄悄结束;
- 不关闭 Issue:关闭动作由后续的 Merge 环节统一执行(见 .sandcastle/merge-prompt.md 中 “For each branch that was merged, close its issue”);
- 完成标志:Agent 必须输出
<promise>COMPLETE</promise>结构化标记,供编排代码(.sandcastle/agent-workflows/shared/run-with-extraction.ts)解析提取。
ONLY WORK ON A SINGLE TASK是最终红线:单个 Agent 会话只处理单个任务,保证每次运行结果可追踪、可评审。
三、提示词如何被真实调用:实现层源码解析
implement-prompt.md不是孤立文档,它在两处被真实调用,下面分别解析。
1. 单 Issue 直连实现:implement.ts
.sandcastle/agent-workflows/implement/implement.ts 是“一个 Issue 一个 Agent”的最简调用方式:
const ISSUE_NUMBER = required("ISSUE_NUMBER"); const ISSUE_TITLE = required("ISSUE_TITLE"); const BRANCH = required("BRANCH"); try { const issueContext = safeSh(`gh issue view ${ISSUE_NUMBER} --comments`) || `Issue #${ISSUE_NUMBER}: ${ISSUE_TITLE}`; const result = await sandcastle.run({ name: `implement-#${ISSUE_NUMBER}`, agent: claudeAgent(), sandbox: noSandbox(), logging: { type: "stdout" }, promptFile: path.join(import.meta.dirname, "prompt.md"), promptArgs: { ISSUE_NUMBER, ISSUE_TITLE, BRANCH, ISSUE_CONTEXT: issueContext, }, }); // ...这段代码展示了模板中所有{{KEY}}的来源:
| 占位符 | 来源 |
|---|---|
{{ISSUE_NUMBER}} | 环境变量ISSUE_NUMBER(经required()校验,缺失即退出) |
{{ISSUE_TITLE}} | 环境变量ISSUE_TITLE |
{{BRANCH}} | 环境变量BRANCH |
{{ISSUE_CONTEXT}} | 运行时通过gh issue view <NUMBER> --comments拉取 |
值得注意的几个工程细节:
required()校验:定义在 .sandcastle/agent-workflows/shared/common.ts,环境变量缺失会打印错误并process.exit(1),保证编排不会带着残缺参数运行;safeSh降级:gh issue view失败(如未登录、网络问题)时返回空字符串,并用Issue #N: TITLE兜底,Agent 依然可以基于标题开工;noSandbox():本次实现运行在宿主机直接执行(见 src/sandboxes/no-sandbox.ts),适合在本地仓库上直接跑;需要隔离环境时可换成docker()(见 src/sandboxes/docker.ts);- 提交数量校验:
git rev-list --count main..HEAD统计当前分支领先main的提交数,如果为 0 则调用fail()报错——“Agent 结束了但没产生任何提交”,这是对“假完成”的第一道防线。
2. 多 Issue 并行编排:run.ts
.sandcastle/run.ts 展示了完全不同的调用形态——Plan/Implement/Review/Merge 四阶段循环,实现阶段运行在独立的 git worktree 沙箱中:
await using sandbox = await sandcastle.createSandbox({ sandbox: docker(), branch: issue.branch, copyToWorktree: ["node_modules"], hooks: { sandbox: { onSandboxReady: [{ command: "npm install && npm run build" }], }, }, }); const result = await sandbox.run({ name: "Implementer #" + issue.number, agent: sandcastle.claudeCode("claude-opus-4-8"), promptFile: "./.sandcastle/implement-prompt.md", promptArgs: { TASK_ID: String(issue.number), ISSUE_TITLE: issue.title, BRANCH: issue.branch, }, });关键点:
createSandbox为每个 Issue 创建独立 worktree(分支即issue.branch),copyToWorktree: ["node_modules"]预拷贝依赖避免重复安装,onSandboxReady钩子在沙箱就绪后执行npm install && npm run build;- 使用
sandcastle.claudeCode("claude-opus-4-8")指定 Claude Code Agent(对比 implement.ts 中通过claudeAgent()并注入CLAUDE_CODE_OAUTH_TOKEN的另一种认证方式,见 .sandcastle/agent-workflows/shared/common.ts); - 这里的
promptArgs使用TASK_ID而非ISSUE_NUMBER,说明模板中的占位符键名可以按编排方需求替换——模板用{{ISSUE_NUMBER}}还是{{TASK_ID}},只要与promptArgs的键一一对应即可; - 实现完成后,如果
result.commits.length > 0,立即在同一沙箱中运行评审 Agent(Reviewer #N,使用 .sandcastle/review-prompt.md),实现与评审共享同一个 worktree 状态。
四、占位符与 shell 块机制的底层原理
模板中同时使用了{{KEY}}与!反引号两种动态语法,二者由 Sandcastle 核心库在把提示词交给 Agent 前处理,其实现位于 src/PromptArgumentSubstitution.ts。
占位符替换({{KEY}})
- 类型:
PromptArgs = Record<string, string | number | boolean>,只接受字符串、数字、布尔三种值; - 替换规则:
{{KEY}}用promptArgs[KEY].toString()替换; - 严格校验(fail-fast 设计,参见 ADR docs/adr/0020-prompt-expansion-fails-fast.md):
- 模板中引用了占位符但
promptArgs没有对应键 → 抛出PromptError:“Prompt argument "{{KEY}}" has no matching value in promptArgs”; - 键存在但值为
null/undefined→ 同样报错; promptArgs提供了多余键(模板未引用)→ 打印警告,除非该键在silentKeys中;- 内置键
SOURCE_BRANCH、TARGET_BRANCH(见BUILT_IN_PROMPT_ARG_KEYS)不允许被promptArgs覆盖; - 内联提示词(
prompt: "...")不允许携带promptArgs,因为内联内容原样透传,占位符不会生效——必须改用promptFile才能使用{{KEY}}替换。
- 模板中引用了占位符但
正是这套严格的 fail-fast 校验,保证了implement-prompt.md中任何一个{{KEY}}缺失时,运行会立即失败并给出明确报错,而不是把带着空占位符的提示词发给 Agent 造成“幻觉式”的误执行。
Shell 块执行(!反引号)
!反引号中的命令(如git log -n 10 ...)在提示词预处理阶段于宿主机上执行,输出直接嵌入<recent-commits>之类的标签中。从 src/PromptArgumentSubstitution.ts 的源码看,这类 shell 块在原始模板中被标记,且在参数替换时会被消毒(剥离伪造标记),防止通过promptArgs注入任意命令——这是提示词注入防护的重要一环。
五、如何在仓库中扩展与定制实现工作流
如果你要在自己的 Sandcastle 流水线中复用或改造这个实现提示词,仓库提供了完整的可参考形态:
- 最简单任务:参考 .sandcastle/agent-workflows/implement/implement.ts,直接以
noSandbox()在本机运行,环境变量传参; - 并行多任务:参考 .sandcastle/run.ts,先由 .sandcastle/plan-prompt.md 产出
<plan>JSON,再用docker()沙箱 + worktree 并行执行,MAX_PARALLEL = 4控制并发上限; - 带评审的实现:参考 .sandcastle/agent-workflows/implement-pr/implement-pr.ts,用
sandcastle.Output.object()声明结构化输出(配合 .sandcastle/agent-workflows/shared/run-with-extraction.ts),把 Agent 产出的评论、回复等结果提取为 JSON 落盘,并校验“有提交或有评论”才算有效完成。
定制时注意保持提示词的内在一致性:占位符键名与promptArgs对齐、FINAL RULES中的单一任务约束、提交消息结构、<promise>COMPLETE</promise>结束标记——这些是编排代码能够可靠解析和判断完成状态的契约。若提示词与编排逻辑(如“是否关闭 Issue”)存在冲突,应像仓库中 implement-prompt 的两个版本那样,以运行时的实际规则为准并保持版本同步。
六、小结
implement-prompt.md的价值在于它把“实现一个 Issue”这件复杂工作压缩成了一段可编排、可验证、可复用的提示词契约:
- 结构上:任务 → 上下文 → 探索 → 执行 → 反馈 → 提交 → 收尾,六段闭环;
- 机制上:
{{KEY}}占位符负责注入参数,!shell 块负责注入动态仓库信息,两者都在运行前由核心库完成严格的替换与校验; - 行为上:单一任务、红-绿-重构、提交前必跑 typecheck 与 test、规范化提交消息、完成时输出
<promise>COMPLETE</promise>。
配合 .sandcastle/agent-workflows/implement/implement.ts 与 .sandcastle/run.ts 两种调用形态,你可以从“单 Issue 自动实现”平滑扩展到“多 Issue 并行实现 + 评审 + 合并”的完整自动化流水线——这正是 Sandcastle 作为沙箱化编码 Agent 编排器的核心使用场景。
【免费下载链接】sandcastle
Orchestrate sandboxed coding agents in TypeScript with sandcastle.run()
相关推荐
深入解析 uv 的 Issue 上下文增量更新机制:update-issue-context 自动化提示词设计与工作流实现
深入解析 uv 的 Issue 上下文增量更新机制:update issue context 自动化提示词设计与工作流实现 uv 仓库中的 update iss
包管理器开发工具CLIOpenChamber Issue-Intake Agent 提示词设计:用 OpenCode 单代理替代双 Bot 的 Issue 分流工作流
OpenChamber Issue Intake Agent 提示词设计:用 OpenCode 单代理替代双 Bot 的 Issue 分流工作流 本文深入剖析
AI Agent人工智能代码智能体交互助手Ekko Agent 内置 gh-issues 技能实战:从 GitHub Issue 分流到 Pull Request 的自动化工作流
Ekko Agent 内置 gh issues 技能实战:从 GitHub Issue 分流到 Pull Request 的自动化工作流 本篇技术指南以 Ekk
AI 应用人工智能AI Agent本地部署前端后端工作流自动化
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考