docs/7-DEVELOPMENT/prompts.md
How prompts are organized and the patterns they use. All prompts are Jinja2 templates under prompts/, rendered with the ai-prompter library — prompt engineering lives in templates, not Python.
Templates are grouped by workflow — ask/, chat/, source_chat/, podcast/ — and referenced by path without extension:
from ai_prompter import Prompter
prompt = Prompter(prompt_template="ask/entry", parser=parser).render(data=state)
Mechanical rules (path syntax, data= key matching, parser injection, no inheritance, cache → restart) are in open_notebook/AGENTS.md. This page covers the patterns.
The ask pipeline is three templates orchestrated by graphs/ask.py:
entry.jinja user question → JSON search strategy (PydanticOutputParser)
↓
query_process.jinja one search term + retrieved results → sub-answer (parallel, one per search)
↓
final_answer.jinja all sub-answers → synthesized final response with citations
The stage boundaries let each prompt do one job well, and the JSON strategy output makes the fan-out deterministic.
Templates accept optional variables via Jinja conditionals, so one template serves several context shapes (podcast outline handles list or string context; source_chat injects optional notebook/insight data):
{% if notebook %}
# PROJECT INFORMATION
{{ notebook }}
{% endif %}
Watch the loose truthiness ({% if var %} is false for empty string/list) and the for-loop assumption (passing a string where a list is expected iterates character by character).
Response-generating templates (ask, chat) state the citation rules — [source:id], [note:id], [insight:id], "do not make up document IDs" — multiple times, with inline examples. LLMs hallucinate citations without this; repetition + examples measurably reduces it. Keep the repetition when editing these templates.
Templates expose an {{ format_instructions }} slot filled by the caller's OutputParser. Output format evolves in Python (Pydantic models) without touching the template. If the placeholder is missing, the parser is silently ignored — check for it when adding structured output.
Podcast templates instruct thinking models to keep reasoning inside <think> tags and emit the JSON after them; clean_thinking_content() strips the tags downstream. If a new template expects structured output from thinking-capable models, include the same instruction block.