DEV Community

Christian Bromann
Christian Bromann

Posted on

Build Better Agent UX: Streaming Progress, Status, and File Ops with LangChain

If you’ve built an agent UI before, you know the uncomfortable truth: most “progress” indicators are vibes.

A spinner means something is happening… probably.

But users don’t need theater — they need truth: what’s running right now, how far along it is, and what the agent is doing to their filesystem.

In this short video, I show how to stream custom, TypeScript-safe events from a LangGraph tool call directly into a React UI.

🎥 Video: https://youtu.be/3daSUNpWErQ

What you’ll build

A simple pattern:

1) Your tool emits events (progress/status/file ops) while it runs

2) The frontend subscribes and renders those events immediately

3) Type guards keep the UI logic safe and predictable

No polling loops. No guessing. No “thinking…” placeholders.

1) Emit typed custom events from a tool call

Inside the tool call, write custom events as the work progresses:

config.writer?.({
  type: "progress",
  id: analysisId,          // stable id => update in place
  step: steps[i].step,
  message: steps[i].message,
  progress: Math.round(((i + 1) / steps.length) * 100),
  totalSteps: steps.length,
  currentStep: i + 1,
  toolCall: config.toolCall,
} satisfies ProgressData);
Enter fullscreen mode Exit fullscreen mode

This is the key shift: tools aren’t just functions — they’re event producers.

2) Receive those events in React

In the UI, pass a handler into the stream hook:

onCustomEvent: handleCustomEvent,
Enter fullscreen mode Exit fullscreen mode

Now every event emitted by your tool arrives in the client as it happens.

3) Narrow event types and update UI state predictably

Treat incoming events as unknown, then narrow with type guards and update state maps keyed by id:

if (isProgressData(data)) { /* update progress */ }
else if (isStatusData(data)) { /* update status */ }
else if (isFileStatusData(data)) { /* update file ops */ }
Enter fullscreen mode Exit fullscreen mode

This keeps the frontend stable:

  • progress updates in place
  • minimal re-renders
  • no stringly-typed event spaghetti

Why it matters (beyond “nice UI”)

When the UI reflects real execution:

  • users trust the agent more
  • debugging becomes dramatically easier
  • failures are understandable without digging through logs
  • you can build better UX: step indicators, timelines, file operation feeds, etc.

If you build something with this pattern, I’d love to see it — share a screenshot or link in the comments.

Top comments (0)