I’m using n8n Embedded Chat with Chat Trigger + AI Agent and tools.
Expected behavior
When I send a message, the agent calls tools correctly and the final assistant reply appears normally in the chat.
Issue
After I reload the page, the Embedded Chat loads the previous session history and all tool calls / intermediate steps are displayed as if they were assistant messages, often as large blocks of JSON/code.
So the live experience is fine, but on refresh/history restore the chat UI renders internal agent/tool data as chat messages.
I need to keep the intermediate steps logged for later analysis/debugging (tool inputs/outputs, reasoning steps, etc.). I just don’t want those steps to be persisted or rendered as end-user chat messages when the session is reloaded.
I’m able to reproduce this, sounds like a bug but I’m not sure,
I don’t think it’s supposed to expose the intermediate calls, which seems to happen only when loading the previous session,
The logic there seems to just loading the messages array, which contains human, ai, tool messages..
If you have the time, maybe report this on GitHub, It would be nice to know whether this is intentional or not…
Does anyone know if this has been resolved, or how to prevent it? It’s still happening on the latest version. I have other agents that are using older versions of the Agent and Postgres Memory node and this doesn’t happen with those.
I just updated Self Hosted to 2.12.2 and built an entirely new workflow from scratch and it is still happening. The tool calls are loading from the chat memory into the chat window on reload:
I reverted back to using AI Agent node v2.1 instead of the latest v3 (on my n8n v1.123.3).
It’s an ugly workaround, but things worked before, and I don’t need its new features yet.
Copy the AI agent node, paste in text editor, change the typeVersion in JSON to be 2.1, select all, copy, paste to n8n back removing the old node beforehand.
this is a known limitation of how n8n’s memory stores the full LangChain message array — it includes HumanMessage, AIMessage, ToolMessage, and FunctionMessage all together, and the embedded chat widget renders everything on reload without filtering.
the cleanest workaround without waiting for a fix: use Postgres Chat Memory (or any external memory) and add a separate “load history” step that queries only human and ai type messages for display, while storing the full trace (including tool calls) in a separate table for analytics.
something like:
messages table: full trace for analytics/debugging (all message types)
chat_history view/query: only type = ‘human’ OR type = ‘ai’ — this is what gets passed to the chat widget on reload
this way you keep 100% of the intermediate steps for analysis but the user only sees the clean conversation on refresh. the separation also makes it much easier to build dashboards on top of the tool call data later.