Skip to content

fix(workflows): escape example placeholders in archon-workflow-builder#1402

Open
3bit wants to merge 11 commits into
coleam00:devfrom
3bit:fix/workflow-builder-placeholder-escape
Open

fix(workflows): escape example placeholders in archon-workflow-builder#1402
3bit wants to merge 11 commits into
coleam00:devfrom
3bit:fix/workflow-builder-placeholder-escape

Conversation

@3bit

@3bit 3bit commented Apr 24, 2026

Copy link
Copy Markdown

Summary

  • Problem: archon-workflow-builder fails DAG-structure validation because two documentation-example placeholders inside a prompt body ($other-node.output, $nodeId.output) are scanned as real cross-node references and rejected.
  • Why it matters: The workflow is bundled, so the failure surfaces in every archon workflow list invocation and the "new workflow" builder is unavailable to users.
  • What changed: Rewrote both placeholders using the existing angle-bracket convention ($<other-node>.output, $<nodeId>.output) so the validator treats them as escape-text rather than references. Regenerated bundled-defaults.generated.ts.
  • What did NOT change: No loader/validator changes. No runtime behavior change. The workflow's node graph, execution, and outputs are identical.

(Skipping UX Journey / Architecture Diagram — change is text-only inside a prompt string, no flow or module edges change.)

Label Snapshot

  • Risk: risk: low
  • Size: size: XS
  • Scope: workflows
  • Module: workflows:defaults

Change Metadata

  • Change type: bug
  • Primary scope: workflows

Linked Issue

Validation Evidence (required)

bun run check:bundled    # up to date (36 commands, 20 workflows)
bun run type-check       # all packages green
bun run lint             # no errors, no warnings
bun run format:check     # clean
bun --filter '@archon/workflows' test   # 72 pass / 0 fail across workflows package

Also verified by invoking parseWorkflow() directly on the patched YAML:

  • Before: returned { error: { structureError: "Node 'generate-yaml' references unknown node '$other-node.output'" } }

  • After: returns { workflow: { name: "archon-workflow-builder", nodes: [5 nodes] } }

  • Evidence provided: test/log output above; direct loader round-trip (not committed).

  • Skipped: full monorepo bun run test. The change is a pure YAML-escape fix in documentation text; workflows package coverage is the meaningful surface.

Security Impact (required)

  • New permissions/capabilities? No
  • New external network calls? No
  • Secrets/tokens handling changed? No
  • File system access scope changed? No

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Database migration needed? No

Human Verification (required)

  • Verified scenarios: Direct parseWorkflow() invocation on the patched YAML returns a valid workflow definition (5 nodes). Type-check / lint / format / bundled-check / workflows-package tests all pass.
  • Edge cases checked: Grepped the file for every remaining $<ident>.output pattern — the only remaining un-bracketed references are to real sibling nodes (scan-codebase, extract-intent) and resolve correctly.
  • What was not verified: End-to-end workflow run (requires a live AI call; outside the scope of this loader fix).

Side Effects / Blast Radius (required)

  • Affected subsystems/workflows: archon-workflow-builder only.
  • Potential unintended effects: None anticipated — the change is inside documentation text inside a prompt string.
  • Guardrails/monitoring: The existing check:bundled step will catch any future bundle/source drift.

Rollback Plan (required)

  • Fast rollback: git revert <commit-sha>; no config or state to clean up.
  • Feature flags: none (bundled workflow).
  • Observable failure symptoms: archon workflow list reports the same dag_structure_invalid error as before.

Risks and Mitigations

  • Risk: The fix addresses the two concrete un-escaped placeholders but does not harden the loader against similar patterns slipping into documentation prompts in other workflows.
    • Mitigation: Out of scope for this PR. A follow-up could either (a) note the angle-bracket convention in CONTRIBUTING.md, or (b) have the validator skip scanning inside triple-backtick code fences or String.raw template literals. Neither is required to fix the immediate breakage.

Summary by CodeRabbit

  • Documentation

    • Updated workflow builder documentation examples with revised syntax for referencing node outputs in templates.
  • Chores

    • Refreshed bundled definitions for default workflows including PR validation and workflow builder tools with latest configuration updates.

