The AI SDK v6 introduced a new way to construct agents - the ToolLoopAgent class. This approach separates agent definition from agent invocation, making your code more modular and easier to distribute.
Instead of calling streamText() directly with tools, you create an agent instance first, then use createAgentUIStreamResponse() to invoke it. This might seem like extra steps, but it opens up possibilities for sharing agents across your codebase or even publishing them as packages.
Let's explore how this new syntax works and compare it to the traditional approach.
streamText()The system parameter has been renamed to instructions. This aligns better with OpenAI's terminology.
Additionally, instructions can accept an array of message parts, allowing you to structure your system prompt with different roles:
instructions: [{role: 'system',content: 'You are a helpful assistant...',},],
The default stopping condition has changed significantly.
| Approach | Default stepCount |
|---|---|
streamText() | 1 |
ToolLoopAgent | 20 |
By default, ToolLoopAgent acts more like a true agent, running up to 20 steps before stopping. You can customize this with the stopWhen parameter.
Notice the ToolLoopAgent instance that creates the agent once, passing in the model, instructions, and tools.
The InferAgentUIMessage type extracts message types directly from the agent instance, so you don't need to manually build the type from your tools.
The agent is passed to createAgentUIStreamResponse(), which handles streaming the agent's responses back to the client.
In client/root.tsx, the useChat hook is typed with the inferred MyAgentUIMessage type, maintaining the same ergonomics as the traditional approach.
If you wanted to invoke the agent outside of a POST request, you could use agent.generate() for one-shot text generation or agent.stream() for streaming responses.
Open your browser to localhost:3000 and try interacting with the agent. Try requesting something like "Create a todo.md file with three items for today" and watch as the agent uses its file system tools to fulfill the request. You'll see the tool calls displayed in the UI, showing exactly which tools the agent is using.
export type MyAgentUIMessage = InferAgentUIMessage<typeof agent>;
export const POST = async (req: Request): Promise<Response> => {const body: { messages: MyAgentUIMessage[] } =await req.json();const { messages } = body;return createAgentUIStreamResponse({agent,uiMessages: messages,});};
const { messages, sendMessage } = useChat<MyAgentUIMessage>({});