Skip to content

templating

templating

Prompt templating for Marianne jobs.

Handles building sheet prompts from templates and generating auto-completion prompts for partial sheet recovery.

Attributes

Classes

SheetContext dataclass

SheetContext(sheet_num, total_sheets, start_item, end_item, workspace, stage=0, instance=1, fan_count=1, total_stages=0, previous_outputs=dict(), previous_files=dict(), skipped_upstream=list(), injected_context=list(), injected_skills=list(), injected_tools=list())

Context for building a sheet prompt.

Includes both sheet-level metadata and optional cross-sheet context from previous sheet executions. Fan-out metadata (stage, instance, fan_count, total_stages) is populated when fan_out is configured.

Attributes
stage class-attribute instance-attribute
stage = 0

Logical stage number (1-indexed). 0 = not set, falls back to sheet_num.

instance class-attribute instance-attribute
instance = 1

Instance within fan-out group (1-indexed). Default 1.

fan_count class-attribute instance-attribute
fan_count = 1

Total instances in this stage's fan-out group. Default 1.

total_stages class-attribute instance-attribute
total_stages = 0

Original stage count before expansion. 0 = not set, falls back to total_sheets.

previous_outputs class-attribute instance-attribute
previous_outputs = field(default_factory=dict)

Stdout outputs from previous sheets. Keys are sheet numbers (1-indexed).

previous_files class-attribute instance-attribute
previous_files = field(default_factory=dict)

File contents captured between sheets. Keys are file paths.

skipped_upstream class-attribute instance-attribute
skipped_upstream = field(default_factory=list)

