Pattern 03. Your First Custom Pattern — Simple Linear Pipeline
Learning Objectives
After completing this tutorial, you will be able to:
- Write and run a 3-node linear pattern from scratch
- Explain the meaning of all top-level fields and node fields in YAML
- Distinguish the differences and purposes of the
validate,show,compile, andruncommands - Install a pattern file into the workspace and reference it by ID
- Control LLM behavior at the node level using
system_append
Prerequisites
01_concepts.mdcompleted (understanding of pattern concepts)02_builtin_patterns.mdcompleted (pattern execution experience)- Text editor ready (VS Code, vim, etc.)
- Working directory: project root (where
euleragent initwas run)
# Verify environment
ls .euleragent/
euleragent agent list
1. Planning: What Are We Building?
Goal: A pattern that takes a topic as input and writes a simple technical blog post.
Flow:
[research] → [write] → [FINALIZE]
research: The LLM investigates key information and ideas about the topic (without web search, using only LLM internal knowledge)write: Writes a blog post based on the researchFINALIZE: Saves toblog_post.md
This is the simplest possible pattern. No cycles, no HITL, no Judge. A completely linear flow.
┌─────────────────────────────────────────────────────────────────┐
│ my_first.pattern Flow Diagram │
├─────────────────────────────────────────────────────────────────┤
│ │
│ [research] │
│ │ Topic investigation, key point summary (llm/execute) │
│ │ when: true │
│ ▼ │
│ [write] │
│ │ Write blog post (llm/execute) │
│ │ when: true │
│ ▼ │
│ ┌──────────────────────────────────────────────────────────┐ │
│ │ [FINALIZE] Save blog_post.md │ │
│ └──────────────────────────────────────────────────────────┘ │
│ │
└─────────────────────────────────────────────────────────────────┘
2. Writing the YAML File
Create the my_first_pattern.yaml file. Comments are added to explain each field.
# Create file in the working directory
touch my_first_pattern.yaml
File contents:
# ─────────────────────────────────────────────
# Top-level Metadata
# ─────────────────────────────────────────────
# Unique identifier for the pattern. "category.description" format recommended
# Referenced by this ID after installation in the workspace
id: my_first.pattern
# Schema version. Currently always 1
version: 1
# Category. Used for grouping in pattern list
# Free string (research, code, ops, writing, etc.)
category: writing
# Pattern description. Displayed in pattern list and show
description: "A simple linear pattern that takes a topic and writes a technical blog post"
# ─────────────────────────────────────────────
# Default Settings (defaults)
# Settings applied commonly to all nodes
# ─────────────────────────────────────────────
defaults:
# Maximum number of iterations when cycles (loops) exist
# Not required for this pattern since there are no cycles, but declared for safety
max_iterations: 1
# Maximum number of tool calls across the entire execution
max_total_tool_calls: 10
# ─────────────────────────────────────────────
# Node Definitions (nodes)
# Each node represents one task step
# ─────────────────────────────────────────────
nodes:
# ── Node 1: research ──
- id: research # Unique ID for the node. Referenced by from/to in edges
kind: llm # Node type: llm (execution), judge (evaluation), finalize is declared separately
runner:
# mode: execute — LLM directly executes tools
# mode: plan — LLM "proposes" tool execution and waits for HITL approval
mode: execute
# exclude_tools: List of tools prohibited in this node
# Forces using only LLM knowledge without web search
exclude_tools: [web.search, web.fetch]
prompt:
# system_append: System prompt applied additionally only in this node
# "Appended" to the agent's default system prompt (does not overwrite)
system_append: |
You are a technical blog research specialist.
For the given topic, organize the following:
1. 3-5 core concepts
2. Background knowledge beginners should know
3. 2-3 practical points
4. List of related technologies worth referencing
Output format: Research notes structured as markdown sections
artifacts:
# primary: Main output artifact filename for this node
# Saved under .euleragent/runs/<run-id>/artifacts/
primary: research_notes.md
# ── Node 2: write ──
- id: write
kind: llm
runner:
mode: execute
# max_loops: Maximum number of times this node can execute
# Default is 1 for linear patterns, but declared explicitly
max_loops: 1
exclude_tools: [web.search, web.fetch, shell.exec]
prompt:
system_append: |
You are a technical blog writer.
Write a complete blog post based on the research notes.
Requirements:
- Length: 800-1200 words
- Structure: Introduction → Body (3 sections) → Conclusion
- Audience: Experienced developers (minimize basic explanations)
- Tone: Practical and direct
- Include code examples (where applicable)
- Markdown format
artifacts:
primary: blog_post.md
# ─────────────────────────────────────────────
# Edge Definitions (edges)
# Transition conditions between nodes
# ─────────────────────────────────────────────
edges:
- from: research # Source node ID
to: write # Destination node ID
when: "true" # Transition condition: "true" = always transition
- from: write
to: finalize # "finalize" is a reserved node name
when: "true"
# ─────────────────────────────────────────────
# Final Node Settings (finalize)
# Do NOT add to the nodes array! Declare as a top-level key
# ─────────────────────────────────────────────
finalize:
# Artifact filename to save as the final result
# Convention is to match it with the write node's primary artifact
artifact: blog_post.md
3. Validation: validate
euleragent pattern validate my_first_pattern.yaml
Expected output (all passing):
Validating pattern: my_first_pattern.yaml
Stage 1 (Schema) PASS Required fields: id, version, nodes, edges, finalize ✓
Types: all valid ✓
Stage 2 (Structural) PASS Node IDs unique: research, write ✓
Edge references valid ✓
No judge nodes — route coverage check skipped ✓
Stage 3 (IR Analysis) PASS Entry node: research (no incoming edges) ✓
No cycles detected — max_iterations not required ✓
All nodes reachable: research, write ✓
finalize reachable from all paths ✓
Validation complete: 0 errors, 0 warnings
Getting output in JSON format is useful for CI/CD:
euleragent pattern validate my_first_pattern.yaml --format json
{
"pattern_id": "my_first.pattern",
"source": "my_first_pattern.yaml",
"stages": {
"schema": {
"status": "pass",
"errors": []
},
"structural": {
"status": "pass",
"errors": []
},
"ir_analysis": {
"status": "pass",
"errors": [],
"warnings": []
}
},
"overall": "pass",
"error_count": 0,
"warning_count": 0
}
4. Structure Inspection: show
euleragent pattern show my_first_pattern.yaml
Expected output:
Pattern: my_first.pattern (source: my_first_pattern.yaml)
Category: writing
Version: 1
Description: A simple linear pattern that takes a topic and writes a technical blog post
Defaults:
max_iterations: 1
max_total_tool_calls: 10
Nodes:
[entry] research llm mode=execute exclude=[web.search, web.fetch]
write llm mode=execute exclude=[web.search, web.fetch, shell.exec]
Edges:
research → write when: true
write → finalize when: true
Finalize:
artifact: blog_post.md
Topology: linear (no cycles)
5. Compilation: compile
Check the IR JSON to see how the runtime interprets the pattern.
euleragent pattern compile my_first_pattern.yaml
Expected output (IR JSON):
{
"id": "my_first.pattern",
"version": 1,
"category": "writing",
"description": "A simple linear pattern that takes a topic and writes a technical blog post",
"entry_node": "research",
"nodes": {
"research": {
"id": "research",
"kind": "llm",
"runner": {
"mode": "execute",
"force_tool": null,
"exclude_tools": ["web.search", "web.fetch"],
"min_proposals": null,
"max_loops": null,
"hitl_required": false
},
"prompt": {
"system_append": "You are a technical blog research specialist..."
},
"guardrails": null,
"artifacts": {
"primary": "research_notes.md"
},
"resolved_defaults": {
"max_iterations": 1,
"max_total_tool_calls": 10
},
"outgoing_edges": [
{
"to": "write",
"condition": { "type": "always" }
}
]
},
"write": {
"id": "write",
"kind": "llm",
"runner": {
"mode": "execute",
"force_tool": null,
"exclude_tools": ["web.search", "web.fetch", "shell.exec"],
"max_loops": 1,
"hitl_required": false
},
"prompt": {
"system_append": "You are a technical blog writer..."
},
"artifacts": {
"primary": "blog_post.md"
},
"outgoing_edges": [
{
"to": "__finalize__",
"condition": { "type": "always" }
}
]
}
},
"adjacency": {
"research": ["write"],
"write": ["__finalize__"]
},
"cycles": [],
"finalize": {
"artifact": "blog_post.md"
}
}
You can see that to: finalize has been transformed to to: "__finalize__" in the IR. This is because finalize is a reserved node ID.
6. Installing to the Workspace
To reference the pattern by ID (my_first.pattern) instead of a local file path, copy it to the workspace patterns directory.
# Check or create the patterns directory
mkdir -p .euleragent/patterns
# Install the pattern file
cp my_first_pattern.yaml .euleragent/patterns/my_first_pattern.yaml
# Verify installation — check if it appears in the list
euleragent pattern list
Expected output:
Built-in Patterns
─────────────────────────────────────────────────────────
report.evidence research Evidence-based report writing
code.tdd code Test-driven development workflow
ops.triage ops Operations ticket triage
research.broad_to_narrow research Broad-to-narrow research synthesis
Workspace Patterns (.euleragent/patterns/)
─────────────────────────────────────────────────────────
my_first.pattern writing A simple linear pattern that takes a topic and writes a technical blog post
5 patterns available.
Now you can reference it by ID instead of file path:
# Reference by file path (before installation)
euleragent pattern validate my_first_pattern.yaml
# Reference by ID (after installation)
euleragent pattern validate my_first.pattern
euleragent pattern show my_first.pattern
7. Execution
euleragent pattern run my_first.pattern my-agent \
--task "Technical blog post about Python asyncio and async/await patterns" \
--project default
Expected output:
[run:f2c5a8e1] Starting pattern: my_first.pattern
[run:f2c5a8e1] Agent: my-agent
[run:f2c5a8e1] Task: Technical blog post about Python asyncio and async/await patterns
✓ research Completed (12s) — research_notes.md generated
✓ write Completed (18s) — blog_post.md generated (1,047 words)
✓ finalize Completed
Run f2c5a8e1 completed.
Artifact: .euleragent/runs/f2c5a8e1/artifacts/blog_post.md
Total time: 31 seconds
8. Checking Results
# Final blog post
cat .euleragent/runs/f2c5a8e1/artifacts/blog_post.md
# Intermediate artifact (research node output)
cat .euleragent/runs/f2c5a8e1/artifacts/research_notes.md
# Execution summary
euleragent runs status f2c5a8e1
# Full event stream
cat .euleragent/runs/f2c5a8e1/pattern_events.jsonl
euleragent runs status output example:
Run: f2c5a8e1
Pattern: my_first.pattern
Agent: my-agent
Status: completed
Started: 2026-02-23 14:32:10
Completed: 2026-02-23 14:32:41
Duration: 31s
Node Executions:
research completed 12s
write completed 18s
finalize completed 1s
Artifacts:
research_notes.md (2,134 bytes)
blog_post.md (5,892 bytes) ← final
9. Key Concept Explanations
mode: execute vs mode: plan
In this pattern, both nodes use mode: execute. In this mode, the LLM can directly use tools. However, tools prohibited by exclude_tools cannot be used.
mode: plan is covered in the next tutorial (05_web_research). In this mode, the LLM only "proposes" tool usage, and actual execution happens after human approval.
Inheritance Structure of system_append
system_append is appended to the agent's default system prompt. The agent's identity (e.g., "You are a helpful assistant") is preserved, while the node can specify more specific roles and output formats.
Agent's default system prompt
+ research node's system_append
= Final system prompt the LLM receives at the research node
Why to: finalize Is Special
to: finalize in edges is a reserved value. It means transitioning to the final node defined in the finalize: section. Using finalize as a regular node ID will cause a RESERVED_NODE_ID error.
Role of artifacts.primary
artifacts.primary is the main output filename for this node. The next node automatically receives the previous node's artifact as context. The write node automatically receives the contents of research_notes.md generated by the research node as context.
10. Practice Exercise: Controlling Output Format with system_append
Try modifying the write node's system_append to generate blog posts in different styles.
Exercise 1: Newsletter Style
# Replace the write node's system_append with the following
prompt:
system_append: |
You are a tech newsletter editor.
Write a newsletter section in the following format:
## This Week's Topic: [Topic Name]
**Why It Matters?** (2-3 sentences)
**Key Points** (5 bullet points)
**Practical Application Tips** (3 bullet points)
**Learn More** (in related topic link format)
Length: 400-600 words. Friendly and enthusiastic tone.
Exercise 2: Tutorial Style
prompt:
system_append: |
You are a technical tutorial writer.
Write a step-by-step tutorial with the following structure:
## Prerequisites
## Step 1: [Title]
## Step 2: [Title]
## Step 3: [Title]
## Summary and Next Steps
Include executable code examples in each step.
Audience: Developers with basic Python knowledge
Validate and run:
euleragent pattern validate my_first_pattern.yaml
euleragent pattern run my_first.pattern my-agent \
--task "Building a REST API with FastAPI" \
--project default
Common Errors and Solutions
Error 1: EDGE_UNKNOWN_NODE
ERROR [EDGE_UNKNOWN_NODE] Edge references unknown node: 'wirte' (did you mean 'write'?)
Cause: There is a typo in the edge's from or to.
Solution: Verify that node IDs exactly match the from/to values in edges.
Error 2: NO_ENTRY_NODE
ERROR [NO_ENTRY_NODE] No entry node found. Every node has an incoming edge.
Cause: All nodes have incoming edges, so there is no entry point. This often occurs when finalize is declared as a node and edges are connected to it.
Solution: Verify that the first node intended as the entry point has no incoming edges.
Error 3: RESERVED_NODE_ID
ERROR [RESERVED_NODE_ID] Node ID 'finalize' is reserved. Choose a different ID.
Cause: A node was declared with id: finalize in the nodes array.
Solution: Change the node name to something like final_step, and declare the final node only with the top-level finalize: key.
Error 4: Edge to: finalize Error
ERROR [EDGE_UNKNOWN_NODE] Edge references unknown node: 'finalize'
Cause: Stage 2 misinterprets finalize as a regular node. This usually occurs when the finalize: section is missing.
Solution: Verify that the finalize: section exists at the YAML top level.
Next Steps
You have successfully created and executed your first custom pattern. Linear patterns are simple, but they are the foundation of all advanced patterns.
- Next tutorial: 04_judge_and_loop.md — Add a Judge node to this pattern to create a quality loop
- Add web search: 05_web_research.md — Add actual web search to the
researchnode - Add human review: 06_human_gate.md — Add a human review step after
write