Skip to content

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.

TypeScript
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.

TypeScript
function appendDynamicSection(system: string, extra: string): string
Parameters
NameTypeDescription
systemstring

System prompt, with or without a boundary marker.

extrastring

Text to append to the dynamic half.

string

src/system-prompt.ts:172

appendStaticSectionFunction#

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.

TypeScript
function appendStaticSection(system: string, extra: string): string
Parameters
NameTypeDescription
systemstring

System prompt, with or without a boundary marker.

extrastring

Text to append to the static half.

string

src/system-prompt.ts:148

hasSystemPromptBoundaryFunction#

True when system contains the boundary marker.

TypeScript
function hasSystemPromptBoundary(system: string): boolean
Parameters
NameTypeDescription
systemstring

System prompt to check.

boolean

src/system-prompt.ts:225

joinSystemPromptFunction#

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.

TypeScript
function joinSystemPrompt(staticPart: string, dynamicPart: string): string
Parameters
NameTypeDescription
staticPartstring

Cacheable prefix.

dynamicPartstring

Per-turn suffix. Empty to omit the marker.

string

src/system-prompt.ts:125

renderSystemForWireFunction#

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.

TypeScript
function renderSystemForWire(system: string): string
Parameters
NameTypeDescription
systemstring

System prompt, with or without a boundary marker.

string

src/system-prompt.ts:209

replaceDynamicSectionFunction#

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.

TypeScript
function replaceDynamicSection(system: string, next: string): string
Parameters
NameTypeDescription
systemstring

System prompt, with or without a boundary marker.

nextstring

Replacement for the dynamic half. Empty drops it.

string

src/system-prompt.ts:190

splitSystemPromptFunction#

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).

TypeScript
function splitSystemPrompt(system: string): SystemPromptParts
Parameters
NameTypeDescription
systemstring

System prompt, with or without a boundary marker.

src/system-prompt.ts:92

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.

TypeScript
const SYSTEM_PROMPT_BOUNDARY = '__ZIDANE_SYSTEM_PROMPT_BOUNDARY__'

src/system-prompt.ts:60

Interfaces#

SystemPromptPartsInterface#

Result of splitSystemPrompt — both halves stripped of the marker.

TypeScript
interface SystemPromptParts
SystemPromptParts properties
NameTypeDescription
dynamicstring

Bytes AFTER the marker. Empty when no marker present. NOT cached.

staticstring

Bytes BEFORE the marker (or the entire string when no marker present). Cacheable.

src/system-prompt.ts:63