Stateful Agents and Basic Memory

Dan Corin February 17, 2026
Source
import ChatContainer from '@components/prose/ChatContainer.astro'; import ChatMessage from '@components/prose/ChatMessage.astro'; One of the most limiting factors of LLM agents is they have limited cross-session memory. When working on software projects, this limitation isn't much of an issue because the agent can search the project and read files to understand the current state and what it needs to do to make the changes you've requested. In a good software project, the code is a lot of the state you need to be successful with an agent. However, agents become a lot more interesting when you give them access to a persistent file system with instructions or scaffolding to load from that file system when a session begins. While most implementations of memory in consumer products today are relatively opaque, it's quite straightforward to add memory to your agent by giving it access to a file system. From the perspective of an agent, memory is just a log of what happened. You can write that record manually after each agent turn with something like a stop hook or you can add instructions to CLAUDE.md/AGENTS.md to tell the agent to write a record to a file after each conversation turn. Once you know where you are writing these "memories", prompt the agent that it can and should read from these memories as well. There are many right ways to do this. Here is an extremely simple one. json { "timestamp": "ISO 8601 UTC", "summary": "1-3 sentence summary of what the user asked and what was done" } bash jq -n -c --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg s "Summary here" '{timestamp: $ts, summary: $s}' >> memories.jsonl bash grep -i "keyword" memories.jsonl | jq -r '.summary' Here's what interacting with this agent looks like. In this example, the agent uses tool calls to search for prior memories and save new ones: hi there, my name is Jack nice to meet you! Hi Jack, nice to meet you too! > /Users/danielcorin/dev/lab/min-mem/memories.jsonl}>(No content) Let me know if there's anything I can help you with! This conversation turn results in the following memories. From here, I closed Claude Code and opened a fresh session, then prompted the following. who am I? I don't have any prior context about you. Let me check if there are any memories that might help. (1 file) Based on our prior conversations, you're Jack. Beyond that, I only know what's been discussed in previous sessions. Is there anything specific you'd like help with? > memories.jsonl}>(No content) And the memories are updated once more. Make it a Skill (optional) If you like this approach, you can do a refactor of sorts to modularize your approach to memory for the agent using a Skill. Skills use a context management technique called progressive disclosure. This is a fancy way of saying the agent only sees the description of the skill by default and when to use it, but not the entire contents of the skill. Given the conversation, the agent decides whether to read the full contents of the skill (markdown and any other additional content). Otherwise, this is kept out of the context window to keep the agent focused on the task at hand. It's up to you whether or not you want to do this with memory. If you always want the agent to read and write from memory after every conversation turn, there's an argument to be made to leave the implementation in CLAUDE.md, but since most agents have project-specific content in there, let's refactor to a skill. I had to make some tweaks to the copy to get the Skill invocation working consistently and writing memories.jsonl to the root of the project. bash touch memories.jsonl && jq -n -c --arg ts "$(date -u +%Y-%m-%dT%H:%M:%SZ)" --arg s "Summary here" '{timestamp: $ts, summary: $s}' >> memories.jsonl json { "timestamp": "ISO 8601 UTC", "summary": "1-3 sentence summary of what the user asked and what was done" } bash grep -i "keyword" memories.jsonl | jq -r '.summary' Here's what happens for the same interaction with the agent that has access to the memory Skill. hi there, my name is Jack nice to meet you! Hi Jack, nice to meet you! Successfully loaded skill · 5 tools allowed (No content) Memory saved. We get our memory written to the file. And now a fresh session. who am I? Let me search for relevant context first. Successfully loaded skill · 5 tools allowed Let me search for relevant context first. User introduced themselves as Jack. Greeted them back. Based on my memory, you're Jack. You introduced yourself in a previous conversation. > memories.jsonl}>(No content) And again we have two memory entries. Wrapping up This is a pretty basic implementation, but it implements the most important part to give an LLM agent something that resembles memory - it reads and writes to a file system that persists beyond the agent's sessions. This approach allows the agent to catch up on what has already happened. In our example, we allow the agent to decide what to read and write to and from memory. You can be more opinionated about this if you want, using a framework like claude-agent-sdk where you have more control over the agent's prompt and can do things like programmatically include the last N entries in the prompt. For more on stateful agents, I recommend this post by Tim.

Discussion in the ATmosphere

Loading comments...