Give Your Python Agent Memory in Ten Lines Memanto released a Python client, version 0.2.28 on PyPI, that gives Python agents persistent memory through a single import, with typed memories across 13 types, semantic recall, grounded answers without the user's own LLM key, and point-in-time recall. The client creates the agent on first use with no server or vector store to provision, supports Cloud via a free key from console.moorcheh.ai with 100K free operations or On-Prem via Docker and Ollama, and requires Python 3.10 or newer. Writes carry no LLM call, default to 0.8 confidence, and batch_remember() accepts up to 100 memories per request. Give Your Python Agent Memory in Ten Lines Memanto’s new Python client gives any Python agent persistent memory with one import: typed memories, recall, grounded answers with no LLM key of your own, extraction straight from chat turns, and point-in-time recall. Every snippet here was run against memanto 0.2.28, outputs included. Your Python agent forgets everything the moment the process exits. The usual fix is a weekend of plumbing: pick a vector store, wire up embeddings, write the extraction prompt, decide what counts as a duplicate. Memanto’s new Python client replaces that with one import. No server to start, no vector store to provision, and the agent is created the first time you use it. This tutorial builds memory into a small support bot, one method at a time: storing typed memories, recalling them, getting grounded answers without an LLM key of your own, letting a conversation write its own memories, and asking what the agent believed before something changed. Every snippet below was run against memanto 0.2.28 from PyPI, and the outputs are copied from those runs. Install and pick a backend pip install memanto memanto one-time setup: choose Cloud or On-Prem You need Python 3.10 or newer. The setup wizard asks where your memories live. Cloud takes a free key from console.moorcheh.ai https://console.moorcheh.ai/api-keys 100K free operations, no card . On-Prem runs everything in Docker with Ollama, so nothing leaves your machine and no key is needed. The client reads whatever you chose from ~/.memanto , so none of it shows up in your code. If you would rather skip the wizard, for example in CI, export MOORCHEH API KEY or pass api key= to the constructor. The runs in this post used the Cloud backend. The whole thing in ten lines Here is the end state before we take it apart. Two memories go in, one question gets a semantic match, another gets a written answer. python from memanto import Memanto memanto = Memanto agent id="support-bot" memanto.remember "Alex prefers oat milk in their coffee.", type="preference" memanto.remember "We ship the mobile app on Thursdays, never Fridays.", type="decision" print memanto.recall "what does Alex drink?" "memories" 0 "content" print memanto.answer "Does Alex take dairy in their coffee?" "answer" Alex prefers oat milk in their coffee. Based on the stored memory, Alex does not take dairy in their coffee . They prefer oat milk instead a non-dairy alternative . Source: Chunk 1 – Alex's coffee preference Memanto agent id="support-bot" does more than it looks. It creates the agent if it does not exist yet pass auto create=False to make a missing agent an error instead , then adopts the agent’s live session or opens a new one. If the session expires mid-run, the client re-establishes it and retries the call once. Store typed memories with remember Every memory has a type, and the type is what lets you filter later. There are 13: fact , preference , decision , instruction , goal , commitment , relationship , context , event , learning , observation , artifact and error . result = memanto.remember "Staging runs Postgres 16 on staging-db-02.", type="fact", tags= "infra", "staging" , confidence=0.9, print result {'memory id': '7ed5cf2f-77d5-456a-abf9-9e6981fb07b4', 'agent id': 'support-bot', 'namespace': 'memanto agent support-bot', 'status': 'queued', 'confidence': 0.9, 'type': 'fact'} title defaults to the first 50 characters of the content. confidence defaults to 0.8. tags takes a single string or a list. There is no LLM call on this path, so the write is cheap, and the memory showed up in recall in the very next call in every run we did. For bulk loads, batch remember takes up to 100 memories in one request, each with the same keys as remember . Recall, filtered by type recall is semantic search over this agent’s memories, ranked by relevance. The type filter takes one type or a list. hits = memanto.recall "when do we release?", type="decision", limit=5 for m in hits "memories" : print m "type" , m "content" , m "score" decision We ship the mobile app on Thursdays, never Fridays. 0.122969 Look at that score: 0.12 for exactly the right memory. Scores depend heavily on how the question is worded, so a correct hit can still score low. Trust the ranking, and be careful with an aggressive min similarity cutoff, which can throw away the answer you wanted. Each result is a plain dict with id , type , content , tags , confidence , provenance , status , created at and score , the same shape the REST API returns. Grounded answers without your own LLM key Sometimes you don’t want ranked memories, you want the answer. answer retrieves the relevant memories and writes a response grounded in them. reply = memanto.answer "Which day does the mobile app ship?" print reply "answer" Based on your stored memory, the mobile app ships on Thursdays — and specifically, it is never shipped on Fridays. Source: Decision memory, item f41101e2 The model runs on the Memanto side, so there is no OpenAI or Anthropic key to manage. That makes answer handy for internal tools, CLI helpers and quick lookups. It is built for questions, though. Pass it a statement and it will tell you, at length, that the statement is not a question. For a real chat agent, use the loop pattern further down. Let the conversation write the memories Calling remember by hand is fine when you know exactly what to store. Most of the time you don’t: the useful facts are buried in chat turns. extract takes those turns, pulls out what is worth keeping, types each memory and stores it. Start with dry run=True to see what it would do. chat = {"role": "user", "content": "Heads up, Alex switched to almond milk last week."}, {"role": "assistant", "content": "Noted, almond milk for Alex from now on."}, {"role": "user", "content": "And the team moved mobile releases to Wednesdays."}, preview = memanto.extract chat, dry run=True for c in preview "candidates" : print c "type" , c "content" , c "confidence" fact Alex switched to almond milk last week. Use almond milk for Alex going forward. 0.95 fact The team changed their mobile release schedule to Wednesdays. 0.95 Nothing has been written yet. When the candidates look right, drop the flag: stored = memanto.extract chat print stored "successful" , "stored,", stored "failed" , "failed" 2 stored, 0 failed Extraction is done by a model, so the wording and even the type vary from run to run. In one of our runs the release change came back as a decision , in this one as a fact . If the type matters for your filters, review a dry run first or store that memory with remember yourself. max memories default 20 caps how many candidates one call can produce. Small talk produces nothing to store, and extract says so with an exception instead of returning an empty result you might miss: python from memanto import NothingToExtractError try: memanto.extract {"role": "user", "content": "thanks "} except NothingToExtractError: print "nothing worth keeping" NothingToExtractError is a ValueError , importable straight from memanto . What changed, and what did we believe before? Alex used to take oat milk and now takes almond. Both memories exist, and the client can query either side of that change. recall changed since returns what was created or updated after a timestamp. recall as of returns the estate as it stood at a moment in time. Both accept an ISO date "2026-10-14" or a full datetime. python from datetime import datetime, timezone before = datetime.now timezone.utc .isoformat take this before the extract call ... extract chat runs here ... changed = memanto.recall changed since before for m in changed "memories" : print m "change type" , m "type" , m "content" then = memanto.recall as of before print then "count" , "memories existed before the chat" created fact The team changed their mobile release schedule to Wednesdays. created fact Alex switched to almond milk last week. Use almond milk for Alex going forward. 3 memories existed before the chat And because both milk memories are there with their timestamps, answer resolves the change on its own: print memanto.answer "What milk does Alex take now?" "answer" Based on the most recent memory Chunk 1 , Alex now uses almond milk . They switched to almond milk last week, which supersedes the earlier preference for oat milk in coffee Chunk 2 . There is also recall recent for the newest memories first, which is handy for a “what did I learn today” view. Put it in your agent loop In a real agent the pattern has two halves: recall before the model call, extract after it. Your own LLM does the talking. Memanto briefs it and keeps what is worth keeping. python from memanto import Memanto, NothingToExtractError memanto = Memanto agent id="support-bot" def chat turn user msg: str - str: 1. Brief the model with what memory already knows. hits = memanto.recall user msg, limit=5 "memories" context = "\n".join f"- {m 'type' } {m 'content' }" for m in hits reply = call your llm OpenAI, Anthropic, a local model: anything system=f"What you know about this user and project:\n{context}", user=user msg, 2. Keep anything durable the user just said. try: memanto.extract {"role": "user", "content": user msg} except NothingToExtractError: pass return reply “Extract from the user’s turn, not the whole exchange. A good assistant repeats facts back, and extracting its reply stores them twice. We learned that one the hard way while writing this post. Our first version passed user, assistant pairs to extract . When the user asked “which Postgres version runs on staging?”, the assistant answered “Postgres 16 on staging-db-02”, and extraction saved that answer as a second copy of a memory that already existed. Extracting only the user’s message keeps the estate clean. Messages like “thanks ” raise NothingToExtractError and cost nothing. Things worth knowing - One session per agent. Activating a session signs out every other client of that agent, including the CLI. The Python client handles this by adopting the live session when there is one, and by recovering if another client takes over. Use one Memanto instance per agent, and do not share one instance across threads. - Everything returns dicts. Each method returns the same shape as the matching REST endpoint, so the API reference https://docs.memanto.ai/api doubles as the client’s return-type documentation. - Editing. update memory memory id, content=..., tags= ... changes fields in place and delete memory memory id removes a single memory. - More than the wrapper. For operations the client does not wrap yet, memanto.client exposes the underlying SDK client. Clean up Tutorial agents should not linger. delete agent removes the agent. By default its memories stay in storage and come back if you recreate an agent with the same id, which is what you want for a deploy or rename. To wipe them too: memanto.delete agent delete memories=True {'status': 'deleted', 'agent id': 'support-bot', 'memories deleted': True} If deleting the memories fails, the agent is left intact and a NamespaceError is raised, so you can safely retry. Where to go next The same memories your Python agent writes are visible everywhere else Memanto runs. memanto ui opens a dashboard over them, memanto connect claude-code or Cursor, Codex, Windsurf and more gives your coding agents the same estate, and expiry policies https://memanto.ai/blog/your-agents-memory-needs-a-retention-policy keep it from rotting at month six. The full method reference is at docs.memanto.ai/sdk/python https://docs.memanto.ai/sdk/python .