Hashbrown v0.4, with a peek around the corner

In case you missed it, we released Hashbrown v0.4 before the holiday season. It’s stuffed with new primitives we hope will help y’all out when building browser agents. Key features include streaming Markdown support for Angular, experimental support for local models in Chrome and Edge, persistence and hydration for threads, and support for Ollama, AWS Bedrock, and Anthropic.

A key initiative we’re exploring in Hashbrown v0.4 is reframing the project as a browser-based agent framework. Inspired by agentic backend frameworks like LangGraph, Mastra, and Pydantic AI, Hashbrown is refocusing on building core agent building blocks like tool calling, structured outputs, generative UI, threads, shared state, and agent composition, with the key differentiator being that these all work entirely in the frontend.

Stepping back, if you look at Hashbrown’s set of APIs today, we already support most of these capabilities as of v0.4, with the exception of shared state and agent composition. We’ve already written up our plans around shared state with the support of lenses, but we haven’t shared our plans around agent composition.

What is an Agent?

In Hashbrown, we define an agent as a combination of a system instruction, tools, and an output format. It operates on a thread to generate messages, using tools to achieve its task until it generates a response that conforms to its output format:

Agents are defined by their system instructions, tools, and output format

On their own, agents work best with well-scoped objectives. Terse system instructions, fewer than 50 tools, and a compact output format are the keys to creating agents with predictable, high-quality results. In reality, however, developers are tasked with creating agents that must achieve complex objectives.

Agent composition, then, is the ability to chain agents together to achieve these complex objectives. As the developer, you break down a complex task into a concrete series of smaller goals that cumulatively lead to the desired output.

There are two types of agent composition we are considering: chaining agent outputs and leveraging agents as tools.

Chaining Agent Outputs

The first is chaining outputs of agents, so that top-level agents can make abstract decisions, leaving it up to subagents to drive concrete results:

Chaining agent outputs

Let me share a little preview of this concept, which we are calling our Fast Food Nutrition sample app. It’s not very pretty right now (as it’s a work in progress!), but type in a prompt like “chicken items across major fast food chains” and hit the arrow button. A research agent works against a source of truth to find nutritional facts, then composes that research with multiple subagents to generate a rich document, complete with citations, visualizations, and generative UI. This sample app is in the Hashbrown monorepo if you want to dig into the code, and we’ll be polishing it ahead of v0.5.

Agents as Tools

The second type of agent composition we are considering is the ability for one agent to call another agent as part of its tool call flow:

Agents calling agents

This is where the Hashbrown starts to get a little burnt, because doing this kind of agent composition is really hard with the ergonomics of Hashbrown’s current APIs. We actually showed this off when Brian and I participated in CodeTV’s Web Dev Challenge, where the agent could write code in the JavaScript runtime that called another agent to validate rules against song lyrics. To achieve that, Brian and I had to reach for internal APIs.

In v0.5, we plan to revamp our API to enable this kind of composition. The missing piece is being able to define an agent separately from Hashbrown’s existing React hooks or Angular resources.

Let me show you some pseudocode to give you an idea of what this might look like.

First, you’ll be able to define an agent (system instruction, tools, output format) in isolation from how it will be consumed:

class MyComponent {
  agent = createAgent({
    name: 'fast-food-researcher',
    instructions: `
      You are a fast food nutrition researcher...
    `,
    tools: [
      searchFastFoodMenuTool,
      getFastFoodRestaurantsTool,
    ],
    outputFormat: FAST_FOOD_FACTS_SCHEMA
  });
}

From there, you can get into familiar Hashbrown territory by using the agent to create a chat experience:

class MyComponent {
  chat = chatResource({
    threadId: 'some-thread-id',
    agent: this.agent,
  });

  sendMessage(message: string) {
    this.chat.sendMessage({
      role: 'user',
      content: message,
    });
  }
}

Or you can use it to create a completion experience:

class MyComponent {
  prompt = signal('chicken items across chains');
  completion = completionResource({
    input: this.prompt,
    agent: this.agent,
  })
}

The key idea here is that we are divorcing the definition of an agent from the stateful consumption of the agent. In other words, you define the logic of an agent, and then you connect it to your Angular components through resources or to your React components through hooks. This also detangles threads and their persistence from the invocation of an agent.

The big unlock with this approach is the ability to invoke an agent programmatically:

class MyComponent {
  doResearch(prompt: string) {
    return this.agent.run(prompt);
  }
}

The Agent#run(…) method will return an RxJS observable that emits a stream of the agent’s output. This will allow you to define Hashbrown tools that themselves use agents:

createTool({
  name: 'fastFoodResearch',
  description: 'Researches fast food nutrition',
  args: s.string('The research prompt'),
  handler: prompt => {
    return lastValueFrom(
      this.agent.run(prompt)
    );
  },
});

This is going to require a significant rewrite of Hashbrown’s internal state management, which, thanks to an extended holiday break, is already well underway. Getting to leverage RxJS as a direct dependency is going to simplify these internals, while affording simpler integration with frameworks. For example, in Angular, we’ll be able to leverage the built-in RxJS-to-Signal interop package to connect Hashbrown’s agents to Angular’s change detection. In React, we’ll be adding observable-hooks to expose Hashbrown agent outputs to React’s form of change detection.

Agent composition is that last major building block we’ll need to call Hashbrown a true agent framework. This building block will be especially important as developers start leveraging small language models, like those available in browsers via the Prompt API, whose capabilities are even more limited than those of large language models. It gives developers the ability to coordinate multiple agents to produce complex outputs, which will be necessary for creating complex generative user interfaces.

As always, we love biting off more potato than we can chew. If you’d like to get involved in this refactor, shoot me an email at [email protected], and I’ll happily get you onboarded into the Hashbrown Slack. Once we’ve got a proof of concept ready, we’ll be looking for folks to try out the new APIs and provide us with feedback.

Bites

  • FastMCP’s Contributing guidelines are among the most succinct we’ve seen on how to contribute to open source in the age of coding agents. We’ve seen a few PRs to Hashbrown that were clearly generated, creating a review burden for us. I suspect we’ll adopt something similar in the future.

  • jax-js really caught our interest this week. It brings fast array computation to the browser, powered by WebAssembly and WebGPU. The demo of machine learning in the browser with full HMR support is especially interesting.

  • Adam Wathan, creator of TailwindCSS, left a somber comment on a recent Tailwind PR. It raises the question: What’s the value of an abstraction layer like Tailwind Components in an era where developers can generate code just as quickly as they learn the abstraction?

  • Codex recently added support for Skills, a concept pioneered by Anthropic’s Claude Code. I think skills are a major advancement in coding agent workflows, enabling teams to collaborate and share common agent patterns.

Keep Reading

No posts found