Skip to content
MenuStep 3: Give It Memory

Step 3: Give your agent memory

Right now the chat history lives in your browser. Clear it or switch laptops, and your agent forgets everything, including which locations it already visited. Let's add a database to store the chat history instead. The Vercel Marketplace has a whole shelf of them, and adding one to your project is a single command: no new account, no keys to copy, and it's billed through Vercel.

  1. Add Neon from the Vercel Marketplace

    vercel integration add neon

    This creates a Neon Postgres database, connects it to your project, and sets DATABASE_URL on every deployment. Prefer clicking? Same thing from the Marketplace page.

  2. Ask your agent

    Prompt for your coding agent

    Store my agent's chat history in Postgres. In `backend/`, read the connection string from `DATABASE_URL` and create a `chat_history` table on first use, keyed by the player id the UI sends as `X-Player-Id` (names can repeat; the id can't). On every chat turn, load the saved history, add the new message from the UI, run the agent, and save the whole conversation back.

    What your agent is going to implement
    1. A chat history table

      One row per character, holding its whole conversation.

      SQL
      create table if not exists chat_history (
        player     text primary key,      -- one conversation per character
        messages   jsonb not null,        -- the whole history, as AI SDK messages
        updated_at timestamptz not null default now()
      );
    2. Load before the turn, save after

      AI SDK for Python messages are Pydantic models, so they go in and out of a jsonb column as is. Any Postgres driver works. On Python, psycopg is the usual pick.

      Python
      import os
      import ai
      import psycopg
      from psycopg.types.json import Jsonb
      
      # Set on your project by the Neon integration. Nothing to copy around.
      DATABASE_URL = os.environ["DATABASE_URL"]
      
      async def load_history(player: str) -> list[ai.messages.Message]:
          async with await psycopg.AsyncConnection.connect(DATABASE_URL) as db:
              cursor = await db.execute(
                  "select messages from chat_history where player = %s", (player,)
              )
              row = await cursor.fetchone()
          return [ai.messages.Message.model_validate(m) for m in row[0]] if row else []
      
      async def save_history(player: str, messages: list[ai.messages.Message]) -> None:
          data = Jsonb([m.model_dump(mode="json") for m in messages if m.role != "system"])
          async with await psycopg.AsyncConnection.connect(DATABASE_URL) as db:
              await db.execute(
                  """insert into chat_history (player, messages) values (%s, %s)
                     on conflict (player) do update
                     set messages = excluded.messages, updated_at = now()""",
                  (player, data),
              )
      
      @app.post("/api/agent/chat")
      async def chat(request: ChatRequest, x_player_id: Annotated[str, Header()]):
          player_id = x_player_id  # one conversation per character, not per name
          incoming, _ = ai_sdk.to_messages(request.messages, tools=GameAgent.TOOLS)
          history = [*await load_history(player_id), incoming[-1]]  # + the new message
          messages = [ai.system_message(SYSTEM_PROMPT), *history]
      
          async def body():
              async with GameAgent().run(model, messages) as stream:
                  async for chunk in ai_sdk.to_sse(stream):
                      yield chunk
              await save_history(player_id, stream.messages)
      
          return StreamingResponse(body(), headers=ai_sdk.UI_MESSAGE_STREAM_HEADERS)