Skip to content

Conversation

@VaguelySerious
Copy link
Member

@VaguelySerious VaguelySerious commented Jan 16, 2026

This makes multi-turn session the default for the flight booking app.

The docs PR vercel/workflow#759 also publishes the page on how to do chat session modeling, which includes example code snippets from this PR.

Critically, this PR also removes all localStorage use for persistence, with the exception of the last run ID. The durable streams serve as the main durability and storage. To do this, we also specifically persist user messages inside the stream, which wasn't done previously

Also see vercel/workflow#739

Screenshots

image

Signed-off-by: Peter Wielander <[email protected]>
Signed-off-by: Peter Wielander <[email protected]>
Signed-off-by: Peter Wielander <[email protected]>
Signed-off-by: Peter Wielander <[email protected]>
Signed-off-by: Peter Wielander <[email protected]>
Signed-off-by: Peter Wielander <[email protected]>
() =>
new WorkflowChatTransport({
api: '/api/chat',
onChatSendMessage: (response) => {
Copy link
Contributor

@vercel vercel bot Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The useChat hook receives resume: false on first render even when a stored session ID exists in localStorage, causing it to start a fresh connection instead of resuming the existing session.

Fix on Vercel

Comment on lines 34 to 35
await writer.write({ type: 'finish' });
await writer.close();
Copy link
Contributor

@vercel vercel bot Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The writeStreamClose function acquires a WritableStream writer lock but fails to release it in a try-finally block, and is missing the writer.close() call, causing a resource leak if write() fails

Fix on Vercel

});

// Update messages with the agent's response
messages.push(...result.messages.slice(messages.length));
Copy link
Contributor

@vercel vercel bot Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agent responses not properly added to messages on subsequent conversation turns due to incorrect array slicing logic

Fix on Vercel

{ params }: { params: Promise<{ id: string }> }
) {
const { id: runId } = await params;
const { message } = await req.json();
Copy link
Contributor

@vercel vercel bot Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

POST /api/chat/[id] lacks input validation for message field and runId parameter, causing client errors to return 500 instead of 400, exposing internal error details

Fix on Vercel

Signed-off-by: Peter Wielander <[email protected]>
VaguelySerious and others added 2 commits January 16, 2026 21:51
Signed-off-by: Peter Wielander <[email protected]>
…g added to conversation history in multi-turn conversations

Co-authored-by: VaguelySerious <[email protected]>
const { message: followUp } = await hook;

// Check for session end signal
if (followUp === '/done') {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like hookPayload.done === true would be cleaner than a magic string, no?

Comment on lines +184 to +202
// All user messages come from stream markers (data-workflow chunks).
// No deduplication needed - single source of truth.
const messages = useMemo(() => {
const result: UIMessage<TMetadata, UIDataTypes>[] = [];

for (const msg of rawMessages) {
// Skip user messages from useChat (we only use stream markers)
if (msg.role === 'user') {
continue;
}

if (msg.role === 'assistant') {
// Process parts in order, extracting user messages from markers
let currentAssistantParts: typeof msg.parts = [];
let partIndex = 0;

for (const part of msg.parts) {
if (isUserMessageMarker(part)) {
const data = part.data;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait, I'm confused, I thought Variant B didn't use the data markers? How come we can't write msg.role === 'user' chunks directly to the stream?

@VaguelySerious VaguelySerious changed the base branch from main to peter/multi-turn-3 January 20, 2026 21:29
@VaguelySerious VaguelySerious changed the base branch from peter/multi-turn-3 to main January 21, 2026 17:56
@VaguelySerious VaguelySerious changed the title [flight-booking-agent] Multi-turn chat and message injection (variant B) [flight-booking-agent] Multi-turn chat and message injection Jan 21, 2026
@VaguelySerious
Copy link
Member Author

@TooTallNate Messed up slightly - I had a Variant B that I liked but apparently it didn't work as intended and Claude went back to Variant A, essentially.

Since the Docs PR and this PR are aligned in functionality and seem to work well, can you re-review with the idea that we'll merge both, and I'll then follow-up with a smaller polish PR improving on this setup so inject user messages as UIMessageChunks, which requires some changes to DurableAgent ?

@VaguelySerious VaguelySerious merged commit 5044158 into main Jan 23, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants