Write system prompts that adapt at runtime using Jinja2 template variables.
The prompt field is a Jinja2 template rendered before every run. That lets one prompt adapt to the
agent's identity, the skills it has, the context a client sends, and the current time, without
rebuilding. This guide shows how to write those templates. For the concept, see
Agents and prompts.
Use {{ variable }} to insert a value and {%- ... %} for logic. This prompt greets by the agent's
name and lists its skills only when there are any:
agent "assistant" {
graph {
type = "react"
}
prompt = <<-EOT
You are {{ agent_name }}, a helpful assistant.
The current date and time is {{ current_datetime }}.
{%- if skills %}
You have access to the following skills:
{%- for skill in skills %}
- {{ skill.name }}: {{ skill.description }}
{%- endfor %}
{%- endif %}
EOT
}The {%- if skills %} guard keeps the block out of the prompt entirely when no skills are registered,
so the rendered prompt stays clean.
The set of variables you can reference is defined by the graph the agent runs on, because the graph
decides what state exists during a run. For the ReAct graph the variables are agent_name, skills,
context_vars, and current_datetime. The authoritative list, with types, lives with the graph in the
ReAct prompt templates reference.
Context variables are values a client attaches to a run, such as the user's name or account tier. Loop
over context_vars to fold them into the prompt:
prompt = <<-EOT
You are a support agent.
{%- if context_vars %}
Context about the customer you are helping:
{%- for var in context_vars %}
- {{ var.description }} is {{ var.value }}
{%- endfor %}
{%- endif %}
EOTSending these values from a client is covered in Pass context from the client.
The prompt field also accepts a list of role and content objects when you want to seed more than a
single system message before the conversation begins. Each item is rendered as its own template:
prompt = [
{ role = "system", content = "You are {{ agent_name }}, a helpful assistant." },
{ role = "user", content = "Always respond in the same language the user writes in." },
{ role = "assistant", content = "Understood. I will match the user's language." }
]The manifest has two interpolation syntaxes and they run at different times. ${locals.value} is HCL
interpolation, evaluated at build time when the manifest is parsed. {{ variable }} and {%- ... %}
are Jinja2, evaluated at runtime for every run. Use HCL interpolation for build-time constants and
Jinja2 for values that change per run:
prompt = "You are {{ agent_name }}, version ${locals.version}. Today is {{ current_datetime }}."Here ${locals.version} is replaced once at build time, while {{ agent_name }} and
{{ current_datetime }} are replaced on every run.
© 2026 pogue.dev. All rights reserved.
CC BY 4.0Search the agentc documentation