Repository navigation
feat: decouple prompt body from compiled pipeline output (runtime-import approach) #237
Description
Problem
Currently, ado-aw embeds the full markdown body (the agent prompt) directly into the generated pipeline YAML at compile time via the {{ agent_content }} placeholder (src/compile/common.rs:2082, templates/base.yml:152-154). This means any edit to the prompt — even a typo fix — forces a pipeline recompilation and produces a diff in the compiled YAML.
In contrast, gh-aw decouples prompt content from compiled output. Editing the markdown body in gh-aw does not change the compiled .lock.yml file.
How gh-aw solves this
gh-aw uses a runtime-import macro pattern:
1. Compile-time: emit a file reference, not the content
Instead of inlining the prompt body, the compiler emits a {{#runtime-import <path>}} macro in the generated workflow:
// pkg/workflow/compiler_yaml.go — generatePrompt(), ~line 556
runtimeImportMacro := fmt.Sprintf("{{#runtime-import %s}}", workflowFilePath)
userPromptChunks = append(userPromptChunks, runtimeImportMacro)The compiled .lock.yml contains:
- name: Create prompt with built-in context
run: |
{
cat << 'PROMPT'
<system>
...system prompts via cat of separate files...
</system>
{{#runtime-import .github/workflows/my-agent.md}}
PROMPT
} > "$GH_AW_PROMPT"2. Runtime: resolve macros from the checked-out repository
A subsequent workflow step runs interpolate_prompt.cjs which:
- Reads the prompt file containing the unresolved macros
- Calls
runtime_import.cjs→processRuntimeImport()to read the.mdfile from$GITHUB_WORKSPACE - Strips the YAML front matter, removes XML comments, validates expressions
- Replaces the
{{#runtime-import ...}}macro with the actual markdown body - Writes the resolved prompt back
Key source: actions/setup/js/runtime_import.cjs:742-870, actions/setup/js/interpolate_prompt.cjs
3. FrontmatterHash excludes the body
gh-aw's FrontmatterHash (SHA-256, embedded in the .lock.yml header) only covers the frontmatter text, not the markdown body (pkg/parser/frontmatter_hash.go:164-182). Body content is only included in the hash when inlined-imports: true is explicitly set.
4. Built-in system prompts are also external files
System prompts (XPIA, safe-outputs guidance, temp folder instructions, etc.) are stored as separate .md files in actions/setup/md/ and loaded via cat at runtime from ${RUNNER_TEMP}/gh-aw/prompts/ — they are not inlined in the compiled output either (pkg/workflow/prompt_constants.go:13-34).
Benefits
- Faster iteration — Edit the agent prompt, push, done. No recompile step.
- Cleaner diffs — Prompt changes only touch the
.mdfile, not the pipeline YAML. Easier code review. - Separation of concerns — Pipeline infrastructure (tools, schedule, network, permissions) changes independently from agent instructions.
- Lock file stability — The compiled output is truly a "lock" of the pipeline shape, not a copy of the prompt content.
How ado-aw could achieve the same
ado-aw already writes the agent markdown body to agents/<name>.md alongside the compiled pipeline. The pipeline already knows the source path via {{ source_path }}. The approach would be:
Option A: Read agent file at runtime (minimal change)
Replace the {{ agent_content }} heredoc in templates/base.yml with a step that reads the agent file from the checked-out repo at runtime:
- bash: |
# Extract markdown body from agent source file (strip YAML front matter)
SOURCE_FILE="{{ source_path }}"
awk '/^---$/{if(++c==2){skip=0;next}else{skip=1;next}} !skip' "$SOURCE_FILE" \
> /tmp/awf-tools/agent-prompt.md
displayName: "Prepare agent prompt"Changes required:
templates/base.yml: Replace the{{ agent_content }}heredoc with a runtime file-read stepsrc/compile/common.rs: Remove("{{ agent_content }}", markdown_body)from the replacements listsrc/compile/mod.rs(check_pipeline): Update integrity check to only compare frontmatter-derived configuration, since the prompt is no longer in the pipeline YAML- Tests: Update snapshot/integration tests that assert on embedded prompt content
Option B: Keep writing agents/<name>.md but reference it
The compiler already writes an agent file to agents/. The pipeline step could cat that file directly, avoiding the need to strip front matter at runtime (since the agent file is already just the body). This is the cleanest path since the file is already being generated.
Considerations
- Security: The prompt is read from the checked-out repo at runtime, so a PR can modify agent behavior without changing the pipeline file. This is acceptable because all write operations go through safe-outputs regardless, and the threat analysis job runs independently. The
checkcommand would need updating. - Backward compatibility: Existing compiled pipelines with embedded prompts would continue to work. New compilations would use the runtime approach.
inlined-importsescape hatch: gh-aw providesinlined-imports: truefor cases where compile-time embedding is explicitly desired (e.g., Wasm/browser contexts). ado-aw could offer a similar opt-in if needed.
Activity
jamesadevine commentedon Apr 16, 2026
Additional context: runtime-import as a prompt composition system
gh-aw's runtime-import is not just for decoupling the main prompt body — it's a general-purpose prompt composition system used across their own agents.
Supported import types
{{#runtime-import path}}{{#runtime-import docs/guidelines.md}}{{#runtime-import path:start-end}}{{#runtime-import src/auth.go:45-52}}{{#runtime-import https://...}}{{#runtime-import https://raw.githubusercontent.com/.../checklist.md}}{{#runtime-import? path}}{{#runtime-import? shared-instructions.md}}@./pathor@https://...{{#runtime-import}}Real-world example from gh-aw
.github/workflows/daily-team-status.mduses two forms of runtime-import:imports: [shared/reporting-otlp.md]— shared workflow config imported via frontmatter (compiler emits a runtime-import macro for its frontmatter){{#runtime-import? .github/shared-instructions.md}}— optional inline import in the prompt body. The?means it silently skips if the file doesn't exist, which lets repos share common guidelines across multiple agents without each.mdduplicating the instructions.Other gh-aw agents using runtime-import include:
daily-assign-issue-to-user.md,daily-news.md,metrics-collector.md,daily-observability-report.md,daily-firewall-report.md,daily-cli-performance.md,daily-repo-chronicle.md,daily-integrity-analysis.md— all importing shared instructions/config.Why not just awk/sed?
A simple
awkone-liner could handle the core use case (strip frontmatter, inject the agent body at runtime). However, the full runtime-import system also provides:?suffix) for graceful degradation.github/or.agents/,${{ }}expressions rejected in imported content@./docs/guidelines.mdinline in their prompt and it gets resolvedFor ado-aw, a simpler approach (Option A/B from the issue description) would cover the immediate need of decoupling prompt edits from recompilation. The full templating system could be a future enhancement if composable prompt includes prove valuable.
Documentation references
generatePrompt())generateUnifiedPromptCreationStep())