Give your n8n AI agent a memory that survives sessions
An n8n AI Agent with the standard memory nodes remembers a conversation. It does not remember a customer. Close the chat, come back tomorrow, and the agent greets the same person from zero — every preference, every past issue, gone. This guide covers why that happens and how to fix it properly with a self-hosted memory layer on Postgres, using the n8n-nodes-haki community node.
Last updated: August 2026. Tested on n8n 1.x, self-hosted and Cloud.
Why your agent forgets (it's usually one of three things)
Before adding anything, check which of these you're actually hitting — the first one accounts for most "my agent has amnesia" threads on the forum:
1. Your sessionId isn't stable. The built-in memory nodes key everything on the session ID. If your workflow generates a new one per execution (a webhook execution ID, a timestamp), every message starts a fresh conversation. The memory works; the key doesn't. Fix: derive the session from something stable — the WhatsApp number, the email, the user ID.
2. The context window ends. Buffer-style memory keeps the last N turns of one session. That's execution context, and trimming it is correct — the mistake is expecting it to also be long-term knowledge. It can't be: it's a sliding window, and yesterday slid out.
3. The key/value workaround destroys history. The common forum answer — a Postgres table of user_id / key / value — works until a value changes. An UPDATE silently erases what the user used to want (and you'll want that history the first time you debug a weird answer). An INSERT leaves two contradicting rows and no way to arbitrate. What's missing is lifecycle: which value is current, which was replaced, and when.
Long-term memory is a different job from session memory: extract durable facts from conversations, keep them updated when they change, and inject the relevant ones at the start of any future conversation — whatever the session ID.
What we're building
Webhook → Haki Context → AI Agent (+ its normal session memory) → Haki Capture → Respond
Two nodes do the work. Haki Context runs before the agent: it fetches this user's current facts (dated, with replaced values excluded) and hands them to the agent as a system-prompt block. Haki Capture runs after: it records the exchange so tonight's consolidation can extract anything durable. The agent's own buffer memory stays — short-term and long-term are complementary, not rivals.
The memory itself runs on your infrastructure: Haki is Apache 2.0, one docker compose up, Postgres 16 + pgvector, embeddings computed locally. If you self-host n8n for data reasons, the same reasons apply double to memory — this setup keeps the whole loop on your machines.
Setup
1. Run Haki (5 minutes)
git clone https://github.com/GetHaki/Haki && cd Haki
docker compose up -d
uv sync && uv run alembic upgrade head
uv run uvicorn app.main:app --port 8100
Then prove the memory loop before wiring anything into n8n:
uv run haki connect --api-url http://localhost:8100
uv run haki verify
You want the last line to read OK — your agent remembered across conversations, and it can prove it. (Alternatively: the hosted version at gethaki.space gives you 1,000 credits/month, no card — the n8n side is identical, only the base URL changes.)
Create an API key:
curl -X POST http://localhost:8100/v1/keys \
-d '{"org_id":"org_main","project_id":"prj_support","label":"n8n"}'
2. Install the community node (2 minutes)
n8n → Settings → Community Nodes → Install → enter n8n-nodes-haki → confirm. Two nodes appear in your palette: Haki Context and Haki Capture. (Self-hosted n8n installs community nodes directly; on n8n Cloud, available once the node is listed as verified.)
3. Create the credential (1 minute)
First use of either node → Create new credential: your API base URL (http://your-host:8100 — from n8n-in-Docker, that's your host's address, not localhost) and the hk_... key. The credential is stored by n8n; it never appears in the workflow JSON.
4. Wire the workflow
On Haki Context, three fields matter:
| Field | Set it to | Why |
|---|---|---|
| Subject ID | an expression resolving to a stable user identifier ({{ $json.from }} for a WhatsApp number, an email, a CRM ID) | this is the identity memory attaches to — the whole point |
| Project ID | prj_support | keys and isolation are per-project |
| Query | the incoming user message | retrieval is relevance-ranked; the message is the query |
Feed the node's output into the agent's system prompt, above your own instructions — it renders as a compact block of dated facts, with anything superseded already excluded.
Haki Capture, after the agent: same Subject ID and Project, plus the user message and the agent's reply. Capture is idempotent — an n8n retry won't create duplicate memories, which matters more than it sounds once webhooks start double-firing.
A note the node enforces rather than suggests: a call without a stable subject identity is refused. Memory attached to nothing is how you end up serving one customer's facts to another.
5. Test the thing that matters: a NEW session
First conversation:
You: Hi, I'm Amara. Quick thing — always reply to me in French please, and I'm on the Pro plan. Agent: (answers, in French)
Now the real test. New chat, new session ID — simulating tomorrow:
You: What plan am I on again? Agent: Vous êtes sur le plan Pro. (in French, unprompted)
Two facts survived a session boundary: the plan, and the language preference — applied, not just recited. Then change your mind (« actually, English is fine ») and ask again in a third session: the agent should switch, and if you open the Haki console you'll find the French preference still there, marked superseded, with the date. That's the difference between memory and a k/v table: the history exists, but it can never be served as current.
Download: the complete workflow JSON —
haki-persistent-support-agent.json— is in the repo underintegrations/n8n/, importable into any n8n instance. It uses plain HTTP nodes, so it also works without installing the community node.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Agent remembers nothing across sessions | Subject ID unstable (execution ID, timestamp) | key on the user, not the run — check what the expression actually resolves to in past executions |
| Memory works in test, dies in production | webhook payload shape differs → Subject ID resolves empty → call refused | inspect the refused-call error; it names the missing field |
| Facts appear but stale ones too | you're injecting raw history alongside — the buffer window re-serves what memory correctly retired | keep the buffer short (last few turns); durable knowledge comes from Context, not the transcript |
ECONNREFUSED from the node | n8n runs in Docker, localhost points at the n8n container | use the host address or a shared Docker network |
| Slow first call after startup | local embedding model loading | one-time cost; subsequent calls are fast (p95 under 50 ms at 10k facts in our benchmark) |
Where this goes next
The same memory follows the same user across surfaces: the MCP server puts it in Cursor or Claude, the OpenAI-compatible gateway adds it to any existing chat app by changing one base URL, and the SDKs cover coded agents. One user, one subject ID, one memory — n8n is just the first place it pays off.