Implementing Agentic Reminders in RavenDB
A really interesting problem for developers building agentic systems is moving away from chatting with the AI model. For example, consider the following conversation:

This is a pretty simple scenario where we need to actually step out of the chat and do something else. This seems like an obvious request, right? But it turns out to be a bit complex to build.
The reason for that is simple. AI models don’t actually behave like you would expect them to if your usage is primarily as a chat interface. Here is a typical invocation of a model in code:
class MessageTuple(NamedTuple):
role: str
content: str
def call_model(
message_history: List[MessageTuple],
tools: List[Callable] = None
):
pass # redactedIn other words, it is the responsibility of the caller to keep track of the conversation and send the entire conversation to the agent on each round. Here is what this looks like in code:
conversation_history = [
{
"role": "user",
"content": "When do I get my anniversary gift?"
},
{
"role": "agent",
"content": "Based on our records, your two-year anniversary is in three days. This milestone means you're eligible for a gift card as part of our company's recognition program.\nOur policy awards a $100 gift card for each year of service. Since you've completed two years, a $200 gift card will be sent to you via SMS on October 1, 2025."
},
{
"role": "user",
"content": "Remind me to double check I got that in a week"
}
]Let’s assume that we have a tool call for setting up reminders for users. In RavenDB, this looks like the screenshot below (more on agentic actions in RavenDB here):

And in the backend, we have the following code:
conversation.Handle<CreateReminderArgs>("CreateReminder", async (args) =>
{
using var session = _documentStore.OpenAsyncSession();
var at = DateTime.Parse(args.at);
var reminder = new Reminder
{
EmployeeId = request.EmployeeId,
ConversationId = conversation.Id,
Message = args.msg,
};
await session.StoreAsync(reminder);
session.Advanced.GetMetadataFor(reminder)["@refresh"] = at;
await session.SaveChangesAsync();
return $"Reminder set for {at} {reminder.Id}";
});from Reminders as r
where r.'@metadata'.'@refresh' != nullAnd here is the client code to actually handle it:
async Task HandleReminder(Reminder reminder)
{
var conversation = _documentStore.AI.Conversation(
agentId: "smartest-agent",
reminder.ConversationId,
creationOptions: null
);
conversation.AddArtificialActionWithResponse(
"GetRaisedReminders", reminder);
var result = await conversation.RunAsync();
await MessageUser(conversation, result);
}The question now is, what should we do with the reminder?
Going back to the top of this post, we know that we need to add the reminder to the conversation. The problem is that this isn’t part of the actual model of the conversation. This is neither a user prompt nor a model answer. How do we deal with this?
We use a really elegant approach here: we inject an artificial tool call into the conversation history. This makes the model think that it checked for reminders and received one in return, even though this happened outside the chat. This lets the agent respond naturally, as if the reminder were part of the ongoing conversation, preserving the full context.
Finally, since we’re not actively chatting with the user at this point, we need to send a message prompting them to check back on the conversation with the model.
Summary
This is a high-level post, meant specifically to give you some ideas about how you can take your agentic systems to a higher level than a simple chat with the model. The reminder example is a pretty straightforward example, but a truly powerful one. It transforms a simple chat into a much more complex interaction model with the AI.
RavenDB’s unique approach of "inserting" a tool call back into the conversation history effectively tells the AI model, "I've checked for reminders and found a reminder for this user." This allows the agent to handle the reminder within the context of the original conversation, rather than initiating a new one. It also allows the agent to maintain a single, coherent conversational thread with the user, even when the system needs to perform background tasks and re-engage with them later.
You can also use the same infrastructure to create a new conversation, if that makes sense in your domain, and use the previous conversation as “background material”, so to speak. There is a wide variety of options available to fit your exact scenario.

Comments
Comment preview
Join the conversation...