YAML Includes
ImplementedFor anything longer than a sentence or two — a multi-page system prompt, a carefully structured extraction instruction, the contents of a reference document you want a model to reason over — embedding the text directly in your pipeline YAML makes the file hard to read and edit.
JigSpec supports a !include YAML tag that inlines the contents of another file at parse time. The value in your YAML is replaced with the file's UTF-8 contents before the pipeline schema is validated.
Basic usage
pipeline:
name: research-agent
steps:
- name: investigate
action: ai
prompt: !include ./prompts/investigate.mdWhen the pipeline is parsed, JigSpec reads ./prompts/investigate.md relative to the pipeline file's directory and substitutes its contents in place of the !include tag. The rest of the pipeline (schema validation, variable resolution, execution) sees a plain string at that key.
Path resolution
Paths are resolved relative to the directory containing the pipeline YAML, not the current working directory. This means a pipeline file works the same no matter where you run jigspec run from.
my-pipeline/
├── pipeline.pipe.yaml # references !include ./prompts/x.md
└── prompts/
└── x.mdRunning jigspec run my-pipeline/pipeline.pipe.yaml from anywhere still resolves ./prompts/x.md relative to my-pipeline/.
Both relative (./prompts/x.md, ../shared/style.md) and absolute paths (/opt/prompts/global.md) are supported. Relative paths are almost always what you want — they make the pipeline directory self-contained and portable.
When to use it
Use !include for anything that is genuinely a document rather than a piece of YAML configuration:
- System prompts — multi-paragraph instructions for agent personas
- Few-shot examples — worked examples the model should follow
- Reference documents — style guides, policy text, or schemas the model should reason over
- Long code snippets — sample
codeaction bodies that you'd rather keep in.jsfiles with proper editor tooling
Keep short literal strings (single-line prompts, short messages, numbers, booleans) inline in the YAML. The tag pays off when the content is long enough that inline embedding hurts readability.
What it does NOT do
!include is a parse-time file substitution. It does not:
- Resolve
{{ }}templates inside the included file (that happens later, in the template engine — but after the include, so template syntax in your included file still works). - Recursively follow nested
!includetags inside included files (the YAML parser resolves each scalar tag once). - Validate the structure of the included content — whatever you include is treated as a plain string.
Example: multi-file agent pipeline
investigator/
├── pipeline.pipe.yaml
└── prompts/
├── system.md # persona + house rules
├── user-template.md # user-turn template with {{ input.topic }}
└── examples.md # 3 worked examplespipeline.pipe.yaml:
pipeline:
name: investigator
config:
model: anthropic/claude-sonnet-4-5
input:
topic:
type: string
steps:
- name: investigate
action: ai
prompt: |
{{ system_prompt }}
=== EXAMPLES ===
{{ examples }}
=== TASK ===
{{ user_prompt }}
input:
system_prompt: !include ./prompts/system.md
examples: !include ./prompts/examples.md
user_prompt: !include ./prompts/user-template.mdNow each prompt lives in its own Markdown file — easy to review, easy to version-control diff, easy to share between pipelines. The YAML stays concise and the prose stays readable.
Keep your prompt files near the pipeline
For pipelines you intend to publish via github:owner/repo composition (see Pipeline composition), make sure any !include targets are inside the same repo and reachable from the pipeline file's directory. Sub-pipelines are fetched alongside their referenced files when composed.