TLDR: A static system prompt makes your embedded AI agent blind to the record the user is actually looking at. Pass the page entity ID in the POST body, fetch a context block server-side, prepend it to the prompt — and read it via ref at send time, not baked into transport config.
the setup
I'm building a custom practice management system for my business partner, a solo attorney.
The app has matters (lawyer-speak for client case files), contacts, documents — the full picture.
And an AI chat panel on every page.
The whole POINT was that my business partner could open a matter, hit the chat button, and get help from an agent that already knew what she was looking at.
the wall we hit
Except it didn't know.
my business partner opens a client's probate matter and types: "What's the fee arrangement on this?"
The agent: "I'd be happy to help! Could you tell me which matter you're referring to?"
She IS on the matter. She can literally see it on her screen.
The agent cannot.
The problem was embarrassingly simple. src/app/api/chat/route.ts was passing exactly one thing to Claude: a static systemPrompt describing my business partner's firm. Nothing about the current page. Nothing about the entity she had open.
Completely. Blind.
what I tried first (and what broke)
First instinct: bake the matterId into the useChat transport config on the frontend.
Wrong move.
When my business partner navigates from one matter to another, the config changes — which recreates the entire chat client. Stream resets. Conversation gone. The UX was toast.
the fix that worked
Two changes. One server-side, one client-side.
Server side: parse matterId and contactId from the POST body, then build a context block before touching the system prompt:
let contextBlock = ""
if (matterId) {
contextBlock = (await buildMatterContext(matterId)) ?? ""
} else if (contactId) {
contextBlock = (await buildContactContext(contactId)) ?? ""
}
const finalSystem = contextBlock
? `${contextBlock}\n\n${systemPrompt}`
: systemPrompt
buildMatterContext queries the DB and returns a clean tagged block that Claude actually understands:
[MATTER CONTEXT]
Display name: [matter name]
Type: Probate
Status: Active
Primary contact: [primary contact]
Client(s): [client]
[/MATTER CONTEXT]
That block goes at the TOP of the system prompt, before anything else.
Null fields are omitted entirely — never write Primary contact: undefined into a model's context, it degrades the output.
Client side: don't bake matterId into useChat. Hold it in a ref, and read it at send time — when my business partner hits submit, grab the current value from the ref and include it in the POST body. Transport stays stable, context stays fresh, stream stays alive.
why this matters to me
This one stings a little, because it's so obvious in hindsight.
The user is ALWAYS looking at something — a record, a page, an entity. That thing is the most important context the agent could possibly have. And the default pattern (static system prompt) silently throws it all away.
The page the user is on IS part of the prompt. You just have to put it there.
Now when my business partner asks "what's the fee on this?", the agent already knows the fee arrangement, that the matter is active, probate type, and who the primary contact is.
That's an assistant.
The other thing was just a chat box with amnesia.