Concepts: Chats
A chat is the durable record of one human conversing with one agent. The dh_chats row holds the metadata; its id doubles as the LangGraph thread_id, so the conversational state is recoverable across reconnects.
Lifecycle
- The operator opens an agent and clicks New chat.
- The first user message creates a
dh_chatsrow withuser_id= the bound user,agent_name= the agent's name, andstatus=active. - The console establishes an SSE stream against
POST /v1/agents/{agent}/streamwith the chat_id passed through. Tokens stream into the message list; tool-call events render as inline collapsible cards. - On disconnect or completion, the LangGraph checkpointer persists the turn. Subsequent fetches of
GET /v1/chats/{chat_id}/messagesreconstruct the conversation byte-for-byte.
Visibility
dh_chats has a dedicated chat_visibility RLS policy combining tenant, user, and scope:
- The chat's owner always sees their own chats.
- A caller holding
conversations:read_otherssees every chat in the tenant. - Everyone else sees only their own.
The console surfaces this on the /chats page. Without conversations:read_others, the user-id filter is hidden. With it, an operator picks another user from the dropdown to inspect their chats.
Note: A caller without
conversations:read_otherswho navigates directly to/chats/<someone-elses-id>receives a 404 fromGET /v1/chats/{chat_id}. The page renders the "Chat not found" empty state. This is intentional — 404 does not leak existence.
History replay
GET /v1/chats/{chat_id}/messages?limit=50&cursor=... paginates the messages in chronological order. Each message has a stable shape:
{
"id": "msg_abc",
"role": "user",
"content": "...",
"tool_calls": [{ "name": "find_people", "args": {}, "result": {} }],
"created_at": "2026-05-18T14:23:11Z"
}The shape matches the AG-UI messages emitted by the live SSE endpoint — assistant turns carry tool_calls, and each tool result is a following role: "tool" message. The same React component renders both — there is no visual gap between a freshly-streamed turn and one replayed from history.
Archive and retention
Set status = archived to flag a chat as no longer needed. The chat is hidden from the default list, but the LangGraph checkpoints remain available until the tenant's langgraph_checkpoint_retention_days window elapses, at which point the worker drops them.