Skip to content

Prompt Templating

The prompt module renders Jinja2 prompt templates, strips YAML frontmatter, and looks up templates across several directories. Agent prompts often include project names, file lists, earlier phase outputs, or mode-specific instructions, and the module fills them in with Jinja2 variables, conditionals, and loops.

Templates render in a sandboxed environment, so template code cannot reach arbitrary Python objects (see Security). Only TemplateLoader.load() and strip_frontmatter() remove frontmatter; render_template() and render_string() render the text as given.

Rendering a Template File

from pathlib import Path
from orchcore.prompt import render_template

output = render_template(
    template_path=Path("prompts/planning.md"),
    variables={
        "project_name": "orchcore",
        "mode": "plan",
        "files": ["src/main.py", "src/utils.py"],
    },
)

Rendering a String

For templates stored in configuration or built at runtime:

from orchcore.prompt import render_string

output = render_string(
    template_str="Analyze {{ project_name }} and focus on {{ focus_area }}.",
    variables={"project_name": "orchcore", "focus_area": "error handling"},
)

Template Loader

TemplateLoader finds a template by name across several directories and returns its text with frontmatter removed. It does not render the template:

from pathlib import Path
from orchcore.prompt import TemplateLoader

loader = TemplateLoader(template_dirs=[
    Path("prompts/custom"),    # Project-specific templates (checked first)
    Path("prompts/defaults"),  # Fallback templates
])

# Searches for: planning, planning.md, planning.j2, planning.txt
content = loader.load("planning")  # raises FileNotFoundError if no file matches

# Check existence without loading
if loader.exists("review"):
    review_prompt = loader.load("review")

The directories are searched in the order given. In each directory, the loader tries the exact name first, then the name with .md, .j2, and .txt appended. Names that would resolve outside a template directory (e.g., ../secrets) are rejected.

Frontmatter Stripping

Templates can include YAML frontmatter (delimited by ---) for metadata:

---
description: Planning phase prompt
author: team
---

# Planning Phase

Analyze the codebase for {{ project_name }}...

Do not pass a file with frontmatter to render_template()

render_template() and render_string() do not strip frontmatter, so it ends up in the rendered prompt. Strip it first with TemplateLoader.load() or strip_frontmatter(), then render the result with render_string().

from pathlib import Path
from orchcore.prompt import render_string, strip_frontmatter

# Option 1: TemplateLoader strips the frontmatter
template = loader.load("planning")

# Option 2: strip it yourself
template = strip_frontmatter(Path("prompts/planning.md").read_text(encoding="utf-8"))

prompt = render_string(template_str=template, variables={"project_name": "orchcore"})

Template Path Resolution

resolve_template_path() resolves a configured path, which may be relative, against a base directory. If the file does not exist, it logs a warning and returns None, so the caller can fall back to a built-in template:

from orchcore.prompt import resolve_template_path

path = resolve_template_path(
    configured_path=Path("prompts/planning.md"),
    base_dir=Path("/project/root"),
    fallback_name="planning prompt",
)
# Returns the resolved absolute Path, or None if the path is None or the file is missing

Security

Templates are rendered in a Jinja2 SandboxedEnvironment, which blocks template code from accessing unsafe attributes and arbitrary Python objects. StrictUndefined is enabled, so a reference to an undefined variable raises jinja2.UndefinedError instead of rendering as an empty string.