---
url: https://lettuceai.app/docs/system-prompts
title: "System Prompts — LettuceAI"
description: "Build modular prompt templates with entries, injection rules, conditions, and runtime rendering for precise AI behavior."
---

Menu 

# System Prompts

In LettuceAI, a system prompt is only one part of a larger prompt system. The app builds chat instructions from structured templates, ordered prompt entries, runtime conditions, and injected context like scenes, lorebook matches, summaries, and memories.

## In plain English

A system prompt is the hidden instruction layer that tells the AI how to behave. It is where you shape things like tone, boundaries, speaking style, and core behavior.

-   Use it to shape behavior
-   Do not use it as long-term memory
-   Do not panic if this page looks advanced, most users barely need to edit it

Who this page is for

If you only want normal chatting or roleplay, the default system prompt is usually enough. This page matters most if you want to customize how the AI behaves very precisely.

### Safe beginner rules

-   Keep instructions short and direct
-   Describe behavior, not backstory
-   Put world facts in lorebooks, not in the system prompt
-   Let memory handle evolving story details
-   Only go deeper into templates and injection rules if the defaults are not enough

That distinction matters. You are not editing one static block that gets pasted forever. You are editing a prompt template that the app renders against the current character, persona, session, memory state, and chat mode.

Current direct-chat precedence

For normal direct chats, the base template currently resolves in this order: session override, then character template, then the app-wide default template.

