{"slug": "build-a-basic-ai-agent-from-scratch-memory", "title": "Build a Basic AI Agent From Scratch: Memory", "summary": "The \"Build a Basic AI Agent From Scratch\" tutorial series added long-term memory to its agent harness, storing append-only notes in project.md and user.md files under the default directory ~/.agent-harness/memory. The implementation uses a MemoryStore class whose _notes_path helper accepts only the scopes \"project\" and \"user\", with read_memory and append_memory tools running in-process and the memory directory kept outside the Docker project mount. The series frames persistent memory as a privacy and safety concern, instructing the model never to store secrets, credentials, or transient task details.", "body_md": "Previous parts of *Build a Basic AI Agent From Scratch*:\n\nYou can find and clone this code in this blog series' [Github repo](https://github.com/rogiia/basic-agent-harness).\n\nIn the previous parts we made our agent capable, autonomous and much safer. It can use tools in a Docker sandbox, plan long tasks, ask for help, respect a tool policy, and stop when it reaches a resource limit. But every time we close the program, it forgets everything.\n\nThis is a big limitation. The agent might learn that you prefer concise answers, that a project uses `uv`, or that a migration was partially completed yesterday. On the next session it has to discover all of that again.\n\nIn this part we will add **long-term memory**. Our agent will be able to save durable notes about the project and its user, then use them in future sessions.\n\nThe `messages` list in our agent loop is already a kind of memory. It contains the system prompt, the user messages, the model replies and the tool results. This is **short-term memory**: it lasts while the process is running and it is sent to the model as context.\n\nIt has two important limits:\n\nLong-term memory is different. It survives across sessions and is retrieved only when it is useful. We will use it for two different things:\n\n`uv` for Python commands\".\nMemory makes an agent more useful, but it also introduces a new privacy and safety problem: memory is persistent data. A bad or overly broad note can influence a later task long after the original context is gone.\n\nFor this first implementation, the system prompt gives the model strict rules:\n\n```\n\"- Long-term memory (read_memory / append_memory): at the start of a session, read \"\n\"both project and user memory. Project memory is for durable lessons, environment \"\n\"facts, and conventions; user memory is for stable preferences and expectations. \"\n\"Append only concise, useful facts that are likely to hold in future sessions. Never \"\n\"store secrets, credentials, or transient task details.\\n\"\n```\n\nThis is important for a few reasons:\n\nBy default, the memory directory is outside the Docker project mount. The agent accesses it through `MemoryStore`, not through its filesystem tools.\n\nWe do not need a database or vector store to start. A few files are enough, easy to inspect, and easy to back up. The default directory is `~/.agent-harness/memory`:\n\n```\n~/.agent-harness/memory/\n  project.md\n  user.md\n```\n\n`project.md` and `user.md` contain append-only notes. With this design in mind, let's implement the memory store:\n\n```\nclass MemoryStore:\n    \"\"\"Persist agent notes, user preferences, and complete session records.\"\"\"\n\n    def __init__(self, root: Path):\n        self.root = root.expanduser()\n        self.sessions_dir = self.root / \"sessions\"\n        self.root.mkdir(parents=True, exist_ok=True)\n        self.sessions_dir.mkdir(exist_ok=True)\n\n    def read_notes(self, scope: str) -> str:\n        path = self._notes_path(scope)\n        return path.read_text(encoding=\"utf-8\") if path.exists() else \"(empty)\"\n\n    def append_notes(self, scope: str, content: str) -> str:\n        path = self._notes_path(scope)\n        with path.open(\"a\", encoding=\"utf-8\") as file:\n            if path.stat().st_size:\n                file.write(\"\\n\")\n            file.write(content.strip() + \"\\n\")\n        return f\"Saved {scope} memory.\"\n```\n\nThe `_notes_path` helper only accepts `project` and `user`. It doesn't let the model choose an arbitrary filename:\n\n``` php\ndef _notes_path(self, scope: str) -> Path:\n    if scope not in {\"project\", \"user\"}:\n        raise ValueError(\"scope must be 'project' or 'user'\")\n    return self.root / f\"{scope}.md\"\n```\n\nThe agent needs tools to access this store. All of them run in-process, alongside the scratchpad and to-do list, because they hold harness state rather than acting on the project files:\n\n``` php\ndef read_memory(scope: str) -> str:\n    return _get_store().read_notes(scope)\n\ndef append_memory(scope: str, content: str) -> str:\n    return _get_store().append_notes(scope, content)\n```\n\nTo work with long-term notes, the agent gets two tools:\n\n`read_memory`: read `project` or `user` notes.`append_memory`: append a concise durable note.\nThese tools are in `PLANNING_TOOLS`, so they do not prompt for permission on every call. That is a reasonable default because they only access the configured memory directory. It also means the memory directory should be treated as sensitive user data and protected with normal operating-system permissions and backups.\n\nFirst, we can ask the agent to remember a stable preference:\n\n``` bash\n$ uv run agent.py\nSession:          z7Pq3hM4kA\nMemory directory: /home/xxxxx/.agent-harness/memory\n\nYou: I prefer concise answers. Remember that for future sessions.\n  [tool] read_memory({'scope': 'user'})\n  [tool result] (empty)\n  [tool] append_memory({'scope': 'user', 'content': 'The user prefers concise answers.'})\n  [tool result] Saved user memory.\nAssistant: I will keep answers concise in future sessions.\n```\n\nNext time, the agent can read that note before it answers:\n\n```\nYou: Help me understand this project.\n  [tool] read_memory({'scope': 'project'})\n  [tool result] Use uv for Python commands.\n  [tool] read_memory({'scope': 'user'})\n  [tool result] The user prefers concise answers.\nAssistant: I will inspect the project and keep the explanation concise.\n```\n\nOur agent now has durable memory without turning its whole history into every model request:\n\nThe agent can now keep useful facts between runs, so it does not have to rediscover the same preferences and project conventions every time.\n\nThis article introduced the foundations for sessions. In the next article, we will build on them: listing past sessions, resuming an unfinished session, and compacting a long session so it can continue within the model's context window.", "url": "https://wpnews.pro/news/build-a-basic-ai-agent-from-scratch-memory", "canonical_source": "https://ruxu.dev/articles/ai/build-an-ai-agent-memory/", "published_at": "2026-10-12 00:00:00+00:00", "updated_at": "2026-10-12 09:41:11.010460+00:00", "lang": "en", "topics": ["ai-agents", "ai-tools", "developer-tools", "artificial-intelligence"], "entities": ["MemoryStore", "read_memory", "append_memory", "Docker", "GitHub", "uv"], "also_reported_by": [], "alternates": {"html": "https://wpnews.pro/news/build-a-basic-ai-agent-from-scratch-memory", "markdown": "https://wpnews.pro/news/build-a-basic-ai-agent-from-scratch-memory.md", "text": "https://wpnews.pro/news/build-a-basic-ai-agent-from-scratch-memory.txt", "jsonld": "https://wpnews.pro/news/build-a-basic-ai-agent-from-scratch-memory.jsonld"}}