github-actions Bot and others added 11 commits April 22, 2026 11:26
…be (coleam00#1359)

The pre-flight binary smoke does a bare `bun build --compile` — it
deliberately skips `scripts/build-binaries.sh` to stay fast. That means
packages/paths/src/bundled-build.ts retains its dev defaults, including
BUNDLED_IS_BINARY = false.

version.ts branches on BUNDLED_IS_BINARY: when true it returns the
embedded string; when false it calls getDevVersion(), which reads
package.json at `SCRIPT_DIR/../../../../package.json`. Inside a compiled
binary SCRIPT_DIR resolves under `$bunfs/root/`, the walk produces a CWD-
relative path that doesn't exist, and the smoke aborts with "Failed to
read version: package.json not found" — a false positive.

Hit during the 0.3.8 release attempt: the real Pi lazy-load fix was
working end-to-end; the smoke test was the only thing failing.

Use --help instead. It exercises the same module-init graph (so it still
catches the real failure modes the skill lists — Pi package.json init
crash, Bun --bytecode bugs, CJS wrapper issues, circular imports under
minify) but has no dev/binary branch, so no false positive.

Also add a longer comment block explaining why --help is preferred, so
this doesn't get "normalized" back to `version` by a future drive-by.
The brew path of /test-release runs `brew uninstall` in Phase 5 to leave the
system in its pre-test state. For operators using the dual-homebrew pattern
(renamed brew binary at `/opt/homebrew/bin/archon-stable` so it coexists with
a `bun link` dev `archon`), that uninstall wipes the Cellar dir the
`archon-stable` symlink points into → `archon-stable` becomes dangling →
`brew cleanup` sweeps it away on the next brew op. Next time the operator
wants stable, they have to manually re-run `brew-upgrade-archon`.

Fix: make the skill aware of `archon-stable` and restore it transparently.

- Phase 2 item 4: detect the `archon-stable` symlink before any brew op;
  export `ARCHON_STABLE_WAS_INSTALLED=yes` so Phase 5 knows to restore it.
  Only triggers for the brew path (curl-mac/curl-vps don't touch brew so
  they leave `archon-stable` alone).
- Phase 5 brew path: after `brew uninstall + untap`, if the flag was set,
  re-tap + re-install + rename. Verifies the restored `archon-stable`
  reports a version and warns (non-fatal) if the rename target is missing.
  Documents the tradeoff: the restored version is "whatever the tap ships
  today", not necessarily the pre-test version — usually that's what the
  operator wants (the release they just tested becomes stable) but the
  back-version-QA case requires a manual `brew-upgrade-archon` after.
- Phase 1 confirmation banner now mentions that `archon-stable` will be
  preserved so the operator isn't surprised by the reinstall during Phase 5.

No changes to curl-mac/curl-vps paths. No changes to Phase 4 test suite.
… a compiled binary (coleam00#1360)

v0.3.9 made Pi boot-safe: lazy-loading its imports meant `archon version`
no longer crashed on `@mariozechner/pi-coding-agent/dist/config.js`'s
module-init `readFileSync(getPackageJsonPath())`. That's what the
`provider-lazy-load.test.ts` regression test guards.

The fix was only half the problem though. When a Pi workflow actually
runs, sendQuery() triggers the dynamic import — and Pi's config.js
module-init fires then, hitting the exact same ENOENT on
`dirname(process.execPath)/package.json`. Discovered by running
`archon workflow run test-pi` against a locally-compiled 0.3.9 binary:

    [main] Failed: ENOENT: no such file or directory,
           open '/private/tmp/package.json'
        at readFileSync (unknown)
        at <anonymous> (/$bunfs/root/archon-providertest:184:7889)
        at init_config

Boot-safe ≠ runtime-safe. The `/test-release` run for 0.3.9 passed
because it only exercised `archon-assist` (Claude); Pi was never
actually invoked on the released binary.

Fix: before the dynamic `import('@mariozechner/pi-coding-agent')` in
sendQuery, install a PI_PACKAGE_DIR shim. Pi's config.js checks
`process.env.PI_PACKAGE_DIR` first in its `getPackageDir()` and
short-circuits the `dirname(process.execPath)` walk. We write a
minimal `{name, version, piConfig:{}}` stub to
`tmpdir()/archon-pi-shim/package.json` (idempotent — existsSync check)
and set the env var. Pi only reads `piConfig.name`, `piConfig.configDir`,
and `version` from that file, all optional, so the stub surface is
genuinely minimal.

Localized to PiProvider: no global state, no mutation of any shared
config, no upstream fork. Claude and Codex providers are unaffected
(their SDKs don't have this class of module-init side effect).

Verified end-to-end: built a compiled archon binary with this patch,
ran `archon workflow run test-pi --no-worktree` (Pi workflow with
model `anthropic/claude-haiku-4-5`), got a clean response. Before the
patch, same binary crashed at `dag_node_started` with the ENOENT above.

Regression test added: asserts `PI_PACKAGE_DIR` is set after sendQuery
hits even its fast-fail "no model" path. Together with the existing
`provider-lazy-load.test.ts` (boot-safe) this covers both halves.
… and Codex (coleam00#1361)

Both binary resolvers previously stopped at env-var + explicit config and
threw a "not found" error when neither was set. Users who followed the
upstream-recommended install flow (Anthropic's `curl install.sh` for
Claude, `npm install -g @openai/codex`) still had to manually set either
`CLAUDE_BIN_PATH` / `CODEX_BIN_PATH` or the corresponding config field
before any workflow could run.

Add a tier-N autodetect step between the explicit config tier and the
install-instructions throw. Purely additive: env and config still win
when set (precedence covered by new tests). On autodetect miss, the same
install-instructions error fires as before.

Claude probe list (verified against docs.claude.com "Uninstall Claude
Code → Native installation" section):
  - $HOME/.local/bin/claude            (mac/linux native installer)
  - $USERPROFILE\.local\bin\claude.exe (Windows native installer)

Codex probe list (verified against openai/codex README; npm global-
install puts the binary at `{npm_prefix}/bin/<name>` on POSIX,
`{npm_prefix}\<name>.cmd` on Windows):
  - $HOME/.npm-global/bin/codex   (user-set `npm config set prefix`)
  - /opt/homebrew/bin/codex       (mac arm64 with homebrew-node)
  - /usr/local/bin/codex          (mac intel / linux system node)
  - %APPDATA%\npm\codex.cmd       (Windows npm global default)
  - $HOME\.npm-global\codex.cmd   (Windows user-set prefix)

Not probed (explicit override still required):
  - Custom npm prefixes — `npm root -g` would need a subprocess per
    resolve, too much surface for a probe helper
  - `brew install --cask codex` — cask layout isn't a PATH binary
  - Manual GitHub Releases extracts — placement is user-determined
  - `~/.bun/bin/codex` — not documented in openai/codex README

Pi provider intentionally has no equivalent change: the Pi SDK is
bundled into the archon binary (no subprocess), so there's no "binary"
to resolve. Pi auth lives at `~/.pi/agent/auth.json` which the SDK
already finds by default, and the PR A shim (`PI_PACKAGE_DIR`) handles
the package-dir case via Pi's own documented escape hatch.

E2E verified: removed both config entries from ~/.archon/config.yaml,
rebuilt compiled binary, ran `archon workflow run archon-assist` and a
Codex workflow. Logs showed `source: 'autodetect'` for both, responses
returned cleanly.
…ry autodetect test

The native-installer autodetect test computed its expected path from
process.env.HOME, but the implementation uses node:os homedir(). On
Windows, HOME is typically unset (Windows uses USERPROFILE), so the
test fell back to '/Users/test' while the resolver returned the real
home dir — making the spy's path-equality check fail and breaking CI
on windows-latest.

Mirror the implementation by importing homedir() from node:os and
joining with node:path so the expected path matches the actual
platform-resolved home and separator.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…ver (coleam00#1365)

Reported in coleam00#1365: a user running `archon serve` with DISCORD_BOT_TOKEN
set but the "Message Content Intent" toggle disabled in the Discord
Developer Portal saw the entire server crash with `Used disallowed
intents`. Discord rejects the gateway connection (close code 4014) when
a privileged intent is requested without being enabled, and the
unguarded `await discord.start()` propagated the error all the way up,
taking the web UI down with it.

Wrap discord.start() in try/catch — log the failure with an actionable
hint (special-cased for the disallowed-intent error) and continue
running. Other adapters and the web UI come up regardless. The shutdown
handler already uses optional chaining (`discord?.stop()`) so nulling
discord after a failed start is safe.

Other adapters (Telegram, Slack, GitHub, Gitea, GitLab) have the same
unguarded-start pattern but are out of scope for this fix — addressing
them is tracked separately.

Also expanded the Discord setup docs with a caution callout that names
the exact error string and the new log event so users can grep for
both.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…0#1362)

* docs(script-nodes): add dedicated guide and teach the archon skill how to write them

Script nodes (script:) have been a first-class DAG node type since v0.3.3 but
were documented only as one-liners in CLAUDE.md and a CI smoke test. Claude
Code reading the archon skill would see "Four Node Types: command, prompt,
bash, loop" and reach for bash+node/python one-liners instead of a proper
script node — losing bun's --no-env-file isolation, uv's --with dependency
pins, and the .archon/scripts/ reuse story.

- New packages/docs-web/src/content/docs/guides/script-nodes.md mirroring the
  structure of loop-nodes.md / approval-nodes.md: schema, inline vs named
  dispatch, runtime/deps semantics, scripts directory precedence (repo > home),
  extension-runtime mapping, env isolation, stdout/stderr contract, patterns,
  and the explicit list of ignored AI fields.
- guides/authoring-workflows.md and guides/index.md updated so the new guide is
  discoverable from both the node-types table and the guides landing page.
- reference/variables.md calls out the no-shell-quote difference between
  bash: and script: substitution — a subtle correctness trap when adapting a
  bash pattern into a script node.
- Sidebar order bumped +1 on hooks/mcp-servers/skills/global-workflows/
  remotion-workflow to slot script-nodes at order 5 next to the other
  node-type guides.

- .claude/skills/archon/SKILL.md: replaces stale "Four Node Types" (which
  also silently omitted approval and cancel) with the accurate seven, with a
  script-node code block showing both inline and named patterns.
- references/workflow-dag.md: full Script Node section covering dispatch,
  resolution, deps, stdout contract, and the list of AI-only fields that are
  ignored; validation-rules list updated.
- references/dag-advanced.md and references/variables.md: retry-support line
  corrected; no-shell-quote note added.
- examples/dag-workflow.yaml: added an extract-labels TypeScript script node
  and updated the header comment.

* fix(docs): review follow-ups for script-node guide

- skills example: extract-labels was reading process.env.ISSUE_JSON which is
  never set; use String.raw`$fetch-issue.output` so the upstream bash node's
  JSON is actually consumed
- guides/script-nodes.md + skills/workflow-dag.md: idle_timeout is accepted
  but ignored on script (and bash) nodes — executeScriptNode only reads
  node.timeout. Clarify that script/bash use `timeout`, not idle_timeout
- archon-workflow-builder.yaml: prompt enumerated only bash/prompt/command/loop,
  so the AI builder could never propose script or approval nodes. Add both
  (plus examples + rule about script output not being shell-quoted) and
  regenerate bundled defaults
- book/dag-workflows.md + book/quick-reference.md + adapters/web.md: fill in
  the node-type references that were missing script, approval, and cancel.
  adapters/web.md also overclaimed "loop" in the palette — NodePalette.tsx
  only drags command/prompt/bash, so note that the other kinds are YAML-only
…nv gaps, add good-practices + troubleshooting (coleam00#1363)

* fix(skill/when): document the full `when:` operator set and compound expressions

The skill reference previously stated "operators: ==, != only" which is
materially wrong — the condition evaluator supports ==, !=, <, >, <=, >=
plus && / || compound expressions with && binding tighter than ||, plus
dot-notation JSON field access. An agent authoring a workflow from the
skill would think half the operators don't exist.

Replaces the single-sentence section with a structured reference covering:
- All six comparison operators (string and numeric modes)
- Compound expressions with precedence rules and short-circuit eval
- JSON dot notation semantics and failure modes
- The fail-closed rules in full (invalid expression, non-numeric side,
  missing field, skipped upstream)

Grounded in packages/workflows/src/condition-evaluator.ts.

* feat(skill): document Approval and Cancel node types

Approval and cancel nodes are first-class DAG node types (approval since the
workflow lifecycle work in coleam00#871, cancel as a guarded-exit primitive) but the
skill never described either one. An agent reading the skill and asked to
"add a review gate before implementation" or "stop the workflow if the input
is unsafe" would fall back to bash + exit 1, losing the proper semantics
(cancelled vs. failed, on_reject AI rework, web UI auto-resume).

Approval node coverage (references/workflow-dag.md, SKILL.md):
- Full configuration block with message, capture_response, on_reject
- The interactive: true workflow-level requirement for web UI delivery
- Approve/reject commands across all platforms (CLI, slash, natural
  language) and the capture_response → $node-id.output flow
- Ignored-fields list + the on_reject.prompt AI sub-node exception

Cancel node coverage (references/workflow-dag.md, SKILL.md):
- Single-field schema (cancel: "<reason>")
- Lifecycle: cancelled (not failed); in-flight parallel nodes stopped;
  no DAG auto-resume path
- The "cancel: vs bash-exit-1" decision rule (expected precondition miss
  vs. check itself failing)
- Two canonical patterns — upstream-classification gate, pre-expensive-step
  gate

Validation-rules list updated to enumerate approval/cancel constraints
(message non-empty, on_reject.max_attempts range 1-10, cancel reason
non-empty), plus a forward note that script: joins the mutually-exclusive
set once PR coleam00#1362 lands.

Placement in both files is after the Loop section and before the validation
section, so this commit stays additive with respect to PR coleam00#1362's Script
node insertion between Bash and Loop — rebase is clean.

* feat(skill): document workflow-level fields beyond name/provider/model

The skill's Schema section previously showed only name, description, provider,
and model at the workflow level — which is most of a stub. Agents asked to
"use the 1M-context Claude beta" or "run this under a network sandbox" or
"add a fallback model in case Opus rate-limits" had no way to discover
that any of these fields existed at the workflow level.

Adds a comprehensive Workflow-Level Fields section covering:
- Core: name, description, provider, model, interactive (with explicit
  callout that interactive: true is REQUIRED for approval/loop gates on
  web UI — a common footgun)
- Isolation: worktree.enabled for pin-on/pin-off (the only worktree field
  at workflow level; baseBranch/copyFiles/path/initSubmodules are
  config.yaml only, so a cross-reference points there)
- Claude SDK advanced: effort, thinking, fallbackModel, betas, sandbox,
  with explicit per-node-only exceptions (maxBudgetUsd, systemPrompt)
- Codex-specific: modelReasoningEffort (with note that it's NOT the same
  as Claude's effort — this has confused users), webSearchMode,
  additionalDirectories
- A complete worked example combining sandbox + approval + interactive

All fields cross-referenced against packages/workflows/src/schemas/workflow.ts
and packages/workflows/src/schemas/dag-node.ts.

* feat(skill/loop): document interactive loops and gate_message

Interactive loop nodes pause between iterations for human feedback via
/workflow approve — used by archon-piv-loop and archon-interactive-prd.
The skill's Loop Nodes section previously omitted both interactive: true
and gate_message entirely, so an agent writing a guided-refinement
workflow wouldn't know the feature exists or that gate_message is
required at parse time.

Adds:
- interactive and gate_message rows to the config table (marking
  gate_message as required when interactive: true — enforced by the
  loader's superRefine)
- A dedicated "Interactive Loops" subsection explaining the 6-step
  iterate-pause-approve-resume flow
- Explicit call-out that $LOOP_USER_INPUT populates ONLY on the first
  iteration of a resumed session — easy to miss and a common surprise
- Workflow-level interactive: true requirement for web UI delivery
  (loader warning otherwise) so the full-flow example is complete
- Note that until_bash substitution DOES shell-quote $nodeId.output
  (unlike script bodies) — called out since the audit surfaced this
  inconsistency

* fix(skill/cli): complete the CLI command reference with missing lifecycle commands

The CLI reference previously documented only list, run, cleanup, validate,
complete, version, setup, and chat — missing nearly every workflow
lifecycle command an agent needs to operate a paused, failed, or stuck
run. The interactive-workflows reference assumed these commands existed
without actually documenting them.

Adds full documentation for:
- archon workflow status — show running workflow(s)
- archon workflow approve <run-id> [comment] — resume approval gate
  (also populates $LOOP_USER_INPUT on interactive loops and the gate
  node's output when capture_response: true)
- archon workflow reject <run-id> [reason] — reject gate; cancels or
  triggers on_reject rework depending on node config
- archon workflow cancel <run-id> — terminate running/paused with
  in-flight subprocess kill
- archon workflow abandon <run-id> — mark stuck row cancelled without
  subprocess kill (for orphan-cleanup after server crashes — matches
  the coleam00#1216 precedent)
- archon workflow resume <run-id> [message] — force-resume specific
  run (auto-resume is default; this is for explicit override)
- archon workflow cleanup [days] — disk hygiene for old terminal runs
  (with explicit callout that it does NOT transition 'running' rows,
  a common confusion)
- archon workflow event emit — used inside loop prompts for state
  signalling; documented so agents don't invent their own mechanism
- archon continue <branch> [flags] [msg] — iterative-session entry
  point with --workflow and --no-context flags

Also:
- Adds --allow-env-keys flag to the `workflow run` flag table with
  audit-log context and the env-leak-gate remediation use case
- Adds an "Auto-resume without --resume" note disambiguating when
  --resume is needed vs. when auto-resume handles it
- Adds --include-closed flag to `isolation cleanup`, which was
  previously missing; converts the flag list to a structured table
- Explains the cancel/abandon distinction (live subprocess vs. orphan)

All grounded in packages/cli/src/commands/workflow.ts, continue.ts,
and isolation.ts.

* feat(skill/repo-init): add scripts/ and state/, three-path env model, per-project env injection

The repo-init reference was missing two first-class .archon/ directories
(scripts/ since v0.3.3, state/ since the workflow-state feature) and had
nothing to say about env — the coleam00#1 thing a user hits on first-run when
their repo has a .env file with API keys.

Directory tree updates:
- Adds .archon/scripts/ with the extension->runtime rule (.ts/.js -> bun,
  .py -> uv) so agents know where to put named scripts referenced by
  script: nodes.
- Adds .archon/state/ with explicit "always gitignore" callout — these
  are runtime artifacts, not source. Previously undocumented in the skill.
- Adds .archon/.env (repo-scoped Archon env) and distinguishes it from
  the target repo's top-level .env.
- Adds a "What each directory is for" list so the structure isn't just
  a tree with no narrative.

.gitignore guidance:
- state/ and .env added as must-gitignore (state/ matches CLAUDE.md and
  reference/archon-directories.md — skill was lagging).
- mcp/ demoted to conditional — gitignore only if you hardcode secrets.

New "Three-Path Env Model" section:
- ~/.archon/.env (trusted, user), <cwd>/.archon/.env (trusted, repo),
  <cwd>/.env (UNTRUSTED, target project — stripped from subprocess env).
- Precedence (override: true across archon-owned paths) and the
  observable [archon] loaded N keys / stripped K keys log lines so
  operators can verify what actually happened.
- Decision tree for where to put API keys vs. target-project env vs.
  things Archon shouldn't touch.
- Links to archon setup --scope home|project with --force for writing
  to the right file with timestamped backups.

New "Per-Project Env Injection" section:
- Documents both managed surfaces: .archon/config.yaml env: block
  (git-committed, $REF expansion) and Web UI Settings → Projects →
  Env Vars (DB-stored, never returned over API).
- Names every execution surface that receives the injected vars:
  Claude/Codex/Pi subprocess, bash: nodes, script: nodes, and direct
  codebase-scoped chat.
- Documents the env-leak gate with all 5 remediation paths so an agent
  hitting "Cannot register: env has sensitive keys" knows the options.

Grounded in CHANGELOG v0.3.7 (three-path env + setup flags), v0.3.0
(env-leak gate), and reference/security.md on the docs site.

* fix(skill/authoring-commands): correct override paths and add home-scoped commands

The file-location and discovery sections described an override layout that
does not match the actual resolver. It showed:

  .archon/commands/defaults/archon-assist.md  # Overrides the bundled

and claimed `.archon/commands/defaults/` was where repo-level overrides
lived. In fact the resolver (executor-shared.ts:152-200 + command-
validation.ts) walks `.archon/commands/` 1 level deep and uses basename
matching — putting `archon-assist.md` at the top of `.archon/commands/`
is the canonical way to override the bundled version. The `defaults/`
subfolder is a Archon-internal convention for shipping bundled defaults,
not a user-facing override pattern.

Also, home-scoped commands (`~/.archon/commands/`, shipped in v0.3.7)
were completely absent — agents authoring personal helpers wouldn't
know they could live at the user level and be shared across every repo.

Changes:
- File Location section now shows all three discovery scopes (repo,
  home, bundled) with precedence ordering and 1-level subfolder rules
- Duplicate-basename rule documented as a user error surface
- Discovery and Priority section rewritten with accurate 3-step lookup
  order — no more references to the nonexistent defaults/ override path
- Adds the Web UI "Global (~/.archon/commands/)" palette label note so
  users authoring helpers for the builder know what to expect

No code changes — this is a pure fix of stale/incorrect skill reference
material.

* feat(skill): add workflow good-practices and troubleshooting reference pages

Closes two gaps from the audit. The skill previously had zero guidance on
designing multi-node workflows (what to avoid, what to reach for first,
how to structure artifact chains) and zero guidance on where to look
when things go wrong (log paths, env-leak gate remediations, orphan-row
cleanup, resume semantics).

New references/good-practices.md (9 Good Practices + 7 Anti-Patterns):

- Use deterministic nodes (bash:/script:) for deterministic work, AI for
  reasoning — the single biggest quality lever
- output_format required whenever downstream when: reads a field — the
  most common source of "workflow silently routes wrong"
- trigger_rule: none_failed_min_one_success after conditional branches —
  the classic bug where all_success fails because a skipped when:-gated
  branch doesn't count as a success
- context: fresh requires artifacts for state passing — commands must
  explicitly "read $ARTIFACTS_DIR/..." when downstream of fresh
- Cheap models (haiku) for glue, strong for substance
- Workflow descriptions as routing affordances
- Validate (archon validate workflows) + smoke-run before shipping
- Artifact-chain-first design
- worktree.enabled: true for code-changing workflows (reversibility)
- Anti-patterns with before/after YAML examples for each (AI-for-tests,
  free-form when: matching, context: fresh without artifacts, long flat
  AI-node layers, secrets in YAML, retry on loop nodes, tiny
  max_iterations, missing workflow-level interactive:, tool-restricted
  MCP nodes)

New references/troubleshooting.md:

- Log location (~/.archon/workspaces/<owner>/<repo>/logs/<run-id>.jsonl)
  with jq recipes for common queries (last assistant message, failed
  events, full stream)
- Artifact location for cross-node handoff debugging
- 9 Common Failure Modes, each with root cause + concrete fix:
  - $BASE_BRANCH unresolvable
  - Env-leak gate (5 remediations)
  - Claude/Codex binary not found (compiled-binary-only)
  - "running" forever (AI working / orphan / idle_timeout)
  - Mid-workflow failure and auto-resume semantics
  - Approval gate missing on web UI (workflow-level interactive:)
  - MCP plugin connection noise (filtered by design)
  - Empty $nodeId.output / field access (4 causes)
- Diagnostic command cheat sheet (list, status, isolation list, validate,
  tail-log, --verbose, LOG_LEVEL=debug)
- Escalation protocol (version + validate + log tail + CHANGELOG + issue)

SKILL.md routing table now dispatches "Workflow good practices /
anti-patterns" and "Troubleshoot a failing / stuck workflow" to the new
references so an agent can find them without having to know they exist.

* docs(book): update node-types coverage from four to all seven

The book is the curated first-contact reading path (landing page → "Get
Started" → /book/). Both dag-workflows.md and quick-reference.md were
stuck on "four node types" — missing script, approval, and cancel. A user
reading the book as their first introduction would form an incomplete
mental model, then find three more node types in the reference section
later with no explanation of when they arrived.

book/dag-workflows.md:
- "four node types" → "seven node types. Exactly one mode field is
  required per node"
- Table now lists Command, Prompt, Bash, Script, Loop, Approval, Cancel
  with one-line "when to use" for each, and cross-links to the dedicated
  guide pages for Script / Loop / Approval
- New sections below the table for Script (inline + named examples with
  runtime and deps), Approval (with the interactive: true workflow-level
  note that's easy to miss), and Cancel (guarded-exit pattern) — keeping
  the existing narrative shape for Bash and Loop

book/quick-reference.md:
- Node Options table now includes script, approval, cancel rows
- agents row added (inline sub-agents, Claude-only)
- New "Script-specific fields" and "Approval-specific fields" subsections
  so the cheat-sheet is actually complete rather than pointing users
  elsewhere for the required constraints
- Retry row callout that loop nodes hard-error on retry — previously
  omitted
- bash timeout note widened to cover script timeout (same semantics)

Both files are docs-web content; the CI build on the docs-script-nodes
PR (coleam00#1362) previously validated the Starlight build path with a similar
table addition, so this should render clean.

* fix(skill/cli): remove nonexistent \`archon workflow cancel\`, fix workflow status jq recipe

Two accuracy issues from the PR code-reviewer (comment 4311243858).

C1: \`archon workflow cancel <run-id>\` does NOT exist as a CLI subcommand.
The switch at packages/cli/src/cli.ts:318-485 dispatches on list / run /
status / resume / abandon / approve / reject / cleanup / event — running
\`archon workflow cancel\` hits the default case and exits with "Unknown
workflow subcommand: cancel" (cli.ts:478-484). Active cancellation is
only available via:
  - /workflow cancel <run-id> chat slash command (all platforms)
  - Cancel button on the Web UI dashboard
  - POST /api/workflows/runs/{runId}/cancel REST endpoint

cli-commands.md: removed the \`### archon workflow cancel <run-id>\`
subsection; kept the \`abandon\` subsection but made it explicit that
abandon does NOT kill a subprocess. Added a call-out box at the bottom
of the abandon section explaining where to go for actual cancellation.

troubleshooting.md "running forever" section: split the original
cancel-vs-abandon advice into three bullets — Web UI / CLI abandon (for
orphans, no subprocess kill) / chat \`/workflow cancel\` (for live runs
that need interruption). Added an explicit "there is no archon workflow
cancel CLI subcommand" parenthetical since the wrong command was being
suggested in flow.

I1: the \`archon workflow list --json\` diagnostic used an incorrect jq
filter. workflow list's --json output (workflow.ts:185-219) has shape
{ workflows: [{ name, description, provider?, model?, ... }], errors: [...] }
with no \`runs\` field — \`jq '.workflows[] | select(.runs)'\` returns empty
unconditionally. Replaced with \`archon workflow status --json | jq '.runs[]'\`,
which matches the actual shape of workflowStatusCommand at
workflow.ts:852+ ({ runs: WorkflowRun[] }). Also tightened the narration
to distinguish JSON from human-readable status output.

No change to the commit history in this PR — these are follow-up fixes
to claims I introduced in earlier commits of this branch (f10b989 for
C1, 66d2b86 for I1).

* fix(skill): remove env-leak gate references (feature was removed in provider extraction)

C2 from the PR code-reviewer (comment 4311243858). The pre-spawn env-leak
gate was removed from the codebase during the provider-extraction refactor
— see TODO(coleam00#1135) at packages/providers/src/claude/provider.ts:908. Zero
hits for --allow-env-keys / allowEnvKeys / allow_env_keys / allow_target_repo_keys
across packages/. The CLI's parseArgs (cli.ts:182-208) has no
--allow-env-keys option, and because parseArgs uses strict: false, an
unknown --allow-env-keys would be silently ignored rather than error.

What remains accurate and is NOT touched:
- Three-Path Env Model section (user/repo archon-owned envs are loaded;
  target repo <cwd>/.env keys are stripped from process.env at boot)
  still correctly describes current behavior, grounded in
  packages/paths/src/strip-cwd-env.ts + env-integration.test.ts
- Per-Project Env Injection section (Option 1: .archon/config.yaml env:
  block; Option 2: Web UI Settings → Projects → Env Vars) is unchanged —
  both remain the sanctioned way to get env vars into subprocesses

Removed claims (all three files):
- cli-commands.md: --allow-env-keys flag row in the workflow run flags
  table
- repo-init.md: the "Env-leak gate" subsection at the end of Per-Project
  Env Injection listing 5 remediations (all of which reference UI/CLI/
  config surfaces that don't exist). Replaced with a succinct callout
  that explains the actual current behavior — target repo .env keys are
  stripped, workflows that need those values should use managed
  injection — so the reader still gets the "where to put my env vars"
  answer
- troubleshooting.md: the "Cannot register: codebase has sensitive env
  keys" section (error message that can no longer be emitted)

If the env-leak gate is ever resurrected per TODO(coleam00#1135), the docs can be
re-added then. The CHANGELOG v0.3.0 entry describing the gate is a
historical record of past behavior and does not need to be rewritten.

* fix(skill/troubleshooting): correct JSONL event type names and field name

C3 from the PR code-reviewer (comment 4311243858). The troubleshooting
reference's event-types table used _started / _completed / _failed
suffixes, but packages/workflows/src/logger.ts:19-30 shows the actual
WorkflowEvent.type enum is:

  workflow_start | workflow_complete | workflow_error |
  assistant | tool | validation |
  node_start | node_complete | node_skipped | node_error

The second jq recipe also queried `.event` but the discriminator is `.type`.

Fixes:
- Event table: renamed columns (_started → _start, _completed → _complete,
  _failed → _error). Explicitly called out the field name as `type` so the
  reader knows what jq selector to use
- Replaced the "tool_use / tool_result" row with a single `tool` row and
  listed its actual payload fields (tool_name, tool_input, duration_ms,
  tokens) — tool_use/tool_result are SDK message kinds that appear within
  the AI stream, not top-level log event types
- Added a `validation` row (was missing; it's emitted by workflow-level
  validation calls with `check` and `result` fields)
- Removed `retry_attempt` row — this event type is not emitted to the
  JSONL file. Retry bookkeeping goes through pino logs, not the workflow
  log file
- Added an explicit callout that loop_iteration_started /
  loop_iteration_completed (and other emitter-only events) go through
  the workflow event emitter + DB workflow_events table, NOT the JSONL
  file. Pointed readers to the DB or Web UI for loop-level detail. This
  distinguishes the two parallel event systems — easy to conflate
  (store.ts:11-17 uses _started/_completed/_failed for the DB side,
  logger.ts uses _start/_complete/_error for JSONL)
- Fixed the "all failed events" jq recipe: .event → .type and _failed → _error
- Minor cleanup: the inline "tool_use events" mention in the "running
  forever" section said the wrong event name — updated to "tool or
  assistant events in the tail"

Grounded in packages/workflows/src/logger.ts (canonical JSONL event
shape) and packages/workflows/src/store.ts (the parallel DB event
naming, which the reviewer correctly flagged as different and worth
keeping distinct).

* fix(skill): two stragglers from the code-reviewer audit

Cleanup of two references that slipped through the earlier C1 and C3 fixes:

- references/troubleshooting.md:126: \`node_failed\` → \`node_error\`
  (the "Node output is empty" diagnostics section references the JSONL
  log, which uses the logger.ts enum — not the DB workflow_events table
  which does use \`node_failed\`). The C3 fix corrected the event table
  and one jq recipe but missed this inline mention.

- references/interactive-workflows.md:106: removed \`archon workflow
  cancel <run-id>\` (nonexistent CLI subcommand) from the
  troubleshooting bullet. This was pre-existing before the hardening
  PR but fell within the C1 remediation scope. Replaced with the
  correct triage: reject (approval gate only) vs abandon (orphan
  cleanup, no subprocess kill) vs chat /workflow cancel (actual
  subprocess termination).

Grounded in the same sources as the earlier C1/C3 commits:
packages/cli/src/cli.ts:318-485 (no cancel case) and
packages/workflows/src/logger.ts:19-30 (JSONL type enum).

* feat(skill): point to archon.diy as the canonical docs source

The skill had no reference to archon.diy (the live docs site built from
packages/docs-web/). Several reference files said "see the docs site"
without naming the URL, leaving the agent to guess or grep the repo for
the hostname. An agent with the skill loaded should know that when the
distilled reference pages don't cover a case, the full canonical docs
are one WebFetch away.

SKILL.md: new "Richer Context: archon.diy" section between Routing and
Running Workflows. Covers:
- When to reach for the live docs (longer examples, tutorial framing,
  features the skill only mentions in passing, "where's that
  documented?" user questions)
- URL map — 13 starting points covering getting-started, book (tutorial
  series), guides/ (authoring + per-node-type + per-node-feature),
  reference/ (variables, CLI, security, architecture, configuration,
  troubleshooting), adapters/, deployment/
- Precedence: skill refs first (context-cheap, tuned for agents), docs
  site as escalation. Prevents agents defaulting to WebFetch when a
  local skill ref already covers the answer

Also upgrades the 5 existing generic "docs site" mentions across
reference files to concrete archon.diy URLs with anchor fragments where
helpful:
- good-practices.md: Inline sub-agents pattern → archon.diy/guides/
  authoring-workflows/#inline-sub-agents
- troubleshooting.md: "Install page on the docs site" → archon.diy/
  getting-started/installation/
- workflow-dag.md: "Workflow Description Best Practices" → anchor link;
  sandbox schema reference → archon.diy/guides/authoring-workflows/
  #claude-sdk-advanced-options
- repo-init.md: Security Model reference → archon.diy/reference/
  security/#target-repo-env-isolation (deep-link into the section that
  covers the <cwd>/.env strip behavior)

URL source of truth: astro.config.mjs:5 (site: 'https://archon.diy').
URL structure mirrors packages/docs-web/src/content/docs/<section>/
<page>.md — verified by the 62 pages the docs build produces.
…#1395)

Anthropic's Opus 4.7 landed 2026-04-16; on the Anthropic API, opus /
opus[1m] now resolve to 4.7 with a 1M context window at standard
pricing. Using the alias instead of the hard-pinned claude-opus-4-6[1m]
lets bundled default workflows auto-track the recommended Opus version.

No explicit effort is set, so nodes inherit the per-model default
(xhigh on 4.7, high on 4.6).
Two `$nodeId.output`-style references inside documentation prompts were
written without angle brackets, so the DAG structure validator treated
them as real (and unresolvable) upstream node references and rejected
the whole workflow with "Node 'generate-yaml' references unknown node".

Rewrite them as `$<other-node>.output` and `$<nodeId>.output` to match
the placeholder convention already used on lines 126, 135, 157, 161,
169, and 170 of the same file.

Regenerate the embedded bundle (bundled-defaults.generated.ts).
@coderabbitai

coderabbitai Bot commented Apr 24, 2026

Copy link
Copy Markdown
📝 Walkthrough

Walkthrough

Updated workflow builder documentation to escape output reference placeholders using angle-bracket syntax ($<nodeId>.output) to prevent false DAG validation errors during workflow loading, and regenerated bundled workflow definitions to reflect these corrections.

Changes

Cohort / File(s) Summary
Workflow Builder Documentation
.archon/workflows/defaults/archon-workflow-builder.yaml
Updated embedded example placeholders from $nodeId.output to $<nodeId>.output syntax in script node documentation and sample code to match the correct templating format for unquoted output variables.
Bundled Defaults Regeneration
packages/workflows/src/defaults/bundled-defaults.generated.ts
Regenerated bundled workflow string constants for archon-validate-pr and archon-workflow-builder in the BUNDLED_WORKFLOWS export to reflect updated YAML definitions and corrected placeholder syntax.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • PR #1263: Introduced the bundled-defaults generator that creates and maintains the bundled-defaults.generated.ts file; this PR regenerates output from that generator.
  • PR #1362: Concurrently modifies the workflow builder YAML and bundled defaults, aligning example placeholders and extraction rules to the correct variable substitution format.

Poem

🐰 Angle brackets guard our placeholders true,
No more false node refs breaking the queue,
$<nodeId>.output now safely rests,
DAGs validate well, passing all tests!

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately captures the main change: escaping example placeholders in the archon-workflow-builder workflow to fix DAG validation failures.
Description check ✅ Passed The description comprehensively follows the template with clear problem statement, validation evidence, security/compatibility confirmations, and rollback plan provided.
Linked Issues check ✅ Passed The PR directly addresses issue #1401 by rewriting two undescaped example placeholders using angle-bracket convention, exactly matching the stated objective to fix the DAG-structure validation failure.
Out of Scope Changes check ✅ Passed All changes are scoped to fixing the identified problem: only the YAML documentation examples and generated bundled defaults were modified, with no unrelated alterations.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/workflows/src/defaults/bundled-defaults.generated.ts`:
- Line 77: The docs incorrectly present the escaped placeholder form
`$<nodeId>.output` as the runtime variable syntax; update the wording and
examples so the escaped form is only shown as a literal/example (e.g.,
explicitly state "literal example: `$<nodeId>.output`") and everywhere the
runtime variable is described or used (Variable Reference, prompt/template
sections, rules, and examples) show the actual runtime reference syntax without
angle brackets (e.g., `$node-id.output` or `$scan-codebase.output`), and adjust
the Generate YAML and Save-or-report prompts and comments (references:
$<nodeId>.output in Variable Reference, generate-yaml prompt block,
save-or-report instructions, and examples inside node script hints) so generated
workflows will use the resolvable `$node-id.output` form instead of the escaped
placeholder.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 8c95823f-f1da-426e-8131-6e32f2eee928

📥 Commits

Reviewing files that changed from the base of the PR and between ad13d83 and 1484d93.

📒 Files selected for processing (2)
  • .archon/workflows/defaults/archon-workflow-builder.yaml
  • packages/workflows/src/defaults/bundled-defaults.generated.ts

"archon-test-loop-dag": "name: archon-test-loop-dag\ndescription: |\n Use when: User explicitly says \"test-loop-dag\" or \"run test-loop-dag\".\n IMPORTANT: This is a DAG workflow with a loop node that iterates until completion.\n NOT for: General testing questions or debugging.\n Does: Initializes a counter, iterates until it reaches 3, then reports completion.\n\nnodes:\n - id: setup\n bash: |\n echo \"0\" > .archon/test-loop-dag-counter.txt\n echo \"Counter initialized to 0\"\n\n - id: loop-counter\n depends_on: [setup]\n loop:\n prompt: |\n You are testing the loop node functionality within a DAG workflow.\n\n ## Your Task\n\n 1. Read the file `.archon/test-loop-dag-counter.txt`\n 2. Parse the current counter value\n 3. Increment it by 1\n 4. Write the new value back to the file\n 5. Report the current iteration\n\n ## User Intent\n\n $USER_MESSAGE\n\n ## Completion Criteria\n\n - If the counter reaches 3 or higher, output: <promise>COMPLETE</promise>\n - Otherwise, just report your progress and end normally\n\n ## Important\n\n Be concise. Just do the task and report the counter value.\n until: COMPLETE\n max_iterations: 5\n fresh_context: false\n\n - id: report\n depends_on: [loop-counter]\n prompt: |\n The loop counter test has completed. The loop node output was:\n\n $loop-counter.output\n\n Read `.archon/test-loop-dag-counter.txt` and confirm the final counter value.\n Report: \"Test loop DAG completed successfully. Final counter: {value}\"\n",
"archon-validate-pr": "name: archon-validate-pr\ndescription: |\n Use when: User wants a thorough PR validation that tests both main (bug present) and feature branch (bug fixed).\n Triggers: \"validate PR\", \"validate pr #123\", \"test this PR\", \"verify PR\", \"full PR validation\",\n \"validate pull request\", \"test PR end-to-end\".\n Does: Fetches PR info -> finds free ports -> parallel code review (main vs feature) ->\n E2E test on main (reproduce bug) -> E2E test on feature (verify fix) -> final verdict report.\n NOT for: Quick code-only reviews (use archon-smart-pr-review), fixing issues, general exploration.\n\n This workflow is designed for running in parallel — each instance finds its own free ports\n to avoid conflicts. Produces artifacts in $ARTIFACTS_DIR/ and posts a validation report.\n\nprovider: claude\nmodel: opus\n\nnodes:\n # ═══════════════════════════════════════════════════════════════\n # PHASE 1: SETUP — Fetch PR info and allocate ports\n # ═══════════════════════════════════════════════════════════════\n\n - id: fetch-pr\n bash: |\n # Extract PR number from arguments\n PR_NUMBER=$(echo \"$ARGUMENTS\" | grep -oE '/pull/[0-9]+' | grep -oE '[0-9]+' | head -1)\n # Fallback: extract first number if no URL path found (e.g., \"validate PR 42\")\n if [ -z \"$PR_NUMBER\" ]; then\n PR_NUMBER=$(echo \"$ARGUMENTS\" | grep -oE '[0-9]+' | head -1)\n fi\n if [ -z \"$PR_NUMBER\" ]; then\n # Try getting PR from current branch\n PR_NUMBER=$(gh pr view --json number -q '.number' 2>/dev/null)\n fi\n\n if [ -z \"$PR_NUMBER\" ]; then\n echo \"ERROR: No PR number found in arguments: $ARGUMENTS\"\n exit 1\n fi\n\n echo \"$PR_NUMBER\" > \"$ARTIFACTS_DIR/.pr-number\"\n\n # Fetch full PR details\n gh pr view \"$PR_NUMBER\" --json number,title,body,url,headRefName,baseRefName,files,additions,deletions,changedFiles,state,author,labels,isDraft\n\n - id: find-ports\n bash: |\n # Use Bun to let the OS pick truly free ports (cross-platform: Linux, macOS, Windows)\n BACKEND_PORT=$(bun -e \"const s = Bun.serve({port: 0, fetch: () => new Response('')}); console.log(s.port); s.stop()\")\n FRONTEND_PORT=$(bun -e \"const s = Bun.serve({port: 0, fetch: () => new Response('')}); console.log(s.port); s.stop()\")\n\n echo \"$BACKEND_PORT\" > \"$ARTIFACTS_DIR/.backend-port\"\n echo \"$FRONTEND_PORT\" > \"$ARTIFACTS_DIR/.frontend-port\"\n\n echo \"BACKEND_PORT=$BACKEND_PORT\"\n echo \"FRONTEND_PORT=$FRONTEND_PORT\"\n\n - id: resolve-paths\n bash: |\n # Resolve canonical repo path (main branch) vs worktree path (feature branch)\n CANONICAL_REPO=$(git rev-parse --path-format=absolute --git-common-dir 2>/dev/null | sed 's|/\\.git$||')\n WORKTREE_PATH=$(pwd)\n FEATURE_BRANCH=$(git branch --show-current)\n\n # Get PR branch info\n PR_NUMBER=$(cat \"$ARTIFACTS_DIR/.pr-number\")\n PR_HEAD=$(gh pr view \"$PR_NUMBER\" --json headRefName -q '.headRefName')\n PR_BASE=$(gh pr view \"$PR_NUMBER\" --json baseRefName -q '.baseRefName')\n\n echo \"$CANONICAL_REPO\" > \"$ARTIFACTS_DIR/.canonical-repo\"\n echo \"$WORKTREE_PATH\" > \"$ARTIFACTS_DIR/.worktree-path\"\n echo \"$FEATURE_BRANCH\" > \"$ARTIFACTS_DIR/.feature-branch\"\n echo \"$PR_HEAD\" > \"$ARTIFACTS_DIR/.pr-head\"\n echo \"$PR_BASE\" > \"$ARTIFACTS_DIR/.pr-base\"\n\n echo \"CANONICAL_REPO=$CANONICAL_REPO\"\n echo \"WORKTREE_PATH=$WORKTREE_PATH\"\n echo \"FEATURE_BRANCH=$FEATURE_BRANCH\"\n echo \"PR_HEAD=$PR_HEAD\"\n echo \"PR_BASE=$PR_BASE\"\n depends_on: [fetch-pr]\n\n # ═══════════════════════════════════════════════════════════════\n # PHASE 2: CODE REVIEW — Parallel analysis of main vs feature\n # ═══════════════════════════════════════════════════════════════\n\n - id: code-review-main\n command: archon-validate-pr-code-review-main\n depends_on: [fetch-pr, resolve-paths]\n context: fresh\n\n - id: code-review-feature\n command: archon-validate-pr-code-review-feature\n depends_on: [fetch-pr, resolve-paths, code-review-main]\n context: fresh\n\n # ═══════════════════════════════════════════════════════════════\n # PHASE 3: E2E TESTING — Sequential (after code reviews finish)\n # ═══════════════════════════════════════════════════════════════\n\n - id: classify-testability\n prompt: |\n You are a PR testability classifier. Determine whether this PR's changes can be\n validated via browser E2E testing, or if it requires code-review-only validation.\n\n ## PR Details\n\n $fetch-pr.output\n\n ## Rules\n\n - **e2e_testable**: Changes affect the Web UI (components, hooks, styles, API routes\n that serve the frontend, SSE streaming, layout, user-visible behavior). These can be\n validated by starting Archon and using agent-browser to interact with the UI.\n - **code_review_only**: Changes are purely backend logic, CLI-only, workflow engine,\n database schemas, git operations, build tooling, tests, documentation, or other\n non-UI code. No visual validation possible.\n\n Consider: even if a change is backend, if it affects what the frontend displays\n (e.g., API response format changes, SSE event changes), it IS e2e_testable.\n depends_on: [fetch-pr]\n model: haiku\n allowed_tools: []\n output_format:\n type: object\n properties:\n testable:\n type: string\n enum: [\"e2e_testable\", \"code_review_only\"]\n reasoning:\n type: string\n test_plan:\n type: string\n required: [testable, reasoning, test_plan]\n\n - id: e2e-test-main\n command: archon-validate-pr-e2e-main\n depends_on: [classify-testability, find-ports, resolve-paths, code-review-main, code-review-feature]\n when: \"$classify-testability.output.testable == 'e2e_testable'\"\n context: fresh\n idle_timeout: 1800000\n\n - id: e2e-test-feature\n command: archon-validate-pr-e2e-feature\n depends_on: [e2e-test-main, find-ports, resolve-paths]\n when: \"$classify-testability.output.testable == 'e2e_testable'\"\n context: fresh\n idle_timeout: 1800000\n\n # ═══════════════════════════════════════════════════════════════\n # PHASE 4: FINAL REPORT — Synthesize all findings\n # ═══════════════════════════════════════════════════════════════\n\n - id: cleanup-processes\n bash: |\n # Safety net: kill any orphaned processes from E2E testing\n # This runs after E2E nodes complete (or timeout/fail) to prevent process accumulation\n BACKEND_PORT=$(cat \"$ARTIFACTS_DIR/.backend-port\" 2>/dev/null | tr -d '\\n')\n FRONTEND_PORT=$(cat \"$ARTIFACTS_DIR/.frontend-port\" 2>/dev/null | tr -d '\\n')\n\n if [ -z \"$BACKEND_PORT\" ] || [ -z \"$FRONTEND_PORT\" ]; then\n echo \"No port files found — skipping cleanup\"\n exit 0\n fi\n\n echo \"Cleaning up ports $BACKEND_PORT and $FRONTEND_PORT...\"\n\n # Kill by all recorded PID files\n for pidfile in \"$ARTIFACTS_DIR\"/.e2e-*-pid; do\n if [ -f \"$pidfile\" ]; then\n PID=$(cat \"$pidfile\" | tr -d '\\n')\n echo \"Killing PID $PID from $pidfile\"\n kill \"$PID\" 2>/dev/null || taskkill //F //T //PID \"$PID\" 2>/dev/null || true\n fi\n done\n\n # Kill by port (cross-platform fallback)\n for PORT in $BACKEND_PORT $FRONTEND_PORT; do\n fuser -k \"$PORT/tcp\" 2>/dev/null || true\n lsof -ti:\"$PORT\" 2>/dev/null | xargs kill -9 2>/dev/null || true\n netstat -ano 2>/dev/null | grep \":$PORT \" | grep LISTENING | awk '{print $5}' | sort -u | while read pid; do\n taskkill //F //T //PID \"$pid\" 2>/dev/null || true\n done\n done\n\n # pkill fallback: catch processes that escaped PID/port cleanup\n pkill -f \"PORT=$BACKEND_PORT.*bun\" 2>/dev/null || true\n pkill -f \"vite.*port.*$FRONTEND_PORT\" 2>/dev/null || true\n\n # Close this workflow's browser session only (scoped by session ID)\n BROWSER_SESSION=$(cat \"$ARTIFACTS_DIR/.browser-session\" 2>/dev/null | tr -d '\\n')\n if [ -n \"$BROWSER_SESSION\" ]; then\n agent-browser --session \"$BROWSER_SESSION\" close 2>/dev/null || true\n fi\n\n # Remove main E2E worktree if it still exists (safety net)\n CANONICAL_REPO=$(cat \"$ARTIFACTS_DIR/.canonical-repo\" 2>/dev/null | tr -d '\\n')\n MAIN_E2E_PATH=$(cat \"$ARTIFACTS_DIR/.e2e-main-worktree\" 2>/dev/null | tr -d '\\n')\n if [ -n \"$MAIN_E2E_PATH\" ] && [ -n \"$CANONICAL_REPO\" ] && [ -d \"$MAIN_E2E_PATH\" ]; then\n echo \"Removing leftover main E2E worktree: $MAIN_E2E_PATH\"\n git -C \"$CANONICAL_REPO\" worktree remove \"$MAIN_E2E_PATH\" --force 2>/dev/null || rm -rf \"$MAIN_E2E_PATH\"\n fi\n\n sleep 1\n echo \"Process cleanup complete\"\n depends_on: [e2e-test-main, e2e-test-feature]\n trigger_rule: all_done\n\n - id: final-report\n command: archon-validate-pr-report\n depends_on: [code-review-main, code-review-feature, e2e-test-main, e2e-test-feature, classify-testability, cleanup-processes]\n trigger_rule: all_done\n context: fresh\n",
"archon-workflow-builder": "name: archon-workflow-builder\ndescription: |\n Use when: User wants to create a new custom workflow for their project.\n Triggers: \"build me a workflow\", \"create a workflow\", \"generate a workflow\",\n \"new workflow\", \"make a workflow for\", \"workflow builder\".\n Does: Scans codebase -> extracts intent (JSON) -> generates YAML -> validates -> saves.\n NOT for: Editing existing workflows or creating non-workflow files.\n\nnodes:\n - id: scan-codebase\n bash: |\n echo \"=== Existing Commands ===\"\n if [ -d \".archon/commands\" ]; then\n find .archon/commands -type f -name \"*.md\" 2>/dev/null | head -30\n else\n echo \"(no .archon/commands/ directory)\"\n fi\n\n echo \"\"\n echo \"=== Existing Workflows ===\"\n if [ -d \".archon/workflows\" ]; then\n find .archon/workflows -type f \\( -name \"*.yaml\" -o -name \"*.yml\" \\) 2>/dev/null | head -30\n else\n echo \"(no .archon/workflows/ directory)\"\n fi\n\n echo \"\"\n echo \"=== Package Info ===\"\n if [ -f \"package.json\" ]; then\n grep -E '\"name\"|\"scripts\"' package.json | head -10\n else\n echo \"(no package.json)\"\n fi\n\n echo \"\"\n echo \"=== Project Context (CLAUDE.md first 50 lines) ===\"\n if [ -f \"CLAUDE.md\" ]; then\n head -50 CLAUDE.md\n else\n echo \"(no CLAUDE.md)\"\n fi\n\n - id: extract-intent\n prompt: |\n You are a workflow design classifier. Given a user's description of what they want\n a workflow to do, extract structured intent.\n\n ## User's Request\n $ARGUMENTS\n\n ## Codebase Context\n $scan-codebase.output\n\n ## Instructions\n\n Analyze the user's request and the existing codebase to determine:\n 1. A kebab-case workflow name (e.g., \"lint-and-test\", \"deploy-staging\")\n 2. A description following the Archon pattern (Use when / Triggers / Does / NOT for)\n 3. Trigger phrases the router should match\n 4. A list of proposed nodes with their types and purposes\n 5. Whether this should be a simple DAG or include a loop node\n\n Be specific and concrete. Each proposed node should have a clear type\n (bash, prompt, command, script, loop, or approval) and a one-line\n description of what it does.\n model: haiku\n allowed_tools: []\n output_format:\n type: object\n properties:\n workflow_name:\n type: string\n description:\n type: string\n trigger_phrases:\n type: string\n proposed_nodes:\n type: string\n execution_mode:\n type: string\n enum: [\"dag\", \"loop\"]\n required: [workflow_name, description, trigger_phrases, proposed_nodes, execution_mode]\n depends_on: [scan-codebase]\n\n - id: generate-yaml\n prompt: |\n You are an Archon workflow author. Generate a complete, valid workflow YAML file\n based on the structured intent provided.\n\n ## Intent\n - **Name**: $extract-intent.output.workflow_name\n - **Description**: $extract-intent.output.description\n - **Trigger Phrases**: $extract-intent.output.trigger_phrases\n - **Proposed Nodes**: $extract-intent.output.proposed_nodes\n - **Execution Mode**: $extract-intent.output.execution_mode\n\n ## Original User Request\n $ARGUMENTS\n\n ## Archon Workflow YAML Schema Reference\n\n A workflow YAML file has this structure:\n\n ```yaml\n name: workflow-name\n description: |\n Use when: ...\n Triggers: ...\n Does: ...\n NOT for: ...\n\n # Optional top-level settings:\n # provider: claude (or codex)\n # model: sonnet (or haiku, opus, etc.)\n # interactive: true (forces foreground execution in web UI)\n\n nodes:\n - id: node-id-kebab-case\n # Choose ONE of: prompt, bash, command, script, loop, approval\n\n # --- prompt node (AI-executed) ---\n prompt: |\n Instructions for the AI...\n # Optional: model, allowed_tools, denied_tools, output_format, context, idle_timeout\n\n # --- bash node (shell script, no AI, stdout = $<nodeId>.output) ---\n bash: |\n #!/bin/bash\n set -e\n echo \"result\"\n\n # --- command node (references a .archon/commands/ file) ---\n command: command-name\n\n # --- script node (TypeScript via bun, or Python via uv — no AI, stdout = $<nodeId>.output) ---\n # Use for deterministic data transforms the shell would mangle (JSON parsing, etc.)\n script: |\n const raw = String.raw`$other-node.output`;\n const data = JSON.parse(raw);\n console.log(JSON.stringify({ count: data.items.length }));\n runtime: bun # required: 'bun' (.ts/.js) or 'uv' (.py)\n # deps: [requests] # uv only\n # Or reference a named script in .archon/scripts/:\n # script: extract-labels # no extension; bun resolves .ts/.js, uv resolves .py\n\n # --- loop node (iterative AI execution) ---\n loop:\n prompt: |\n Instructions repeated each iteration...\n until: COMPLETION_SIGNAL\n max_iterations: 10\n fresh_context: true # optional: reset context each iteration\n\n # --- approval node (human gate — pauses workflow) ---\n approval:\n message: \"Review the plan above. Approve to continue.\"\n # capture_response: true # store reviewer comment as $<nodeId>.output\n\n # Common options for all node types:\n depends_on: [other-node-id] # DAG edges\n when: \"$<other-node>.output == 'value'\" # conditional execution\n trigger_rule: all_success # all_success | one_success | all_done\n timeout: 120000 # ms, for bash and script nodes\n ```\n\n ## Variable Reference\n - `$ARGUMENTS` — user's input text\n - `$ARTIFACTS_DIR` — pre-created directory for workflow artifacts\n - `$<nodeId>.output` — stdout from a bash/script node or AI response from a prompt node\n - `$<nodeId>.output.field` — JSON field from a node with output_format\n - `$BASE_BRANCH` — base git branch\n\n ## Rules\n 1. The `name:` field MUST match: $extract-intent.output.workflow_name\n 2. The `description:` MUST follow the \"Use when / Triggers / Does / NOT for\" pattern\n 3. Every node MUST have a unique kebab-case `id`\n 4. Use `depends_on` to define execution order\n 5. Use `bash` nodes for deterministic shell operations (file checks, git commands, installs)\n 6. Use `script` nodes for typed data transforms (TypeScript JSON parsing, Python with deps) — stdout is captured as output, stderr is forwarded as a warning. $nodeId.output is NOT shell-quoted in script bodies — parse with JSON.parse / json.loads, not shell interpolation\n 7. Use `prompt` nodes for AI reasoning tasks\n 8. Use `approval` nodes to pause for human review at risky gates (plan→execute boundary, destructive actions)\n 9. Use `output_format` on prompt nodes when downstream nodes need structured data\n 10. Use `allowed_tools: []` on classification/analysis nodes that don't need tools\n 11. Use `denied_tools: [Edit, Bash]` when a node should only use Write (not edit existing files)\n 12. Prefer `model: haiku` for simple classification tasks to save cost\n\n ## Output\n\n Write the complete workflow YAML to: `$ARTIFACTS_DIR/generated-workflow.yaml`\n\n Use the Write tool. Do NOT use Edit or Bash. The file must be valid YAML and follow\n all the patterns above.\n denied_tools: [Edit, Bash]\n depends_on: [extract-intent]\n\n - id: validate-yaml\n bash: |\n FILE=\"$ARTIFACTS_DIR/generated-workflow.yaml\"\n\n if [ ! -f \"$FILE\" ]; then\n echo \"ERROR: generated-workflow.yaml not found at $FILE\"\n exit 1\n fi\n\n if [ ! -s \"$FILE\" ]; then\n echo \"ERROR: generated-workflow.yaml is empty\"\n exit 1\n fi\n\n if ! grep -q \"^name:\" \"$FILE\"; then\n echo \"ERROR: missing 'name:' field\"\n exit 1\n fi\n\n if ! grep -q \"^nodes:\" \"$FILE\"; then\n echo \"ERROR: missing 'nodes:' field\"\n exit 1\n fi\n\n echo \"VALID\"\n depends_on: [generate-yaml]\n\n - id: save-or-report\n prompt: |\n You are a workflow installer. Save the generated workflow and report to the user.\n\n ## Workflow Details\n - **Name**: $extract-intent.output.workflow_name\n - **Trigger Phrases**: $extract-intent.output.trigger_phrases\n\n ## Instructions\n\n 1. Read the generated workflow from `$ARTIFACTS_DIR/generated-workflow.yaml`\n 2. Create the directory `.archon/workflows/` if it doesn't exist (use Bash: `mkdir -p .archon/workflows/`)\n 3. Save the workflow to `.archon/workflows/$extract-intent.output.workflow_name.yaml`\n Use the Write tool to write the file.\n 4. Report to the user:\n - Workflow name and file location\n - Trigger phrases that will invoke it\n - How to run it: `bun run cli workflow run $extract-intent.output.workflow_name \"your input\"`\n - How to test it: `bun run cli validate workflows $extract-intent.output.workflow_name`\n depends_on: [validate-yaml]\n",
"archon-workflow-builder": "name: archon-workflow-builder\ndescription: |\n Use when: User wants to create a new custom workflow for their project.\n Triggers: \"build me a workflow\", \"create a workflow\", \"generate a workflow\",\n \"new workflow\", \"make a workflow for\", \"workflow builder\".\n Does: Scans codebase -> extracts intent (JSON) -> generates YAML -> validates -> saves.\n NOT for: Editing existing workflows or creating non-workflow files.\n\nnodes:\n - id: scan-codebase\n bash: |\n echo \"=== Existing Commands ===\"\n if [ -d \".archon/commands\" ]; then\n find .archon/commands -type f -name \"*.md\" 2>/dev/null | head -30\n else\n echo \"(no .archon/commands/ directory)\"\n fi\n\n echo \"\"\n echo \"=== Existing Workflows ===\"\n if [ -d \".archon/workflows\" ]; then\n find .archon/workflows -type f \\( -name \"*.yaml\" -o -name \"*.yml\" \\) 2>/dev/null | head -30\n else\n echo \"(no .archon/workflows/ directory)\"\n fi\n\n echo \"\"\n echo \"=== Package Info ===\"\n if [ -f \"package.json\" ]; then\n grep -E '\"name\"|\"scripts\"' package.json | head -10\n else\n echo \"(no package.json)\"\n fi\n\n echo \"\"\n echo \"=== Project Context (CLAUDE.md first 50 lines) ===\"\n if [ -f \"CLAUDE.md\" ]; then\n head -50 CLAUDE.md\n else\n echo \"(no CLAUDE.md)\"\n fi\n\n - id: extract-intent\n prompt: |\n You are a workflow design classifier. Given a user's description of what they want\n a workflow to do, extract structured intent.\n\n ## User's Request\n $ARGUMENTS\n\n ## Codebase Context\n $scan-codebase.output\n\n ## Instructions\n\n Analyze the user's request and the existing codebase to determine:\n 1. A kebab-case workflow name (e.g., \"lint-and-test\", \"deploy-staging\")\n 2. A description following the Archon pattern (Use when / Triggers / Does / NOT for)\n 3. Trigger phrases the router should match\n 4. A list of proposed nodes with their types and purposes\n 5. Whether this should be a simple DAG or include a loop node\n\n Be specific and concrete. Each proposed node should have a clear type\n (bash, prompt, command, script, loop, or approval) and a one-line\n description of what it does.\n model: haiku\n allowed_tools: []\n output_format:\n type: object\n properties:\n workflow_name:\n type: string\n description:\n type: string\n trigger_phrases:\n type: string\n proposed_nodes:\n type: string\n execution_mode:\n type: string\n enum: [\"dag\", \"loop\"]\n required: [workflow_name, description, trigger_phrases, proposed_nodes, execution_mode]\n depends_on: [scan-codebase]\n\n - id: generate-yaml\n prompt: |\n You are an Archon workflow author. Generate a complete, valid workflow YAML file\n based on the structured intent provided.\n\n ## Intent\n - **Name**: $extract-intent.output.workflow_name\n - **Description**: $extract-intent.output.description\n - **Trigger Phrases**: $extract-intent.output.trigger_phrases\n - **Proposed Nodes**: $extract-intent.output.proposed_nodes\n - **Execution Mode**: $extract-intent.output.execution_mode\n\n ## Original User Request\n $ARGUMENTS\n\n ## Archon Workflow YAML Schema Reference\n\n A workflow YAML file has this structure:\n\n ```yaml\n name: workflow-name\n description: |\n Use when: ...\n Triggers: ...\n Does: ...\n NOT for: ...\n\n # Optional top-level settings:\n # provider: claude (or codex)\n # model: sonnet (or haiku, opus, etc.)\n # interactive: true (forces foreground execution in web UI)\n\n nodes:\n - id: node-id-kebab-case\n # Choose ONE of: prompt, bash, command, script, loop, approval\n\n # --- prompt node (AI-executed) ---\n prompt: |\n Instructions for the AI...\n # Optional: model, allowed_tools, denied_tools, output_format, context, idle_timeout\n\n # --- bash node (shell script, no AI, stdout = $<nodeId>.output) ---\n bash: |\n #!/bin/bash\n set -e\n echo \"result\"\n\n # --- command node (references a .archon/commands/ file) ---\n command: command-name\n\n # --- script node (TypeScript via bun, or Python via uv — no AI, stdout = $<nodeId>.output) ---\n # Use for deterministic data transforms the shell would mangle (JSON parsing, etc.)\n script: |\n const raw = String.raw`$<other-node>.output`;\n const data = JSON.parse(raw);\n console.log(JSON.stringify({ count: data.items.length }));\n runtime: bun # required: 'bun' (.ts/.js) or 'uv' (.py)\n # deps: [requests] # uv only\n # Or reference a named script in .archon/scripts/:\n # script: extract-labels # no extension; bun resolves .ts/.js, uv resolves .py\n\n # --- loop node (iterative AI execution) ---\n loop:\n prompt: |\n Instructions repeated each iteration...\n until: COMPLETION_SIGNAL\n max_iterations: 10\n fresh_context: true # optional: reset context each iteration\n\n # --- approval node (human gate — pauses workflow) ---\n approval:\n message: \"Review the plan above. Approve to continue.\"\n # capture_response: true # store reviewer comment as $<nodeId>.output\n\n # Common options for all node types:\n depends_on: [other-node-id] # DAG edges\n when: \"$<other-node>.output == 'value'\" # conditional execution\n trigger_rule: all_success # all_success | one_success | all_done\n timeout: 120000 # ms, for bash and script nodes\n ```\n\n ## Variable Reference\n - `$ARGUMENTS` — user's input text\n - `$ARTIFACTS_DIR` — pre-created directory for workflow artifacts\n - `$<nodeId>.output` — stdout from a bash/script node or AI response from a prompt node\n - `$<nodeId>.output.field` — JSON field from a node with output_format\n - `$BASE_BRANCH` — base git branch\n\n ## Rules\n 1. The `name:` field MUST match: $extract-intent.output.workflow_name\n 2. The `description:` MUST follow the \"Use when / Triggers / Does / NOT for\" pattern\n 3. Every node MUST have a unique kebab-case `id`\n 4. Use `depends_on` to define execution order\n 5. Use `bash` nodes for deterministic shell operations (file checks, git commands, installs)\n 6. Use `script` nodes for typed data transforms (TypeScript JSON parsing, Python with deps) — stdout is captured as output, stderr is forwarded as a warning. `$<nodeId>.output` is NOT shell-quoted in script bodies — parse with JSON.parse / json.loads, not shell interpolation\n 7. Use `prompt` nodes for AI reasoning tasks\n 8. Use `approval` nodes to pause for human review at risky gates (plan→execute boundary, destructive actions)\n 9. Use `output_format` on prompt nodes when downstream nodes need structured data\n 10. Use `allowed_tools: []` on classification/analysis nodes that don't need tools\n 11. Use `denied_tools: [Edit, Bash]` when a node should only use Write (not edit existing files)\n 12. Prefer `model: haiku` for simple classification tasks to save cost\n\n ## Output\n\n Write the complete workflow YAML to: `$ARTIFACTS_DIR/generated-workflow.yaml`\n\n Use the Write tool. Do NOT use Edit or Bash. The file must be valid YAML and follow\n all the patterns above.\n denied_tools: [Edit, Bash]\n depends_on: [extract-intent]\n\n - id: validate-yaml\n bash: |\n FILE=\"$ARTIFACTS_DIR/generated-workflow.yaml\"\n\n if [ ! -f \"$FILE\" ]; then\n echo \"ERROR: generated-workflow.yaml not found at $FILE\"\n exit 1\n fi\n\n if [ ! -s \"$FILE\" ]; then\n echo \"ERROR: generated-workflow.yaml is empty\"\n exit 1\n fi\n\n if ! grep -q \"^name:\" \"$FILE\"; then\n echo \"ERROR: missing 'name:' field\"\n exit 1\n fi\n\n if ! grep -q \"^nodes:\" \"$FILE\"; then\n echo \"ERROR: missing 'nodes:' field\"\n exit 1\n fi\n\n echo \"VALID\"\n depends_on: [generate-yaml]\n\n - id: save-or-report\n prompt: |\n You are a workflow installer. Save the generated workflow and report to the user.\n\n ## Workflow Details\n - **Name**: $extract-intent.output.workflow_name\n - **Trigger Phrases**: $extract-intent.output.trigger_phrases\n\n ## Instructions\n\n 1. Read the generated workflow from `$ARTIFACTS_DIR/generated-workflow.yaml`\n 2. Create the directory `.archon/workflows/` if it doesn't exist (use Bash: `mkdir -p .archon/workflows/`)\n 3. Save the workflow to `.archon/workflows/$extract-intent.output.workflow_name.yaml`\n Use the Write tool to write the file.\n 4. Report to the user:\n - Workflow name and file location\n - Trigger phrases that will invoke it\n - How to run it: `bun run cli workflow run $extract-intent.output.workflow_name \"your input\"`\n - How to test it: `bun run cli validate workflows $extract-intent.output.workflow_name`\n depends_on: [validate-yaml]\n",

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep the escaped placeholder form scoped to literal examples.

This section currently teaches $<nodeId>.output as the actual variable-reference syntax, but that form is only the escape convention. If the workflow builder copies this literally, it will generate workflows whose refs never resolve at runtime.

Suggested fix
-      - `$<nodeId>.output` — stdout from a bash/script node or AI response from a prompt node
-      - `$<nodeId>.output.field` — JSON field from a node with output_format
+      - Use `$nodeId.output` for real runtime references.
+      - Use `$<nodeId>.output` only in literal examples that must not be parsed as refs.
+      - Use `$nodeId.output.field` for JSON field access.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/workflows/src/defaults/bundled-defaults.generated.ts` at line 77,
The docs incorrectly present the escaped placeholder form `$<nodeId>.output` as
the runtime variable syntax; update the wording and examples so the escaped form
is only shown as a literal/example (e.g., explicitly state "literal example:
`$<nodeId>.output`") and everywhere the runtime variable is described or used
(Variable Reference, prompt/template sections, rules, and examples) show the
actual runtime reference syntax without angle brackets (e.g., `$node-id.output`
or `$scan-codebase.output`), and adjust the Generate YAML and Save-or-report
prompts and comments (references: $<nodeId>.output in Variable Reference,
generate-yaml prompt block, save-or-report instructions, and examples inside
node script hints) so generated workflows will use the resolvable
`$node-id.output` form instead of the escaped placeholder.

@Wirasm

Wirasm commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

@3bit related to #1413 — overlapping area or partial fix.

@Wirasm

Wirasm commented Apr 27, 2026

Copy link
Copy Markdown
Collaborator

Review Summary

Verdict: minor-fixes-needed

Your PR correctly escapes $nodeId.output placeholders in the archon-workflow-builder workflow to fix the DAG validation failure reported in #1401 — great catch and the fix direction is right. However, the fix is incomplete: the generate-yaml node's prompt: field contains three occurrences of $nodeId.output, and only one was escaped.

Blocking issues

(none — the incomplete fix is HIGH, not CRITICAL)

Suggested fixes

  • .archon/workflows/defaults/archon-workflow-builder.yaml:182 — inline code example `const data = $nodeId.output;` still uses unescaped $nodeId.output. The loader's validateDagStructure() scans prompt: fields with a regex that matches this pattern, causing a false validation error: Node 'generate-yaml' references unknown node '$nodeId.output'. Fix: `const data = $<nodeId>.output;`
  • .archon/workflows/defaults/archon-workflow-builder.yaml:184 — same issue in the Python example `json.loads("""$nodeId.output""")`. Fix: `json.loads("""$<nodeId>.output""")`

Minor / nice-to-have

  • line 181 — the plain-text $nodeId.output in the same section would look more consistent with the <> convention used throughout the file (e.g., when: "$<other-node>.output == 'value'" on line 161). Optional but nice for documentation polish.

Compliments

  • The validateDagStructure() code you used to verify the fix is exactly the kind of concrete validation evidence that makes review easy. Well done.
  • The PR is tight and focused — two files, one logical change, clean alignment with the workflow-driven direction.

Reviewed via maintainer-review-pr workflow (Pi/Minimax). Aspects run: code-review.

@leex279

leex279 commented May 4, 2026

Copy link
Copy Markdown
Collaborator

🔗 Suggested link: This PR also appears to fully resolve issue #1446 ("bug(workflow-builder): archon-workflow-builder.yaml fails to load — schema example uses $other-node.output which triggers DAG validation error"). Consider adding Closes #1446 to the PR description (in addition to the existing Closes #1401).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug(workflows): archon-workflow-builder fails to load — example placeholders scanned as node refs

4 participants