Sheet numbers of upstream sheets that were skipped (#120). Allows prompts to handle incomplete fan-in data explicitly.

injected_context class-attribute instance-attribute
injected_context = field(default_factory=list)

Resolved content from 'context' category injections.

injected_skills class-attribute instance-attribute
injected_skills = field(default_factory=list)

Resolved content from 'skill' category injections.

injected_tools class-attribute instance-attribute
injected_tools = field(default_factory=list)

Resolved content from 'tool' category injections.

Functions
to_dict
to_dict()

Convert to dictionary for template rendering.

Provides both old terminology (stage, instance, fan_count, total_stages) and new terminology (movement, voice, voice_count, total_movements) so templates can use either vocabulary. Matches Sheet.template_variables().

Source code in src/marianne/prompts/templating.py
def to_dict(self) -> dict[str, Any]:
    """Convert to dictionary for template rendering.

    Provides both old terminology (stage, instance, fan_count, total_stages)
    and new terminology (movement, voice, voice_count, total_movements) so
    templates can use either vocabulary. Matches Sheet.template_variables().
    """
    effective_stage = self.stage if self.stage > 0 else self.sheet_num
    effective_total = self.total_stages if self.total_stages > 0 else self.total_sheets
    return {
        SHEET_NUM_KEY: self.sheet_num,
        "total_sheets": self.total_sheets,
        "start_item": self.start_item,
        "end_item": self.end_item,
        "workspace": str(self.workspace),
        # Old terminology (backward compat)
        "stage": effective_stage,
        "instance": self.instance,
        "fan_count": self.fan_count,
        "total_stages": effective_total,
        # New terminology aliases (movement/voice vocabulary)
        "movement": effective_stage,
        "voice": self.instance,
        "voice_count": self.fan_count,
        "total_movements": effective_total,
        # Cross-sheet context
        "previous_outputs": self.previous_outputs,
        "previous_files": self.previous_files,
        "skipped_upstream": self.skipped_upstream,
        "injected_context": self.injected_context,
        "injected_skills": self.injected_skills,
        "injected_tools": self.injected_tools,
    }

CompletionContext dataclass

CompletionContext(sheet_num, total_sheets, passed_validations, failed_validations, completion_attempt, max_completion_attempts, original_prompt, workspace)

Context for generating completion prompts.

Uses ValidationResult objects (not just ValidationRule) to ensure that file paths are properly expanded with actual values like workspace and sheet_num, rather than showing template placeholders.

PromptBuilder

PromptBuilder(config, jinja_env=None)

Builds prompts including completion prompts for partial recovery.

Handles Jinja2 template rendering and auto-generation of completion prompts when a sheet partially completes.

Initialize prompt builder.

Parameters:

Name Type Description Default
config PromptConfig

Prompt configuration from job config.

required
jinja_env Environment | None

Optional custom Jinja2 environment.

None
Source code in src/marianne/prompts/templating.py
def __init__(
    self,
    config: PromptConfig,
    jinja_env: jinja2.Environment | None = None,
) -> None:
    """Initialize prompt builder.

    Args:
        config: Prompt configuration from job config.
        jinja_env: Optional custom Jinja2 environment.
    """
    self.config = config
    self.env = jinja_env or jinja2.Environment(
        undefined=jinja2.StrictUndefined,
        autoescape=False,
        keep_trailing_newline=True,
    )
Functions
build_sheet_context
build_sheet_context(sheet_num, total_sheets, sheet_size, total_items, start_item, workspace)

Build sheet context from job parameters.

Parameters:

Name Type Description Default
sheet_num int

Current sheet number (1-indexed).

required
total_sheets int

Total number of sheets.

required
sheet_size int

Items per sheet.

required
total_items int

Total items to process.

required
start_item int

First item number (1-indexed).

required
workspace Path

Workspace directory.

required

Returns:

Type Description
SheetContext

SheetContext with calculated item range.

Source code in src/marianne/prompts/templating.py
def build_sheet_context(
    self,
    sheet_num: int,
    total_sheets: int,
    sheet_size: int,
    total_items: int,
    start_item: int,
    workspace: Path,
) -> SheetContext:
    """Build sheet context from job parameters.

    Args:
        sheet_num: Current sheet number (1-indexed).
        total_sheets: Total number of sheets.
        sheet_size: Items per sheet.
        total_items: Total items to process.
        start_item: First item number (1-indexed).
        workspace: Workspace directory.

    Returns:
        SheetContext with calculated item range.
    """
    sheet_start = (sheet_num - 1) * sheet_size + start_item
    sheet_end = min(sheet_start + sheet_size - 1, total_items)

    return SheetContext(
        sheet_num=sheet_num,
        total_sheets=total_sheets,
        start_item=sheet_start,
        end_item=sheet_end,
        workspace=workspace,
    )
build_sheet_prompt
build_sheet_prompt(context, patterns=None, validation_rules=None, failure_history=None, spec_fragments=None)

Build the standard sheet prompt from config.

Parameters:

Name Type Description Default
context SheetContext

Sheet context with item range and workspace.

required
patterns list[str] | None

Optional list of learned pattern descriptions to inject.

None
validation_rules list[ValidationRule] | None

Optional list of validation rules to inject as requirements. These are the rules that will be checked after sheet execution - injecting them helps Claude understand exactly what success looks like.

None
failure_history list[HistoricalFailure] | None

Optional list of historical failures from previous sheets. Injected to help Claude learn from past mistakes and avoid repeating the same errors (Evolution v6).

None
spec_fragments list[SpecFragment] | None

Optional list of spec corpus fragments to inject as project context. Fragments are inserted after injected context and before failure history per the prompt assembly order (Phase 1: Spec Corpus Pipeline).

None

Returns:

Type Description
str

Rendered prompt string.

Source code in src/marianne/prompts/templating.py
def build_sheet_prompt(
    self,
    context: SheetContext,
    patterns: list[str] | None = None,
    validation_rules: list[ValidationRule] | None = None,
    failure_history: list["HistoricalFailure"] | None = None,
    spec_fragments: list[SpecFragment] | None = None,
) -> str:
    """Build the standard sheet prompt from config.

    Args:
        context: Sheet context with item range and workspace.
        patterns: Optional list of learned pattern descriptions to inject.
        validation_rules: Optional list of validation rules to inject as
            requirements. These are the rules that will be checked after
            sheet execution - injecting them helps Claude understand
            exactly what success looks like.
        failure_history: Optional list of historical failures from previous
            sheets. Injected to help Claude learn from past mistakes and
            avoid repeating the same errors (Evolution v6).
        spec_fragments: Optional list of spec corpus fragments to inject
            as project context. Fragments are inserted after injected
            context and before failure history per the prompt assembly
            order (Phase 1: Spec Corpus Pipeline).

    Returns:
        Rendered prompt string.
    """
    template_context = context.to_dict()

    # Merge config variables.
    # Normalize nested dict keys: JSON roundtrip (model_dump → model_validate)
    # converts integer dict keys to strings. Jinja2 templates use
    # ``dict[instance]`` where ``instance`` is an integer, so string-keyed
    # dicts cause UndefinedError. Restore integer keys where possible.
    template_context.update(_normalize_variable_keys(self.config.variables))

    # Add stakes and thinking method
    template_context["stakes"] = self.config.stakes or ""
    template_context["thinking_method"] = self.config.thinking_method or ""

    # PROMPT ASSEMBLY ORDER (optimized for prompt caching):
    # 1. Static prelude/cadenza content first (better cache hits)
    # 2. Dynamic template content second (changes on retries)
    # 3. Additional context/patterns/validations last

    # Inject skills/tools FIRST (static content from prelude/cadenza)
    # These are typically unchanged across retries, so placing them
    # at the front maximizes prompt cache utility.
    skills_tools_section = self._format_injection_section(
        context.injected_skills, context.injected_tools
    )
    prompt = skills_tools_section if skills_tools_section else ""

    # Inject context SECOND (static content from prelude/cadenza)
    if context.injected_context:
        context_parts = "\n\n".join(context.injected_context)
        context_section = f"## Injected Context\n\n{context_parts}"
        if prompt:
            prompt = f"{prompt}\n\n{context_section}"
        else:
            prompt = context_section

    # Render template THIRD (dynamic content that changes on retries)
    # Template contains per-attempt variables, sheet-specific logic,
    # and retry-specific state. By placing this after static prelude/cadenza,
    # we maximize the cacheable prefix.
    if self.config.template:
        template = self.env.from_string(self.config.template)
        template_body = template.render(**template_context)
    elif self.config.template_file and self.config.template_file.exists():
        template_content = self.config.template_file.read_text()
        template = self.env.from_string(template_content)
        template_body = template.render(**template_context)
    else:
        template_body = self._build_default_prompt(context)

    if prompt:
        prompt = f"{prompt}\n\n{template_body}"
    else:
        prompt = template_body

    # Inject spec corpus fragments (Phase 1: Spec Corpus Pipeline)
    # Placed after injected context, before failure history — aligns with
    # architecture.yaml's prompt assembly order (layer 5→6 boundary).
    if spec_fragments:
        spec_section = PromptBuilder._format_spec_fragments(spec_fragments)
        prompt = f"{prompt}\n\n{spec_section}"

    # Inject failure history first (lessons learned from past)
    if failure_history:
        history_section = self._format_historical_failures(failure_history)
        prompt = f"{prompt}\n\n{history_section}"

    # Inject learned patterns if available
    if patterns:
        pattern_section = self._format_patterns_section(patterns)
        prompt = f"{prompt}\n\n{pattern_section}"

    # Inject validation requirements if available
    if validation_rules:
        validation_section = self._format_validation_requirements(
            validation_rules, template_context
        )
        prompt = f"{prompt}\n\n{validation_section}"

    return prompt
build_completion_prompt
build_completion_prompt(ctx, semantic_hints=None)

Generate auto-completion prompt for partial failures.

This prompt tells the agent: 1. What validations already passed (don't redo) 2. What validations failed (focus on these) 3. Semantic hints for focused recovery 4. Clear instruction to complete only missing items

Parameters:

Name Type Description Default
ctx CompletionContext

Completion context with passed/failed validations.

required
semantic_hints list[str] | None

Optional list of actionable hints from semantic validation analysis. These are suggested fixes derived from the failure_category and suggested_fix fields of failed ValidationResults.

None

Returns:

Type Description
str

Completion prompt string.

Source code in src/marianne/prompts/templating.py
    def build_completion_prompt(
        self,
        ctx: CompletionContext,
        semantic_hints: list[str] | None = None,
    ) -> str:
        """Generate auto-completion prompt for partial failures.

        This prompt tells the agent:
        1. What validations already passed (don't redo)
        2. What validations failed (focus on these)
        3. Semantic hints for focused recovery
        4. Clear instruction to complete only missing items

        Args:
            ctx: Completion context with passed/failed validations.
            semantic_hints: Optional list of actionable hints from semantic
                validation analysis. These are suggested fixes derived from
                the failure_category and suggested_fix fields of failed
                ValidationResults.

        Returns:
            Completion prompt string.
        """
        passed_section = self._format_passed_validations(ctx.passed_validations)
        failed_section = self._format_failed_validations(ctx.failed_validations)
        hints_section = self._format_semantic_hints(semantic_hints)

        # Truncate original prompt if very long
        max_prompt_context_chars = 3000
        original_context = ctx.original_prompt
        if len(original_context) > max_prompt_context_chars:
            truncation_msg = "\n\n[... original prompt truncated for brevity ...]"
            original_context = original_context[:max_prompt_context_chars] + truncation_msg

        completion_prompt = f"""## COMPLETION MODE - Sheet {ctx.sheet_num}

This is completion attempt {ctx.completion_attempt} of {ctx.max_completion_attempts}.

A previous execution of this sheet partially completed. Your job is to \
finish ONLY the incomplete items.

### ALREADY COMPLETED (DO NOT REDO)
The following outputs were successfully created and validated:
{passed_section}

These files exist and are valid. DO NOT recreate or modify them unless absolutely necessary.

### INCOMPLETE ITEMS (FOCUS HERE)
The following validations failed and need to be completed:
{failed_section}
{hints_section}
### INSTRUCTIONS
1. Review what already exists to understand the context
2. Complete ONLY the missing items listed above
3. Do not duplicate work that was already done
4. Ensure all validation requirements are met before finishing

### ORIGINAL TASK CONTEXT
{original_context}

---
Focus on completing the missing items. Do not start over from scratch."""

        return completion_prompt.strip()

Functions

build_sheet_prompt_simple

build_sheet_prompt_simple(config, sheet_num, total_sheets, sheet_size, total_items, start_item, workspace)

Convenience function to build a sheet prompt.

This provides a simpler interface for cases where you don't need to reuse the PromptBuilder.

Parameters:

Name Type Description Default
config PromptConfig

Prompt configuration.

required
sheet_num int

Current sheet number.

required
total_sheets int

Total number of sheets.

required
sheet_size int

Items per sheet.

required
total_items int

Total items.

required
start_item int

First item number.

required
workspace Path

Workspace directory.

required

Returns:

Type Description
str

Rendered prompt string.

Source code in src/marianne/prompts/templating.py
def build_sheet_prompt_simple(
    config: PromptConfig,
    sheet_num: int,
    total_sheets: int,
    sheet_size: int,
    total_items: int,
    start_item: int,
    workspace: Path,
) -> str:
    """Convenience function to build a sheet prompt.

    This provides a simpler interface for cases where you don't need
    to reuse the PromptBuilder.

    Args:
        config: Prompt configuration.
        sheet_num: Current sheet number.
        total_sheets: Total number of sheets.
        sheet_size: Items per sheet.
        total_items: Total items.
        start_item: First item number.
        workspace: Workspace directory.

    Returns:
        Rendered prompt string.
    """
    builder = PromptBuilder(config)
    context = builder.build_sheet_context(
        sheet_num=sheet_num,
        total_sheets=total_sheets,
        sheet_size=sheet_size,
        total_items=total_items,
        start_item=start_item,
        workspace=workspace,
    )
    return builder.build_sheet_prompt(context)