Skip to main content

Documentation Index

Fetch the complete documentation index at: https://docs.standardagentbuilder.com/llms.txt

Use this file to discover all available pages before exploring further.

Overview

Standard Agents provides utility functions that wrap common ThreadState operations. While you can call ThreadState methods directly, these utilities are exported from @standardagents/builder for convenience and backwards compatibility.
Most utilities are now methods on ThreadState. You can use either the standalone functions or call methods directly on state.

queueTool

Queue a tool call for execution in the current thread.
// Using the utility function
import { queueTool } from "@standardagents/builder";
queueTool(state, 'tool_name', { arg: 'value' });

// Or call directly on state
state.queueTool('tool_name', { arg: 'value' });
Parameters:
  • state: The ThreadState context
  • toolName: The name of the tool to call
  • args: Arguments to pass to the tool
Example:
import { defineTool } from "@standardagents/spec";
import { z } from "zod";

export default defineTool({
  description: "Process user order and send confirmation",
  args: z.object({
    orderId: z.string(),
    userId: z.string(),
  }),
  execute: async (state, args) => {
    // Validate the order
    const order = await validateOrder(args.orderId);

    if (!order.valid) {
      return {
        status: "error",
        error: "Order validation failed",
      };
    }

    // Queue additional tools to execute after this one
    state.queueTool("charge_payment", {
      orderId: args.orderId,
      amount: order.total,
    });

    state.queueTool("send_confirmation_email", {
      userId: args.userId,
      orderId: args.orderId,
    });

    state.queueTool("update_inventory", {
      items: order.items,
    });

    return {
      status: "success",
      result: `Order ${args.orderId} queued for processing`,
    };
  },
});

invokeTool

Invoke a tool directly and wait for the result.
const result = await state.invokeTool('tool_name', { arg: 'value' });

if (result.status === 'success') {
  console.log(result.result);
}
Parameters:
  • toolName: The name of the tool to invoke
  • args: Arguments to pass to the tool
Returns: Promise<ToolResult>

injectMessage

Inject a message into the thread conversation.
const message = await state.injectMessage({
  role: 'user',
  content: 'Additional context...',
  silent: true,
  metadata: { source: 'tool' },
});
Options:
interface InjectMessageInput {
  role: 'user' | 'assistant' | 'system';
  content: string;
  silent?: boolean;          // Hide from UI
  metadata?: Record<string, unknown>;
}
Examples:
export default defineTool({
  description: "Load user context",
  args: z.object({ userId: z.string() }),
  execute: async (state, args) => {
    const user = await fetchUser(args.userId);

    await state.injectMessage({
      role: "system",
      content: `User context loaded: ${user.name} (${user.email})
        - Account tier: ${user.tier}
        - Preferences: ${JSON.stringify(user.preferences)}`,
    });

    return {
      status: "success",
      result: "User context injected",
    };
  },
});

getMessages

Retrieve message history from the thread.
const { messages, total, hasMore } = await state.getMessages({
  limit: 50,
  offset: 0,
  order: 'desc',
});
Options:
OptionTypeDefaultDescription
limitnumber-Max messages to return
offsetnumber0Messages to skip
order'asc' | 'desc''desc'Sort order
includeSilentbooleanfalseInclude silent messages
maxDepthnumber-Max sub-prompt nesting
Example:
export default defineTool({
  description: "Analyze recent conversation tone",
  execute: async (state) => {
    // Get last 10 messages
    const { messages } = await state.getMessages({ limit: 10 });

    // Filter user and assistant messages only
    const conversationMessages = messages.filter(
      (m) => m.role === "user" || m.role === "assistant"
    );

    const tone = analyzeTone(conversationMessages);

    return {
      status: "success",
      result: `Conversation tone: ${tone}`,
    };
  },
});

emit (Custom Events)

Send custom events to WebSocket clients.
state.emit('event_type', {
  key: 'value',
  progress: 50,
});
Parameters:
  • event: Event type name
  • data: Event payload (must be JSON-serializable)
Event Structure (received by clients):
{
  "type": "event",
  "eventType": "<your-type>",
  "data": <your-data>,
  "timestamp": 1234567890
}
Examples:
export default defineTool({
  description: "Process large dataset",
  args: z.object({ datasetId: z.string() }),
  execute: async (state, args) => {
    const dataset = await loadDataset(args.datasetId);
    const total = dataset.length;

    for (let i = 0; i < total; i++) {
      await processItem(dataset[i]);

      // Emit progress event every 10 items
      if (i % 10 === 0) {
        state.emit("progress", {
          current: i + 1,
          total,
          percentage: ((i + 1) / total) * 100,
          message: `Processing item ${i + 1} of ${total}`,
        });
      }
    }

    return {
      status: "success",
      result: `Processed ${total} items`,
    };
  },
});

Frontend Integration

