Skip to content

Find a component

Loading the directory…

↑ ↓ to select · Enter to open · Esc to closeBrowse all →
Contributor login

Durable Agents

Durable agent execution

Bijection source
git clone https://github.com/bijectionhq/durable-agents.git
cd durable-agents
git checkout 6322213b09dc69d3c9c82ab465ca30fbcb7d2070

Bijection repository access is required. Request access.

Description

Durable agent execution

Explore the package README for API concepts, configuration, and examples. Check the Bijection source for adaptation-specific setup.

Available as a reference

Review the source and its dependencies before adopting this package. Source availability does not establish npm publication or runtime qualification.

Getting started

  1. Get the source at the revision above and read its package documentation.
  2. Check the required Bijection SDK version, dependencies, and external service credentials.
  3. Follow the package setup instructions and the guide to using components.
  4. Exercise the component in your deployment according to your project’s verification rules.

Open source

Maintained source: bijectionhq/durable-agents. Based on ziegfried/convex-durable-agents. License: Apache-2.0. Consult the repository for license terms, notices, and contributor attribution.

From the upstream publisher

A durable AI agents component for Convex with automatic retries, crash recovery, and tool execution.

Explore the demo ↗

Upstream overview

From the upstream package documentation.

This component provides durable AI agents that can run indefinitely and survive server restarts or failures. Built on AI SDK v6, it executes agent tool loops asynchronously to avoid Convex action time limits while maintaining persistent streaming and automatic retries. Agents can call external APIs through Convex actions and optionally route execution through workpools for parallelism control.

Use cases

From the upstream package documentation.

  • Long-running AI workflows that need to survive dev server restarts and production failures without losing conversation state
  • AI agents with external API calls that require automatic retry logic and durable execution when tools fail or timeout
  • Multi-step agent tasks that exceed Convex action time limits by running tool loops asynchronously with persistent state
  • Production chatbots that need real-time streaming with message persistence and the ability to resume interrupted conversations
  • Background AI processing where agents need to continue working even when the user disconnects or the browser closes

How it works

From the upstream package documentation.

The component wraps AI SDK's streamText API with Convex's durable execution model. You define agents using streamHandlerAction with your language model and tools, then export a complete API using defineAgentApi for public access or defineInternalAgentApi for server-only access.

Tools are implemented as Convex actions using createActionTool for synchronous results or createAsyncTool for background processing. The component handles automatic retries with configurable backoff strategies and can route execution through @convex-dev/workpool for parallelism control.

Real-time streaming works through streamUpdates queries that deliver message deltas to React components via the useAgentChat hook. Thread state persists across failures, allowing agents to resume exactly where they left off using resumeThread. Authorization callbacks protect thread access, and the component maintains full conversation history with support for both user messages and tool execution results.

Upstream package FAQ

Is this component ready for production use?

The Durable Agents Component for Convex is currently in early development and not recommended for production use. The API is likely to change significantly before the first stable release, so expect breaking changes during development.

How does this differ from the official Convex Agents Component?

The Durable Agents Component focuses specifically on persistence and durability while deliberately omitting advanced features like context management, RAG, and rate limiting found in the official component. It exposes the full AI SDK v6 streamText API with added persistence and fault tolerance.

What AI SDK version does this component support?

The Durable Agents Component for Convex is built on top of AI SDK v6 and aims to expose its complete streamText API. This ensures compatibility with the latest AI SDK features while adding persistence and durable execution capabilities.

Can I control parallelism for multiple agents?

Yes, the Durable Agents Component supports optional integration with @convex-dev/workpool for parallelism control. This allows you to route agent and tool execution through a workpool to manage concurrent agent workflows effectively.

Use with agents and CLI

Give your coding agent the component’s source identity and setup guidance.

agent-prompt.md
Help me integrate Durable Agents with Bijection.

Read: https://bijection.com/components/durable-agents/SKILL.md

Check SDK compatibility and dependency availability.
Follow this project's execution and verification rules.

Upstream README.md

View repository ↗

Documentation from ziegfried/convex-durable-agents, recorded October 23, 2026. APIs and setup may differ in the Bijection adaptation.

Durable Agents Component for Convex

A Convex component for building durable AI agents with an async tool loop. The goal of this component is to provide a way to build AI agents that can run indefinitely and survive failures and restarts. It provides some of the functionality of the Convex Agents Component (such as persistent streaming), while deliberately leaving out some of the more advanced features (context management, RAG, rate limiting, etc.). The component is built on top of the AI SDK v6 and aims to expose its full streamText API with persistence and durable execution.

Note: This component is still in early development and is not yet ready for production use. The API will very likely change before a first stable release.

Features

  • Async Execution: Agent tool loop is executed asynchronously to avoid time limits of convex actions
  • Tool Execution: via convex actions - support for both sync and async tools
  • Automatic Retries: Failed tool calls are automatically retried (opt-in)
  • Durable Execution: Agent tool loops survive crashes and dev server restarts
  • Workpool Support: Optionally route agent and tool execution through @convex-dev/workpool for parallelism control

