Archon 变量替换完全指南:Workflow 与命令中的占位符系统详解
【免费下载链接】ArchonThe first open-source harness builder for AI coding. Make AI coding deterministic and repeatable.项目地址: https://gitcode.com/GitHub_Trending/archon3/Archon
导读
Archon 在命令文件(command files)与工作流(workflow)提示词中通过$VARIABLE形式的占位符在执行时完成文本替换,这是把用户输入、运行上下文、上游节点输出与后续 AI 步骤衔接起来的核心机制。本文以官方 Variable Substitution Reference 为骨架,结合仓库源码梳理全部内置变量、替换发生的时机与位置、bash:/script:节点特有的注入防护与环境变量通道,以及 DAG 模式下的节点输出引用($nodeId.output)与 Artifact 指针契约。读完后你将能准确设计可安全传递用户输入、跨节点传递结构化数据的工作流模板,并规避静默失效与 Shell 注入两类常见陷阱。
变量总览
所有变量都遵守「执行时替换」的同一原则:它们在节点真正运行前被解析,解析结果取决于当次运行的触发消息、git 状态与配置。
| 变量 | 作用域 | 说明 |
|---|---|---|
$ARGUMENTS | 所有模式 | 触发工作流的用户原始消息 |
$USER_MESSAGE | 所有模式 | 与$ARGUMENTS等价,二者都解析为用户消息 |
$WORKFLOW_ID | 所有模式 | 唯一的工作流运行 ID(用于追踪与日志关联) |
$ARTIFACTS_DIR | 所有模式 | 本次运行预先创建好的产物目录,节点输出请写在这里 |
$BASE_BRANCH | 所有模式 | 基础分支名。优先从 git 自动检测,或由配置worktree.baseBranch指定;被引用但无法解析时直接抛错 |
$DOCS_DIR | 所有模式 | 文档目录(配置docs.path,默认docs/)。永不抛错 |
$CONTEXT | 所有模式 | GitHub issue/PR 上下文(平台可用时才有);不可用时为空字符串 |
$EXTERNAL_CONTEXT | 所有模式 | $CONTEXT的别名 |
$ISSUE_CONTEXT | 所有模式 | $CONTEXT的别名 |
$LOOP_USER_INPUT | 循环 / loop_group 提示词 | 交互式循环门(/workflow approve <id> <text>)中的用户反馈。只在恢复后的第一次迭代填充,其余位置均为空字符串 |
$LOOP_PREV_OUTPUT | 循环提示词 | 上一轮迭代清理后的输出(已剥掉 completion 标签)。第 1 轮为空。是fresh_context: true循环获知「上一轮做了什么」的关键工具 |
$LOOP_PREV.<nodeId>.output[.field] | loop_group 正文提示词与when:条件 | 上一轮迭代中某个正文节点的输出。第 1 轮为空。字段访问遵循下面的严格契约(真正缺失的先前输出→'');在正文when:中它是类型化条件引用而非文本替换 |
$REJECTION_REASON | 旧式approval.on_reject提示词 | 来自/workflow reject <id> <reason>的评审反馈。其他位置均为空字符串;新式门读取$gate.output.text |
$nodeId.output | 仅 DAG | 某个已完成的上游节点的完整文本输出;未知或被跳过的生产者 →'' |
$nodeId.output.field | 仅 DAG | JSON 字段访问——严格模式:字段无法解析会使消费节点失败(见下文) |
仓库完整版参考文档还额外补充了两个引擎级变量,见 reference/variables.md:
$STATE_DIR:预创建的跨运行状态目录(~/.archon/workspaces/<project>/state/),按项目(而非按运行)共享,供相互协作的工作流共享记忆(如去重账本、last processed游标)。它在仓库与 worktree 之外,worktree 拆除后依然存活,也永不进入git status。与$BASE_BRANCH一样,被引用但无法解析时抛错而非替换为空串。引擎对其不做任何锁,跨运行并发写需自行按$STATE_DIR/<name>/命名空间隔离。$ADOPTED_RUN_DIR(源码 executor-shared.ts):仅在显式--adopt <run-id>采纳前序运行时可用,用于按引用读取先前运行的产物目录,未启用采纳时引用会抛错。
变量在哪些位置被替换
- 命令文件(
.archon/commands/*.md)——核心集合($ARGUMENTS/$USER_MESSAGE、$WORKFLOW_ID、$ARTIFACTS_DIR、$BASE_BRANCH、$DOCS_DIR、$CONTEXT家族),以及当该命令作为 DAG 节点运行时额外的$nodeId.output[.field]。被loop.command或loop_group正文引用的命令文件,与内联循环提示词一样获得填充好的循环变量;普通 DAG 命令节点对循环/拒绝类变量拿到''。$REJECTION_REASON只在approval.on_reject提示词中填充。 - 内联
prompt:字段——DAG 提示词节点、循环提示词、loop_group 正文提示词。 bash:脚本——特殊:用户可控变量($ARGUMENTS、$USER_MESSAGE、$LOOP_USER_INPUT、$LOOP_PREV_OUTPUT、$REJECTION_REASON、$CONTEXT)不做文本替换(Shell 注入防护),而是作为环境变量传入:ARGUMENTS、USER_MESSAGE、LOOP_USER_INPUT、LOOP_PREV_OUTPUT、REJECTION_REASON、CONTEXT,外加ARTIFACTS_DIR、LOG_DIR、BASE_BRANCH——用"$ARGUMENTS"按普通 Shell 环境变量读取即可。$nodeId.output引用会被替换且自动加 Shell 引号;超过 32KB 的值会溢出写入文件并替换为$(cat <path>)。script:正文——与bash:相同:用户可控变量不做文本替换(注入防护,见 issue #2115),以环境变量形式到达——ARGUMENTS、USER_MESSAGE、LOOP_USER_INPUT、LOOP_PREV_OUTPUT、REJECTION_REASON、CONTEXT(外加EXTERNAL_CONTEXT/ISSUE_CONTEXT),以及ARTIFACTS_DIR、LOG_DIR、BASE_BRANCH和受管的项目级环境变量——通过process.env.ARGUMENTS(bun)或os.environ['ARGUMENTS'](uv/python)读取。源码中留在正文里的字面$ARGUMENTS/$USER_MESSAGE/$CONTEXT不再解析,并会记录一条「单版本迁移」警告。$nodeId.output值仍以原始文本替换(不加 Shell 引号)。
这一「引擎变量文本替换 + 用户可控变量环境变量传递」的双通道设计,在源码 executor-shared.ts 中可以看到:$WORKFLOW_ID、$ARTIFACTS_DIR、$STATE_DIR、$BASE_BRANCH、$DOCS_DIR无条件替换(即便shellSafe开启),而$USER_MESSAGE/$ARGUMENTS/$LOOP_USER_INPUT/$REJECTION_REASON/$LOOP_PREV_OUTPUT只在非 shell 分支替换——这正是为了防止把不可信输入拼进可执行源码。
bash 与 script 节点的安全取值姿势
由于bash:/script:对用户可控变量走环境变量通道,读取方式如下:
# bash 节点:环境变量即参数,正常加引号 echo "用户消息: $ARGUMENTS" mkdir -p "$ARTIFACTS_DIR"// script 节点(runtime: bun) const args = process.env.ARGUMENTS ?? ''; const base = process.env.BASE_BRANCH ?? '';# script 节点(runtime: uv) import os args = os.environ.get('ARGUMENTS', '')$nodeId.output则按节点类型不同处理:bash:中自动 Shell 引号(小值内联单引号;>32KB 写入引擎所有的$ARTIFACTS_DIR/.archon/node-output-spills/<node>[.<field>].nodeoutput并替换为$(cat '<path>')),而script:中原样嵌入(不引号化)。因此对 script 节点,要把替换值当不可信输入用语言特性解析(如JSON.parse),而不是插进 Shell 语法。
直接赋值有前提:const data = $nodeId.output;只有runtime: bun且生产者声明了output_format时才安全;JSON 布尔值与 null 不是合法的 Python 字面量,所以runtime: uv的脚本应改用with: { data: "$nodeId.output" }绑定,再从os.environ['INPUTS_DATA']用json.loads解析。对任意文本生产者(bash/script 的 stdout 或散文输出)同样建议走环境变量绑定,仅在确认文本含 JSON 时才防御性地解析。不要把$nodeId.output包进String.raw模板字面量——当输出含反引号(AI 生成的 markdown 与output_format载荷中很常见)时会静默破坏。
bash 节点双重引号陷阱
bash:的替换自带引号,再套一层双引号会引入字面单引号,导致条件判断静默失败:
# 错误——小值场景下 $emit.output.status 被注入为 'ok'(单引号), # status="$emit.output.status" 实际变成 status="'ok'",引号变成数据 status="$emit.output.status" [ "$status" = "ok" ] && echo pass # → 静默失败($status 是 'ok',不是 ok) # 正确——保持替换不带引号;Archon 的引号就是引号 status=$emit.output.status # → status='ok' → bash 赋值:ok [ "$status" = "ok" ] && echo pass # → 通过大输出(>32KB)的替换形态是$(cat '/path'),var="$(cat ...)"在 bash 里正确——但作者无法在编写时预知大小,所以规则是无条件的:始终用var=$node.output.field,绝不用var="$node.output.field"。数字与布尔字段以裸值注入(无引号),双引号对它们「碰巧」有效,这让 bug 变得时有时无。
替换顺序
- 标准工作流变量(
$WORKFLOW_ID、$ARGUMENTS、$ARTIFACTS_DIR、$BASE_BRANCH、$DOCS_DIR、$CONTEXT、循环/拒绝类变量) - 节点输出引用(
$nodeId.output、$nodeId.output.field、$LOOP_PREV.*)——仅 DAG 模式
完整版参考文档进一步细分为三步(reference/variables.md):工作流变量 → 上下文变量($CONTEXT家族)→ 节点输出引用;且loop_group正文中$LOOP_PREV.<nodeId>.output引用最先解析(先于$LOOP_USER_INPUT拼接,保证用户文本不会被二次当作工作流引用处理),随后再执行该节点正常的替换流程。
上下文自动追加(Context Auto-Append)
如果提示词模板中完全没有出现$CONTEXT/$EXTERNAL_CONTEXT/$ISSUE_CONTEXT,但上下文确实存在(例如来自 GitHub issue),则该上下文会在---分隔符之后自动追加到提示词末尾。这条规则有两个目的:其一,避免命令显式使用$CONTEXT时上下文被重复发送;其二,在无 issue 上下文时把三个别名替换为空串,避免把字面$CONTEXT文本发给 AI。
转义美元符号
在命令文件中用\$产生字面$(阻止变量替换)。
明确不支持:$1…$9位置参数
尽管旧文档曾暗示支持,工作流引擎不替换位置参数$1…$9——命令文件与提示词只通过$ARGUMENTS/$USER_MESSAGE接收完整消息,不存在按空白拆分成编号槽位的机制(直接命令调用与工作流节点皆如此)。代码库中存在一个遗留的位置替换辅助函数,但未接入执行路径。需要结构化输入时,在提示词内部解析$ARGUMENTS,或用bash:/script:节点处理。
节点输出细节(仅 DAG)
$nodeId.output解析为上游节点的完整文本输出。若节点使用了output_format:(结构化输出),输出是经校验的 JSON 字符串化结果;无output_format的 bash/script 输出则是去掉尾部换行的 stdout;有output_format时 stdout 必须在同一结果契约下被认证为 JSON(见 node-reference.md 的 Result contracts 一节)。loop/loop_group 输出是剥掉完成信号标签后的最终迭代输出。带作者自定approval.decisions的门总是输出 JSON{decision, text},读取其字段用$gate.output.decision与$gate.output.text;未自定决策的旧式门保持旧行为——只有capture_response: true时才输出其审批评论,否则为''。未知或被跳过的生产者解析为空串(并记录警告)。
字段访问的严格契约(no-silent-drop)
$nodeId.output.field是严格的:要么解析成功,要么让消费节点失败:
- 生产者声明了
output_format:模式中已声明的字段解析为其值,若缺失则解析为''(声明为可选);未在模式中声明的字段会使消费者失败(防拼写错误)。 - 无模式生产者(生产节点未声明
output_format):输出必须是包含该键的 JSON 对象——非 JSON 输出、键缺失都会使消费者失败。 - 生产者被跳过或未决:消费者失败——用
when:或宽松的trigger_rule保护引用。
取值规则:字符串原样通过;数字/布尔字符串化;对象/数组 JSON 字符串化。
对workflow:子运行结果,这些字段规则使用子运行returns:节点的模式——字段名随值一同传递并能在冷恢复后存活;调用方不能增删契约。include:别名在展平后直接使用其选中的生产者。
源码中对应注释亦印证了「未知输入名 THROWS,而非替换''」的严格性——拼错的输入静默变空比加载可见的错误更糟(executor-shared.ts)。
实战示例
nodes: - id: classify command: classify-issue output_format: type: object properties: type: { type: string, enum: [BUG, FEATURE] } required: [type] - id: fix prompt: | The issue was classified as: $classify.output.type Full classification: $classify.output User's original request: $USER_MESSAGE depends_on: [classify]命令/脚本节点的with:绑定
命令文件与命名脚本对内联文本替换是不透明的——引擎从不改写其正文。with:在command:/script:节点上按名字把上游值绑定到这些正文已经会读取的通道:命令文件读$INPUTS.<name>,脚本读INPUTS_<UPPER_SNAKE>环境变量。
nodes: - id: validate prompt: Run validation and report the verdict. output_format: type: object properties: green: { type: boolean } required: [green] - id: record script: record-verdict # 命名脚本——读取 process.env.INPUTS_GREEN runtime: bun depends_on: [validate] with: green: $validate.output.green绑定值有三种形态:非字符串字面量(true、42、[a, b])按自身逻辑类型传递;恰好是一个完整$node.output/$node.output.field/$INPUTS.<name>引用的字符串按逻辑值传递(布尔字段到达时就是布尔,对象就是对象;环境变量投递用其规范文本:字符串原样、其余 JSON);其他字符串当作文本模板,按input:的规则替换(先工作流变量,再$node.output引用)。绑定指令对象{ from, if_skipped }可在生产者被跳过时提供回退值——被跳过且无if_skipped会直接失败节点,绑定永不静默解析为空串。每个被引用的生产者都必须是depends_on的上游依赖(否则加载器拒绝),保证绑定永远不会与生产者竞态。
Artifact 指针:大文件结果的标准通道
当机器消费方需要文件结果时,在结果中返回一个包含指针的小型 JSON 值。保留形状:
{"type":"archon_artifact","run_id":"<producing run id>","path":"review/report.md"}run_id必须使用实际的WORKFLOW_ID值(而非字面变量名)。生产者必须先把自己的文件写到$ARTIFACTS_DIR之下。允许指针上携带兄弟键。在持久化结果前,引擎会针对生产运行校验带标签的指针:自身的 run id、非空相对路径且不含..段或 NUL 字节、词法包含、且必须是存在的常规文件。绝对路径/越界路径、目录、缺失文件、其他运行的 id 都会使生产者失败。
校验规则(reference/variables.md):
| 规则 | 拒绝的情形 |
|---|---|
| 自有运行 | run_id非生产运行自身($WORKFLOW_ID)。当前结果只能指向自身运行的产物 |
| 可寻址运行 | 输出位置从未记录,或记录在 Archon home 目录之外 |
| 相对路径 | 绝对路径、..段或 NUL 字节 |
| 包含性 | 词法上解析到运行产物目录之外 |
| 真实文件 | 目标缺失,或是目录 |
workflow:父运行与扇出聚合原样转发指针而不针对父运行重新校验。事件与恢复保留 run id 与相对路径。引擎不会把指针展开为绝对路径、不会把文件内容读进提示词,也不提供工作流内解析器——生产运行内部的提示词交接请继续使用$ARTIFACTS_DIR/<path>(磁盘上是同一个文件)。
读侧(谁可以看该运行、路径经符号链接跟随后的 realpath 包含性)由读取方自持授权与校验责任:GET /api/artifacts/<run_id>/<path>目前只做词法包含校验,读时 realpath 解析尚未实现(跟踪于 #3160)。
各上下文中的变量可用性
| 变量 | 工作流节点 | 直接命令调用 | when:条件 |
|---|---|---|---|
$ARGUMENTS/$USER_MESSAGE | 是 | 是(两个别名均可) | 否 |
$WORKFLOW_ID | 是 | 否 | 否 |
$ARTIFACTS_DIR | 是 | 否 | 否 |
$STATE_DIR | 是 | 否 | 否 |
$BASE_BRANCH | 是 | 否 | 否 |
$DOCS_DIR | 是 | 否 | 否 |
$CONTEXT/ 别名 | 是 | 否 | 否 |
$LOOP_USER_INPUT | 是(循环节点) | 否 | 否 |
$REJECTION_REASON | 是(仅on_reject) | 否 | 否 |
$LOOP_PREV_OUTPUT | 是(循环节点) | 否 | 否 |
$LOOP_PREV.<nodeId>.output | 是(loop_group 正文节点) | 否 | 是(loop_group 正文节点) |
$nodeId.output | 是(DAG 节点) | 否 | 是 |
此外,systemPrompt:与agents.<id>.prompt/agents.<id>.description中,工作流变量与$nodeId.output引用均可解析(这些文本直接进入 provider,与prompt:一样经过两轮替换;三者中任何一处出现悬空的$nodeId.output都是加载错误)。
常见误区速查
$1…$9不可用:在提示词内解析$ARGUMENTS,或交给bash:/script:节点处理。bash:中不要对$node.output.field套双引号:替换已自带引号,嵌套引号会让引号字符变成数据(数字/布尔字段会掩盖此问题,使其间歇性出现)。script:中不要直接嵌入$nodeId.output到源码:对任意文本生产者优先走with:环境变量绑定,再防御性解析;bun 下直接赋值仅限生产者声明了output_format的场景。- 不要把
$nodeId.output包进String.raw模板字面量:输出含反引号时会静默破坏。 $BASE_BRANCH与$STATE_DIR会 fail-fast:被引用却无法解析时直接抛错,而非静默替换为空串——「响亮的失败」优于「静默写错位置」。- 位置参数未接入执行路径:代码库中的遗留辅助函数不参与执行,不要依赖它。
深入阅读
- 本主题的完整版参考:reference/variables.md(含
$STATE_DIR并发与命名冲突讨论、with:绑定三形态、环境读取的静态词法检查等扩展内容) - 节点类型与结果契约:node-reference.md
- 工作流编写指南(DAG、
when:、trigger_rule、持久会话等):guides/authoring-workflows.md - 变量替换核心实现:executor-shared.ts(
substituteWorkflowVariables与buildPromptWithContext,含shellSafe双通道逻辑与 fail-fast 校验) - 相关测试与校验逻辑:
packages/workflows/src/executor-shared.test.ts、packages/workflows/src/dag-executor.ts、packages/workflows/src/validator.ts(可用bun test在仓库内运行验证行为)
【免费下载链接】ArchonThe first open-source harness builder for AI coding. Make AI coding deterministic and repeatable.项目地址: https://gitcode.com/GitHub_Trending/archon3/Archon
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考