home / skills / sstobo / convex-skills / convex-agents-streaming
This skill enables real-time, non-blocking streaming of agent responses to UIs, improving responsiveness and multi-client collaboration.
npx playbooks add skill sstobo/convex-skills --skill convex-agents-streamingReview the files below or copy the command above to add this skill to your agents.
---
name: "Convex Agents Streaming"
description: "Streams agent responses in real-time to clients without blocking. Use this for responsive UIs, long-running generations, and asynchronous streaming to multiple clients."
---
## Purpose
Streaming allows responses to appear character-by-character in real-time, improving UX and perceived performance. Supports async streaming and multiple clients.
## When to Use This Skill
- Building real-time chat interfaces with live updates
- Generating long responses that benefit from progressive display
- Streaming to multiple clients from single generation
- Using asynchronous streaming in background actions
- Implementing smooth text animation
## Basic Async Streaming
Stream and save deltas to database:
```typescript
export const streamResponse = action({
args: { threadId: v.string(), prompt: v.string() },
handler: async (ctx, { threadId, prompt }) => {
const { thread } = await myAgent.continueThread(ctx, { threadId });
await thread.streamText(
{ prompt },
{ saveStreamDeltas: true }
);
return { success: true };
},
});
```
## Configure Stream Chunking
```typescript
await thread.streamText(
{ prompt },
{
saveStreamDeltas: {
chunking: "line", // "word" | "line" | regex | function
throttleMs: 500, // Save deltas every 500ms
},
}
);
```
## Retrieve Stream Deltas
```typescript
import { vStreamArgs, syncStreams } from "@convex-dev/agent";
export const listMessagesWithStreams = query({
args: {
threadId: v.string(),
paginationOpts: paginationOptsValidator,
streamArgs: vStreamArgs,
},
handler: async (ctx, { threadId, paginationOpts, streamArgs }) => {
const messages = await listUIMessages(ctx, components.agent, {
threadId,
paginationOpts,
});
const streams = await syncStreams(ctx, components.agent, {
threadId,
streamArgs,
});
return { ...messages, streams };
},
});
```
## Display Streaming in React
```typescript
import { useUIMessages, useSmoothText } from "@convex-dev/agent/react";
function ChatStreaming({ threadId }: { threadId: string }) {
const { results } = useUIMessages(
api.streaming.listMessages,
{ threadId },
{ initialNumItems: 20, stream: true }
);
return (
<div>
{results?.map((message) => (
<StreamingMessage key={message.key} message={message} />
))}
</div>
);
}
function StreamingMessage({ message }: { message: UIMessage }) {
const [visibleText] = useSmoothText(message.text, {
startStreaming: message.status === "streaming",
});
return <div>{visibleText}</div>;
}
```
## Key Principles
- **Asynchronous streaming**: Best for background generations
- **Delta throttling**: Balances responsiveness with write volume
- **Stream status**: Check `message.status === "streaming"`
- **Smooth animation**: Use `useSmoothText` for text updates
- **Persistence**: Deltas survive page reloads
## Next Steps
- See **messages** for message management
- See **fundamentals** for agent setup
- See **context** for streaming-aware context
This skill streams agent responses in real-time to clients without blocking, enabling responsive UIs and progressive content display. It supports asynchronous background generations, configurable delta chunking, and broadcasting a single generation to multiple clients. Use it to improve perceived performance and maintain persistence across reloads.
The skill continues an agent thread and emits text deltas as the model generates, optionally saving those deltas to a database. You can configure chunking (by word, line, regex, or custom function) and throttle saves to balance responsiveness and write volume. Clients subscribe to stream deltas and render updates live; stream status flags (e.g., message.status === "streaming") indicate active streams.
How do I persist partial outputs so users can refresh and still see progress?
Enable saveStreamDeltas when calling streamText so deltas are stored and can be synchronized later with syncStreams.
How do I avoid excessive database writes from many tiny deltas?
Configure chunking (e.g., "line" or a regex) and set throttleMs to batch deltas at a reasonable interval.