Repository navigation
Asynchronous Report Generation #660
Description
I understand you want to make report generation asynchronous so users can interact with other parts of the system while reports are being generated. Currently, the submit server action in app/actions.tsx processes requests synchronously, blocking until all agent work completes before returning 1 .
Current Blocking Behavior
The system uses streaming primitives for progressive UI rendering, but the server action itself is synchronous:
- The
submitfunction initializes streaming state but waits for all processing to complete 1 - The researcher agent runs in a synchronous loop until a non-empty response is generated 2
- Resolution search processing is called synchronously 3
- The function only returns after all work is done 4
Required Changes for Asynchronous Processing
To make report generation truly asynchronous, you would need to implement a background job queue system:
1. Job Queue Infrastructure
- Implement a job queue (e.g., Bull, Agenda, or database-backed queue)
- Create a separate worker process to handle report generation jobs
- Store job state in a database for persistence
2. Modified Submit Flow
// Instead of blocking, return immediately with job ID
async function submit(formData?: FormData, skip?: boolean) {
const jobId = nanoid()
// Enqueue the job for background processing
await queue.add('report-generation', {
jobId,
formData,
skip
})
// Return immediately with job ID
return {
jobId,
status: 'processing'
}
}3. Status Checking Mechanism
- Create a new server action to check job status
- Implement polling or websocket updates on the client
- Stream partial results as they become available from the worker
4. Worker Process
- Move the current
processEvents()andprocessResolutionSearch()logic to a worker - Update AIState progressively as results are generated
- Store intermediate results in the database
Notes
This is a significant architectural change that requires implementing a job queue system, worker processes, and status tracking infrastructure. The current streaming approach in app/actions.tsx provides progressive UI updates but still blocks the server action. True asynchronous processing would require background job processing with a queue system like Bull or a database-backed solution. The existing optimizations in OPTIMIZATION_SUMMARY.md focus on reducing re-renders and caching, not on making the actual generation process non-blocking.
Wiki pages you might want to explore:
Citations
File: app/actions.tsx (L36-42)
async function submit(formData?: FormData, skip?: boolean) {
'use server'
const aiState = getMutableAIState<typeof AI>()
const uiStream = createStreamableUI()
const isGenerating = createStreamableValue(true)
const isCollapsed = createStreamableValue(false)File: app/actions.tsx (L223-223)
processResolutionSearch();File: app/actions.tsx (L516-550)
while (
useSpecificAPI
? answer.length === 0
: answer.length === 0 && !errorOccurred
) {
const { fullResponse, hasError, toolResponses } = await researcher(
currentSystemPrompt,
uiStream,
streamText,
messages,
mapProvider,
useSpecificAPI,
drawnFeatures
)
answer = fullResponse
toolOutputs = toolResponses
errorOccurred = hasError
if (toolOutputs.length > 0) {
toolOutputs.map(output => {
aiState.update({
...aiState.get(),
messages: [
...aiState.get().messages,
{
id: groupeId,
role: 'tool',
content: JSON.stringify(output.result),
name: output.toolName,
type: 'tool'
}
]
})
})
}File: app/actions.tsx (L619-624)
return {
id: nanoid(),
isGenerating: isGenerating.value,
component: uiStream.value,
isCollapsed: isCollapsed.value
}
Activity
ngoiyaeric commentedon Jun 8, 2026
@coderabbitai plan https://vercel.com/docs/queues perhaps use these Queues or use semaphores in typesrcipt
coderabbitai commentedon Jun 8, 2026
Coding Plan
Summary
Design Choices
Design Choice 1: Queue technology selection
Options Considered:
Chosen Option: 3
Rationale: Simple database polling is selected because it requires no additional infrastructure beyond the existing Postgres database, aligns with the codebase's simplicity-first approach, and can be enhanced later if needed.
Design Choice 2: Client update mechanism
Options Considered:
Chosen Option: 1
Rationale: Polling is selected for simplicity and compatibility with serverless deployments; the polling interval can be tuned for responsiveness.
Design Choice 3: Worker deployment model
Options Considered:
Chosen Option: 3
Rationale: A hybrid approach where the worker logic is encapsulated in a module that can be invoked either by a cron-triggered API route or a standalone process, providing deployment flexibility.
💡 User Tips
Regenerate the plan with different choices with
@coderabbitai <feedback>.Implementation Steps
Phase 1: Job Queue Database Infrastructure
This phase establishes the persistence layer for tracking asynchronous jobs, including their state, progress, and results. The jobs table will serve as both the queue and the status store.
Task 1: Create Jobs Table Schema
Add a new database table to track job state, progress, and results for asynchronous report generation.
jobstable definition inlib/db/schema.tswith columns:id(uuid primary key),user_id(FK to users),chat_id(FK to chats),type(enum:'report_generation','resolution_search'),status(enum:'pending','processing','completed','failed'),progress(integer 0-100),input(jsonb for serialized form data and parameters),result(jsonb for final output),error(text for failure messages),created_at,updated_at,started_at,completed_atstatusanduser_idfor efficient polling queriesTask 2: Create Database Migration
Generate and apply the migration for the new jobs table.
drizzle/migrations/using drizzle-kit generate0000_sweet_metal_master.sqland0001_add_calendar_notes.sqlTask 3: Implement Job Service Layer
Create a service module with functions to manage job lifecycle operations.
lib/services/jobs.tswith functions:createJob(),getJob(),updateJobStatus(),updateJobProgress(),completeJob(),failJob(),getPendingJobs(),getUserJobs()getPendingJobs()usingstatusupdate to prevent duplicate processing🤖 Prompt for AI agents
Phase 2: Worker Implementation
This phase extracts the agent processing logic into a context-independent worker module that can execute jobs from the queue without depending on the Server Action request lifecycle.
Task 1: Extract Agent Processing Logic
Refactor
processEventsandprocessResolutionSearchto be callable from a worker context without streaming primitives.lib/workers/report-processor.tscontaining the core processing logic extracted fromapp/actions.tsxcreateStreamableUIandcreateStreamableValuedependencies with callback-based progress reporting (e.g.,onProgress,onPartialResult)Task 2: Implement Worker Entry Point
Create the worker module that polls for pending jobs and executes them.
lib/workers/job-worker.tswith aprocessNextJob()function that claims and processes one pending jobrunWorkerLoop()function for continuous processing with configurable poll interval'processing'and return the job in one transaction)failJob()with error details, continue to next jobTask 3: Create Worker API Route for Cron Triggering
Add an API endpoint that can be called by a cron scheduler to trigger job processing.
app/api/workers/process-jobs/route.tsas a POST endpointprocessNextJob()in a loop with a time limit (e.g., 25 seconds for Vercel function timeout safety)🤖 Prompt for AI agents
Phase 3: Modified Submit Flow and Status API
This phase modifies the submit action to enqueue jobs instead of processing synchronously and adds an API endpoint for clients to check job status.
Task 1: Modify Submit Action for Job Enqueueing
Update the
submitserver action to create a job record and return immediately for async processing.app/actions.tsxto detect when async processing is appropriate (e.g., complex queries, resolution search)createJob(), return job ID and status immediatelyjobIdandstatus: 'queued'alongside the existing streaming return shapeTask 2: Create Job Status API Endpoint
Add an API route for clients to poll job status and retrieve results.
app/api/jobs/[id]/route.tswith GET methodgetCurrentUserIdOnServer()Task 3: Handle Job Result Reconstruction
Implement logic to reconstruct the UI state from completed job results.
lib/utils/job-result-renderer.tswith functions to convert serialized job results back into React componentsgetUIStateFromAIStateinapp/actions.tsxto handle job-based message reconstructionsaveChatmechanism🤖 Prompt for AI agents
Phase 4: Client-Side Integration
This phase updates the frontend to handle async job states, poll for status updates, and render results when jobs complete.
Task 1: Create Job Polling Hook
Implement a React hook for polling job status with automatic cleanup.
lib/hooks/use-job-status.tswithuseJobStatus(jobId)hookTask 2: Create Job Progress Component
Build a UI component to display job processing status and progress.
components/job-progress.tsxshowing job status, progress bar, and elapsed timeTask 3: Update Chat Interface for Async Jobs
Modify the chat UI to handle both synchronous streaming and asynchronous job-based responses.
components/chat.tsx(or equivalent) to detect job-based responses from submit🤖 Prompt for AI agents
Research
The application is a Next.js project using Drizzle ORM with Supabase PostgreSQL, Vercel AI SDK (
ai/rsc) for streaming, and a multi-agent architecture (researcher, task-manager, inquire, writer, query-suggestor, resolution-search). Currently, thesubmitserver action inapp/actions.tsxuses fire-and-forget async functions (processEvents,processResolutionSearch) that stream results viacreateStreamableUIandcreateStreamableValue, but these are tied to the request lifecycle. There is no existing job queue or background worker infrastructure. Authentication uses Supabase SSR with cookie-based sessions.🚀 Next Steps
🤖 All AI agent prompts combined
💡 Iterate on the plan with:
`@coderabbitai` <feedback>💬 Have feedback or questions? Drop into our discord!
ngoiyaeric commentedon Jun 15, 2026
https://workflow-sdk.dev/