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.
Add Neon from the Vercel Marketplace
vercel integration add neonThis creates a Neon Postgres database, connects it to your project, and sets
DATABASE_URLon every deployment. Prefer clicking? Same thing from the Marketplace page.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
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() );Load before the turn, save after
AI SDK for Python messages are Pydantic models, so they go in and out of a
jsonbcolumn 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)