Learn/Cookbook/Patterns

Background Tasks

Use waitUntil to run work after responding to the client

Run tasks after sending a response using waitUntil. This keeps response times fast while handling analytics, notifications, or other fire-and-forget work.

The Pattern

waitUntil accepts an async function that runs after the response is sent. Multiple calls run concurrently.

This example uses ArkType for schema validation:

src/agent/order-processor/agent.ts
import { createAgent } from '@agentuity/runtime';
import { type } from 'arktype';
 
const agent = createAgent('OrderProcessor', {
  schema: {
    input: type({
      orderId: 'string',
      userId: 'string',
    }),
    output: type({
      status: 'string',
      orderId: 'string',
    }),
  },
  handler: async (ctx, input) => {
    const { orderId, userId } = input;
 
    // Process the order synchronously
    const order = await processOrder(orderId);
 
    // Background: send confirmation email
    ctx.waitUntil(async () => { 
      await sendConfirmationEmail(userId, order);
      ctx.logger.info('Confirmation email sent', { orderId });
    });
 
    // Background: update analytics
    ctx.waitUntil(async () => { 
      await trackPurchase(userId, order);
    });
 
    // Background: notify warehouse
    ctx.waitUntil(async () => { 
      await notifyWarehouse(order);
    });
 
    // Response sent immediately, background tasks continue
    return {
      status: 'confirmed',
      orderId,
    };
  },
});
 
export default agent;

With Durable Streams

Create a stream for the client to poll, then populate it in the background:

src/agent/async-generator/agent.ts
import { createAgent } from '@agentuity/runtime';
import { streamText } from 'ai';
import { openai } from '@ai-sdk/openai';
import { type } from 'arktype';
 
const agent = createAgent('AsyncGenerator', {
  schema: {
    input: type({ prompt: 'string' }),
    output: type({
      streamId: 'string',
      streamUrl: 'string',
    }),
  },
  handler: async (ctx, input) => {
    // Create a durable stream the client can read from
    const stream = await ctx.stream.create('generation', { 
      contentType: 'text/plain',
      metadata: { sessionId: ctx.sessionId },
    });
 
    // Generate content in the background
    ctx.waitUntil(async () => { 
      try {
        const { textStream } = streamText({
          model: openai('gpt-5-mini'),
          prompt: input.prompt,
        });
 
        for await (const chunk of textStream) {
          await stream.write(chunk);
        }
      } finally {
        await stream.close(); 
      }
    });
 
    // Return stream URL immediately
    return {
      streamId: stream.id,
      streamUrl: stream.url,
    };
  },
});
 
export default agent;

Progress Reporting

Write progress updates to a stream as background work proceeds:

src/agent/batch-processor/agent.ts
import { createAgent } from '@agentuity/runtime';
import { type } from 'arktype';
 
const agent = createAgent('BatchProcessor', {
  schema: {
    input: type({ items: 'string[]' }),
    output: type({ progressUrl: 'string' }),
  },
  handler: async (ctx, input) => {
    const progress = await ctx.stream.create('progress', {
      contentType: 'application/x-ndjson',
    });
 
    ctx.waitUntil(async () => {
      try {
        for (let i = 0; i < input.items.length; i++) {
          await processItem(input.items[i]);
 
          await progress.write(JSON.stringify({
            completed: i + 1,
            total: input.items.length,
            percent: Math.round(((i + 1) / input.items.length) * 100),
          }) + '\n');
        }
 
        await progress.write(JSON.stringify({ done: true }) + '\n');
      } finally {
        await progress.close();
      }
    });
 
    return { progressUrl: progress.url };
  },
});

Key Points

  • Non-blocking: Response returns immediately, tasks run after
  • Concurrent: Multiple waitUntil calls run in parallel
  • Error isolation: Background task failures don't affect the response
  • Always close streams: Use finally blocks to ensure cleanup

See Also

Need Help?

Join our DiscordCommunity for assistance or just to hang with other humans building agents.

Send us an email at hi@agentuity.com if you'd like to get in touch.

Please Follow us on

If you haven't already, please Signup for your free account now and start building your first agent!