#Chat History not persisting after Frontend Restart (Agno + Postgres + Next.js)

1 messages · Page 1 of 1 (latest)

leaden raptor
#

Hi CopilotKit Team! 👋

I’m currently evaluating **CopilotKit **for a production use case and I've run into a persistence issue. While chat history works during an active session, it disappears after a frontend server restart.

My Tech Stack:
Backend: Python with Agno Framework (v2.13)
Database: Postgres (Verified: chat sessions are correctly stored here)
Frontend: Next.js (v15/16) + CopilotKit (v1.50)

The Setup: In page, I have a sidebar on the left showing a list of past chat sessions. On the right, I use the <CopilotSidebar />. When a user selects a session from my left panel, the past chat history loads perfectly into the sidebar. I haven’t written any custom "history loading" logic—CopilotKit seems to handle this automatically when the session is active.

The Issue:
I start a chat and see it saved in Postgres.
I switch sessions; history loads fine.

If I restart the frontend server and try to load that same session again, the **CopilotSidebar **is completely empty, even though the data is still in my Postgres DB.

What I've Checked:
The session data exists in the database.
The backend is connected and healthy.
It seems like the link between the frontend's session reference and the backend's persisted state is being lost on refresh. Has anyone else faced this with the Agno integration? How should I ensure the frontend "re-attaches" to the existing Postgres thread after a hard reload?

Here is the partial code how I am using it in my application.

layout.tsx:
<CopilotKit
runtimeUrl={/api/copilotkit}
agent="my_agent" key={threadId}
threadId={threadId}
>
<ChatHistorySidebar
currentId={threadId}
onSelect={(id) => setThreadId(id)}
/>
<div className="w-full">{children}</div>
</CopilotKit>

page.tsx
<CopilotSidebar defaultOpen={true} />

Would love some guidance here so I can confidently move this to production!

#

Chat History not persisting after Frontend Restart (Agno + Postgres + Next.js)

plain quartzBOT
#

This solution has been AI generated

This behavior occurs when the CopilotKit provider mounts without a stable threadId after a hard reload. CopilotSidebar only rehydrates history when it can attach to an existing threadId; otherwise it initializes an empty session.

Why this happens

  • CopilotKit uses threadId as the persistence key. If threadId is undefined/changes on first render after a refresh, the sidebar mounts on a new empty thread.
  • History auto-load works during session switching because threadId is set in-memory, but is lost on a full reload unless you restore it.

Fix: persist and reattach threadId across reloads

  • Persist the current threadId (URL or localStorage) and render CopilotKit only after it’s restored. Ensure the provider remounts on threadId changes with key={threadId}.
#
'use client'
import { useEffect, useState } from 'react'
import { v4 as uuidv4 } from 'uuid'

export default function Layout({ children }: { children: React.ReactNode }) {
  const [threadId, setThreadId] = useState<string | null>(null)

  useEffect(() => {
    const stored = localStorage.getItem('copilot-thread-id')
    if (stored) setThreadId(stored)
    else {
      const id = uuidv4()
      localStorage.setItem('copilot-thread-id', id)
      setThreadId(id)
    }
  }, [])

  if (!threadId) return null // avoid mounting on an empty thread

  return (
    <CopilotKit runtimeUrl="/api/copilotkit" agent="my_agent" threadId={threadId} key={threadId}>
      <ChatHistorySidebar
        currentId={threadId}
        onSelect={(id) => {
          localStorage.setItem('copilot-thread-id', id)
          setThreadId(id)
        }}
      />
      <div className="w-full">{children}</div>
    </CopilotKit>
  )
}

Backend check (Agno)

  • Your /api/copilotkit should load messages by the provided threadId. No extra props (like initialMessages) are needed; history loads automatically when threadId is stable.

Common gotchas

  • Ensure the CopilotKit wrapper is a client component and does not render before threadId is known.
  • Keep agent name consistent.
  • Using a route param (e.g., /chat/[threadId]) instead of localStorage works equally well.

Was this helpful?

If this solution worked for you, please click on the appropriate option below to help us improve:

plain quartzBOT
#

Thank you for your feedback! Our team will provide additional assistance. 🔄

plain quartzBOT
#

Hi @1458371246898020372, Check out this doc on thread switching in LangGraph – the approach should work similarly for other frameworks too. This is from one of our community members showing how they implemented it: Langgraph persistence
Let me know if this helps!

GitHub

Contribute to clickspider/copilotkit-langgraph-history development by creating an account on GitHub.

leaden raptor
#

This worked. Thanks