hashbrown

Hashbrown v0.6 speaks AG-UI end to end

Today we are releasing Hashbrown v0.6.

It's a breaking release: every Hashbrown app needs to update its server endpoint. The docs have a step-by-step migration guide, and this post ends with a prompt you can hand to an AI coding assistant to make most of the edits.

Here's the short version. In v0.5, Hashbrown talked to your server using its own binary frame protocol. In v0.6, Hashbrown speaks AG-UI, the open protocol for connecting agents to user interfaces. Your server receives an AG-UI run and streams AG-UI events back.

Release at a glance

  • AG-UI end to end: the client, the HTTP transport and all six provider adapters speak AG-UI events.
  • Interrupt and resume, for human-in-the-loop approvals from an agent server.
  • Shared agent state and message history, synchronized over AG-UI.
  • Server-executed tool calls on assistant messages, with arguments that resolve while they stream.
  • New streaming JSON and Markdown parsers under the hood.
  • A new Migrations section in the docs, with a guide for every breaking change.

Why AG-UI?

When we started Hashbrown, the client and the server were both ours. We shipped the provider adapters, and they spoke to the client in a compact binary format. That worked well, as long as your server was one of our adapters.

More and more, the model sits behind an agent framework: LangGraph, an internal orchestration service, or something like B4. Many of those can already stream AG-UI. With our own protocol in the middle, you needed an adapter layer just to get the agent's output into Hashbrown.

So, we made AG-UI the only way in. Hashbrown's HTTP transport posts an AG-UI RunAgentInput to /run and parses AG-UI server-sent events. Our provider adapters for OpenAI, Anthropic, Azure, Bedrock, Google and Ollama return AG-UI events. And any AG-UI server can drive a Hashbrown chat directly. For structured output and generative UI, the server also needs to honor the schema Hashbrown sends with the run.

The trade-off is that the old frame protocol is gone, and there's no compatibility layer. Keeping it would have meant detecting the protocol on every run and carrying compatibility code in every adapter. One contract is simpler to build on and simpler to maintain.


Interrupt and Resume

Some agent runs need a human before they can continue. Approving a payment. Confirming a destructive action. Choosing between two plans.

AG-UI models this as an interrupt: the run finishes with an interrupt outcome, and a later run resumes it with the user's decision. Hashbrown v0.6 surfaces pending interrupts on your chat and lets you resume them, so you can build approval UIs on top of any AG-UI agent.

Read Interrupt and Resume for React or for Angular to get started.


Shared Agent State

Agents often keep state that your UI wants to show: a plan, a draft, a set of selected records.

Hashbrown v0.6 synchronizes AG-UI state snapshots and deltas, along with message history, so your components can read the agent's state directly instead of parsing it out of chat messages. See Shared Agent State for React or for Angular.


Server-Executed Tool Calls

Until now, a Hashbrown message only listed tool calls for tools you registered in the browser. When the agent server ran its own tools, those calls were invisible to your UI.

In v0.6, they show up on the assistant message as serverToolCalls. And because the arguments resolve as they stream, you can paint from them before the tool even runs.

export function Answer({ message }: { message: UiAssistantMessage<never> }) {
  return (
    <>
      {message.serverToolCalls?.map((call) =>
        call.name === 'render' && call.args ? (
          <Draft key={call.toolCallId} args={call.args} />
        ) : null,
      )}
      {message.ui}
    </>
  );
}

A few things to note:

  • Each server tool call has a name, a toolCallId, the args resolved so far, and a status.
  • The status is inProgress while arguments stream, executing once they're complete, and complete when the server reports the result.
  • Hashbrown never runs these calls. They're for display only.

Our Invoicing example uses this to render the assistant's answer from its render tool while the model is still writing it. The table and customer card appear as the arguments arrive, and the same components settle into the final answer once the server validates them.

Read more in the React or Angular tools docs.


New Parsers Under the Hood

Hashbrown's streaming JSON parser and Magic Text now run on Cacheplane's streaming parsers. The APIs you use, such as useJsonParser, injectJsonParser, MagicTextRenderer and MagicText, work as before.

What changed is the low-level surface. @hashbrownai/core no longer exports its internal Markdown and JSON parser helpers. If you imported those directly, the migration guide shows the replacement.


Breaking Changes

Here's what changed, in the order most apps will hit it:

  1. Your server must speak AG-UI. Adapters take an AG-UI RunAgentInput and return AG-UI events. Your route encodes those events as server-sent events.
  2. Model selection moved to the server. Hooks and resources no longer accept model.
  3. Structured output controls are gone. Remove structuredOutput and emulateStructuredOutput. Your schema still drives streaming and validation.
  4. Thread loading and saving are gone. Persist messages in your own data layer and hydrate the chat with them.
  5. Custom transports and providers use AG-UI. The frame API is removed.
  6. Core no longer exports parser internals for Markdown and JSON.
  7. The Writer provider was removed.

Most React and Angular apps only need the first three.


Upgrading Your Server

Let's take a look at the change every app needs.

Before:

