Durable stream agent with background execution and live UI#1
Open
KyleAMathews wants to merge 5 commits into
Open
Durable stream agent with background execution and live UI#1KyleAMathews wants to merge 5 commits into
KyleAMathews wants to merge 5 commits into
Conversation
Wire Vercel AI SDK ToolLoopAgent to run as a BullMQ background job, capturing all events into a durable state stream. React client subscribes via StreamDB and renders a live timeline. - OutboundBridge maps AI SDK fullStream to typed state events (runs, steps, texts, textDeltas, toolCalls, errors, inbox) - POST /api/agent creates stream + enqueues job - POST /api/agent/:runId/message for follow-up messages - Conversation history reconstructed from stream (source of truth) - Per-collection useLiveQuery for incremental React updates - Timeline builder handles materialized state (catch-up + live) - Sidebar with run list, expandable tool call cards Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Simplifier: - Remove unused createTimelineQuery + dead imports from timeline.ts - Simplify row building in AgentChat with makeRow helper - Remove unnecessary try/catch re-throw in worker - Fix indentation in test file Review fixes: - Fix hostname mismatch: use 127.0.0.1 instead of localhost in use-agent-db - Add input validation on /api/agent and /api/agent/:runId/message - Add res.ok checks and catch blocks in handleNewRun/handleFollowUp - Add .catch() on preload() to surface connection errors - Narrow eval catch to SyntaxError only in execute_javascript tool Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
RunView reports isWorking state up to parent via onWorkingChange callback. Input and send button disabled while agent is active, preventing follow-up messages from colliding with in-flight events. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The disabled prop on the input prevents UI interaction, but handleSubmit's early return only checked submitting. Add agentWorking to the guard so programmatic/keyboard submissions are also blocked during active runs. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Screen.Recording.2026-03-18.at.4.51.26.PM.mp4
Demo of a Vercel AI SDK agent running as a BullMQ background job, with all events captured into a durable state stream. A React client live-tails the stream via StreamDB and renders an incremental timeline — text streaming in token by token, tool calls appearing with expandable args/results, follow-up messages continuing the conversation.
Built as a prototype for Runable, who need to move long-running AI agents (2-5 hours, 50k concurrent streams) out of the HTTP request and into background workers with durable, resumable event streams.
Approach
The stream is the source of truth. Every agent lifecycle event (run start/end, step start/end, text deltas, tool calls) is written to a durable stream as typed state events following the State Protocol. The client materializes these into TanStack DB collections and renders reactively.
Key design decisions:
OutboundBridge adapter — maps AI SDK `fullStream` events to typed `ChangeEvent` writes using schema helpers (`schema.runs.insert()`, etc.).
Multi-collection schema — 7 entity types (runs, steps, texts, textDeltas, toolCalls, errors, inbox) with insert/update lifecycle semantics.
Per-collection live queries — `useLiveQuery` with direct collection references (not `createLiveQueryCollection` derived queries) for reliable incremental updates. Collections are merged into `TimelineRow[]` in JS and sorted by `_seq`.
Stream as conversation memory — follow-up messages are written to the same stream. The worker reconstructs conversation history by reading the stream's inbox and text_delta events, then passes the full message array to the agent.
Key invariants
Verification
Files changed
🤖 Generated with Claude Code