# How Hiring-Agent Manages Prompts: A Jinja2 Template Architecture

> Discover how Hiring-Agent manages LLM prompts using Jinja2 templates and a central TemplateManager. Decouple prompt content from application logic for cleaner code.

- Repository: [HackerRank/hiring-agent](https://github.com/interviewstreet/hiring-agent)
- Tags: architecture
- Published: 2026-06-30

---

**Hiring-Agent stores all LLM prompts as Jinja2 templates in `prompts/templates` and uses a centralized `TemplateManager` class to load, cache, and render them with runtime variables, completely decoupling prompt content from application logic.**

Hiring-Agent, the open-source resume evaluation tool from InterviewStreet, implements a declarative approach to prompt management. Rather than embedding hardcoded strings throughout the codebase, every prompt lives as a version-controlled Jinja template. A dedicated [`TemplateManager`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py) handles the lifecycle of these templates, ensuring that prompt engineers can iterate on wording without modifying Python code.

## The TemplateManager Class

The [`TemplateManager`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py) is the single source of truth for prompt retrieval and rendering. It initializes a Jinja2 `Environment` with a `FileSystemLoader` pointed at the templates directory, then eagerly loads all defined templates into an internal cache.

### Initialization and Template Loading

During instantiation, the manager maps logical section names to physical filenames and loads them into memory:

```python

# prompts/template_manager.py

class TemplateManager:
    def __init__(self, template_dir: str = "prompts/templates"):
        self.template_dir = template_dir
        self.env = Environment(
            loader=FileSystemLoader(template_dir), 
            trim_blocks=True, 
            lstrip_blocks=True
        )
        self._templates: Dict[str, Template] = {}
        self._load_templates()

    def _load_templates(self):
        template_files = {
            "basics": "basics.jinja",
            "work": "work.jinja",
            "education": "education.jinja",
            "skills": "skills.jinja",
            "projects": "projects.jinja",
            "awards": "awards.jinja",
            "system_message": "system_message.jinja",
            "github_project_selection": "github_project_selection.jinja",
            "resume_evaluation_criteria": "resume_evaluation_criteria.jinja",
            "resume_evaluation_system_message": "resume_evaluation_system_message.jinja",
        }
        for section_name, filename in template_files.items():
            template_path = os.path.join(self.template_dir, filename)
            if os.path.exists(template_path):
                self._templates[section_name] = self.env.get_template(filename)

```

This eager loading means disk I/O happens once at startup, and subsequent render calls operate against cached `Template` objects.

### Rendering Logic

The `render_template()` method accepts a logical section name and keyword arguments that populate the Jinja placeholders:

```python
def render_template(self, section_name: str, **kwargs) -> str:
    template = self._templates.get(section_name)
    if not template:
        raise ValueError(f"Template {section_name} not found")
    return template.render(**kwargs)

```

Components call this method with runtime data—such as raw resume markdown—and receive a fully interpolated string ready for the LLM.

## Template Organization

All template files reside in [`prompts/templates/`](https://github.com/interviewstreet/hiring-agent/tree/main/prompts/templates). The naming convention follows resume sections and functional concerns:

- **Section extraction**: [`basics.jinja`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/templates/basics.jinja), `work.jinja`, `education.jinja`, `skills.jinja`, `projects.jinja`, `awards.jinja`
- **System context**: `system_message.jinja`
- **GitHub integration**: `github_project_selection.jinja`
- **Evaluation**: `resume_evaluation_criteria.jinja`, `resume_evaluation_system_message.jinja`

A typical template, such as [`basics.jinja`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/templates/basics.jinja), uses the `{{ text_content }}` placeholder to inject the resume text at runtime:

```jinja
Extract ONLY the basic information (name, email, phone, location, profiles) from this resume.

--- The input resume markdown starts here ---
{{ text_content }}
--- The input resume markdown ends here ---

```

## How Components Consume the Prompt System

### PDF Resume Extraction

In [[`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py)](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py), the extractor instantiates `TemplateManager` and renders section-specific prompts to guide the LLM in parsing unstructured PDF text:

```python

# pdf.py

self.template_manager = TemplateManager()
work_prompt = self.template_manager.render_template(
    "work", 
    text_content=resume_text
)

```

The rendered prompt is then passed to the LLM to extract structured work history.

### GitHub Project Selection

The GitHub integration in [[`github.py`](https://github.com/interviewstreet/hiring-agent/blob/main/github.py)](https://github.com/interviewstreet/hiring-agent/blob/main/github.py) uses the manager to generate selection prompts:

```python

# github.py

selection_prompt = self.template_manager.render_template(
    "github_project_selection",
    projects=candidate_projects,
    job_description=job_desc
)

```

### Resume Evaluation

The [[`evaluator.py`](https://github.com/interviewstreet/hiring-agent/blob/main/evaluator.py)](https://github.com/interviewstreet/hiring-agent/blob/main/evaluator.py) module composes complex evaluation prompts by rendering multiple templates:

```python

# evaluator.py

criteria = self.template_manager.render_template(
    "resume_evaluation_criteria", 
    text_content=resume_text
)
system_msg = self.template_manager.render_template(
    "resume_evaluation_system_message"
)

```

These strings are combined and sent to the LLM defined in [[`prompt.py`](https://github.com/interviewstreet/hiring-agent/blob/main/prompt.py)](https://github.com/interviewstreet/hiring-agent/blob/main/prompt.py), which holds model configuration constants and provider mappings but does not define the prompt text itself.

## Summary

- **Prompts are Jinja2 templates** stored in `prompts/templates/` and mapped to logical names in `TemplateManager._load_templates()`.
- **TemplateManager handles the lifecycle**, loading all templates on initialization and rendering them via `render_template(section_name, **kwargs)`.
- **Components remain decoupled** from prompt text; [[`pdf.py`](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py)](https://github.com/interviewstreet/hiring-agent/blob/main/pdf.py), [[`github.py`](https://github.com/interviewstreet/hiring-agent/blob/main/github.py)](https://github.com/interviewstreet/hiring-agent/blob/main/github.py), and [[`evaluator.py`](https://github.com/interviewstreet/hiring-agent/blob/main/evaluator.py)](https://github.com/interviewstreet/hiring-agent/blob/main/evaluator.py) simply request the section they need.
- **Runtime variables** like `text_content` are injected at render time, allowing the same template to be reused across different candidate resumes.

## Frequently Asked Questions

### Where are prompts stored in the hiring-agent repository?

All prompt files are stored as `.jinja` templates in the [`prompts/templates/`](https://github.com/interviewstreet/hiring-agent/tree/main/prompts/templates) directory. This includes files for resume sections (`basics.jinja`, `work.jinja`, etc.), system messages, and evaluation criteria.

### What templating engine does hiring-agent use for prompt management?

The system uses **Jinja2**, configured via `jinja2.Environment` with `FileSystemLoader`, `trim_blocks=True`, and `lstrip_blocks=True` to handle whitespace control. This engine powers the `TemplateManager` central to the prompt architecture.

### How can I add a new prompt template to hiring-agent?

Create a new `.jinja` file in `prompts/templates/`, then register it by adding a key-value pair to the `template_files` dictionary inside `TemplateManager._load_templates()` in [[`prompts/template_manager.py`](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py)](https://github.com/interviewstreet/hiring-agent/blob/main/prompts/template_manager.py). The key becomes the logical name used by other components.

### How does hiring-agent inject dynamic data into prompts?

Components call `TemplateManager.render_template(section_name, **kwargs)`, passing variables such as `text_content` that correspond to Jinja placeholders (e.g., `{{ text_content }}`) in the template files. The method returns a fully interpolated string ready for the LLM API call.