app.post('/chat', async (req, res) => {
  const stream = HashbrownOpenAI.stream.text({
    apiKey: process.env.OPENAI_API_KEY!,
    request: req.body,
  });

  res.header('Content-Type', 'application/octet-stream');

  for await (const chunk of stream) {
    res.write(chunk);
  }

  res.end();
});

After:

// imports omitted for brevity

app.post('/run', async (req, res) => {
  const abortController = new AbortController();
  res.once('close', () => abortController.abort());

  const stream = HashbrownOpenAI.stream.text({
    apiKey: process.env.OPENAI_API_KEY!,
    model: 'gpt-5-mini',
    input: req.body as RunAgentInput,
    signal: abortController.signal,
  });
  const encoder = new EventEncoder();

  res.header('Content-Type', encoder.getContentType());
  res.flushHeaders();

  for await (const event of stream) {
    res.write(encoder.encodeSSE(event));
  }

  res.end();
});

A few things to note:

  • The route moves to POST /run, which is where the client posts by default.
  • The request body is an AG-UI RunAgentInput, passed to the adapter as input.
  • The model lives on the server now.
  • Each event is encoded as a server-sent event with EventEncoder from @ag-ui/encoder.
  • The abort signal cancels the provider call when the user stops the run or closes the page.

The full guide covers the client side, thread persistence, custom transports and everything else, one step at a time. Start with Upgrade to v0.6 for React or for Angular.


Let an AI Assistant Do the Upgrade

Most of this upgrade is mechanical: find the server route, swap the adapter options, move the model to the server, and delete a few client options. That's a good job for an AI coding assistant like Claude Code.

So, here's a prompt. Paste it into your assistant from the root of your repository. It inventories your code first and shows you a plan before it touches anything:

Upgrade this repository from Hashbrown v0.5 to v0.6. The full guide is at https://hashbrown.dev/docs/react/migrations/v0-6 (React) and https://hashbrown.dev/docs/angular/migrations/v0-6 (Angular). Read the guide for my framework before changing anything, and do not invent APIs that are not in it.

First, inventory. Search the repository and list every place that:

1. Imports from any `@hashbrownai/*` package, with its current version.
2. Calls a provider adapter such as `HashbrownOpenAI.stream.text(...)` on the server, and the HTTP route that calls it.
3. Passes `model`, `structuredOutput` or `emulateStructuredOutput` to a Hashbrown hook, resource, provider or runtime.
4. Reads `isLoadingThread`, `isSavingThread`, `threadLoadError` or `threadSaveError`, or passes `loadThread` / `saveThread` to an adapter.
5. Uses `fryHashbrown`, the `Hashbrown` type, `.sizzle()`, or direct core `apiUrl` / `middleware` options.
6. Implements a custom transport or provider, or uses `encodeFrame`, `decodeFrames` or `Frame`.
7. Imports Magic Text or JSON parser helpers from `@hashbrownai/core`, such as `createMagicTextParserState`, `parseMagicTextChunk`, `finalizeMagicText` or `JsonAstNode`.
8. Uses the Writer provider package, or a tool named `output`.

Show me the inventory and a short plan before editing.

Then make these changes, only where the inventory found something:

- Update every `@hashbrownai/*` package to exactly 0.6.0, together. On the server, also install `@ag-ui/core` and `@ag-ui/encoder`.
- Rewrite each server route to accept an AG-UI `RunAgentInput` body, pass it to the adapter as `input` along with the provider `model` and an abort `signal`, and stream the returned events as server-sent events with `EventEncoder` from `@ag-ui/encoder`. Hashbrown's client posts to `/run` by default; either move the route there or point the client at it.
- Remove `model` from the client and set it on the server adapter. Replace `model: experimental_local(...)` with `transport: experimental_local(...)`.
- Remove `structuredOutput` and `emulateStructuredOutput`. Keep `schema`; the server adapter now owns provider-specific enforcement.
- Remove thread loading and saving state. Load saved messages yourself and pass them to the hook or resource (`messages` or `setMessages(...)`), and save them through the app's own data layer.
- If core is used directly: `fryHashbrown` becomes `createChatRuntime`, `Hashbrown` becomes `ChatRuntime`, `.sizzle()` becomes `.start()`, and a custom endpoint moves into `transport: createHttpTransport({ baseUrl, middleware })`.
- Custom transports receive a `TransportRequest` whose `input` is required and has no `params`, and return AG-UI events. Custom providers map their SDK stream to AG-UI events; delete any frame encoding.
- Replace Magic Text parser helpers from core with `createPartialMarkdownParser` from `@cacheplane/partial-markdown`, or with the framework's Magic Text component. Keep using the framework JSON parser hooks or functions.
- Replace the Writer provider with another supported provider.

Finally, run the project's type check, build, lint and tests, fix what fails, and summarize every file you changed and anything you could not migrate automatically.

Be sure to review the inventory and the plan before you let it continue. Custom transports and providers are the part most likely to need your judgment.


Conclusion

Moving to AG-UI lets you put Hashbrown in front of any agent server that speaks AG-UI, and it's what made interrupts, shared state and server-executed tool calls possible.

The new Migrations section of the docs walks through every change for React and Angular, and the prompt above handles the mechanical edits.

Have questions? Hit a snag while upgrading? Open an issue on GitHub and let me know!