Home > EulerAgent > Tutorials > Pattern > Your First Custom Pattern

Pattern 03. Your First Custom Pattern — Simple Linear Pipeline

Learning Objectives

After completing this tutorial, you will be able to:

Prerequisites

# 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]

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.

← Prev Back to List Next →