Skip to content

Jinja prompt templates cannot use include/import/extends because TemplateRenderer uses BaseLoader #287

Description

@hertznsk

Summary

Prompt templates loaded via prompt: !file ... are rendered as Jinja2 templates, but loader-dependent Jinja constructs such as {% include %}, {% import %}, and {% extends %} cannot work in normal CLI usage.

The renderer creates the Jinja environment with loader=BaseLoader() and renders file-backed prompts via env.from_string(...). Because the YAML !file constructor inlines the file contents into the prompt string, the original template file path/directory is not available at render time, and there is no user-facing way to provide a Jinja search path.

This makes it impossible to factor prompt templates into reusable partials.

Minimal Reproduction

Using Conductor 0.1.20, Jinja2 3.1.6, Python 3.12.

Example files:

workflow.yaml
prompts/main.md.jinja
prompts/_shared.md.jinja

workflow.yaml:

version: "1"
tasks:
  example:
    prompt: !file prompts/main.md.jinja

prompts/main.md.jinja:

Before include.

{% include "_shared.md.jinja" %}

After include.

prompts/_shared.md.jinja:

Shared prompt text.

Run the workflow with the normal Conductor CLI.

Expected Behavior

The prompt should render successfully and include the contents of _shared.md.jinja, either relative to the prompt file directory or via a documented template search path.

Actual Behavior

Rendering fails with a wrapped template error similar to:

Template rendering failed: _shared.md.jinja

The underlying Jinja error is jinja2.exceptions.TemplateNotFound.

Root Cause

In src/conductor/executor/template.py, TemplateRenderer.__init__ creates the environment with a hard-coded BaseLoader:

self.env = _DictSafeEnvironment(
    loader=BaseLoader(),
    undefined=StrictUndefined,
    autoescape=False,
    keep_trailing_newline=True,
)

TemplateRenderer.render() then compiles prompt text using self.env.from_string(template).

Jinja's BaseLoader cannot resolve template names for {% include %}, {% import %}, or {% extends %}. Those constructs require a real loader such as FileSystemLoader, PackageLoader, or a custom loader.

Separately, src/conductor/config/loader.py implements the !file YAML constructor by reading the referenced file and inlining its raw contents into the schema field. That means the prompt's source path/directory is discarded before rendering, so the renderer has no template origin from which to resolve relative includes.

Suggested Direction

Possible fixes:

  1. Preserve source metadata for !file prompt values so the renderer knows the prompt file directory.
  2. Configure a Jinja loader for file-backed prompt templates, such as a FileSystemLoader rooted at the prompt directory, workflow directory, or an explicitly configured template search path.
  3. Document the resolution rules for {% include %}, {% import %}, and {% extends %} in prompt templates.
  4. If loader-dependent Jinja features are intentionally unsupported, fail earlier with a clearer error message and document that limitation.

Workaround

The only practical workaround I found is to manually inline shared prompt text into each prompt file instead of using Jinja includes.

Environment

  • Conductor: 0.1.20
  • Jinja2: 3.1.6
  • Python: 3.12

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions