Appearance
Prompt Templating
Bonsai Backend uses Handlebars as its templating engine for all prompts — stage system prompts, agent prompts, tool prompts, and various effect templates. This allows dynamic content injection based on conversation state.
Available Variables
Templates have access to these data contexts:
| Variable | Description |
|---|---|
vars | Current stage variables |
userProfile | End user's profile data |
consts | Project-level constants |
userInput | Current user input text |
history | Conversation message history (auto-injected) |
context.results | Results from tool calls, webhooks, and actions |
time | Current date/time context, timezone-aware (see Time Context) |
project | Project-level settings: timezone, languageCode, and language (see Project Context) |
agent | The agent's personality prompt text — must be explicitly placed in the template (see Agent & Knowledge Variables) |
faq | Array of { question, answer } objects from knowledge classification — stage system prompts only, must be explicitly placed (see Agent & Knowledge Variables) |
copy | Selected sample copy content joined by newlines and rendered using selected decorator, must be explicitly placed (see Sample Copy Variables) |
copyContent | Same as copy — the raw selected content before any copy decorator is applied |
sampleCopy | Array of all sample copies active for the current stage: { name, trigger, content[] } |
Basic Syntax
Variable Interpolation
Nested Properties
Built-in Helpers
Bonsai Backend registers custom Handlebars helpers for common operations:
get — Safe Nested Access
Safely access nested properties without errors if intermediate values are undefined:
exists — Check Value Existence
Block helper that renders content only if a value exists (is not null/undefined):
hasItems — Check Array Length
Block helper that renders content only if an array has elements:
join — Join Array Elements
Join array elements with a separator:
contains — Check Array Membership
Check if an array contains a specific value:
default — Fallback Values
Provide a fallback value if the primary value is undefined:
json — JSON Stringify
Convert a value to its JSON string representation:
Template Caching
Templates are compiled and cached for performance (up to 1,000 templates). Caching is transparent — when a prompt changes, the new version is automatically compiled on next use.
Usage in Prompts
Stage System Prompt
Effect Templates
The modify_user_input effect uses Handlebars:
The call_tool effect (for webhook tools) supports Handlebars in URLs, headers, and body:
Best Practices
- Use
existsguards — Prevent rendering undefined values in prompts - Keep prompts focused — Include only relevant context for each stage
- Use constants for shared values — Avoid hardcoding company names, URLs, etc.
- Leverage variables — Use stage variables to build progressive context across turns
- Test with edge cases — Consider what happens when variables are empty or unset
Agent & Knowledge Variables
agent
Expands to the personality prompt text of the agent linked to the current stage. Available in stage system prompts, classifier prompts, and context transformer prompts.
Important:
{{agent}}is not auto-injected. The agent's personality only reaches the LLM if you explicitly include{{agent}}in your prompt template. Without it, the agent'spromptfield has no effect.
Recommended placement is at the top of the system prompt, followed by stage-specific instructions:
faq
Expands to an array of { question, answer } objects matched from the knowledge base during the current (or most recent) knowledge classification. Available only in stage system prompts — not in classifier or transformer prompts.
Important:
{{faq}}is not auto-injected. If knowledge classification matched results but your prompt does not include{{faq}}, those results are silently discarded and the LLM never sees them.
The faq array persists across turns until a new knowledge classification fires. Use {{#hasItems faq}} to guard against the empty case:
Sample Copy Variables
copy
Expands to the content items selected by the sample copy distributor for the current turn, joined by newlines with applied decorator. Only populated when the sample copy classifier matched a copy on this turn; otherwise an empty string.
Important:
{{copy}}is not auto-injected and is the activation signal for sample copy processing — if the stage prompt does not contain{{copy}}or{{copy., the entire sample copy pipeline (classification, sampling) is skipped for that stage.
Use {{#if copy}} to guard against the empty case:
copyContent
Similar to copy. Exposes the raw selected content string, which is the same value that copy holds (the content after sampling but before any copy decorator template is applied).
sampleCopy
Array of all sample copies active for the current stage, regardless of whether any was selected this turn. Each item has:
| Field | Type | Description |
|---|---|---|
name | string | Sample copy name (used as match identifier) |
trigger | string | The promptTrigger string used by the classifier |
content | string[] | The full array of variant answers |
Primarily useful in classifier and transformer prompts that need to enumerate available copies:
Project Context
Every prompt template has access to a project object with settings configured on the project:
| Field | Example | Description |
|---|---|---|
project.timezone | "Europe/Warsaw" | IANA timezone identifier set on the project, or null if not configured |
project.languageCode | "en-US" | ISO language code set on the project, or null if not configured |
project.language | "American English" | Human-readable English name derived from languageCode, or null if not configured |
These values are static for the lifetime of the conversation and useful for language-aware prompts:
Time Context
Every prompt template receives a time object containing the current date and time anchored to the conversation's resolved timezone. This eliminates LLM hallucinations on date/time questions and gives prompt authors first-class support for relative date expressions like "next Tuesday" or "this week".
Timezone Precedence
The timezone is resolved once when the conversation starts and persisted for its lifetime:
start_conversation.timezone
→ userProfile.timezone
→ project.timezone
→ UTC (fallback)Set a project-wide default in project settings (timezone field, IANA identifier e.g. Europe/Warsaw). Override per-conversation by passing timezone in the WebSocket start_conversation message. Override per-user by storing timezone in the user's profile JSON.
Quick Start — LLM Grounding
Drop the pre-formatted anchor sentence at the top of any system prompt:
This produces a single sentence the LLM can consume immediately, e.g.:
Today is Friday, 27 February 2026 (Europe/Warsaw, UTC+01:00). This week (Mon–Sun): 23 Feb–1 Mar. Next week: 2 Mar–8 Mar. Next Mon: 2 Mar, Tue: 3 Mar, Wed: 4 Mar, Thu: 5 Mar, Fri: 6 Mar, Sat: 7 Mar, Sun: 8 Mar.
Current Moment Fields
| Field | Example | Description |
|---|---|---|
time.iso | "2026-02-27T14:30:00.000+01:00" | Full ISO 8601 timestamp |
time.timestamp | 1772150200000 | Unix epoch (ms) |
time.date | "2026-02-27" | Date in YYYY-MM-DD |
time.time | "14:30:00" | Time in HH:MM:SS (24-hour) |
time.dateTime | "2026-02-27 14:30:00" | Combined date and time |
time.year | "2026" | Four-digit year |
time.month | "02" | Zero-padded month |
time.day | "27" | Zero-padded day of month |
time.hour | "14" | Zero-padded hour (24-h) |
time.minute | "30" | Zero-padded minute |
time.second | "00" | Zero-padded second |
time.monthName | "February" | Full month name |
time.monthNameShort | "Feb" | Abbreviated month name |
time.dayOfWeek | "Friday" | Full weekday name |
time.dayOfWeekShort | "Fri" | Abbreviated weekday name |
time.timezone | "Europe/Warsaw" | IANA timezone identifier in use |
time.offset | "+01:00" | UTC offset string |
Relative Date Fields
These fields always hold the date (YYYY-MM-DD) of the next occurrence of each weekday, or today if today is that weekday. They are essential for booking, scheduling, and reminder scenarios.
| Field | Description |
|---|---|
time.nextMonday | Date of next (or current) Monday |
time.nextTuesday | Date of next (or current) Tuesday |
time.nextWednesday | Date of next (or current) Wednesday |
time.nextThursday | Date of next (or current) Thursday |
time.nextFriday | Date of next (or current) Friday |
time.nextSaturday | Date of next (or current) Saturday |
time.nextSunday | Date of next (or current) Sunday |
Upcoming Calendar
time.calendar is an array of the next 14 days starting from today. Each entry has:
| Property | Type | Description |
|---|---|---|
date | string | YYYY-MM-DD |
dayName | string | Full weekday name, e.g. "Monday" |
dayNameShort | string | Abbreviated, e.g. "Mon" |
month | string | Full month name, e.g. "March" |
dayOfMonth | number | Day of month, e.g. 2 |
isToday | boolean | true for the first entry (today) |
Render it with {{{json time.calendar}}} to give a structured LLM model the full two-week window:
Examples
Booking assistant — anchor + specific day reference:
Appointment reminder with full date:
Show current time and timezone to user: