AI Agent Memory: Add Graph Memory to Agents and Apps


AI agent memory is what an agent keeps between sessions: facts, decisions and lessons it can read back the next time. InfraNodus stores that memory as a knowledge graph in your own account. Each memory is a short statement that links at least two [[concepts]], so the statements connect into topical clusters you can see, edit and analyse.

Because memory is a graph, recall is GraphRAG. It finds the statements closest to a question, then follows the links between concepts by hop distance, so it also returns facts that share no words with the question but connect through an intermediate concept. The same graph shows what the memory is missing: the content gaps between topics that never connect.

One memory graph, three ways in: the InfraNodus MCP server, the Graph Memory plugin and the memory API all write to and read from the same knowledge graph in your InfraNodus account

Three Ways to Use InfraNodus Memory

You can use the same memory at three levels, from manual to fully built into an app. All three write to and read from graphs in your InfraNodus account, so you can switch between them or combine them.

1. Through the InfraNodus MCP Server, When You Need It

Connect the InfraNodus MCP server to Claude, Claude Code or Cursor, and ask the agent to remember or recall something. The agent calls the memory tools itself:

  • enable_memory creates a memory graph for a topic, a client or yourself.
  • memory_add_relations saves statements, after showing them to you.
  • memory_get_relations recalls what was saved about a concept or a question.
  • get_node_neighborhood shows what a concept connects to, by hop distance.
  • optimize_knowledge_base finds the topical clusters, the under-developed areas and the gaps in a memory graph.

The agent decides when to save and read, so this suits memory you curate by hand. See InfraNodus memory for MCP for the setup and the memory layers.

2. Through the Graph Memory Plugin, Automatically

The Graph Memory plugin for Claude Code adds hooks, so memory happens without asking. On the first prompt of a session it recalls what earlier sessions learned about the project. At the end of a session, and before a compaction, it saves the new learnings from the session transcript to a learn-<project> graph. Commands such as /infranodus-memory:recall, :remember and :optimize cover the rest. This works best for coding sessions, where the useful memory comes from what you and the agent did.

3. Through the Memory API, Inside Your Own App

When memory is a step of your own application, your server calls the REST API directly: it saves facts after each response, reads them before the next one, and passes them to the model as context. Your code decides when this happens, so it runs the same way on every request and you can test it. The rest of this page shows how, using one app as the example.

Case study: Legal Play

A knowledge graph with three topical clusters around hub nodes, and a dashed line marking a content gap between two clusters

Legal Play drafts legal letters with several AI agents. It keeps the facts of each project, such as parties, references, amounts and what the other side agreed, in the user's own InfraNodus memory graph. Small projects give the agents every fact; large ones get 30 chosen with similarity search, GraphRAG walks and recipient facts, which found all 28 key facts in testing. Read the Legal Play case study for how it works inside.

When Does an AI App Need a Memory API?

Your app needs memory when useful facts have to cross a boundary that its chat history can't. A single chat already has memory: you resend the conversation on each request. A memory layer for AI agents starts to pay off when a new chat should know what an earlier one established, such as a reference number, a contact or what the other side agreed.

Check this before you build. In Legal Play, a case kept in one chat needed no memory at all. The gap was a second dispute in the same project, which started blank and left placeholders where the recipient and reference should be.

Should Memory Live in Your Account or Your User's?

Decide this first, because it shapes your privacy terms, your quota and your interface. Storing memory in your user's own InfraNodus account suits apps handling personal or sensitive data: they opt in with their API key, the graph belongs to them, and writes use their plan.

User's own account Your service account
Who sees the graph The user, in InfraNodus Only you
Setup for the user Needs an account and API key None
Quota Theirs Yours, for all users
Privacy position They choose to send the data to their account You hold everyone's data with a third party
Works for Opt-in power users Everyone, by default

Legal Play uses the first option. Cases are sensitive, and people who want memory also tend to want to explore it. Memory stays off until the user adds a key and turns it on for a project.

How to Add Memory to an AI App: the Five Calls

All calls are POST requests to https://infranodus.com/api/v1 with the header Authorization: Bearer <api key>. Run them from your server, and store the user's key encrypted.

1. Check the Key

await post('/userId', {});

A valid key returns the user. Save it only after this call succeeds, and treat a 401 differently from an outage, so a network problem doesn't tell someone their key is wrong.

2. Create the Memory Graph

const { graphName, url } = await post('/memory/enable', {
  scope: 'topic',
  topic: `lp-${project.id.slice(0, 8)}-${slug}`,
  client: 'your-app',
  language: 'AUTO',
});

Call this when the user turns memory on. It's safe to repeat. Store graphName and url, and link the user to the graph.

