In the last lesson, we added an episodic memory in the form of few-shot examples. In this lesson, we're going to add in the third and final type of memory, procedural memory. This will be in the form of the system prompts that exist in the main agent and also in the triage agent. So as a reminder, procedural memories are instructions about how to do things. And the representation that that takes in agents is typically in the form of prompt instructions. We've been using hardcoded values for these throughout the lesson so far. In this lesson, we're going to make it possible to update those prompts automatically. Let's dive in. The start should look pretty similar to before. We're going to load some environment variables. We're going to define our profile. And we're going to define our prompt instructions here. This is what we're going to be updating over time. So these are specific instructions for when to ignore, when to notify, when to respond, and how the overall agent should behave. And so these are hardcoded here and have been hardcoded here. And we're still going to run this cell to keep them here. But we're going to use them in a slightly different way. And we'll dive into that later. This email is an example email. Here we're going to load the in memory store the long-term memory store which we're going to use for everything. We are going to define our helpers for the triage step. And now we get down here. And here we can see these are where our updated memories for the procedural steps are going to go. And so this is our this is our template. This is the same as before. But I'm just pointing out that this is where they are going to go. We're going to define a bunch of stuff for this triage step. We've got our LLM router. We've got our user prompt. We're defining the state of the graph. We're defining some things. And then this is our triage router node. And this is what we're eventually going to modify. So right now, you can see that we're passing in the prompt instructions here. What we're going to do is we're going to modify it so that we are retrieving these from the long-term memory store and then passing them in. Why are we doing this? We're going to do this so that we can update them from outside the agent. And then that way if we update them, they're automatically pulled in to future runs. Okay. So, how are we going to modify this. First, let's get our LangGraph user ID. And we're going to use this as the namespace for the procedural memories. So we're getting our LangGraph user ID from the config. And we're creating this namespace which is a tuple. So make sure that you've got this comma here. We're now going to try to fetch from the store instructions for when to ignore emails in the triage step. And so we're going to get from the store with this given namespace this key triage ignore. Now, this may not always exist in the store. And so what we're going to do is we're going to check to see if it exists. If it does we're going to use the value there. If it doesn't, we're going to add a default. This will make sure that when we run this agent with a new user ID, even if we haven't already set up the store, it will add the default there. So here we have the logic if the result is none. So if nothing exists in the store for these ignore triage instructions, we are going to put something there. We're going to put something in that namespace with that key. And it's going to be a dictionary with one key prompt. And the values are going to be those default prompt instructions that we had for ignore. And then are we're going to set ignore prompt, which we're going to use later on to that default value. If the result does exist we're going to get the value from prompt. And that's what we're going to use. And so then we're going to pass it in down here. So let's update this to be ignore prompt. We now want to do the same for notify. So we are going to now use the same namespace but with a different key, triage notify. And we're going to get notify prompt. Similarly, we're going to update the formatting down below to use this value. And finally we're going to do the same for the respond prompt. So now it's triage respond is the key. And respond prompt is the new variable we get. And we're going to insert that here. So we've just updated our agent to use prompts that are stored in long-term memory. Note that there's nothing that updates them yet. We're going to go over that later on. But this just updated this triage router to use stuff in long-term memory. And this is step number one. After that we're going to go on to the rest of the agent. So we're going to import the tool. We're going to create the three different tools we have. We are going to import the memory tools and use those. We are going to have the system message for the agent. And now we get down to create prompt. So, create prompt is where we are also going to update. So right now, we're passing in the prompt instructions with agent instructions. And we're going to do the same thing as above. We're going to update it. So it looks for these in the long-term memory store first. All right. So now we've modified it in a few ways. We've passed in the config. And the store. We're going to use the config to get the LangGraph user ID. And that's going to create the namespace that we look in. The key that we're going to use is agent instructions. And we're going to do similar logic as before. We're going to first try to get this result from the store. If it does not exist, we're going to put a default one in there using prompt instructions as the default. If it does, we're actually just going to use the value that's there. And then what we're going to do, is we're going to update the system message. And rather than using the prompt instructions here, we are going to use the prompt value that we get from above. With that now done we can create our main agent. So we can import create react agent. We define a bunch of tools and then we create the email agent. So now this agent is all set up. It's pulling its instructions from long-term memory rather than from those hardcoded values. Why does this matter? This matters because now we can update those values outside the agent and automatically update the agent's instructions. So now let's see how to do that. Let's work with this example email. Hi John, urgent issue. Your service is down. Let's define the config that we're going to use. In this case it has the LangGraph user id of Lance. Let's now run this email agent over this email. And we can see that we get a response. If we look through the messages we can see that we have a human message. Then the AI message that is calling the write email tool and it's writing this email. And then the response from the tool message saying that we sent this email. And then a final response from the agent. Let's take a look at the existing long-term memory for these instructions. If we look at the lance namespace with the key agent instructions, we can see these default instructions. These are the same as where in the prompt instructions at the top of the notebook. Let's look and we should see that the triage respond is also populated. As is triage ignore. And as is triage notify. If we wanted to, we could update these instructions manually and pass in new instructions. But that's kind of a manual update. That's not really memory in terms of how we think about it in terms of agents. With agents, we think about in LLM updating these based on some user feedback. So that's exactly what we're going to do. So we're going to import from Langmem this function called create multi prompt optimizer. As the name suggests, this is going to create an optimizer that optimizes multiple prompts. The reason we want to optimize multiple prompts is that in this assistant there are actually four different prompt instructions that we have. And we might want to update any number of them, but we don't get feedback for each instruction. Rather, we get feedback for the whole overall agent. So we're going to take this overall feedback and first determine which prompts, if any, should be updated based on this feedback and then update them. First, we need a list of the conversations that we're going to use to update the prompts. For this, we're going to take the response messages. So this is the trajectory that we have above. And then we're going to add in some feedback. This can be in a few different formats but the simplest format is just a string. So here we're going to take in this feedback. Always sign your emails John Doe. We now need to define the prompts that we want to potentially update. Each entry in this list should have four keys. First, is the name of the prompt. Next, is the value of the prompt. Next, is update instructions. These are instructions for how you should update the prompt. And then finally, we have the when to update key. These are instructions for when you should update the prompt. So under the hood what's going to happen is we're first going to use the when to update value to determine which prompts, if any, to update. If we determined that it's worth updating, we're then going to use the update instructions and apply that to the original prompt to get a new updated prompt. Here, we've filled these in for the four prompts that we want to potentially update. So we have the main agent prompt, which we're going to get the most current values from this agent instructions key. The update instructions are just going to be keep the instructions short and to the point. And we're actually going to use that for all of them. And then when to update. Updates prompt whenever there is feedback on how the agent should write emails or schedule events. What about triage ignore? Similarly, we're going to name it that. We're going to fetch the most recent value from long-term memory. Update instructions are the same. And then update this prompt whenever there's feedback on which emails should be ignored. Triaged notify and triage respond are pretty similar, except rather than updating it when there is information about when the email should be ignored, we're going to update it when there's information about when the user should be notified or responded. After we do this, we're going to create the Multi prompt optimizer. We're going to use Anthropic's Claude three sonnet model to optimize the prompt. We've found that it's currently the best for this type of prompt optimization. Kind equals prompt memory is the algorithm that we're going to use to update the prompt. This is the simplest one. And you should look at the API reference of Langmem for more complicated examples. This optimizer itself is a graph. So we're going to invoke it by passing in these trajectories, and then these prompts. And we're going to get back this updated variable. Let's take a look at what this returns. We can see that it returns a list of prompts. These have all the same keys as before. So the name prompt update instructions and when to update. Now some of these prompts may be different than before. And we can actually see that here. The first main agent prompt. It's prompt is now different. When sending emails always sign them as John Doe. This is the feedback that we passed in. Once we have these updated prompts, we can loop through them and update the long term memory if appropriate. So here you can see that we're comparing the updated prompt to the old prompt. And we're comparing the prompt values. If they're not the same, we're going to update the store with their instructions. Here, I've only added logic to handle what would have happened if the name of the prompt that's been updated is Main Agent. But remember, there's three other prompts that could potentially be updated. They shouldn't, so they shouldn't throw an error. But for completeness, you'll probably want to implement all three conditions. And we can see that we print out updated main agent prompt as we went along. And we didn't raise any value errors, so no other prompts got updated. If we now check the long-term memory for these agent instructions, we can see that it now has this sentence, when sending emails, always sign them as John Doe. We can now rerun the email agent and see what happens. If we take a look at the messages, we can see that when we write the email we now sign it John Doe. So it's remembering and using these instructions. Let's look at another example. We now have this email. And we invoke the email agent. And it responds to this email above. What if we didn't want it to respond to this? We could then create a list of conversations with the response messages as the conversation, and we could give the feedback: Ignore any emails from Alice Jones. We then define the list of prompts, and we're running this again, because the old list of prompts that we defined before has the outdated instructions for the main agent. And so we're just running it again to get the most updated instructions for the agent at this point in time. After that, we can invoke this optimizer agent with the conversations we have above and the prompts. We can now run this code snippet to update the prompts in long-term memory. Notice that we've added this new section which handles how to deal with the fact, if the updated name of the prompt that we're updating is the ignore step. And here we can see that this was the only prompt of the four that we decided to update. And if we rerun the email agent from here, we can see that it actually now ignores it, because it's using the same email from Alice Jones. And it now has the instructions to ignore emails from there. To see what exactly these instructions are, we can look into the long-term memory itself, in the triage ignore key, and we can see that this is now updated to contain ignore all emails from Alice Jones. This is the end of this lesson. This puts together all the types of memory into this email agent. Now is a good time to try this out. Try passing in other conversations with other feedback and see which parts of memory get updated and what they get updated to.