Reminders
Inject situational context into model-visible carriers so the LLM stays aware of hidden runtime facts, active skills, and recent conversation signals
Reminders inject hidden text into the model conversation to give the LLM situational awareness — current date, active skills, recent tool behavior, recurring instructions. Depending on the target, the text either merges into a real user message or becomes a persisted synthetic user message between model steps. Reminder text is model-facing, not user-facing.
How Reminders Work
A reminder is always a context fragment. Create it with reminder() and declare it on the engine with engine.set(); the engine folds it into the model's view when its when predicate fires. Two decisions control where and how:
- Where —
target: 'user'(the default),target: 'tool-output', ortarget: 'steer' - How — inline (appended as a
<system-reminder>tag inside the last text part) or as a separate text part
The when predicate is optional for target: 'user' — omit it for an always-on instruction. For tool-output and steer a when predicate is required.
import { everyNTurns, once, reminder } from '@deepagents/context';
engine.set(reminder('Keep responses concise')); // user, always on
engine.set(reminder('Welcome!', { when: once('welcome') })); // user, one-time
engine.set(reminder('RECAP', { when: everyNTurns(3), target: 'steer' }));User Reminders
target: 'user' is the default. The engine folds the reminder text into the last pending user message during engine.save(), baking it as a <system-reminder> tag (or its own text part). The folded text is persisted in the chain, so reloading reproduces exactly what the model saw.
Always-On Reminders
Omit when for a reminder that folds into every user message — a recurring instruction the model should always see.
import { reminder, user } from '@deepagents/context';
engine.set(reminder('Keep responses under 100 words'), user('Hello'));
await engine.save();
// The saved user message carries: Hello<system-reminder>Keep responses under 100 words</system-reminder>The text may also be a (ctx) => string factory that receives the message content. A factory that returns an empty string skips injection entirely — useful for inline conditional logic without a predicate.
engine.set(
reminder((ctx) =>
ctx.content.includes('prod')
? 'Ask for confirmation before destructive actions'
: '',
),
user('deploy to prod'),
);Conditional User Reminders
Add a when predicate to fold the reminder only when it fires — gated by turn count, message content, assistant/tool history, token usage, or time. The engine builds a WhenContext at save() time and evaluates each predicate.
import {
afterTurn,
and,
contentIncludes,
reminder,
user,
} from '@deepagents/context';
// Only fires when the user mentions code AND after the first 2 turns
engine.set(
reminder('Include code examples in your response', {
when: and(contentIncludes(['code', 'example', 'snippet']), afterTurn(2)),
}),
user('Show me how to use async/await'),
);Pass once(id) as the predicate for a one-time user reminder. The id is durably latched in the folded message's metadata, so a fresh engine re-reads it and the reminder never fires twice — even across restarts.
import { once, reminder, user } from '@deepagents/context';
engine.set(
reminder('Welcome! Type /help to see commands', { when: once('welcome') }),
user('hi'),
);See Predicates for the full predicate catalog and composition rules.
Inline vs Part Mode
User reminders render in one of two modes:
- Inline — appended as
<system-reminder>tags to the last text part (default). - Part — added as a separate text part on the user message (
asPart: true).
Reminders default inline so the model sees the context next to the user's words. Set asPart: true when structured content needs to stay in a standalone text part:
import { reminder, workflow } from '@deepagents/context';
engine.set(reminder('hint')); // inline
engine.set(reminder(workflow({ task: 'Deploy', steps: ['…'] }))); // inline
engine.set(
reminder(workflow({ task: 'Deploy', steps: ['…'] }), { asPart: true }),
); // forced partUse part mode for structured data (dates, skill lists, fragments) to keep reminder text separate from the user's message. asPart only applies to target: 'user'; tool-output and steer ignore it.
Reminder Targets
A reminder fires in one of three places, chosen by target:
| Target | When it runs | What the model sees |
|---|---|---|
user | During engine.save() on the last pending user message | Reminder text folded into that user message |
tool-output | At the next prepareStep boundary, after a tool has a terminal outcome and before the next model generation | The raw tool result followed by a synthetic user message containing the reminder |
steer | At an eligible prepareStep boundary after the model has already produced a content step | A synthetic user message containing one or more <system-reminder> parts |
user is the default and may omit when. tool-output and steer require a when predicate — reminder() throws if it is missing.
During tool-output evaluation, the predicate context includes a discriminated ctx.toolOutcome. The engine evaluates every terminal state and lets the predicate decide which ones matter:
type ToolOutcome =
| {
state: 'output-available';
name: string;
input: unknown;
output: unknown;
}
| {
state: 'output-error';
name: string;
input: unknown;
error: unknown;
errorText: string;
}
| {
state: 'output-denied';
name: string;
input: unknown;
reason?: string;
};Use toolOutput(...) for normal state/name/input/output matching. Read
ctx.toolOutcome directly when the condition needs more custom logic.
lastAssistantMessage and lastAssistantMessages remain history fields; they
do not represent the terminal outcome currently being evaluated.
import {
elapsedExceeds,
everyNTurns,
reminder,
toolOutput,
} from '@deepagents/context';
engine.set(
reminder('Double-check filesystem side effects', {
when: toolOutput({
name: 'bash',
state: 'output-available',
input: (input) =>
typeof input === 'object' &&
input !== null &&
'command' in input &&
String(input.command).startsWith('rm '),
}),
target: 'tool-output',
}),
reminder('Explain why the tool could not run', {
when: toolOutput({ state: 'output-denied' }),
target: 'tool-output',
}),
reminder('Checkpoint before taking another tool step', {
when: elapsedExceeds(40 * 60_000),
target: 'steer',
}),
reminder('Cite sources every third turn', {
when: everyNTurns(3),
}),
);Use tool-output when the reminder should follow a terminal tool outcome. Raw tool outputs are never wrapped or replaced. prepareStep appends a synthetic user message after the tool result, and AI SDK v7 carries that message override into later steps. Provider adapters may serialize adjacent provider-neutral messages into their native compound wire format; the runtime does not contain provider-specific reminder handling.
Use steer when the agent needs a hidden nudge during a multi-step turn without waiting for the next user message. Steer reminders do not run before the initial generation: context needed there belongs on a user reminder. A predicate that stays true fires at every eligible continuation boundary; compose once(id) when it should latch after the first fire.
A steer predicate is evaluated against the chain as it stands at that boundary, so it observes what the model did earlier in the same turn — a tool it called at step 3, a streak of failures, a segment count. That is what makes predicates like toolCallCount('bash', { gte: 2 }) or toolFailedStreak('bash', { gte: 3 }) usable as steer triggers.
For streamed chat turns, the engine persists the same
assistant/tool result → synthetic user message → assistant split the model
saw. Later top-level requests replay that exact prefix, preserving provider
prompt cache stability. Tool-output and steer reminders that fire at the same
boundary are merged into one synthetic user message.
once(id) works on user, tool-output, and steer. Its id is persisted only when the enclosing reminder actually fires, and the latch survives engine restarts.
Built-in Reminders
The library ships with pre-built reminders for common needs:
Temporal Reminders
Date, time, season, and locale atoms that fire only when their value changes
Skill Reminders
BM25-based skill matching that surfaces relevant skills per user query
ReminderContext
A reminder's text factory receives a context object:
interface ReminderContext {
content: string; // Plain text of the user message
turn?: number; // Current user turn count
lastMessageAt?: number; // Timestamp of last persisted user message
lastMessage?: UIMessage; // Last persisted user message (with metadata)
currentMessage?: UIMessage; // Pending user message for the current turn
chat?: StoredChatData; // Active chat record
usage?: LanguageModelUsage; // Accumulated usage metadata
branch?: string; // Active branch name
elapsed?: number; // Milliseconds since last persisted user message
messageCount?: number; // Total message count across all roles
lastAssistantMessage?: UIMessage; // Current assistant segment (see below)
lastAssistantMessages?: UIMessage[]; // Every assistant segment in the chain
lastAssistantReplies?: UIMessage[]; // One entry per reply, segments merged back
toolOutcome?: ToolOutcome; // Current terminal outcome for tool-output
}Derive counters from lastAssistantMessages, not lastAssistantMessage. When a
steer or tool-output reminder fires, the engine carves the assistant message
at that boundary (this is what preserves the prompt prefix — see Reminder
Targets), so lastAssistantMessage afterwards holds only the
parts produced since the last fire. A "this tool failed N times in a row"
counter read from it silently resets to 1 on every fire. lastAssistantMessages
keeps every segment, so it counts correctly — that is what
toolFailureStreak() reads.
The engine populates these fields when it resolves reminder text. During tool-output evaluation, both the predicate and a dynamic text factory can read toolOutcome. A reminder gated by a when predicate receives the richer WhenContext (see Predicates); a plain (ctx) => string factory on a user reminder receives at least content.
Stripping Reminders
User reminders carry range metadata so they can be stripped. Tool-output and steer reminders use a whole-message synthetic marker:
type SyntheticReminderMetadata = {
source: 'reminder';
firedAt: number;
onceIds?: string[];
};Use isSyntheticReminderMessage() to omit these model-only messages from a UI. Use stripReminders() to remove reminder content and metadata when exporting or sanitizing individual messages.
import {
isSyntheticReminderMessage,
stripReminders,
} from '@deepagents/context';
const visibleMessages = messages
.filter((message) => !isSyntheticReminderMessage(message))
.map(stripReminders);synthesizeReminderMessage(text, firedAt, onceIds?) builds the same synthetic user message for advanced host integrations. Normal agent() + chat() users do not need to call it: the agent creates the messages and chat() persists the matching assistant/reminder split. Direct AI SDK integrations that call createPrepareStep() own assistant persistence and must pair the hook with writeAssistantSegment() if they need prompt/store parity.