In this lesson, we're going to start with the email agent that we created previously. And we're going to add in tools to allow it to operate on semantic memory. Now as a reminder, semantic memory normally stores facts about the user. So we're going to use these tools to learn facts about users. We're then going to store them in a long-term memory store. And then we're going to add a separate tool to search over these facts. Let's see it in code. So here we have a lot of set up from the previous lesson. And this should look all the same. We're going to load some environment variables. We're going to define our profile. We're going to define our prompt instructions. An example email. We're going to set up the router. Nothing's changed here. We're going to import some prompts. We're going to define our three tools that we had before. Now we're going to start to define our tools for managing memory. The first thing we're going to want to do is properly set up our long-term memory store. And so for this lesson we're just going to use an in-memory store a store that will store everything in memory. And so we're going to import that from LangGraph. When we initialize it, we're going to want to make sure that we pass in this index param. Here we're passing in an embedding model to use to index the memories that we store. So we're going to use the OpenAI text embedding three small model. Afterwards, we're going to import two functions from Langmem. Langmem is a wrapper on top of LangGraph for working with memory. These two functions are going to be called create manage memory tool and create search memory tool. And these will be tools for managing and searching over memories. To create them, it's pretty simple. We're just going to define the namespace that we want these tools to operate on. So the namespace that we're going to use for both is email assistant as the first value. Then this templated LangGraph user ID and I'll come back to that as a second value and then collection as the third value. So what exactly is this? When we invoke the agent now we're going to pass in a LangGraph user ID as part of the runtime configuration. And that's going to be use to namespace this memory. That way if we're dealing with emails from multiple users, we can pretty easily just have different collections of memories for different users. We wouldn't want them to be the same. And so here's where we define that we store them separately because these are just LangChain tools, we can inspect them and see what attributes they have. So they should have a name so we can see that manage memory tools name is called manage memory. They should also probably have a description. So we can see that this is the description of how to use the tool. Let's print it out to make it a little bit more legible. Here, we can see that it contains instructions for creating, updating or deleting persistent memories. We can also take a look at the arguments to this tool. We can see that it takes in content, then an action. So the action can be create, update or delete and then an ID. And so basically what's happening is these memories are managed by IDs. And so when we call to this tool we can pass in the ID of a memory, the action we want to take on it or whether we should update it or create it or delete it, and then the content of that memory as well. If we look at the search memory tool, we can see that's called search memory. The description is pretty simple. It's just search your long-term memories for information relevant to your current context. And the arguments are also pretty simple. It's just a query, a limit for the number of memories that we want to return, some offset, and then some filter if we want. Let's now go on to define the agent. Here, we're going to take the agent system prompt and we're actually going to modify it a little bit because now we need to add in these two extra tools. So this is slightly modified from the previous lesson. We're still going to have the similar create prompt function, which basically creates this same message and appends it before the list of messages in the state. After that, we can build our agent as normal. We'll import this create react agent function. We'll create our list of tools which now contains this manage memory tool in the search memory tool. And then we'll create our react agent which we'll now call response agent. And notice that we're also passing in the store to ensure that this is passed to the agent. Let's now test the agent. The first thing we're going to want to do is actually define a config that we're going to pass to the agent with some runtime configuration. And it's going to have to have this LangGraph user ID. So let's create that first. You can change this value which I'm calling lance to be anything you want. Let's now call it with a message that has some information that we should probably remember. Like Jim is my friend. And notice here I'm passing in the config as a second parameter. Let's take a look at the response. If we print out the list of messages, we can see that we have this human message which we pass in. And then we have this AI message. It calls the manage memory tool with the arguments content. Jim is John Doe's friend. Notice that we're not specifying an action, because the default is to create, and we're not specifying an ID because it will create one by default. We get back a message that we created this memory with this user ID. And then the final message is that it's just recorded this information about Jim. Let's try this with another input. Let's ask now who is Jim? And notice that we're passing in the same config as before. Let's take a look at the list of messages that we get back as a response. Who is Jim? This is our user message. We then get a message from the AI which calls a tool. It calls this search memory tool with a query parameter of Jim. We get back this list of messages. In this case, we only have one message, which is this namespace memory at this value. This key is the ID, and then the content is Jim is John Doe's friend. And then we get back a response from the AI message that summarizes the above interaction. So it says, based on my memory search, I can see that Jim is John Doe's friend. If we want to inspect the store directly, we can. We can call list namespaces, and we can see that this one namespace exists. And remember, this is the template that we defined above where this is the LineGraph user ID. Let me pass in Lance for that value. If we now call search on this namespace we can see the different items that exist there. So we can see that we only have one item because we've only remembered one piece of information, and its value has this content: Jim is John Doe's friend. Notice the score here is none. That's because we haven't passed in a query parameter. Let's pass one in, and let's see what the score is. If we pass in a query of Jim, we can now see that the score is 0.5 something. The score is calculated as cosine similarity using the embedding model that we passed in when we created the store in the first place. Now is a good time to pause. Use this response agent directly. Try querying it with different things after to see if it remembers, and you can also inspect the store itself. Now, let's go on to creating the rest of the agent. So this should all look very similar to the previous lesson, because not much is changing. We've mostly just added these tools to the response agent. So first, we're creating the state. This is the same as before. We're making some imports, same as before. We're creating this triage router node, same as before. We then create the email agent. This is the same as before with two changes. First, I've changed this to be response agent. So before, in previous lesson two this was called agent. I've just updated the variable name. And so it's changed it here. Nothing. Nothing big. But next, I'm going to change the compile step. So I'm going to pass in store equals store. When I create the email agent. This is giving it access to that long-term memory store that our memory tools are going to use. If I display the email agent I get the same graph as before. So nothing's changed here. I can create this example email input. And then when I call the email agent this is now also going to change slightly. I'm going to remember to pass in my configuration this contains the LangGraph user ID that will we will use in the memory tools. We can see that it runs and that at first classifies that it should respond. And then it continues down that path. Once it's finished, let's take a look at the list of messages that are in the agent's trajectory so we can see what it does. So the first message there is a human message. This is the input of the email. We then get back an AI message where it's writing an email. So it has a tool call with this write email and it's passing in and all these arguments. And so you can see the content of the email that it writes here. We then get back a tool message saying that it sent that email successfully. We then call the AI message again. And we see that it calls the Managed Memory tool, and it creates this memory with this content follow up needed. Alice Smith inquired about missing API endpoints. We get back a tool message that it created that memory, and then we get back the final response from the agent that it's taking two actions. It sent a response to Alice, and it's created a memory entry. So this is the agent with the semantic tools in place. Let's try out another example that requires some of the memory that was saved as part of this previous email. So let's pass in this new email from Alice Smith as well. So same author and it says, hi John. Any update on my previous ask? When we invoke it, we can see that it correctly classifies it as an email that it needs a response to, and that continues from there. Let's take a look at the agent trajectory. So we can see that we have the human message with the new email, and we can see that it now first calls the search memory tool with this query Alice Smith previous request as communication. So it's trying to figure out what information it has. It gets back this memory that it saved before. We can see its logic. And then it calls this write email tool and it writes this email. And we can see that in its response it says: thanks for following up regarding the API endpoints documentation. So this is remember from before, even though it's a completely separate email. We can see the tool message that it sent a response to Alice. And then, we can see the final response. This is an example of how you can see that the memory tools are saving relevant information, and then also searching over it in the future. This is it for this lesson. So now is a great time to pause and spend a few more minutes playing around with different emails, trying out different questions and seeing if I can remember different facts. See you in the next lesson.