Agent citations + multi-turn conversations - #9
Merged
Conversation
query_documents already returns {answer, citations}. Previously the
agent looped the result back through modelTurn, letting the outer
model rewrite the answer and risk dropping or renumbering the [N]
citation markers relative to the emitted citations array.
- Add finalAnswerOverride to AgentState; toolDispatchNode detects a
cited-answer-shaped tool result and short-circuits straight to END
instead of another modelTurn iteration.
- Stream consumer emits the (previously dead) SSE event
alongside the tool's own answer text.
- Dedupe the 3x-duplicated TurnCompleted emission into one helper.
- Add spec coverage asserting exactly one generate() call on this path.
No schema, DTO, or frontend changes — useChatStream/chat page already
handled the event.
Neither chat endpoint remembered prior turns. This adds persistent, trimmed history to /v1/agent/chat (the only surface the frontend uses). - Prisma: Conversation + Message models, MessageRole enum. - ConversationsModule: create/list conversations, load+persist messages, ownership checks. GET /v1/conversations and GET /v1/conversations/:id/messages for future history reload. - trimHistory(): hybrid cap — last 20 messages, then ~3000 token budget within that window, always keeping at least the latest turn. - AgentChatDto.conversationId (optional); AgentController creates a conversation and emits `conversation_started` when omitted. - AgentService.run() seeds trimmed history into the graph's initial messages state, persists the user query immediately and the final assistant answer once produced. Nothing persisted on a turn that pauses for external_write confirmation. - Frontend: useChatStream now holds a messages[] thread + conversationId instead of single content/citations state; /chat renders a scrollable thread with a "New conversation" control. Scoped to /v1/agent/chat only; /v1/chat/stream stays single-turn.
- Escape apostrophe in chat/page.tsx JSX text - Add explicit parameter type to generate() mock in agent.service.spec.ts - Use custom generated client import path for Prisma namespace in conversations.service.ts
- Escape apostrophe in chat/page.tsx JSX text - Add explicit parameter type to generate() mock in agent.service.spec.ts - Use custom generated client import path for Prisma namespace in conversations.service.ts
- Escape apostrophe in chat/page.tsx JSX text - Add explicit parameter type to generate() mock in agent.service.spec.ts - Use custom generated client import path for Prisma namespace in conversations.service.ts
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes the two chat-surface gaps from the DevX review: citations were
silently dropped on /v1/agent/chat (the endpoint the frontend actually
uses), and neither chat endpoint retained conversation history.
What changed
Citations (short-circuit)
query_documents already returns {answer, citations} — the gap was
purely that AgentService never forwarded it. Rather than let the outer
model re-synthesize (risking marker/citation misalignment), a cited
tool result now short-circuits straight to a final answer and emits
the citations event the frontend was already built to render.
Multi-turn conversations
New Conversation/Message tables, trimmed history hydration into the
agent's message state, and a conversationId round-tripped via SSE
(conversation_started on first turn). History trimming is hybrid:
hard cap of 20 messages, then a ~3000-token budget trim within that,
always preserving the latest turn intact. Scoped to /v1/agent/chat only.
Frontend
/chat is now a real thread (message list + conversationId) instead of
a single-answer view; a "New conversation" control resets it.
Testing
agent.service.spec.ts: citation short-circuit (1 generate() callonly), history seeding, persistence across all exit paths (cited,
normal, max-iterations, confirmation-pause).
history.util.spec.ts: hard ceiling, token-budget trim, min-keepsafeguard.
npx prisma migrate dev --name add_conversationsagainst a local Postgres before
pnpm test.Manual verification checklist
prior context
and doesn't persist a phantom assistant message