-
Notifications
You must be signed in to change notification settings - Fork 3.2k
docs(script-nodes): dedicated guide + teach the archon skill #1362
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -152,9 +152,9 @@ nodes: | |
| depends_on: [first-node] | ||
| ``` | ||
|
|
||
| ### Four Node Types | ||
| ### Node Types | ||
|
|
||
| Each node has exactly ONE of: `command`, `prompt`, `bash`, or `loop`. | ||
| Each node has exactly ONE of: `command`, `prompt`, `bash`, `script`, `loop`, `approval`, or `cancel`. | ||
|
|
||
| **Command node** — runs a `.archon/commands/*.md` file: | ||
| ```yaml | ||
|
|
@@ -177,6 +177,22 @@ Each node has exactly ONE of: `command`, `prompt`, `bash`, or `loop`. | |
| timeout: 15000 | ||
| ``` | ||
|
|
||
| **Script node** — TypeScript/JavaScript (via `bun`) or Python (via `uv`), no AI, stdout captured as output: | ||
| ```yaml | ||
| - id: transform | ||
| script: | | ||
| const raw = process.argv.slice(2).join(' ') || '{}'; | ||
| console.log(JSON.stringify({ parsed: JSON.parse(raw) })); | ||
| runtime: bun # 'bun' (.ts/.js) or 'uv' (.py) — REQUIRED | ||
| timeout: 30000 # Optional, ms, default 120000 | ||
|
|
||
| # Or reference a named script from .archon/scripts/ or ~/.archon/scripts/ | ||
| - id: analyze | ||
| script: analyze-metrics # loads .archon/scripts/analyze-metrics.py | ||
| runtime: uv | ||
| deps: ["pandas>=2.0"] # Optional, uv only — 'uv run --with <dep>' | ||
| ``` | ||
|
Comment on lines
+180
to
+194
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Confirm dispatch does not append any positional args after -e or <path>.
rg -nP -C5 "--no-env-file|'-e'|'run'" --type=tsRepository: coleam00/Archon Length of output: 514 🏁 Script executed: #!/bin/bash
# Search for how inline bun scripts are invoked in dispatch/workflow
rg -n "bun.*-e|--no-env-file" --type ts --type yaml -A3 -B3Repository: coleam00/Archon Length of output: 50374 🏁 Script executed: #!/bin/bash
# Read the specific file and lines mentioned in SKILL.md
head -195 .claude/skills/archon/SKILL.md | tail -20Repository: coleam00/Archon Length of output: 765 🏁 Script executed: #!/bin/bash
# Check if script-nodes.md exists and read the relevant sections
fd "script-nodes.md" -x wc -l {}Repository: coleam00/Archon Length of output: 122 🏁 Script executed: #!/bin/bash
# Read SKILL.md lines 180-194 to see the example in question
sed -n '175,200p' .claude/skills/archon/SKILL.mdRepository: coleam00/Archon Length of output: 942 🏁 Script executed: #!/bin/bash
# Read script-nodes.md to check lines 184 and 281-287
sed -n '179,190p' packages/docs-web/src/content/docs/guides/script-nodes.md
echo "---"
sed -n '276,295p' packages/docs-web/src/content/docs/guides/script-nodes.mdRepository: coleam00/Archon Length of output: 952 🏁 Script executed: #!/bin/bash
# Verify the actual invocation pattern in dag-executor confirms no argv passed
grep -A5 -B2 "'-e'" packages/workflows/src/dag-executor.ts | head -25Repository: coleam00/Archon Length of output: 483 Examples use Per 🤖 Prompt for AI Agents |
||
|
|
||
| **Loop node** — iterates AI prompt until completion: | ||
| ```yaml | ||
| - id: implement | ||
|
|
@@ -230,7 +246,7 @@ For details: Read `references/dag-advanced.md` | |
|
|
||
| ### Example Files | ||
|
|
||
| - `examples/dag-workflow.yaml` — workflow with conditions, bash nodes, structured output | ||
| - `examples/dag-workflow.yaml` — workflow with conditions, bash + script + loop nodes, structured output | ||
| - `examples/command-template.md` — Command file skeleton with all variables | ||
|
|
||
| --- | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,7 +1,8 @@ | ||
| # Example: Workflow with all four node types | ||
| # Example: Workflow demonstrating multiple node types | ||
| # | ||
| # Demonstrates: bash nodes, structured output, when: conditions, | ||
| # trigger_rule, per-node model, context: fresh, loop nodes, and output substitution. | ||
| # Demonstrates: bash nodes, script nodes (TypeScript via bun), structured output, | ||
| # when: conditions, trigger_rule, per-node model, context: fresh, loop nodes, | ||
| # and output substitution. | ||
| # | ||
| # IMPORTANT: This is a reference example. Design your actual workflow | ||
| # around the user's specific needs — the number of nodes, their types, | ||
|
|
@@ -42,6 +43,27 @@ nodes: | |
| fi | ||
| timeout: 5000 | ||
|
|
||
| # ── SCRIPT NODE: TypeScript (bun runtime), no AI, stdout captured as output ── | ||
| # Deterministic parsing the shell would mangle — extracts labels cleanly as JSON. | ||
| # | ||
| # NOTE: `$fetch-issue.output` is substituted *raw* into the script body (no shell | ||
| # quoting — see reference/variables.md). Wrapping it in a String.raw template | ||
| # preserves backslashes and newlines in the JSON payload without needing any | ||
| # escaping. Safe here because gh issue view --json emits clean JSON. | ||
| - id: extract-labels | ||
| script: | | ||
| const raw = String.raw`$fetch-issue.output`; | ||
| try { | ||
| const issue = JSON.parse(raw); | ||
| const labels = (issue.labels ?? []).map((l) => l.name); | ||
| console.log(JSON.stringify({ labels, count: labels.length })); | ||
|
Comment on lines
+49
to
+59
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Demonstrates why raw JSON containing backticks / interpolation markers is unsafe
# inside a JavaScript template literal, but safe as a JSON/JS literal.
python - <<'PY'
payload = '{"body":"contains ` and ${process.exit(1)}","labels":[{"name":"bug"}]}'
print("Current pattern after substitution:")
print(f"const raw = String.raw`{payload}`;")
print("\nSafer literal pattern after substitution:")
print(f"const issue = {payload};")
PYRepository: coleam00/Archon Length of output: 319 🏁 Script executed: cat -n .claude/skills/archon/examples/dag-workflow.yaml | head -80Repository: coleam00/Archon Length of output: 3443 🏁 Script executed: # Check for documentation on variable substitution and timing
find . -name "*.md" -type f | xargs grep -l "variables\|substitution" 2>/dev/null | head -5Repository: coleam00/Archon Length of output: 313 🏁 Script executed: # Look for similar patterns in other YAML files
fd -e yaml -type f | xargs grep -l "String.raw" 2>/dev/nullRepository: coleam00/Archon Length of output: 231 🏁 Script executed: # Search for String.raw patterns in YAML files
rg "String.raw" --type yaml -A 2 -B 2Repository: coleam00/Archon Length of output: 41 🏁 Script executed: # Check the reference/variables.md documentation mentioned in the comment
find . -name "variables.md" -o -name "reference.md" | xargs cat 2>/dev/null | head -100Repository: coleam00/Archon Length of output: 6435 Remove the unsafe
Fix- # NOTE: `$fetch-issue.output` is substituted *raw* into the script body (no shell
- # quoting — see reference/variables.md). Wrapping it in a String.raw template
- # preserves backslashes and newlines in the JSON payload without needing any
- # escaping. Safe here because gh issue view --json emits clean JSON.
+ # NOTE: `$fetch-issue.output` is substituted *raw* into the script body (no shell
+ # quoting — see reference/variables.md). Because gh issue view --json emits JSON,
+ # assign it as a JavaScript literal instead of wrapping in a string.
- const raw = String.raw`$fetch-issue.output`;
try {
- const issue = JSON.parse(raw);
+ const issue = $fetch-issue.output;
const labels = (issue.labels ?? []).map((l) => l.name);
console.log(JSON.stringify({ labels, count: labels.length }));🤖 Prompt for AI Agents |
||
| } catch { | ||
| console.log(JSON.stringify({ labels: [], count: 0 })); | ||
| } | ||
| runtime: bun | ||
| depends_on: [fetch-issue] | ||
| timeout: 10000 | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| # ── PROMPT NODE: Inline AI prompt with structured output ── | ||
| - id: classify | ||
| prompt: | | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -20,9 +20,9 @@ nodes: | |
| depends_on: [other-node] # Node IDs that must complete first | ||
| ``` | ||
|
|
||
| ## Four Node Types (Mutually Exclusive) | ||
| ## Node Types (Mutually Exclusive) | ||
|
|
||
| Each node must have exactly ONE of these fields: | ||
| Each node must have exactly ONE of these fields: `command`, `prompt`, `bash`, `script`, `loop`, `approval`, or `cancel`. | ||
|
coderabbitai[bot] marked this conversation as resolved.
|
||
|
|
||
| ### Command Node | ||
| Runs a command file from `.archon/commands/`: | ||
|
|
@@ -54,6 +54,54 @@ Runs a shell script without AI: | |
| - **stderr** forwarded as warning, does not fail the node | ||
| - No AI invoked — AI-specific fields are ignored | ||
| - Use `timeout:` (milliseconds) for execution time limit | ||
| - `$nodeId.output` substitutions are **auto shell-quoted** (safe to embed) | ||
|
|
||
| ### Script Node | ||
| Runs TypeScript/JavaScript (via `bun`) or Python (via `uv`) without AI. Same stdout/stderr contract as bash nodes. | ||
|
|
||
| **Inline script (TypeScript):** | ||
| ```yaml | ||
| - id: parse | ||
| script: | | ||
| const raw = process.argv.slice(2).join(' ') || '{}'; | ||
| const data = JSON.parse(raw); | ||
| console.log(JSON.stringify({ items: data.items?.length ?? 0 })); | ||
| runtime: bun # REQUIRED: 'bun' or 'uv' | ||
| timeout: 30000 # ms, default: 120000 | ||
|
Comment on lines
+62
to
+70
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do not show Inline Bun scripts are executed as 📝 Proposed fix **Inline script (TypeScript):**
```yaml
- id: parse
script: |
- const raw = process.argv.slice(2).join(' ') || '{}';
- const data = JSON.parse(raw);
+ const data = $fetch-data.output;
console.log(JSON.stringify({ items: data.items?.length ?? 0 }));
runtime: bun # REQUIRED: 'bun' or 'uv'
timeout: 30000 # ms, default: 120000
+ depends_on: [fetch-data]Verify each finding against the current code and only fix it if needed. In @.claude/skills/archon/references/workflow-dag.md around lines 62 - 70, The |
||
| ``` | ||
|
|
||
| **Inline script (Python) with uv dependencies:** | ||
| ```yaml | ||
| - id: fetch | ||
| script: | | ||
| import httpx, json | ||
| r = httpx.get("https://api.github.com/repos/anthropics/anthropic-cookbook") | ||
| print(json.dumps({ "stars": r.json()["stargazers_count"] })) | ||
| runtime: uv | ||
| deps: ["httpx>=0.27"] # Optional — 'uv run --with <dep>'. Ignored for bun. | ||
| ``` | ||
|
|
||
| **Named script from `.archon/scripts/`:** | ||
| ```yaml | ||
| - id: analyze | ||
| script: analyze-metrics # Resolves .archon/scripts/analyze-metrics.py | ||
| runtime: uv # Must match file extension (.ts/.js → bun, .py → uv) | ||
| deps: ["pandas>=2.0"] | ||
| ``` | ||
|
|
||
| - **Inline vs named**: a `script` value is treated as inline code if it contains a newline or any shell metacharacter (space, or any of: `;` `(` `)` `{` `}` `&` `|` `<` `>` `$` `` ` `` `"` `'`). Otherwise it's a named-script lookup (bare identifier). | ||
| - **Named script resolution**: `<cwd>/.archon/scripts/` (wins) → `~/.archon/scripts/`. 1-level subfolder grouping allowed. Extension determines runtime (`.ts`/`.js` → `bun`, `.py` → `uv`) and MUST match the declared `runtime:` | ||
| - **Dispatch**: | ||
| - `bun` + inline → `bun --no-env-file -e '<code>'` | ||
| - `bun` + named → `bun --no-env-file run <path>` | ||
| - `uv` + inline → `uv run [--with dep ...] python -c '<code>'` | ||
| - `uv` + named → `uv run [--with dep ...] <path>` | ||
| - **`deps`** is uv-only. Bun auto-installs on import; `deps` with `runtime: bun` emits a validator warning | ||
| - **stdout** captured as `$nodeId.output` (trailing newline trimmed) | ||
| - **stderr** forwarded as warning, does NOT fail the node. Non-zero exit DOES fail it. | ||
| - **`bun --no-env-file`** prevents target repo `.env` from leaking into the subprocess | ||
| - `$nodeId.output` substitutions are **NOT shell-quoted** in script bodies — parse with `JSON.parse` / `json.loads`, don't interpolate into shell syntax | ||
| - AI-specific fields (`model`, `provider`, `hooks`, `mcp`, `skills`, `output_format`, `allowed_tools`, `denied_tools`, `agents`, `effort`, `thinking`, `maxBudgetUsd`, `systemPrompt`, `fallbackModel`, `betas`, `sandbox`) emit a loader warning and are ignored | ||
|
|
||
| ### Loop Node | ||
| Iterates an AI prompt until a completion signal or max iterations: | ||
|
|
@@ -83,7 +131,7 @@ All node types share these fields: | |
| | `depends_on` | string[] | `[]` | Node IDs that must settle before this node runs | | ||
| | `when` | string | — | Condition expression. Node **skipped** when false | | ||
| | `trigger_rule` | string | `all_success` | Join semantics for multiple dependencies | | ||
| | `idle_timeout` | number (ms) | 300000 | Per-node idle timeout. On loop nodes, applies per-iteration | | ||
| | `idle_timeout` | number (ms) | 300000 | Idle timeout for AI streaming (`command`, `prompt`) and per-iteration idle for `loop`. Accepted but ignored on `bash` and `script` — use `timeout` there | | ||
|
|
||
| **Command, prompt, and bash nodes** (silently ignored on loop nodes, except `retry` which is a hard error): | ||
|
|
||
|
|
@@ -302,7 +350,9 @@ Use `--json` for machine-readable output. Use `archon validate commands <name>` | |
| - All `depends_on` reference existing IDs | ||
| - No cycles | ||
| - `$nodeId.output` refs in `when:`, `prompt:`, `loop.prompt:` must point to known IDs | ||
| - Exactly one of `command`, `prompt`, `bash`, `loop` per node | ||
| - Exactly one of `command`, `prompt`, `bash`, `script`, `loop`, `approval`, `cancel` per node | ||
| - Script nodes require `runtime: bun` or `runtime: uv` | ||
| - Named scripts must exist in `.archon/scripts/` or `~/.archon/scripts/` with extension matching declared runtime | ||
| - `retry` on loop node = hard error | ||
| - `steps:` format rejected (deprecated — use `nodes:` only) | ||
|
|
||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Avoid literal
$*.outputplaceholders in the bundled workflow prompt.$other-node.outputand$nodeId.outputlook like real workflow output refs, but this workflow has noother-nodeornodeIdnodes. Use the angle-bracket placeholder form here so validation/substitution does not treat the documentation text as an actual dependency.🐛 Proposed fix
Also applies to: 179-179
🤖 Prompt for AI Agents