![Template source selection for system prompts](https://lhdgeo5fms.ufs.sh/f/m0TBUtMLsaiEk7rXOtNIHUl6r78TbaLPjui1K5EfoCqge3SR)

Prompt templates can be assigned at different levels. For direct chat, the runtime resolves session override first, then character template, then the app-wide default.

## What the system actually is

LettuceAI stores prompt templates in a database and renders them at runtime.

-   A **template** is a saved prompt definition with content, ordered entries, and an optional condense flag.
-   An **entry** is one prompt block with a role, injection mode, optional conditions, and optional image payload slots.
-   A **render pass** replaces placeholders like `{{char.name}}`, `{{scene}}`, `{{context_summary}}`, and `{{key_memories}}`.
-   A **request builder** then turns the rendered entries into provider-specific chat messages.

The same system powers more than direct-chat roleplay. LettuceAI also ships internal protected templates for dynamic summary generation, dynamic memory maintenance, reply helper, group chat, avatar tools, scene generation, and design reference writing.

## How a normal chat prompt is assembled

1.  The app chooses a base template for the current session.
2.  It walks the template entries in order and checks whether each entry is active in the current context.
3.  It renders placeholders from the selected character, persona, scene, lorebook matches, memory state, and app settings.
4.  Entries that render to empty content are dropped instead of leaving dead sections behind.
5.  If important placeholders are missing, the engine can still append generic fallback blocks for context summary, key memories, or lorebook content.
6.  If the template has condense enabled, the rendered entries are merged into one final system message before the API request is built.

Fallback is a safety net, not ideal placement

The runtime can rescue missing context blocks, but it appends them in a generic way. If you care about prompt structure, keep the placeholders you want and place them deliberately.

## Templates are entry-based

The modern prompt editor is entry-first. That means you should think in blocks, not in one monolithic paragraph.

A template is the container. Entries are the actual movable parts. The engine renders them in order, drops the ones that do not apply, and then injects the survivors according to their role and timing rules.

| Layer | What it means in practice |
| --- | --- |
| Template | A saved prompt configuration with content, entry order, and an optional condense flag. |
| Entry | A single prompt block with its own role, position, depth, conditions, and optional image binding. |
| Rendered output | The final runtime message list actually sent to the model after placeholder replacement and filtering. |

This structure is why LettuceAI can do things a raw textarea cannot do well: inject memory only when available, insert reminder entries into the chat stream, gate entries by conditions, and attach image payloads in special internal templates.

### What one entry can control

-   **Role**: system, user, or assistant.
-   **Position**: relative, in-chat, conditional, or interval.
-   **Depth**: how far back from the end of the chat buffer an in-chat entry is placed.
-   **Conditions**: whether the entry should appear at all in the current runtime context.
-   **Payload binding**: optional image slots in scene and design-reference templates.

That is the main mental model shift: you are not editing one prompt, you are editing a small prompt program.

Plain-text templates still work

If a template has content but no structured entries, the engine can wrap that content into a single system entry. So older raw templates still run, but they give up most of the modular behavior.

For normal direct-chat roleplay, the shipped default template is already broken into sections like scenario, character definition, persona, lorebook, summary, memories, and instructions. Editing those as separate entries is usually safer than flattening everything into one long block.

![System prompt entry stack overview](https://lhdgeo5fms.ufs.sh/f/m0TBUtMLsaiE2qFvXJ0CPqXAWsoHbY7GZ0FzOUpuygEvIwme)

A template is a stack of entries, not one long text field. Order, enablement, and structure all matter at runtime.

## Injection rules

Injection is the rule set that decides whether an entry appears at all, where it appears, and what role it uses when the final API message list is built.

### 1\. An entry must survive activation first

Before position is even considered, the engine checks whether an entry is active in the current context.

-   Disabled entries are skipped unless they are marked as protected system entries.
-   If an entry has conditions, that condition tree is evaluated against the current runtime context.
-   After rendering, entries that resolve to empty text are dropped.
-   Scene-heavy entries can also be skipped entirely when there is no selected scene and no scene message to render against.

### 2\. There are four injection positions

| Mode | What it does at runtime |
| --- | --- |
| Relative | Becomes part of the prompt stack before chat history is appended. |
| In Chat | Always splices into the assembled chat-message list. |
| Conditional | Splices into chat only when the current turn count is at least the configured minimum. |
| Interval | Splices into chat only when the current turn count is a non-zero multiple of the configured interval. |

Relative entries and in-chat entries are partitioned into separate buckets. The direct-chat request builder emits relative entries first, then appends conversation history, then inserts in-chat entries into that history buffer.

### 3\. Relative means system-stack placement

Relative entries are emitted ahead of the live conversation window, in the same order they appear in the template.

-   A relative entry with role `system` is mapped to the provider-specific system role.
-   A relative entry with role `user` or `assistant`stays as that role in the outgoing message list.
-   In normal roleplay templates, most core instruction sections are relative entries.

Condense changes everything

If condense mode is enabled, the engine merges every rendered entry into one final system entry before injection. That flattening step effectively removes in-chat placement, per-entry roles, and depth behavior for that template.

### 4\. In-chat insertion is depth-based from the end

In-chat, conditional, and interval entries all use the same insertion algorithm.

1.  The engine first assembles the current chat-history message list.
2.  It measures that list length and uses it as the current turn count for insertion-mode checks.
3.  Each active in-chat entry computes an insertion point as current chat length minus injection depth.
4.  Entries are sorted by insertion point, then by original template order.
5.  They are inserted one by one, adjusting later positions as earlier inserts shift the list.

In practice, depth `0` means the entry sits at the very end of the assembled chat-history buffer. Depth `1` places it one message earlier, depth `2` two messages earlier, and so on. Large depths clamp toward the front instead of crashing.

### 5\. Conditional and interval use message count, not token count

The timing rules are based on message counts, not prompt length or token usage.

-   **Conditional** entries insert when the assembled message count is greater than or equal to the configured minimum.
-   **Interval** entries insert only when the message count is greater than zero and divides evenly by the configured interval.
-   These checks happen at request-build time, after the current chat window has already been chosen.

That means a conditional or interval entry responds to the current request window, not to total token usage and not to a hypothetical full chat export.

### 6\. Role mapping happens during message construction

When an entry becomes an API message, its role is normalized.

-   `system` maps to whatever system-role keyword the selected provider adapter expects.
-   `user` stays `user`.
-   `assistant` stays `assistant`.
-   In normal direct chat history, stored `scene` messages are not forwarded as ordinary chat messages.

This is why entry roles are meaningful even inside one template. You can deliberately place framing or examples in different conversational roles when a model responds better to that structure.

### 7\. Some runtime extras inject outside the template itself

Not every injected message comes strictly from the entry list.

-   Direct completion currently adds a separate per-turn "Relevant memories" system block after the relative entries when memory data is available.
-   Swap-places mode adds an extra system instruction that tells the model the roles have been inverted for that turn.
-   The scene-image protocol entry is stripped entirely when scene generation is disabled in settings.
-   If summary, lorebook, or memory placeholders are absent, the engine can append generic fallback entries after rendering.

### 8\. Multimodal prompt entries are special

Scene-generation and design-reference templates can attach image payload bindings to entries. Those entries still use the same activation and ordering rules, but the final message content can become a multimodal user message instead of plain text.

-   Bound image slots such as `{{image[character]}}`, `{{image[persona]}}`, `{{image[avatar]}}`, and `{{image[references]}}` are converted into image parts in the outgoing request.
-   Any remaining explanatory text on that entry is preserved as the text part of the multimodal message.
-   If an image-bound entry has no usable images, it simply does not emit a multimodal payload.

So the injection system is not only about ordering text. It is also the layer that decides whether an entry becomes a system message, a chat message, or a multimodal user payload.

![Prompt entry timing and conditions editor](https://lhdgeo5fms.ufs.sh/f/m0TBUtMLsaiElfgB1pR7C73dLiJV9kSZtR4oG2cbhwPzgNan)

One entry can control placement, insertion depth, timing rules, and activation logic from the same editor surface.

## What gets rendered into the template

The renderer does more than direct string replacement. It also decides what to omit when context is unavailable.

-   **Character and persona**: names, definitions, and legacy aliases like `{{char}}`, `{{persona}}`, and `{{user}}`.
-   **Scene**: selected scene content and optional scene direction, including variant handling.
-   **Content rules**: strictness from Pure Mode is injected through `{{content_rules}}`.
-   **Memory**: dynamic summary and retrieved memories, or manual memories when dynamic memory is not active.
-   **Lorebook**: only active lorebook entries matched from recent conversation are rendered, not the whole lorebook database.
-   **Time placeholders**: when an entry references them, slots like `{{date}}`, `{{date_full}}`, `{{weekday}}`, `{{time_hour}}`, `{{time_minute}}`, `{{time_second}}`, `{{time_full}}`, `{{time_12hour_format}}`, `{{time_timezone}}`, `{{time_timezone_name}}`, and `{{datetime_iso}}` resolve against the device's local time at render. These are populated whether or not time awareness is on, but the default templates only inject a time block when the time-awareness condition is true (see Companion Mode for the toggle).

If a scene, summary, or lorebook block has no real content, the engine strips the empty section instead of leaving raw placeholders behind.

## Required variables and runtime fallback

Protected templates still use required-variable validation. For example, the default direct-chat template expects core placeholders like `{{scene}}`, `{{scene_direction}}`, `{{char.name}}`, `{{char.desc}}`, `{{context_summary}}`, and `{{key_memories}}`.

But validation and runtime behavior are not identical. At runtime, if a custom template omits `{{context_summary}}`, `{{key_memories}}`, or `{{lorebook}}`, the engine can still append fallback entries so that memory and lore are less likely to vanish entirely.

Best practice

Keep the placeholders you actually want and place them exactly where you want them. The fallback path is there to keep context alive, not to give you precise layout control.

## Conditions

Conditions are separate from injection rules. Injection decides where an active entry lands. Conditions decide whether that entry becomes active in the first place.

That separation matters because two entries can share the same injection mode and still behave very differently depending on the current chat, model, memory state, or tool context.

### What conditions can check

-   chat mode: direct or group
-   scene or avatar generation feature toggles
-   whether a scene or scene direction exists
-   whether a persona exists
-   message count and participant count
-   keyword presence or absence in recent text
-   dynamic memory state, summary presence, and key-memory presence
-   lorebook availability
-   reference-image and reference-text availability
-   provider id, input scopes, output scopes, reasoning state, and vision support
-   time awareness state, and whether the chat is running in companion mode

The time awareness condition lets an entry gate on whether the chat has time awareness enabled (currently the per-session companion preference). The default companion template uses it to inject a "Current Local Time" block only when the user has turned the toggle on.

### Condition composition

The runtime supports nested logic, not just one-off toggles. A single entry can combine conditions with `all`, `any`, and `not`.

-   `all`: every nested condition must match.
-   `any`: at least one nested condition must match.
-   `not`: inverts another condition.

### How AND and OR work in the editor

The editor uses human-friendly **AND** and **OR**joins, but it stores them as grouped logic.

-   **AND** means every rule in that joined group must be true.
-   **OR** means any rule in that joined group can be true.
-   Adjacent rules with the same join are grouped together before the full expression is evaluated.
-   Those groups are then evaluated from top to bottom, so mixed AND/OR chains become a nested expression rather than one flat sentence.

A useful mental model is: the editor first builds AND-groups and OR-groups, then combines those groups into the final condition tree. Exclusions are handled separately and become a final `not(...)` layer.

So if you build include rules like _scene generation on_ AND_character reference text exists_ AND _persona reference text exists_, then add an exclusion that says _persona reference images missing_, the explanation becomes:

```
This entry is active when scene generation is on and character reference text exists and persona reference text exists and not (persona reference images missing).
```

![Conditions builder for a prompt entry](https://lhdgeo5fms.ufs.sh/f/m0TBUtMLsaiEJFJJLD3QzmGRuHMUw96s2tbYxETkejcg13FO)

The conditions editor exposes include rules, exclusions, and logical joins so one entry can react to runtime state without requiring a separate template.

![Condition explanation output for a prompt entry](https://lhdgeo5fms.ufs.sh/f/m0TBUtMLsaiEgZDBt5z1uLhxGKCwTQRqZ4bmt2ikFOrvslNd)

The editor also generates a plain-English explanation, which is useful for checking whether grouped AND, OR, and exclusion rules match your intent.

Conditions run before insertion

The runtime first checks whether an entry qualifies, then applies its injection mode. If a condition fails, position and depth never get a chance to matter.

## Internal prompt types

The prompt system is broader than the character system prompt. These are all built on the same template engine.

| Template | What it does |
| --- | --- |
| App default | Main direct-chat roleplay template. |
| Dynamic summary | Compresses processed conversation windows into one rolling summary. |
| Dynamic memory | Runs the memory-manager tool pass that creates, deletes, pins, or unpins memories. |
| Reply helper | Generates a suggested next message for the user. |
| Group chat and group RP | Formats multi-character context and message rules for group sessions. |
| Avatar and scene generation | Builds image-generation prompts, including optional reference-image payload entries. |
| Design reference writer | Turns reference images into a reusable design brief for later image work. |

## Default direct-chat structure

The shipped direct-chat template is modular and sectioned. It is closer to this than to a flat personality paragraph:

```
Base directive
Scenario + scene direction
Character definition
Persona definition
World information (matched lorebook entries)
Context summary
Key memories
Scene image protocol
Behavior and pacing instructions
Content rules
```

That separation is deliberate. It keeps identity, scene framing, memory, lore, and policy-style constraints from collapsing into one noisy blob.

## Preview and troubleshooting

The editor supports both raw and rendered preview. That is useful for checking whether placeholders actually resolve before you save.

-   If you preview without a live session, the app synthesizes a minimal preview session.
-   That preview includes sample memory text, a placeholder context summary, and sample lorebook content so the render is easier to read.
-   This means preview is great for structure checking, but not a perfect reproduction of one specific live chat.

![Rendered system prompt preview](https://lhdgeo5fms.ufs.sh/f/m0TBUtMLsaiEBCG3T2Xeaxpi2W1v7t9cGgFBwm4kZdrEyDTJ)

Rendered preview shows what the template becomes after placeholder replacement, which is the fastest way to verify real prompt structure before saving.

## Writing advice that matches the real engine

-   Use the system prompt for durable behavior, not conversation memory.
-   Keep lore in lorebooks and let keyword matching inject only relevant entries.
-   Keep memory placeholders if you want dynamic memory to stay readable and controlled.
-   Prefer modular entry edits over turning the whole template into one giant paragraph.
-   Use preview often when you add conditions, interval entries, or custom sections.

Practical rule

If a fact should always shape behavior, put it in the prompt. If it should be recalled because it happened in the chat, let memory or lorebook systems carry it instead.

[

PreviousDiscovery

](/docs/discovery)[

NextCompanion Mode

](/docs/companion-mode)
