What is ReAct?

First, we’re not talking about the ui library React.js - today we are learning about the ReAct agent pattern.

ReAct is really just a while() loop - it’s a looping structure where the model alternates between Reasoning, Acting, and observing. The way I think about it is that the model is iteratively thinking about the task, calling a tool, and ingesting the tool call results in order to build a high quality output. This pattern enables an agent to continually refine its plan as tools provide more context.

How can I build agents using the ReAct pattern?

Here’s a quick example that uses hashbrown (with Angular) to implement the react pattern using an agent.

First, let’s create a small TaskService

@Injectable({ providedIn: 'root' })
export class TaskService {
  private readonly _tasks = signal<string[]>([
    'Write ReAct blog post',
    'Ship hashbrown v0.4',
  ]);

  readonly tasks = computed(() => this._tasks());

  addTask(title: string) {
    this._tasks.update((tasks) => [...tasks, title]);
  }
}

Now, let’s build some tools to list and add tasks using my TaskService

import { inject } from '@angular/core';
import { createTool } from '@hashbrownai/angular';
import { s } from '@hashbrownai/core';
import { TaskService } from './task.service';

export const listTasks = createTool({
  name: 'listTasks',
  description: 'List the current user tasks',
  handler: () => {
    const tasks = inject(TaskService).tasks();
    return { tasks };
  },
});

export const addTaskTool = createTool({
  name: 'addTask',
  description: 'Add a new task to the user list',
  schema: s.object('Add task input', {
    title: s.string('Short description of the task to add'),
  }),
  handler: ({ title }) => {
    inject(TaskService).addTask(title);
    return { success: true };
  },
});

Next, let’s build our chat agent:

import { Component, signal } from '@angular/core';
import { chatResource } from '@hashbrownai/angular';
import { listTasks, addTaskTool } from './task.tools';

@Component({
  selector: 'app-agent-chat',
  imports: [Field],
  template: `
    <section class="chat">
      <h2>Task Agent (ReAct)</h2>

      <form (submit)="onSubmit($event)" class="chat-input">
        <label>
          <span>Ask the agent</span>
          <input
            type="text"
            [field]="chatInputForm.message"
            placeholder="Ask for help planning or updating your tasks"
          />
        </label>
        <button type="submit" [disabled]="!chatInputForm().valid()">
          Send
        </button>
      </form>

      <div class="messages">
        @for (message of chat.value(); track $index) {
          <p [class.user]="message.role === 'user'">
            {{ message.content }}
          </p>
        }
      </div>
    </section>
  `,
})
export class AgentChatComponent {
  chatInputModel = signal({
    message: '',
  });

  chatInputForm = form(this.chatInputModel);

  chat = chatResource({
    model: 'gpt-5',
    debugName: 'task-react-agent',
    tools: [listTasks, addTaskTool],
    system: `

You are a task-planning assistant that follows a Reason–Act–Observe loop.

REASON:
- Think step-by-step about what the user wants.
- Write your reasoning as short, clear natural language.

ACT:
- When you need data or should modify tasks, call a tool:
  - listTasks: to inspect current tasks.
  - addTask: to add a new task the user will see.

OBSERVE:
- After each tool call, look at the result and reconsider your plan before deciding what to do next.

Continue looping Reason, Act, and Observe until you can give a concise, helpful final answer to the user.
    `,
  });

  onSubmit(event: Event) {
    event.preventDefault();

    const f = this.chatInputForm();
    if (!f.valid()) return;

    const message = f.message().value.trim();
    if (!message) return;

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

Let’s review the code above.

  • First, I’m using Angular’s experimental signal forms. Ohhh nice.

  • We then define a new chatResource() that connects to the gippity 5 model and exposes two strongly typed tools: listTasks() and addTask().

  • The system message provides the instructions to the model to use the ReAct pattern.

  • Hashbrown loops over each tool call and send the results to the model.

  • Finally, the resource value() signal is updated as the model streams the text result into the template.

Tell me a funny joke about React and ReAct

React and ReAct are pair-programming.

React says, “I render components.”

ReAct says, “I render decisions.”

React sighs. “Can you just pick a button color?”

ReAct pauses, reasons for eight steps, calls a tool, observes the result, and finally says:

“I’ve concluded the real button was the friends we made along the way.”

(and, yes, I asked gippity to write this one for me)

Bites

Keep Reading

No posts found