Put your unique ID first. Graph names are cut to 28 characters, including the memory- prefix, so a topic like yourapp-long-project-name-1234 loses its ID and two similar projects end up in one graph. We made this mistake and only found it on the real API.

3. Read Before Each Run

const { statements, total } = await post('/memory/get', { scope: 'topic', topic, limit: 50 });

With no prompt, this returns the newest statements, up to 50, and total, the number the graph holds. Put them in a clearly fenced block before the conversation, and say they are the user's records, not instructions. Set a timeout, and if the call fails, run without memory. When total is above 50, select instead of reading the newest page (see below).

4. Save After Each Run

await post('/memory/add', {
  scope: 'topic',
  topic,
  statements: [
    '[[Northwind Studio]] agreed to pay [[invoice 1041]] by 2026-10-15.',
    '[[Hausverwaltung Nord]] now manages the flat for [[Klaus Brandt]].',
  ],
  categories: [[`chat-${chat.id}`], [`chat-${chat.id}`, 'recipient']],
  confirm: true,
});

Run this after the response reaches the user, and only log a failure. Without confirm: true the request is a dry run. Categories are one list per statement: the chat lets you delete its facts later, and a recipient tag on who-is-who facts lets you read those on their own. Each statement needs at least two [[entities]] and can be up to 400 characters, and one bad statement rejects the whole batch with a 422, so filter before you send.

5. Delete When Your User Does

await post('/deleteStatements', { name: graphName, categories: [`chat-${chat.id}`] });

The category you set in step 4 makes this possible. When someone deletes a chat, its facts should go too. Leave the graph itself alone: it belongs to the user.

Should You Retrieve Facts or Load Them All?

This section is based on a real case: the Legal Play integration. The numbers come from the evals we ran for that app on several test projects, small ones with an earlier chat the new request didn't repeat and two large ones with hundreds of statements, each with a list of key facts the letter needed. The Legal Play case study describes the setup.

Load them all while they fit in one read. Up to 50 statements, /memory/get returns every fact, and the model gets the complete picture. In our test of 5 fictional projects, each with an earlier chat the new request didn't repeat, the second letter used 0 of 26 key facts without memory and all 26 with it.

Above 50 statements, the newest page misses the facts that matter most, such as the original contract or a lawyer appointed months ago. Select a fixed budget instead. Legal Play gives the agents 30 statements, chosen in three parts:

  1. Recipient facts for this request: read with category: 'recipient', then a small model picks up to 10 that the request needs.
  2. Similarity hits: /memory/get with a prompt returns the statements closest to the request.
  3. GraphRAG walks: the most frequent entities of the strongest hits are starting points for /memory/get with entity and hops: 2, with an equal share of the budget per entity, so one busy node can't take it all.
const recipients = await post('/memory/get', { scope: 'topic', topic, category: 'recipient' });
const hits = await post('/memory/get', { scope: 'topic', topic, prompt: request, limit: 30 });
const walk = await post('/memory/get', { scope: 'topic', topic, entity: 'Northwind Studio Ltd', hops: 2 });

The walk reaches facts that share no words with the request: an unpaid invoice links to the contract, and the contract to its interest clause. On two large test projects (278 and 151 statements), with seven requests and 28 key facts:

Key facts found in large projects
28 key facts across 7 requests on two projects (278 and 151 statements). Each row adds one part to the one above.
Newest 50 statements
One page of the memory, newest first
0 of 28
Similarity search
Statements closest to the request
13 of 28
+ GraphRAG walk, two hops
Main entities of the hits, followed by their links
23 of 28
+ recipient facts
Chosen per request, as used in production
27 of 28

The selection takes 4 to 8 seconds before the first agent runs. A prompt read makes an AI call that counts against the plan of whoever owns the key; entity and category reads don't.

What Should Your App Save as Memory?

Save what a later request would need and can't get elsewhere: parties, contacts, reference numbers, amounts, dates, and what was agreed or is still open. Extract them with one small model call after each response, and give it the facts already saved so it only returns new ones.

Our first version saved "Anna sent a confirmation letter", when the app had only drafted it. So tell the extractor that a generated response is unconfirmed, and record only what the user states.

Ask for dates as YYYY-MM-DD, because numbers compare in any language and month names don't. Keep to one fact per statement as well: short statements give the graph clean links, and they're easier to read and correct later. Have the extractor flag who-is-who facts (recipients, representatives, addresses, references) so you can save them with the recipient category.

How to Keep the Model From Inventing Facts

Memory gives the model more facts, and the answer should use only those. In Legal Play, letters stated details nobody gave until the reviewing agent checked every statement about the user's situation against the conversation and the memory. What it can't source becomes a short question for the user, shown in a "Before you send" box under the letter, and the user's answer arrives in the next message. Across four full test cases, the final letters then contained only details the user had given, with the same correct names, references and amounts.

