Skip to main content

What Is The Thread Class?

The Thread class is your application’s extension point for the underlying Durable Object that manages each conversation. By extending DurableThread, you can add custom RPC methods that are accessible from your API endpoints, tools, hooks, and other parts of your application.
The Thread class lives in agents/Thread.ts and is automatically exported as the DurableThread binding for Cloudflare Workers.

Default Implementation

When you scaffold a project, you get a minimal Thread class:
// agents/Thread.ts
import { DurableThread } from 'virtual:@standardagents/builder'

export class Thread extends DurableThread {}
This empty class inherits all built-in functionality from DurableThread.

Built-in Methods

Your Thread class inherits these methods from DurableThread. All methods are available as RPC calls.

Message Methods

MethodDescription
getMessages(limit?, offset?, order?, includeSilent?, maxDepth?)Retrieve message history with pagination
sendMessage(threadId, content, role?)Send a new message and trigger execution
deleteMessage(messageId)Delete a specific message
updateMessageContent(messageId, content)Update a message’s content

Execution Methods

MethodDescription
execute(threadId, agentId, messages?, data?)Start agent execution with optional initial messages
stop()Stop current execution and mark pending messages as failed
shouldStop()Check if execution stop flag is set

Log Methods

MethodDescription
getLogs(limit?, offset?, order?)Retrieve execution logs with pagination
getLogDetails(logId)Get detailed information for a specific log

Thread Management

MethodDescription
getThreadMeta(threadId)Get thread metadata including agent info and stats
updateThreadMeta(threadId, params)Update thread metadata (agent, user_id, tags)
deleteThread()Permanently delete thread and all its data

Configuration Loaders

MethodDescription
loadAgent(name)Load an agent definition by name
loadPrompt(name)Load a prompt definition by name
loadModel(name)Load a model definition by name
getAgentNames()List all available agent names
getPromptNames()List all available prompt names
getModelNames()List all available model names

Registry Methods

MethodDescription
tools()Get the tools registry (auto-implemented by virtual module)
hooks()Get the hooks registry (auto-implemented by virtual module)
models()Get the models registry (auto-implemented by virtual module)
prompts()Get the prompts registry (auto-implemented by virtual module)
agents()Get the agents registry (auto-implemented by virtual module)
Since Thread extends Cloudflare’s DurableObject, you also have access to this.ctx and this.env along with all other built-in Durable Object features like alarms, WebSockets, and transactional storage.

Adding Custom Methods

Extend the Thread class to add application-specific functionality:
// agents/Thread.ts
import { DurableThread } from 'virtual:@standardagents/builder'

export class Thread extends DurableThread {
  /**
   * Get analytics for this thread
   */
  async getAnalytics() {
    const messageCursor = await this.ctx.storage.sql.exec<{ count: number }>(
      `SELECT COUNT(*) as count FROM messages`
    )
    const logCursor = await this.ctx.storage.sql.exec<{
      total_tokens: number
    }>(
      `SELECT SUM(total_tokens) as total_tokens FROM logs`
    )

    return {
      messageCount: messageCursor.one().count,
      totalTokens: logCursor.one().total_tokens || 0,
    }
  }

  /**
   * Archive old messages
   */
  async archiveOldMessages(olderThanMs: number) {
    const cutoff = (Date.now() - olderThanMs) * 1000 // Convert to microseconds

    await this.ctx.storage.sql.exec(
      `DELETE FROM messages WHERE created_at < ?`,
      cutoff
    )

    return { success: true }
  }
}
Custom methods are automatically available as RPC calls on the Durable Object instance.
After adding custom methods, run npx wrangler types to regenerate TypeScript definitions for your Durable Object bindings.

Accessing Custom Methods

From API Endpoints

Use defineThreadEndpoint to access your custom methods:
// agents/api/analytics.get.ts
import { defineThreadEndpoint } from '@standardagents/builder'

export default defineThreadEndpoint(async (req, { instance }) => {
  // Call your custom method
  const analytics = await instance.getAnalytics()

  return Response.json(analytics)
})

From Tools

Access the thread instance via FlowState:
// agents/tools/get_thread_stats.ts
import { defineTool } from '@standardagents/builder'
import { z } from 'zod'

