Core Mental Model
If you want to customize RED, you only need one product mental model:
A user sends a message.An agent decides what to do.Tools read or change your product data.Long work becomes a task.Task events update the UI.When a task finishes, the result can re-enter the conversation.The Brief AI Assistant included in RED is the reference implementation of that loop.
The Brief Example
Section titled “The Brief Example”In the demo, each thread is bound to one brief. The assistant can:
- Read all sections with
getBriefSections. - Edit a section with
patchBrief. - Add a section with
addBriefSection. - Start a Markdown export with
exportBrief.
The assistant does not ask the model to invent a briefId. The agent resolves it every turn:
threadId -> assistantAgent.resolveContext() -> looks up the brief attached to the thread -> returns { briefId } -> every tool receives it as ToolContext.extras.briefIdThat pattern is important. Product-specific context flows through resolveContext and ToolContext.extras, not through the engine.
Core Terms
Section titled “Core Terms”Agent
Instructions, model choice, tools, and optional per-turn context. Example: assistantAgent.
Tool
A function the model can call. Tools are where your agent reads data, edits data, starts tasks, or calls services.
Thread
A conversation. Agent choice is locked when the thread is created because the stored checkpoint contains tool names from that agent.
Boundary
A shared workspace around threads. It can hold memory, members, and a default agent key.
Task node
A backend unit of work registered with the task engine. A node can be internal or external.
Task event
A progress, success, error, heartbeat, cancelled, or custom event attached to a task.
Callback handle
A random capability token for external tasks. Remote services post events to POST /api/tasks/{handle}/event.
Four Ways Tools Do Work
Section titled “Four Ways Tools Do Work”RED has four practical tool modes.
1. Direct Sync Tool
Section titled “1. Direct Sync Tool”Use defineSyncTool() for quick work that can finish inside the model step and does not need a task row.
Good for:
- Reading a small record.
- Formatting a quick answer.
- Running short deterministic logic.
The tool body receives the AI SDK options object. RED’s engine context is inside options.experimental_context:
const toolContext = options.experimental_context as ToolContext<MyContext> | undefinedif (!toolContext) throw new Error("Missing tool context")2. Sync Internal Task Tool
Section titled “2. Sync Internal Task Tool”Use defineSyncInternalTaskTool() when the work is still fast, but you want task history and task events.
Good for:
- Short mutations that should be visible in the task system.
- Work that should share logic with a task node.
- Audit-friendly internal operations.
The node must be an internal node with inlineExecution: "sync".
3. Async Internal Task
Section titled “3. Async Internal Task”Use defineAsyncTool() with an internal task node when the work runs inside Convex but may take time or needs progress events.
The Brief export uses this pattern. It is an internal node:
exportBrief tool -> startTaskForAi(nodeTypeKey: "tasks.briefs.export") -> briefExportNodeDefinition.kind = "internal" -> runBriefExport schedules progress events -> final action writes the Markdown artifactThe tool intentionally returns blocking: false, so the chat remains usable while the export finishes. This is a product choice, not a requirement of internal tasks.
4. Async External Task
Section titled “4. Async External Task”Use defineAsyncTool() with an external node when the real work happens outside Convex, such as a Python worker, AWS Lambda, Render job, or third-party API.
External nodes implement trigger(ctx, task, { handle, handleUrl }). The trigger sends handleUrl to the remote service. The remote service posts events back:
POST /api/tasks/{handle}/eventContent-Type: application/json
{ "type": "progress", "payload": { "percent": 50, "message": "Rendering" }}The HTTP endpoint stores events. The engine polls stored task events and forwards new progress to the UI.
What Happens When a User Sends a Message
Section titled “What Happens When a User Sends a Message”sendMessage -> writes the user message -> creates or wakes an aiRun -> schedules driveRun
driveRun -> loads the thread checkpoint -> resolves the agent and its context -> streams model text and tool calls -> writes message parts for the UI -> suspends if async tasks are pending
pollWaitingTasks -> reads new taskEvents -> writes progress status parts -> settles finished tasks -> requeues the run when the agent should continueUsers see updates through Convex reactive queries. There is no separate WebSocket server to maintain for chat streaming.
Blocking vs Non-Blocking
Section titled “Blocking vs Non-Blocking”Async tools return an async sentinel with a blocking flag.
Blocking means: the agent needs the result before it can answer. The run waits in waiting_tasks. When the task succeeds, the checkpoint is rewritten with the real tool result and the agent continues.
Non-blocking means: the task can finish later. The run moves to awaiting_background, the user can keep chatting, and task completion becomes a task_event message in a later turn.
The Brief export is non-blocking because the user can continue editing while the Markdown file is generated.
Why Task Events Matter
Section titled “Why Task Events Matter”A task event stream makes long work visible:
startedprogress: Preparing briefprogress: Serializing sectionsprogress: Rendering markdownprogress: Finalizing artifactsuccess: artifact metadataThe task engine stores those events in taskEvents. The AI engine polls for new events and writes UI status parts into the originating assistant message. For non-blocking completions, it also writes a task_event message so the agent can naturally say something like “Your export is ready.”
Why This Survives Tab Closes
Section titled “Why This Survives Tab Closes”RED stores the run, messages, message parts, checkpoint parts, tasks, and task events in Convex. If a user closes the tab mid-run, the data is still there. When they return, reactive queries load the current state.
This is the main difference from a simple HTTP streaming endpoint. RED treats agent work as persisted application state, not as a browser connection.
What You Usually Change
Section titled “What You Usually Change”To build your own AI feature, you usually add:
- An agent file in
packages/backend/src/convex/modules/ai/agents/. - Tool files in
packages/backend/src/convex/modules/ai/tools/. - Task nodes in
packages/backend/src/convex/modules/tasks/node_types/when work should be tracked or backgrounded. - A frontend route or workspace UI that creates threads with your agent.
The core engine remains agent-agnostic. It knows about runs, threads, checkpoints, tasks, and boundaries; your product-specific IDs live in agent context and product tables.
Next: Building AI Features gives copy-paste recipes for each extension point.