Installation

npm install convex-durable-agents ai zod

Quick Start

1. Configure the Component

Create a convex.config.ts file in your app's convex/ folder:

// convex/convex.config.ts
import { defineApp } from "convex/server";
import durableAgents from "convex-durable-agents/convex.config.js";

const app = defineApp();
app.use(durableAgents);

export default app;

2. Define Your Agent

Create a chat handler with your AI model and tools:

// convex/chat.ts
import { z } from "zod";
import { components, internal } from "./_generated/api";
import { createActionTool, defineAgentApi, streamHandlerAction } from "convex-durable-agents";

// Define the stream handler with your model and tools
export const chatAgentHandler = streamHandlerAction(components.durableAgents, {
  model: "anthropic/claude-haiku-4.5",
  system: "You are a helpful AI assistant.",
  tools: {
    get_weather: createActionTool({
      description: "Get weather for a location",
      args: z.object({ location: z.string() }),
      handler: internal.tools.weather.getWeather,
      retry: {
        enabled: true,
        maxAttempts: 5,
      },
    }),
  },
  saveStreamDeltas: true, // Enable real-time streaming
});

// Export the agent API (public - callable from clients)
export const {
  createThread,
  sendMessage,
  getThread,
  listMessages,
  streamUpdates,
  listThreads,
  deleteThread,
  resumeThread,
  stopThread,
  addToolResult,
  addToolError,
} = defineAgentApi(components.durableAgents, internal.chat.chatAgentHandler, {
  // Optional: Add authorization to protect thread access
  authorizationCallback: async (ctx, threadId) => {
    // Example: verify the user owns this thread
    // const identity = await ctx.auth.getUserIdentity();
    // if (!identity) throw new Error("Unauthorized");
  },
});

Using Internal API

If you want to restrict the agent API to only be callable from other Convex functions (not directly from clients), use defineInternalAgentApi instead:

// convex/chat.ts
import { defineInternalAgentApi } from "convex-durable-agents";

// Export internal agent API (only callable from other Convex functions)
export const {
  createThread,
  sendMessage,
  getThread,
  // ... other functions
} = defineInternalAgentApi(components.durableAgents, internal.chat.chatAgentHandler);

This is useful when you want to:

  • Add authentication/authorization checks before calling agent functions
  • Wrap agent functions with additional business logic
  • Prevent direct client access to the agent API
  • Run agents in the background

You can then create your own public functions that call the internal API:

// convex/myChat.ts
import { mutation, query } from "./_generated/server";
import { internal } from "./_generated/api";
import { v } from "convex/values";

// Public wrapper with auth check
export const sendMessage = mutation({
  args: { threadId: v.string(), prompt: v.string() },
  handler: async (ctx, args) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Not authenticated");

    // Call the internal agent API
    return ctx.runMutation(internal.chat.sendMessage, args);
  },
});

3. Create Tool Handlers

Tools are defined as Convex actions:

// convex/tools/weather.ts
import { v } from "convex/values";
import { internalAction } from "../_generated/server";

export const getWeather = internalAction({
  args: { location: v.string() },
  returns: v.object({ weather: v.string(), temperature: v.number() }),
  handler: async (_ctx, args) => {
    // Call your weather API here
    return { weather: "sunny", temperature: 72 };
  },
});

4. Build Your UI

Use the React hooks to build your chat interface:

import { useAgentChat } from "convex-durable-agents/react";
import { api } from "../convex/_generated/api";

function ChatView({ threadId }: { threadId: string }) {
  const { messages, status, isRunning, sendMessage, stop } = useAgentChat({
    listMessages: api.chat.listMessages,
    streamUpdates: api.chat.streamUpdates,
    getThread: api.chat.getThread,
    sendMessage: api.chat.sendMessage,
    stopThread: api.chat.stopThread,
    resumeThread: api.chat.resumeThread,
    threadId,
  });

  return (
    <div>
      {messages.map((msg) => (
        <div key={msg.metadata?.key}>
          <strong>{msg.role}:</strong> {msg.parts.map((p) => (p.type === "text" ? p.text : null))}
        </div>
      ))}

      {isRunning && <button onClick={() => stop()}>Stop</button>}

      <input
        onKeyPress={(e) => {
          if (e.key === "Enter" && !isRunning) {
            sendMessage(e.currentTarget.value);
            e.currentTarget.value = "";
          }
        }}
        disabled={isRunning}
      />
    </div>
  );
}

API Reference

Client API

defineAgentApi(component, streamHandler, options?)

Creates the full agent API with public functions that can be called directly from clients:

  • createThread({ prompt?, messages?, autoStart? }) - Create a new conversation thread
  • sendMessage({ threadId, prompt }) - Send a message to a thread
  • addMessage({ threadId, msg }) - Add a message to a thread without triggering the agent
  • resumeThread({ threadId, prompt? }) - Resume a stopped/failed thread
  • stopThread({ threadId }) - Stop a running thread
  • getThread({ threadId }) - Get thread details
  • listMessages({ threadId }) - List messages in a thread
  • streamUpdates({ threadId, fromSeq? }) - Get streaming message updates for real-time UI
  • listThreads({ limit? }) - List all threads
  • deleteThread({ threadId }) - Delete a thread
  • addToolResult({ threadId, toolCallId, result }) - Add result for async tool
  • addToolError({ threadId, toolCallId, error }) - Add error for async tool