export default defineTool(
  'Get statistics about the current conversation',
  z.object({}),
  async (flow) => {
    const analytics = await flow.thread.instance.getAnalytics()

    return {
      status: 'success',
      content: [{
        type: 'text',
        text: JSON.stringify(analytics)
      }],
    }
  }
)

Available Context

Inside your Thread methods, you have access to:

this.ctx

The Durable Object state, including SQLite storage:
// Execute SQL queries
const cursor = await this.ctx.storage.sql.exec<{ id: string }>(
  `SELECT id FROM messages WHERE role = ?`,
  'user'
)
const rows = cursor.toArray()

// Access key-value storage
await this.ctx.storage.put('my_key', 'my_value')
const value = await this.ctx.storage.get('my_key')

this.env

Environment bindings configured in wrangler.jsonc:
// Access other Durable Objects you've configured
const rateLimiterId = this.env.RATE_LIMITER.idFromName(userId)
const rateLimiter = this.env.RATE_LIMITER.get(rateLimiterId)

// Access custom bindings (if configured)
const kv = this.env.MY_KV
const db = this.env.MY_D1

Database Schema

The Thread’s SQLite database includes these tables:

messages

ColumnTypeDescription
idTEXTPrimary key (UUID)
roleTEXT’system’, ‘user’, ‘assistant’, or ‘tool’
contentTEXTMessage content
tool_callsTEXTJSON array of tool calls
tool_call_idTEXTFor tool response messages
tool_statusTEXT’success’ or ‘error’
created_atINTEGERMicroseconds since epoch
statusTEXT’pending’, ‘completed’, or ‘failed’
depthINTEGERNesting depth for sub-prompts

logs

ColumnTypeDescription
idTEXTPrimary key (UUID)
message_idTEXTAssociated message
providerTEXT’openai’, ‘anthropic’, etc.
modelTEXTModel identifier
input_tokensINTEGERPrompt tokens used
output_tokensINTEGERCompletion tokens used
total_tokensINTEGERTotal tokens
cost_totalREALEstimated cost
latency_msINTEGERRequest latency
created_atINTEGERMicroseconds since epoch

Best Practices

Durable Object methods should complete quickly. For long-running operations, consider using alarms:
async scheduleCleanup(delayMs: number) {
  await this.ctx.storage.setAlarm(Date.now() + delayMs)
  return { scheduled: true }
}

async alarm() {
  // Handle scheduled work
  await this.archiveOldMessages(7 * 24 * 60 * 60 * 1000)
}
Always type your SQL query results:
const cursor = await this.ctx.storage.sql.exec<{
  id: string
  content: string
  created_at: number
}>(
  `SELECT id, content, created_at FROM messages WHERE role = ?`,
  'user'
)

// TypeScript knows the shape of each row
for (const row of cursor) {
  console.log(row.id, row.content)
}
Wrap database operations in try/catch:
async safeGetStats() {
  try {
    const cursor = await this.ctx.storage.sql.exec(
      `SELECT COUNT(*) as count FROM messages`
    )
    return { count: cursor.one().count }
  } catch (error) {
    console.error('Failed to get stats:', error)
    return { count: 0, error: 'Query failed' }
  }
}
Add JSDoc comments for better IDE support:
/**
 * Archive messages older than the specified age
 * @param olderThanMs - Age threshold in milliseconds
 * @returns Object with success status and count of archived messages
 */
async archiveOldMessages(olderThanMs: number) {
  // ...
}

Server Entry Point

Your Thread class must be exported as DurableThread from your server entry point. This is a hard requirement for the framework to function:
// server/index.ts
import { router } from 'virtual:@standardagents/builder'
import { Thread } from '../agents/Thread'
import { AgentBuilder } from '../agents/AgentBuilder'

export default {
  async fetch(request, env) {
    const response = await router(request, env)
    if (response) return response
    return env.ASSETS.fetch(request)
  },
} satisfies ExportedHandler<Env>

// Required: Export Thread as DurableThread
export { Thread as DurableThread }
export { AgentBuilder as DurableAgentBuilder }
The DurableThread export name is required and must match the binding name in your wrangler.jsonc configuration. The framework will not work without this export.

Next Steps