zidane/ prompt
Helpers for splitting a system prompt into a cacheable static half and a per-turn dynamic half.
import { … } from 'zidane/prompt'src/system-prompt.ts
Use zidane/prompt to build or edit system prompts so the static part stays byte-identical across turns and the provider prompt cache keeps hitting. A boundary marker separates the halves, and providers strip it before the request goes out. The helpers join, split, append to and replace either half without touching the other.
import { appendDynamicSection, joinSystemPrompt, renderSystemForWire, splitSystemPrompt } from 'zidane/prompt'
const system = joinSystemPrompt('You are a careful code reviewer.', 'Today is Monday.')
const withBranch = appendDynamicSection(system, 'Current branch: main')
const { static: doctrine, dynamic } = splitSystemPrompt(withBranch)
console.log(doctrine)
console.log(dynamic)
console.log(renderSystemForWire(withBranch))Functions#
appendDynamicSectionFunction#
Append extra to the DYNAMIC half of a system prompt. Inserts the boundary
marker if the input didn't already carry one.
Used by hosts (typically the TUI) to inject per-turn state — current cwd,
IDE selection, project root — without forcing every caller to know the
marker format. The host's system:transform hook rewrites the dynamic
half each turn; the static doctrine above stays byte-stable and rides the
cache.
Returns a new string; the input is not mutated. When extra is empty,
returns the input verbatim.
function appendDynamicSection(system: string, extra: string): stringParameters
| Name | Type | Description |
|---|---|---|
| system | string | System prompt, with or without a boundary marker. |
| extra | string | Text to append to the dynamic half. |
Returns
stringappendStaticSectionFunction#
Append extra to the STATIC half of a system prompt, preserving any
existing dynamic suffix.
Used by the agent to fold in run-stable content (skills catalog, lazy tool catalog) without bumping it into the dynamic half — both catalogs are built once per run and remain byte-stable for the duration, so they belong in the cached prefix.
Returns a new string; the input is not mutated. When extra is empty,
returns the input verbatim.
function appendStaticSection(system: string, extra: string): stringParameters
| Name | Type | Description |
|---|---|---|
| system | string | System prompt, with or without a boundary marker. |
| extra | string | Text to append to the static half. |
Returns
stringhasSystemPromptBoundaryFunction#
True when system contains the boundary marker.
function hasSystemPromptBoundary(system: string): booleanParameters
| Name | Type | Description |
|---|---|---|
| system | string | System prompt to check. |
Returns
booleanjoinSystemPromptFunction#
Compose a system prompt from a static prefix and an optional dynamic
suffix. Inserts SYSTEM_PROMPT_BOUNDARY between them only when the
dynamic side is non-empty — single-block prompts stay marker-free, so
callers that never opt in pay zero overhead and providers fall back to the
existing whole-string caching path.
Spacing is \n\n on both sides of the marker so doctrine fragments,
which conventionally separate sections with a blank line, read cleanly
around the boundary.
function joinSystemPrompt(staticPart: string, dynamicPart: string): stringParameters
| Name | Type | Description |
|---|---|---|
| staticPart | string | Cacheable prefix. |
| dynamicPart | string | Per-turn suffix. Empty to omit the marker. |
Returns
stringrenderSystemForWireFunction#
Strip the boundary marker so it never reaches the wire — collapses
<static>${BOUNDARY}<dynamic> into <static>\n\n<dynamic> for providers
that can't honor cache_control (vanilla OpenAI Chat Completions, Codex,
Cerebras, ...) or for the cache-disabled path on any provider.
Cache-aware providers re-derive the split from the original (un-rendered)
system string via splitSystemPrompt — renderSystemForWire is the
marker-free counterpart used to build the actual wire bytes.
No-op when the input has no marker. Pure / no I/O.
function renderSystemForWire(system: string): stringParameters
| Name | Type | Description |
|---|---|---|
| system | string | System prompt, with or without a boundary marker. |
Returns
stringreplaceDynamicSectionFunction#
Replace the entire dynamic half of a system prompt with next. Used by
the TUI's <env> rewriter where the entire dynamic section is regenerated
each turn rather than appended to.
When next is empty, drops the dynamic half (and the marker) entirely.
function replaceDynamicSection(system: string, next: string): stringParameters
| Name | Type | Description |
|---|---|---|
| system | string | System prompt, with or without a boundary marker. |
| next | string | Replacement for the dynamic half. Empty drops it. |
Returns
stringsplitSystemPromptFunction#
Split a system prompt around the first SYSTEM_PROMPT_BOUNDARY.
Splits on the FIRST occurrence — subsequent markers are folded into the
dynamic half. This way callers can append additional <env> style blocks
with extra markers without each one creating a new cache layer (Anthropic
caps breakpoints; we use the budget elsewhere). The marker itself is
stripped from both sides — providers attach cache_control directly to
the static half's text content.
A single blank line (\n\n) immediately adjacent to the marker on each
side is trimmed — callers conventionally write
<doctrine>\n\n<MARKER>\n\n<env> and expect the rendered wire bytes to
read as one logical paragraph. Blank lines beyond that immediate pair
(e.g. \n\n\n<env>) are preserved verbatim so callers composing their
own spacing don't lose intentional gaps.
Pure / no I/O / no allocation when the marker is absent (returns the input verbatim on the static side and the empty string on the dynamic side).
function splitSystemPrompt(system: string): SystemPromptPartsParameters
| Name | Type | Description |
|---|---|---|
| system | string | System prompt, with or without a boundary marker. |
Returns
Constants#
SYSTEM_PROMPT_BOUNDARYConstant#
Literal marker inserted in a system prompt to separate cacheable doctrine from per-turn dynamic content. Providers split on this token.
Underscored on both sides for visual distinctiveness — a stray instance in model-written prose is implausible. Don't change the value without shipping a migration; existing sessions carry the old marker in their cached prompts.
const SYSTEM_PROMPT_BOUNDARY = '__ZIDANE_SYSTEM_PROMPT_BOUNDARY__'Interfaces#
SystemPromptPartsInterface#
Result of splitSystemPrompt — both halves stripped of the marker.
interface SystemPromptPartsProperties
| Name | Type | Description |
|---|---|---|
| dynamic | string | Bytes AFTER the marker. Empty when no marker present. NOT cached. |
| static | string | Bytes BEFORE the marker (or the entire string when no marker present). Cacheable. |