Skip to content

Commit 409e04e

Browse files
fix: keep default prompt date context cache-stable (#796)
* fix: keep default prompt date context cache-stable * fix: escape documented template variables
1 parent 25076ce commit 409e04e

4 files changed

Lines changed: 37 additions & 18 deletions

File tree

internal/agents/builtin/agent-builder/system.md

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -61,16 +61,16 @@ mcp:
6161
```
6262
6363
**system.md** - System prompt with optional template variables:
64-
- `{{date}}`, `{{datetime}}`, `{{datetime_rfc3339}}`, `{{time}}`, `{{year}}`, `{{weekday}}`
65-
- `{{timezone}}`, `{{timezone_abbr}}`, `{{timezone_offset}}`, `{{utc_date}}`, `{{utc_datetime}}`
66-
- `{{cwd}}`, `{{cwd_name}}`, `{{home}}`, `{{user}}`
67-
- `{{git_branch}}`, `{{git_repo}}`, `{{git_diff_stat}}`
68-
- `{{files}}`, `{{file_count}}` (from -f flags)
69-
- `{{os}}`, `{{platform}}`, `{{resource_dir}}`
70-
- `{{provider}}`, `{{model}}`, `{{provider_model}}`
71-
- `{{agents}}` - Loads project instructions (hierarchical AGENTS.md + fallbacks)
72-
73-
**The `{{agents}}` variable** loads project instructions using:
64+
- `{{!date}}`, `{{!datetime}}`, `{{!datetime_rfc3339}}`, `{{!time}}`, `{{!year}}`, `{{!weekday}}`
65+
- `{{!timezone}}`, `{{!timezone_abbr}}`, `{{!timezone_offset}}`, `{{!utc_date}}`, `{{!utc_datetime}}`
66+
- `{{!cwd}}`, `{{!cwd_name}}`, `{{!home}}`, `{{!user}}`
67+
- `{{!git_branch}}`, `{{!git_repo}}`, `{{!git_diff_stat}}`
68+
- `{{!files}}`, `{{!file_count}}` (from -f flags)
69+
- `{{!os}}`, `{{!platform}}`, `{{!resource_dir}}`
70+
- `{{!provider}}`, `{{!model}}`, `{{!provider_model}}`
71+
- `{{!agents}}` - Loads project instructions (hierarchical AGENTS.md + fallbacks)
72+
73+
**The `{{!agents}}` variable** loads project instructions using:
7474
1. User-level: `~/.config/term-llm/AGENTS.md` (always included if present)
7575
2. Project-level: AGENTS.md hierarchy from repo root → cwd (at each level, AGENTS.override.md takes precedence over AGENTS.md)
7676
3. Fallback (only if no AGENTS.md found): CLAUDE.md, .github/copilot-instructions.md, .cursor/rules, CONTRIBUTING.md
@@ -109,7 +109,7 @@ tools:
109109
- Use `custom` instead of `run_agent_script` when the tool has a clear name/schema the LLM should understand natively
110110
- No directory paths are exposed to the LLM — scripts are resolved from the agent's own directory
111111
- No permission prompts — scripts in the agent directory are implicitly trusted
112-
- This replaces the old `{{agent_dir}}` shell allow pattern approach which never worked
112+
- This replaces the old `{{!agent_dir}}` shell allow pattern approach which never worked
113113
- Scripts must be executable files (not symlinks to outside the agent directory)
114114

115115
**Additional .md files** - Include reference material:
@@ -199,10 +199,10 @@ Structure system.md with:
199199

200200
1. **Role** - Clear statement of what the agent does
201201
2. **Context** - Use template variables for dynamic info (date, repo, etc.)
202-
- **Always include `Current local time: {{weekday}} {{datetime_rfc3339}} ({{timezone}}).`** at the top so the agent knows the current date and timezone
203-
- Use `UTC: {{utc_datetime}}.` as well for jobs, logs, or cross-timezone coordination
204-
- Add `User: {{user}}.` if user context is helpful
205-
- Add `Working in: {{cwd_name}}` or `Repository: {{git_repo}}` for project-aware agents
202+
- **Always include `Current local date: {{!weekday}} {{!date}} ({{!timezone}}).`** at the top so the agent knows the current local date without changing every minute and breaking prompt caches
203+
- Add `{{!time}}`, `{{!datetime_rfc3339}}`, or `UTC: {{!utc_datetime}}.` only for agents that genuinely need time-of-day precision, logs, jobs, or cross-timezone coordination
204+
- Add `User: {{!user}}.` if user context is helpful
205+
- Add `Working in: {{!cwd_name}}` or `Repository: {{!git_repo}}` for project-aware agents
206206
3. **Process** - Step-by-step workflow the agent should follow
207207
4. **Guidelines** - Best practices and constraints
208208
5. **Output format** - How to structure responses (if relevant)

internal/agents/registry.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ tools:
358358
// Create system.md
359359
systemMD := fmt.Sprintf(`You are a helpful assistant for the {{git_repo}} project.
360360
361-
Current local time: {{weekday}} {{datetime_rfc3339}} ({{timezone}}). Working directory: {{cwd}}
361+
Current local date: {{weekday}} {{date}} ({{timezone}}). Working directory: {{cwd}}
362362
363363
## Your Role
364364

internal/agents/template.go

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,10 @@ import (
1919
// templateVarPattern matches {{variable}} tokens, allowing optional whitespace inside delimiters.
2020
var templateVarPattern = regexp.MustCompile(`\{\{\s*(\w+)\s*\}\}`)
2121

22+
// escapedTemplateVarPattern matches {{!variable}} tokens. Escaped variables render as
23+
// literal {{variable}} text so prompts can document template syntax without expanding it.
24+
var escapedTemplateVarPattern = regexp.MustCompile(`\{\{\s*!\s*(\w+)\s*\}\}`)
25+
2226
// TemplateContext holds values for template variable expansion.
2327
type TemplateContext struct {
2428
// Time-related
@@ -223,8 +227,9 @@ func (c TemplateContext) WithLLM(provider, model string) TemplateContext {
223227
}
224228

225229
// ExpandTemplate replaces {{variable}} placeholders with values from context.
230+
// Use {{!variable}} to render a literal {{variable}} without expanding it.
226231
func ExpandTemplate(text string, ctx TemplateContext) string {
227-
return templateVarPattern.ReplaceAllStringFunc(text, func(match string) string {
232+
expanded := templateVarPattern.ReplaceAllStringFunc(text, func(match string) string {
228233
// Extract variable name.
229234
matches := templateVarPattern.FindStringSubmatch(match)
230235
if len(matches) != 2 {
@@ -312,6 +317,7 @@ func ExpandTemplate(text string, ctx TemplateContext) string {
312317
return match
313318
}
314319
})
320+
return escapedTemplateVarPattern.ReplaceAllString(expanded, "{{$1}}")
315321
}
316322

317323
// gitProbeTimeout bounds git subprocesses used during template expansion so prompt construction

internal/agents/template_test.go

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,16 @@ func TestExpandTemplate(t *testing.T) {
9191
template: "{{weekday}} {{datetime_rfc3339}} {{timezone}} {{timezone_abbr}} {{timezone_offset}} UTC={{utc_datetime}}",
9292
expected: "Friday 2026-01-16T14:30:00+11:00 Australia/Sydney AEDT +11:00 UTC=2026-01-16T03:30:00Z",
9393
},
94+
{
95+
name: "escaped variable renders literally",
96+
template: "Document {{!date}} and {{ ! timezone }}; expand {{date}}",
97+
expected: "Document {{date}} and {{timezone}}; expand 2026-01-16",
98+
},
99+
{
100+
name: "escaped unknown variable renders literally",
101+
template: "Old token {{!agent_dir}}",
102+
expected: "Old token {{agent_dir}}",
103+
},
94104
{
95105
name: "all variables",
96106
template: "{{date}} {{datetime}} {{datetime_rfc3339}} {{time}} {{year}} {{weekday}} {{timezone}} {{timezone_abbr}} {{timezone_offset}} {{utc_date}} {{utc_datetime}} {{cwd}} {{cwd_name}} {{home}} {{user}} {{git_branch}} {{git_repo}} {{files}} {{file_count}} {{os}} {{platform}} {{provider}} {{model}} {{provider_model}} {{resource_dir}} {{handover_dir}} {{handover_path}}",
@@ -279,12 +289,15 @@ func TestNewTemplateContextForTemplate_GitDiffStatTimeoutFailsOpen(t *testing.T)
279289
}
280290

281291
func TestTemplateVariablesAllowsWhitespace(t *testing.T) {
282-
vars := templateVariables("{{ git_repo }}/{{git_branch}} {{ git_diff_stat }} {{ agents }} {{ handover_dir }} {{ handover_path }}")
292+
vars := templateVariables("{{ git_repo }}/{{git_branch}} {{ git_diff_stat }} {{ agents }} {{ handover_dir }} {{ handover_path }} {{!date}}")
283293
for _, name := range []string{"git_repo", "git_branch", "git_diff_stat", "agents", "handover_dir", "handover_path"} {
284294
if !vars[name] {
285295
t.Fatalf("templateVariables missing %q in %#v", name, vars)
286296
}
287297
}
298+
if vars["date"] {
299+
t.Fatalf("templateVariables should ignore escaped variable {{!date}}: %#v", vars)
300+
}
288301
}
289302

290303
func TestNewTemplateContext(t *testing.T) {

0 commit comments

Comments
 (0)