Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Dynamic Variables Plugin

Injects dynamic system information (date, time, OS, working directory) into the turn’s user message, as an XML block prepended on the way to the provider.

Details

IDnexus.system.dynvars
DependenciesNone

Configuration

Each variable is opt-in — defaults to false and must be explicitly enabled:

KeyTypeDefaultDescription
dateboolfalseInclude current date
timeboolfalseInclude current time
timezoneboolfalseInclude timezone
cwdboolfalseInclude current working directory
session_dirboolfalseInclude session directory path
osboolfalseInclude operating system
request_headerslist of strings[]Normalized X-Nexus-* request header names to surface — see Request Headers

Events

Subscribes to before:llm.request (priority 20) and emits nothing. It applies its block to the outbound request in place; it registers no Prompt Registry section.

Priority 20 puts it after the handlers that rewrite a request’s shape — fanout (2), fallback (3), the schema registry (5), progressive discovery (8) and skills (15) — so none of them sees a message this plugin has already decorated.

Output

The block is prepended to the request’s last user message:

<runtime_context>
Current date: 2026-04-08
Current time: 10:30:00
Timezone: EST
Working directory: /Users/frank/projects/myapp
Session directory: ~/.nexus/sessions/abc123
OS: darwin/arm64
</runtime_context>

what files changed today?

A request with no user message — a planner prompt, a gate repair prompt — is left alone rather than having one invented to hold the block.

For a multimodal message the block is also inserted as a leading text part, because a provider may serialize Parts instead of Content.

Why the user message and not the system prompt

These values change every turn, the clock most obviously. A system prompt is the most cache-stable region of a request and sits at the very front of it, so a per-turn value there invalidates the prompt cache at position zero and re-bills the whole conversation on every turn. On the live user message the only volatile bytes sit at the end of the prompt, which is where a cache wants its variance.

It never enters conversation history

The block is applied to the outbound llm.request, never to the message a memory plugin persisted — the plugin clones the message slice before writing, so a provider that hands out its internal slice cannot be edited through it.

That is what rules out the usual failure of per-turn injection: a history ten turns deep carrying ten increasingly stale copies of “Current time”, contradicting each other. Nothing needs to detect or strip an old block, because no old block is ever written. Do not add a cleanup pass that rewrites stored messages — it would break prompt caching (the stable prefix would change every turn), diverge journal and eval golden traces from what was actually sent, and risk eating a user’s own text that happened to look like a block.

Example Configuration

# Empty config → no variables emitted (every flag defaults to false).
nexus.system.dynvars: {}

# Enable only the variables you want.
nexus.system.dynvars:
  date: true
  cwd: true