Options:

type AgentApiOptions = {
  authorizationCallback?: (ctx: QueryCtx | MutationCtx | ActionCtx, threadId: string) => Promise<void> | void;
  workpoolEnqueueAction?: FunctionReference<"mutation", "internal">;
  toolExecutionWorkpoolEnqueueAction?: FunctionReference<"mutation", "internal">;
  onStatusChange?: FunctionReference<"mutation", "internal">;
  excludeSystemMessages?: boolean;
};
  • authorizationCallback - Called before any operation that accesses an existing thread. Use it to verify the user has permission to access the thread. Throw an error to deny access.
  • workpoolEnqueueAction - Route agent and tool execution through a workpool for parallelism control
  • toolExecutionWorkpoolEnqueueAction - Override workpool for tool execution only (falls back to workpoolEnqueueAction if not set)
  • onStatusChange - Mutation callback invoked when thread status changes
  • excludeSystemMessages - Whether to exclude system messages from message lists (defaults to true)

Protected endpoints: sendMessage, resumeThread, stopThread, getThread, listMessages, deleteThread

Example with ownership check:

defineAgentApi(components.durableAgents, internal.chat.chatAgentHandler, {
  authorizationCallback: async (ctx, threadId) => {
    const identity = await ctx.auth.getUserIdentity();
    if (!identity) throw new Error("Unauthorized");

    // Query your threads table to verify ownership
    const thread = await ctx.runQuery(api.threads.getOwner, { threadId });
    if (thread?.userId !== identity.subject) {
      throw new Error("Access denied");
    }
  },
});

defineInternalAgentApi(component, streamHandler, options?)

Same as defineAgentApi but creates internal functions that can only be called from other Convex functions. Use this when you want to add authentication, authorization, or other business logic before calling agent functions.

streamHandlerAction(component, options)

Creates the stream handler action:

streamHandlerAction(component, {
  model: languageModel,        // AI SDK language model
  system?: string,             // System prompt
  tools?: Record<string, DurableTool>,
  saveStreamDeltas?: boolean | StreamingOptions,
  transformMessages?: (messages) => messages,
  // ... other AI SDK streamText options
});

createActionTool(options)

Creates a sync tool that returns results directly:

createActionTool({
  description: string,
  args: ZodSchema,
  handler: FunctionReference<"action">,
  retry?: true | {
    enabled: true,
    maxAttempts?: number,        // default 3
    backoff?: {
      strategy?: "fixed",
      delayMs: number,
      jitter?: boolean,
    } | {
      strategy: "exponential",
      initialDelayMs: number,
      multiplier?: number,       // default 2
      maxDelayMs?: number,
      jitter?: boolean,
    },
    shouldRetryError?: FunctionReference<"action">,
  },
});

Pass retry: true for defaults (3 attempts, exponential backoff) or provide a full config object. The optional shouldRetryError action receives { threadId, toolCallId, toolName, args, error, attempt, maxAttempts } and should return a truthy value to retry or falsy to fail immediately.

createAsyncTool(options)

Creates an async tool where results are provided later:

createAsyncTool({
  description: string,
  args: ZodSchema,
  callback: FunctionReference<"action">,
});

React Hooks

useAgentChat(options)

All-in-one hook for chat functionality that combines thread state with mutations:

const {
  messages, // UIMessageWithConvexMetadata[]
  thread, // ThreadDoc | null
  status, // ThreadStatus
  isLoading, // boolean
  isRunning, // boolean
  isComplete, // boolean
  isFailed, // boolean
  isStopped, // boolean
  sendMessage, // (prompt: string) => Promise<null>
  stop, // () => Promise<null>
  resume, // (prompt?: string) => Promise<null>
} = useAgentChat({
  listMessages: api.chat.listMessages,
  streamUpdates: api.chat.streamUpdates,
  getThread: api.chat.getThread,
  sendMessage: api.chat.sendMessage,
  stopThread: api.chat.stopThread,
  resumeThread: api.chat.resumeThread,
  threadId,
});

// Send a message (threadId is automatically included)
await sendMessage("Hello!");

// Stop the agent
await stop();

// Resume after stopping or failure
await resume();

useThreadMessages(options)

Lower-level hook for thread messages with streaming support (use useAgentChat for most cases):

const {
  messages, // UIMessageWithConvexMetadata[]
  thread, // ThreadDoc | null
  status, // ThreadStatus
  isLoading, // boolean
  isRunning, // boolean
  isComplete, // boolean
  isFailed, // boolean
  isStopped, // boolean
} = useThreadMessages({
  messagesQuery: api.chat.listMessages,
  streamingMessageUpdatesQuery: api.chat.streamUpdates

[README truncated for prompt length]