Archon Script Nodes 实战指南:用 TypeScript 与 Python 构建确定性 DAG 节点
Archon Script Nodes 实战指南用 TypeScript 与 Python 构建确定性 DAG 节点【免费下载链接】ArchonThe first open-source harness builder for AI coding. Make AI coding deterministic and repeatable.项目地址: https://gitcode.com/GitHub_Trending/archon3/ArchonArchon 的 DAG 工作流Workflow节点支持一个script字段让你直接用 TypeScript、JavaScript 或 Python 片段作为工作流的一环执行全程不调用任何 AI Agent。脚本由bun或uv运行时驱动stdout被捕获为节点输出并可在下游以$nodeId.output引用。阅读本文后你将掌握脚本节点的全部配置参数、内联代码与命名脚本的判定规则、output_format结果契约、依赖管理与环境隔离机制并能组合出“AI 产出 → 脚本清洗 → 确定性后续处理”的高质量流水线。脚本节点适合承担确定性的“真编程”工作解析 JSON、在两个 AI 节点之间转换数据、用类型化客户端调用 HTTP API、或计算 shell 一行命令难以表达的值。如果一条普通 shell 命令就足够请优先使用bash:节点只有在需要完整编程语言能力时才引入脚本节点。快速上手三种基本写法内联 TypeScriptbun 运行时nodes: - id: parse script: | const data { count: 42, label: ok }; console.log(JSON.stringify(data)); runtime: bun内联 Pythonuv 运行时nodes: - id: compute script: | import json, statistics values [1, 2, 3, 4, 5] print(json.dumps({ mean: statistics.mean(values) })) runtime: uv引用.archon/scripts/下的命名脚本nodes: - id: fetch-pages script: fetch-github-pages # resolves .archon/scripts/fetch-github-pages.ts runtime: bun timeout: 60000文件.archon/scripts/fetch-github-pages.ts会被加载并以bun --no-env-file run path执行。工作原理一次脚本节点执行的完整生命周期变量替换执行前$ARGUMENTS、$WORKFLOW_ID、$ARTIFACTS_DIR、$BASE_BRANCH、$DOCS_DIR以及上游$nodeId.output引用会被替换进script文本。内联 vs 命名判定若script值包含换行或任何 shell 元字符则视为内联代码否则按命名脚本引用处理详见内联与命名脚本。分发执行runtime: bun 内联 →bun --no-env-file -e coderuntime: bun 命名 →bun --no-env-file run pathruntime: uv 内联 →uv run [--with dep ...] python -c coderuntime: uv 命名 →uv run [--with dep ...] path结果捕获stdout去除尾部换行成为$nodeId.output。成功运行时stderr仅记录为警告并发布到对话不会使节点失败。非零退出码则使节点失败失败时两条流的尾部会出现在错误信息中Script node X failed [exit N]: [stderr] ... [stdout] ...共享约 2 KB 诊断预算——stderr 优先stdout 取剩余部分且仅当两条流都非空时才加前缀标签。若 stderr 为空stdout 尾部即作为诊断内容。脚本正文永远不会回显给用户。超时默认也判定失败若结果可选且下游节点通过if_skipped绑定处理缺失可设on_timeout: skip。证据保留无论成败两条流都会以脱敏、限长后的尾部写入运行转录形成exec_output行见保留的子进程证据。该保留仅作证据永不截断$nodeId.output。这套分发逻辑可在源码 packages/workflows/src/dag-executor.ts 中直接验证内联分支用[--no-env-file, -e, finalScript]bun或[run, ...withFlags, python, -c, finalScript]uv命名分支则按scriptDef.runtime分发到uv run [--with ...] path或bun --no-env-file run path其中--no-env-file专门用于阻止 Bun 自动加载执行目录目标仓库下的.env。实现还通过PYTHONDONTWRITEBYTECODE: 1禁用 CPython 字节码缓存避免导入缓存污染冻结源码dag-executor.ts。YAML Schema 与字段详解- id: node-name script: inline code OR named identifier # required, non-empty runtime: bun | uv # required deps: [httpx, pydantic2] # optional, uv-only (见下文) timeout: 60000 # optional ms, 默认 120000 on_timeout: skip # optional; 默认是失败 depends_on: [upstream] # optional when: $upstream.output ! [] # optional (upstream 是 bash/script 节点; # AI 生产者需要 output_format 字段) output_format: # optional JSON Schema; 让 stdout 成为契约 type: object properties: severity: { type: string } required: [severity] trigger_rule: all_success # optional (default) retry: # optional; 与 bash/AI 节点同构 max_attempts: 3 on_error: transient字段速查表字段类型必填说明scriptstring是内联代码或所属工作流scripts/目录下的命名脚本打包工作流或共享脚本目录旧式工作流runtimebun|uv是执行脚本的运行时命名脚本必须与文件扩展名匹配depsstring[]否本次运行安装的 Python 依赖。仅 uv 生效——bun 下会忽略并给出警告timeoutnumber (ms)否超过该毫秒数后强制终止。默认1200002 分钟on_timeoutskip否超时后以“跳过”状态完成节点默认为失败。持久化的跳过原因为timeoutoutput_formatobject否节点 stdout 必须满足的 JSON Schema见声明结果契约标准 DAG 字段id、depends_on、when、trigger_rule、retry全部可用output_format同样可用。AI 专属字段model、provider、context、allowed_tools、denied_tools、hooks、mcp、skills、agents、effort、maxBudgetUsd、systemPrompt、fallbackModel、betas、sandbox会被解析器接受但会触发加载器警告并在运行时忽略——脚本节点不会调用任何 AI。idle_timeout同样被接受但忽略脚本节点是一次性子进程请改用timeoutN 毫秒后硬杀。内联与命名脚本Inline vs Named Scripts执行器直接根据script字符串本身决定模式包含换行或任何 shell 元字符即为内联代码否则按命名脚本查找。触发内联模式的元字符空格;(){}|$内联示例const x 1; console.log(x)、多行代码块、任何含空格的片段命名示例fetch-pages、analyze_metrics、triage-fmt——无空白、无 shell 语法的裸标识符如果你想要一段恰好语法上是单个标识符的内联代码追加一个尾注释或换行即可强制进入内联模式。该判定的实现位于 packages/workflows/src/executor-shared.ts 的isInlineScriptscript.includes(\n) || /[;(){}|$ ]/.test(script)被 DAG 执行器与验证器共用保证运行时与校验行为一致。命名脚本解析命名脚本使用两种解析模式之一打包工作流仅从所属工作流的scripts/目录解析。捆绑包内脚本使用相同的归属规则并在二进制发行时内嵌。旧式工作流从repoRoot/.archon/scripts/再至~/.archon/scripts/解析共享脚本。工作流本地查找限定在声明该节点的工作流范围内包括通过include:展开作者仍然只写裸名称script: publish归属键是内部实现细节。同名冲突时仓库本地条目静默胜出详见全局工作流的共享优先级规则。从源码看脚本发现实现在 packages/workflows/src/script-discovery.tsdiscoverScriptsForCwd依次合并 bundled 打包脚本、home 共享/打包脚本、仓库共享/打包脚本仓库覆盖 home打包脚本使用所有者限定的内部键共享目录每个子文件夹只下探一层如.archon/scripts/triage/foo.ts解析为foo更深层嵌套被忽略script-discovery.ts 的MAX_SCRIPT_DISCOVERY_DEPTH 1同名脚本跨扩展名重复会直接抛错。在包Pack内共享代码把可复用的.ts、.js或.py模块放在pack/.shared/下支持模块子目录。请使用普通文件——二进制生成器拒绝.shared下的符号链接。.shared专为模块保留其中的文件既不是工作流也不是命名脚本目标。命名了不可用脚本包括共享模块的打包工作流会在加载时失败。my-pack/ ├── .shared/ │ ├── result.ts │ └── result.py ├── release/ │ ├── release.yaml │ └── scripts/ │ └── publish.ts └── inspect/ ├── inspect.yaml └── scripts/ └── report.pyBun 侧从脚本位置导入// release/scripts/publish.ts import { summary } from ../../.shared/result.ts; console.log(summary);Python 脚本以文件方式运行from ...shared这类包相对语法不适用。用 Python 标准库把包的共享目录加入路径# inspect/scripts/report.py from pathlib import Path import sys sys.path.insert(0, str(Path(__file__).resolve().parents[2] / .shared)) from result import summary print(summary)Archon 会在项目与全局源码树、捆绑二进制和冻结快照中保留这些相对路径。二进制会把每个包的脚本与模块作为一个单元缓存修改任一共享模块都会生成新单元。作者编写的脚本仍是唯一入口因此工作流节点依然使用script: publish这样的名称。请把输出写到提供的ARTIFACTS_DIR或STATE_DIR下永远不要写到脚本旁边。Bun 模块加载不会在源码旁新增文件Archon 也在工作流执行与可执行 fixture 中禁用了 Python 字节码缓存——这防止导入缓存改变冻结源码但并不阻止你的脚本显式写文件。冻结源码完整性当一次运行使用捕获captured源码时Archon 会在每次命名脚本尝试含重试之前、以及查找与子进程分发之前将完整快照与运行固定的摘要及源码解析设置重新比对。任何变更都会在节点启动前拒绝它。内联脚本已包含在工作流定义中执行时不读取快照。这是检查点检测不是密封或沙箱。以 Archon 用户身份运行的进程可以在检查后更改源码已启动的并行节点也不会被取消。扩展名与运行时映射命名脚本的运行时由文件扩展名推导扩展名运行时.ts,.jsbun.pyuv节点上声明的runtime:必须与文件扩展名匹配——验证器会拒绝runtime: uv指向.ts文件反之亦然。内联脚本则可以使用所选运行时支持的任何语言。该映射在源码中定义于 packages/workflows/src/script-discovery.tsEXTENSION_RUNTIME_MAP并在执行前由scriptDef.runtime作为唯一事实来源dag-executor.ts。依赖管理仅 uvdeps直接透传给uv run --with dep把包安装进每次运行独立的临时环境- id: scrape script: | import httpx r httpx.get(https://api.github.com/repos/anthropics/anthropic-cookbook) print(r.text) runtime: uv deps: [httpx0.27]版本固定——任何 PEP 508 说明符都可用pkg1.2.3、pkg2,3。bun 忽略deps——Bun 在首次运行时自动安装导入的包因此验证器会在runtime: bun配合deps时发出警告。要么删除该字段要么在需要显式依赖管理时改用uv。无持久环境——每次运行相互隔离不需要维护requirements.txt或 lockfile。命令构建中deps的展开逻辑可在 dag-executor.ts 看到nodeDeps.flatMap(dep [--with, dep])生成uv run --with dep1 --with dep2 ...bun 内联分支则不加任何依赖标志。该行为有专门的测试覆盖packages/workflows/src/script-node-deps.test.ts断言 bun 内联带deps时仍只得到[--no-env-file, -e, node.script]。输出与数据流stdout去除尾部换行成为$nodeId.output。若希望下游节点用$nodeId.output.field访问结构化字段请打印 JSON——工作流引擎在when:条件和提示词替换中会尝试把输出解析为 JSON 以便字段访问。当你希望这份 JSON 是契约而非约定时声明output_format见下节。- id: classify script: | const input process.argv.slice(2).join( ); const severity input.includes(crash) ? high : low; console.log(JSON.stringify({ severity, length: input.length })); runtime: bun - id: investigate command: investigate-bug depends_on: [classify] when: $classify.output.severity high声明结果契约Declaring a result contract没有output_format时上面的$classify.output.severity靠约定工作引擎解析文本并“期望”键存在。声明output_format后同样的结果就变成节点拥有的契约- id: classify script: | const input process.env.ARGUMENTS ?? ; console.log(JSON.stringify({ severity: input.includes(crash) ? high : low, units: [], })); runtime: bun output_format: type: object properties: severity: { type: string, enum: [low, high] } units: { type: array, items: { type: object } } required: [severity, units]声明 schema 后节点会把 stdout 解析为一个严格的 JSON 文档——无代码围栏、无散文前言、无修复过程、无第二次尝试对照 schema 校验不匹配即节点失败并指出违规的 JSON 路径、引用 stdout 开头将规范化后的 JSON 文档发布为$classify.output下游绑定与fan_out.items的逻辑值并把声明的属性名暴露给$classify.output.field对未声明字段的引用会让消费节点失败而不是静默解析为。这与 AI 节点output_format所承载的契约完全一致因此脚本节点与 Agent 在returns:节点后面可以互换。这对调用方通过include:别名、workflow:子运行、扇出或工件指针意味着什么统一在工作流编写指南 → 结果契约中说明。无 schema 的脚本行为不变stdout 保持原始文本仅像以前一样去除尾部换行。该契约在源码中由certifyExecOutput实现dag-executor.tsstdout 不是严格 JSON 或不符合 schema 时抛出ExecOutputContractError且该契约失败不进入子进程错误分类因此不会触发重试节点以retryable: false结束见 dag-executor.ts。脚本中的变量替换变量以原始字符串、不做 shell 引号包裹的方式替换进script文本——这与bash:节点不同后者$nodeId.output的值会被自动加引号。请把替换进来的值视为不可信输入用语言特性去解析而不要插值进 shell 语法。:::caution[避免对$nodeId.output使用 String.raw]String.raw$nodeId.output看起来安全但当替换值包含反引号时会静默失败——这在 AI 生成的 markdown、output_format载荷或任何含内联代码片段的输出中很常见。反引号会提前终止模板字面量产生运行时的神秘Expected ;解析错误。请改用直接赋值。JSON 是 JavaScript 表达式语法的严格子集因此替换值永远是合法的 JS 字面量// 安全——适用于任何合法 JSON包括含反引号的内容 const data $fetch-issue.output; // 脆弱——输出含反引号时会出错 const data JSON.parse(String.raw$fetch-issue.output); // 不要这样写:::对命名脚本变量不会自动传入。请从环境变量读取process.env.USER_MESSAGE、os.environ[USER_MESSAGE]或通过 stdin 接收。对内联脚本替换后的变量会在执行时直接嵌入代码字符串。从源码看脚本子进程的环境由 packages/workflows/src/exec-environment.ts 的buildExecNodeEnvironment构造包含ARTIFACTS_DIR、STATE_DIR、LOG_DIR、ADOPTED_RUN_DIR、WORKFLOW_ID、BASE_BRANCH、USER_MESSAGE、ARGUMENTS、LOOP_USER_INPUT、LOOP_PREV_OUTPUT、REJECTION_REASON、CONTEXT等键。配置的项目级环境变量会先展开dag-executor.ts引擎保留键永远优先防止 codebase 环境变量如命名为ARGUMENTS遮蔽传递通道。环境与隔离:::note[防止 shell 注入] 用户可控的工作流变量如$USER_MESSAGE和$ARGUMENTS通过环境变量传递给bash:节点而非内联替换进 shell 命令以阻止 shell 注入。脚本节点以同样的方式通过process.env接收这些值。 :::脚本子进程接收process.env与你在 Web UI设置 → 项目 → 环境变量或.archon/config.yaml的env:块中配置的 codebase 级环境变量的合并结果。这与 Claude、Codex 和 bash 节点使用的注入面相同。目标仓库.env隔离Bun 子进程以--no-env-file调用因此目标仓库.env中的变量不会泄漏进脚本。Archon 管理的环境来自~/.archon/.env和repo/.archon/.env正常透传。uv启动的 Python 子进程根本不会自动加载.env。完整机制见安全模型 → 目标仓库 env 隔离。执行器还在提交前对工作流做静态输入检查validateInlineExecInputs会扫描内联脚本中读取的环境变量bun 匹配process.env.Xuv 匹配os.environ[X]对未由绑定、声明输入或引擎提供的读取发出错误或警告并精确到行号packages/workflows/src/exec-input-validation.ts。校验Validationarchon validate workflows name会检查脚本节点脚本文件存在——命名脚本的基本名必须存在于所属工作流的scripts/目录或旧式共享搜索路径中且扩展名与声明的运行时匹配。文件缺失会校验失败并给出期望路径的提示。运行时在 PATH 上——bun或uv必须已安装。缺失的运行时发出警告并附官方安装命令curl -fsSL https://bun.sh/install | bashcurl -LsSf https://astral.sh/uv/install.sh | shdeps搭配runtime: bun——警告deps在 Bun 下是空操作。运行时可用性按进程缓存——检查只执行一次which bun/which uv并记忆结果。实战模式Patterns在下一个节点前转换 AI 输出用脚本节点作为两个 AI 节点之间的确定性适配器解析上游分类器的 JSON、过滤、转发干净的载荷- id: classify prompt: Classify: $ARGUMENTS allowed_tools: [] output_format: type: object properties: items: type: array items: { type: object } - id: filter script: | const upstream JSON.parse(process.env.UPSTREAM ?? {}); const high (upstream.items ?? []).filter(i i.severity high); console.log(JSON.stringify(high)); runtime: bun depends_on: [classify] - id: triage command: triage-high-severity depends_on: [filter] when: $filter.output ! []注要真正填充UPSTREAM需要把$classify.output内联替换进脚本正文。上面的示例用于说明结构。在~/.archon/scripts/放一个可复用助手希望每个仓库都可用的助手——比如一个 triage 摘要格式化器——放在~/.archon/scripts/triage-fmt.ts// ~/.archon/scripts/triage-fmt.ts const raw process.argv.slice(2).join( ) || {}; const data JSON.parse(raw); const lines data.issues?.map((i: { id: string; title: string }) - [${i.id}] ${i.title} ).join(\n) ?? ; console.log(lines || no issues);然后在任何仓库的工作流中按名引用- id: format script: triage-fmt runtime: bun depends_on: [gather]Python 科学计算依赖- id: analyze script: | import json, sys import pandas as pd data json.loads(sys.argv[1]) if len(sys.argv) 1 else [] df pd.DataFrame(data) print(df.describe().to_json()) runtime: uv deps: [pandas2.0] depends_on: [collect]脚本节点不做什么What Does NOT WorkAI 专属功能——hooks、mcp、skills、allowed_tools、denied_tools、agents、model、provider、effort、maxBudgetUsd、systemPrompt、fallbackModel、betas、sandbox全部在运行时忽略加载器会发出列出被忽略字段的警告。output_format不在此列——脚本拥有它见声明结果契约。JSON 修复与重试——认证脚本的 stdout 第一次就必须完全正确。没有围栏剥离、没有第二次尝试不符合声明 schema 的 stdout 是脚本自身的 bug。交互式提示——脚本以无头方式运行任何stdin读取会立即遇到 EOF。bun与uv之外的运行时——解析阶段即被拒绝。执行中途取消——工作流取消时脚本子进程会被杀死但没有协作式取消信号。请把脚本设计成快速完成或快速失败。延伸阅读工作流编写指南——完整工作流参考bash:节点、returns:节点、结果契约、子进程证据全局工作流、命令与脚本——~/.archon/scripts/的 home 级作用域安全模型 → 目标仓库 env 隔离——环境隔离细节变量参考——替换规则全集【免费下载链接】ArchonThe first open-source harness builder for AI coding. Make AI coding deterministic and repeatable.项目地址: https://gitcode.com/GitHub_Trending/archon3/Archon创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考