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.
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` ,
};
} ,
}) ;
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:
System Context
Silent Message
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" ,
};
} ,
}) ;
export default defineTool ({
description: "Add internal note" ,
args: z . object ({ note: z . string () }) ,
execute : async ( state , args ) => {
await state . injectMessage ({
role: "system" ,
content: args . note ,
silent: true , // Hidden from LLM, stored for audit
});
return {
status: "success" ,
result: "Internal note added" ,
};
} ,
}) ;
getMessages
Retrieve message history from the thread.
const { messages , total , hasMore } = await state . getMessages ({
limit: 50 ,
offset: 0 ,
order: 'desc' ,
});
Options:
Option Type Default Description limitnumber- Max messages to return offsetnumber0 Messages to skip order'asc' | 'desc''desc'Sort order includeSilentbooleanfalse Include 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:
Progress Updates
Status Updates
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` ,
};
} ,
}) ;
export default defineTool ({
description: "Upload file" ,
args: z . object ({ fileUrl: z . string () }) ,
execute : async ( state , args ) => {
state . emit ( "upload_status" , {
status: "started" ,
message: "Starting download..." ,
});
const file = await downloadFile ( args . fileUrl );
state . emit ( "upload_status" , {
status: "processing" ,
message: "Processing file..." ,
});
const result = await processFile ( file );
state . emit ( "upload_status" , {
status: "complete" ,
message: "Upload complete!" ,
});
return {
status: "success" ,
result: "File processed" ,
};
} ,
}) ;
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 using execution-specific features, check if execution is active: if ( state . execution ) {
console . log ( `Step: ${ state . execution . stepCount } ` );
state . execution . forceTurn ( 'b' );
}
Troubleshooting
Events not received by frontend
Solution: Ensure you’re using ThreadProvider and that you’ve subscribed to the correct event type name.
Tool not executing after queueTool
Solution: Tools are queued for sequential execution. Ensure the current tool completes successfully and doesn’t stop execution.
getMessages returns empty
Solution: Check that messages exist in the thread and that limit/offset parameters are correct.
Next Steps
ThreadState Understand the ThreadState interface
Hooks Use utilities in lifecycle hooks
React Integration Connect frontend to backend events