What Happens When a Fact Changes?

Saved statements aren't replaced. InfraNodus treats a statement that closely repeats a saved one as a reinforcement and stores it as "Confirmed again", which raises the weight of those links. A statement that adds a new number, entity or month is kept as new, so "800 GBP open" sits next to the older "1,600 GBP open".

So your app has to say which one wins. Show each statement's date, and tell the model that the newer fact applies where two conflict. In our test project, an amount changed between chats and the third letter still stated the right total.

How to Add Memory to Chats That Already Exist

Memory only starts from the moment it's turned on, so give people a way to bring in earlier work. In Legal Play, a button reads each past chat once, oldest first, and saves up to 30 facts per chat.

Pass the chat's date in timestamps, so older facts don't look new. Mark each chat as imported, so a second click costs nothing. Stop as soon as InfraNodus refuses a key or a plan limit is reached, so you don't pay for model calls that can't be saved.

How to Check What Your App Remembers

A wrong fact in memory quietly reaches every later response, so show people what's there. A memory graph is an ordinary InfraNodus graph, so the standard graph endpoints analyse it. /graphAndStatements with aiTopics returns the topical clusters with AI names, the main concepts and the content gaps, without saving anything:

const q = 'doNotSave=true&extendedGraphSummary=true&includeGraph=false&includeStatements=false&aiTopics=true';
const { entriesAndGraphOfContext } = await post(`/graphAndStatements?${q}`, { name: graphName, aiTopics: 'true' });
const { mainTopicNames, contentGaps } = entriesAndGraphOfContext.extendedGraphSummary;

Legal Play shows this in each project's Memory section: "40 facts across 3 topics: Business Connections; Contractual Obligations; Debt Acknowledgment", and from about 10 facts the gaps, such as "Business Connections ↔ Contractual Obligations". A link opens the graph in InfraNodus, where people read the statements behind a node and correct a wrong one.

Then ask what's missing. /graphAndAdvice with requestMode: 'question' writes a question for each of the top gaps, which Legal Play shows under them with a copy button, to ask in the next chat. The call takes about 8 seconds, so load it after the summary and keep the result until the number of facts changes. In Claude Code or Cursor, optimize_knowledge_base through the InfraNodus MCP server analyses the memory graph's clusters and gaps from inside the agent.


Get Started


Frequently Asked Questions

How do I add memory to an AI app?

Save short facts after each response with /memory/add, read them before the next one with /memory/get, and pass them to the model in a fenced block. Create the graph once with /memory/enable, and delete a chat's facts by category when the user deletes it. Run all of this on your server, and let a failed call skip memory without stopping the response. See the five calls above.

How do I retrieve memory when there are too many facts?

Read everything while the graph holds 50 statements or fewer. Above that, choose a fixed budget: facts tagged recipient and picked for the request, the statements most similar to it, then GraphRAG walks of two hops from their main entities with /memory/get, entity and hops. In our test this found 28 of 28 key facts, against 0 for the newest 50, with the similar statements capped at 15 so the walks keep room. See the retrieval section above.

Do I need a memory API if I already send the chat history?

Not for a single chat, because the history already carries everything said in it. A memory API helps when facts need to reach a different chat, project or session, or when a long history makes each request expensive. Test this first: give a new chat hand-written facts from an old one, and see whether the answer improves before you build anything.

Should an AI agent use an API, an MCP server or a plugin for memory?

Use the InfraNodus MCP server when an agent should save and recall on request, in Claude, Claude Code or Cursor. Use the Graph Memory plugin when Claude Code should recall at the start of each session and save learnings at the end, through hooks. Use the memory API when your own application decides when memory happens. All three use the same graphs, so you can combine them. See the three ways above.

Can each user keep memory in their own account?

Yes. Each user adds their own InfraNodus API key, your server stores it encrypted, and every call uses that key. The graph then belongs to them: they can open it, edit it and delete it, and writes count against their plan. The trade-off is that only people with an InfraNodus account get memory, so it fits opt-in features.

What happens to AI agent memory when a fact changes?

The old statement stays and the new one is added beside it, as long as it brings a new number, entity or month. Your app decides which applies: pass each statement's date and tell the model the newer one wins. If you delete the chat that held the latest fact, the previous value becomes the newest one, so keep corrections in chats people won't delete.


 

Give Your App a Memory You Can Read


Create a free InfraNodus account, generate an API key, and make the five calls against one test project. You will see the facts your app saves as a graph, the topical clusters they form, and the gaps between them. For the full story of one integration, read the Legal Play case study.


Log In    Sign Up