Listen for events on the frontend using @standardagents/react:
import {
  StandardAgentsProvider,
  ThreadProvider,
  useThread,
} from "@standardagents/react";

function App() {
  return (
    <StandardAgentsProvider config={{ endpoint: "https://api.example.com" }}>
      <ThreadProvider threadId="123e4567-e89b-12d3-a456-426614174000">
        <OrderTracker />
      </ThreadProvider>
    </StandardAgentsProvider>
  );
}

function OrderTracker() {
  const { onCustomEvent } = useThread();

  // Listen for custom events
  useEffect(() => {
    const unsubscribe = onCustomEvent('progress', (data) => {
      console.log('Progress:', data.percentage);
    });
    return unsubscribe;
  }, [onCustomEvent]);

  return <div>...</div>;
}

forceTurn (Execution Control)

Force the next execution turn to a specific side (for dual_ai agents).
// Only available during execution
if (state.execution) {
  state.execution.forceTurn('b');
}
Parameters:
  • side: The side to force ('a' or 'b')
This is primarily used for dual_ai agents to control turn order.

stop (Execution Control)

Stop the current execution after the current operation completes.
if (state.execution) {
  state.execution.stop();
}

scheduleEffect

Schedule an effect for delayed or background execution.
const effectId = await state.scheduleEffect(
  'send_reminder_email',
  { to: 'user@example.com', subject: 'Reminder' },
  30 * 60 * 1000  // 30 minutes
);
Parameters:
  • name: Effect name (file in agents/effects/)
  • args: Arguments passed to effect handler
  • delay: Delay in milliseconds (default: 0)
Returns: Effect ID (UUID) for later cancellation

Complete Example

Here’s a comprehensive example showing multiple utilities working together:
import { defineTool } from "@standardagents/spec";
import { z } from "zod";

export default defineTool({
  description: "Complete order processing workflow",
  args: z.object({
    orderId: z.string(),
    userId: z.string(),
    notifyUser: z.boolean().default(true),
  }),
  execute: async (state, args) => {
    // Emit initial status event
    state.emit("order_status", {
      orderId: args.orderId,
      status: "started",
    });

    // Inject system message for context
    await state.injectMessage({
      role: "system",
      content: `Processing order ${args.orderId} for user ${args.userId}`,
    });

    // Queue validation tool
    state.queueTool("validate_order", {
      orderId: args.orderId,
    });

    // Queue payment processing
    state.queueTool("process_payment", {
      orderId: args.orderId,
      userId: args.userId,
    });

    // Queue inventory update
    state.queueTool("update_inventory", {
      orderId: args.orderId,
    });

    // Conditionally queue notification
    if (args.notifyUser) {
      state.queueTool("send_notification", {
        userId: args.userId,
        type: "order_confirmation",
        orderId: args.orderId,
      });
    }

    // Get conversation history for audit
    const { messages } = await state.getMessages({ limit: 50 });
    const orderMessages = messages.filter(
      (m) => m.content?.includes(args.orderId)
    );

    // Emit completion event
    state.emit("order_status", {
      orderId: args.orderId,
      status: "queued",
      steps: 4,
      relatedMessages: orderMessages.length,
    });

    return {
      status: "success",
      result: `Order ${args.orderId} workflow initiated`,
    };
  },
});

Best Practices

Always wrap async operations in try-catch blocks:
try {
  await state.injectMessage({
    role: "system",
    content: "Context updated",
  });
} catch (error) {
  console.error("Failed to inject message:", error);
  return {
    status: "error",
    error: `Message injection failed: ${error.message}`,
  };
}
Avoid excessive event emissions that could overwhelm WebSocket clients:
// Good: Throttle emissions
for (let i = 0; i < 10000; i++) {
  if (i % 100 === 0) {
    state.emit("progress", {
      current: i,
      total: 10000,
    });
  }
}

// Avoid: Emitting inside tight loop
for (let i = 0; i < 10000; i++) {
  state.emit("progress", { value: i });
}
When retrieving large message histories, use pagination:
const pageSize = 50;
let offset = 0;
let hasMore = true;

while (hasMore) {
  const result = await state.getMessages({ limit: pageSize, offset });
  await processMessages(result.messages);
  hasMore = result.hasMore;
  offset += pageSize;
}
When using execution-specific features, check if execution is active:
if (state.execution) {
  console.log(`Step: ${state.execution.stepCount}`);
  state.execution.forceTurn('b');
}

Troubleshooting

Solution: Ensure you’re using ThreadProvider and that you’ve subscribed to the correct event type name.
Solution: Tools are queued for sequential execution. Ensure the current tool completes successfully and doesn’t stop execution.
Solution: Check that messages exist in the thread and that limit/offset parameters are correct.

Next Steps

ThreadState

Understand the ThreadState interface

Tools

Create custom tools

Hooks

Use utilities in lifecycle hooks

React Integration

Connect frontend to backend events