The Observability system automatically collects execution traces for analysis and debugging, and provides a track() API for emitting custom business events that power real-time dashboards.
Automatic Tracing (Agent)
// Traces are collected automatically
const agent = new Agent ({
name: 'Support Agent' ,
instructions: 'Help customers.' ,
model: openai ( 'gpt-4o' ),
});
// Each execution automatically generates traces
await agent . process ({
message: 'Help me' ,
companyId: 'company_123' , // Optional
sessionId: 'session_456' , // Optional
executionId: 'exec_123' , // Optional
threadId: 'thread_789' , // Optional
});
Automatic Tracing (Workflow)
Workflows automatically trace every step with full hierarchy:
const workflow = flow ({
id: 'validate-order' ,
name: 'Order Validation' ,
inputSchema: z . object ({ orderId: z . string () }),
outputSchema: z . any (),
})
. step ( 'fetch' , async ( input ) => db . getOrder ( input . orderId ))
. step ( 'validate' , async ( input ) => ({ valid: input . status === 'active' }))
. agent ( 'respond' , responseAgent )
. build ();
await workflow . execute ({ orderId: 'ORD-123' });
// Generates trace hierarchy:
// workflow_execution > "Order Validation"
// ├── workflow_step > "fetch" [function]
// ├── workflow_step > "validate" [function]
// └── workflow_step > "respond" [agent]
// └── agent_execution
// └── llm_call
Each step type (function, agent, connector, condition, switch, foreach, parallel) is traced with its own color and label in the portal.
Verbose Tracing Mode
Control how much data is saved in traces. Works identically for Agent and Workflow .
Modes:
full : Complete data including prompts and responses (default)
standard : Balanced metadata with truncation
minimal : Disables tracing entirely (no traces sent)
Simple API (string preset):
const agent = new Agent ({
name: 'My Agent' ,
model: openai ( 'gpt-4o' ),
observability: 'minimal' // Disable traces completely
});
const workflow = flow ({
id: 'my-workflow' ,
inputSchema , outputSchema ,
observability: 'standard' // Truncate large inputs/outputs
}). step ( 'a' , handler ). build ();
Granular Control (object config):
const agent = new Agent ({
name: 'My Agent' ,
model: openai ( 'gpt-4o' ),
observability: {
mode: 'standard' , // Base mode
verboseLLM: true , // Override: save complete prompts
verboseMemory: false , // Override: keep memory minimal
verboseTools: true , // Override: save tool data (default)
maxInputLength: 5000 , // Truncate large inputs
maxOutputLength: 5000 , // Truncate large outputs
}
});
Trace Interceptor (onTrace)
Intercept, modify, or cancel traces before they are sent. Available in Agent , Workflow , and standalone logging .
// Agent
const agent = new Agent ({
observability: {
onTrace : ( trace ) => {
// Remove sensitive data
if ( trace . input ?. cpf ) delete trace . input . cpf ;
if ( trace . input ?. password ) delete trace . input . password ;
return trace ;
}
}
});
// Workflow
const workflow = flow ({
id: 'pipeline' ,
inputSchema , outputSchema ,
observability: {
onTrace : ( trace ) => {
// Cancel LLM traces (only keep step-level)
if ( trace . type === 'llm_call' ) return null ;
return trace ;
}
}
}). step ( 'a' , handler ). build ();
// Standalone logging
import { configureLogging } from '@runflow-ai/sdk' ;
configureLogging ({
onTrace : ( trace ) => {
// Send to external system
datadog . sendTrace ( trace );
return trace ;
}
});
Return values:
Return the trace (modified or not) to send it
Return null to cancel (trace is not sent)
Return void to send unchanged
Trace Hierarchy (startSpan)
Create parent-child relationships between custom logs for structured traces:
import { startSpan , log } from '@runflow-ai/sdk' ;
async function processBatch ( items : any []) {
// Create a parent span
const batch = startSpan ( 'process-batch' );
for ( const item of items ) {
// Child logs grouped under the parent
log ( 'process-item' , {
input: { id: item . id },
output: { status: 'ok' }
}, { parentId: batch . traceId });
}
// Close the parent span
batch . end ({ output: { total: items . length } });
}
This produces a hierarchical trace in the portal:
custom_event > "process-batch"
├── custom_event > "process-item"
├── custom_event > "process-item"
└── custom_event > "process-item"
Custom Executions (Non-Agent Flows)
For scenarios without agent.process() (document analysis, batch processing, etc.):
import { identify , startExecution , log } from '@runflow-ai/sdk/observability' ;
export async function analyzeDocument ( docId : string ) {
// 1. Identify context
identify ({ type: 'document' , value: docId });
// 2. Start custom execution
const exec = startExecution ({
name: 'document-analysis' ,
input: { documentId: docId }
});
try {
// 3. Process with LLM calls
const llm = LLM . openai ( 'gpt-4o' );
const text = await llm . chat ( "Extract text from document..." );
exec . log ( 'text_extracted' , { length: text . length });
const category = await llm . chat ( `Classify this: ${ text } ` );
exec . log ( 'document_classified' , { category });
// exec.log() automatically parents to the execution span
const summary = await llm . chat ( `Summarize: ${ text } ` );
// 4. Finish with custom output
await exec . end ({
output: {
summary ,
category ,
documentId: docId
}
});
return { summary , category };
} catch ( error ) {
exec . setError ( error );
await exec . end ();
throw error ;
}
}
Custom Logging
Log custom events within any execution:
import { log , logEvent , logError } from '@runflow-ai/sdk/observability' ;
// Simple log
log ( 'cache_hit' , { key: 'user_123' });
// Structured log with parent
log ( 'step_completed' , {
input: { orderId: '123' },
output: { valid: true },
}, { parentId: parentSpan . traceId });
// Structured log
logEvent ( 'validation' , {
input: { orderId: '123' , amount: 100 },
output: { valid: true , score: 0.95 },
metadata: { rule: 'fraud_detection' }
});
// Error log
try {
await riskyOperation ();
} catch ( error ) {
logError ( 'operation_failed' , error );
throw error ;
}
Business Event Tracking
Use track() to emit custom business events from your agent. These events power the Metrics dashboard in the portal, where you can build KPI cards, charts, and real-time feeds without writing any backend code.
import { track } from '@runflow-ai/sdk/observability' ;
// Inside your agent's tool or logic
track ( 'alert_received' , {
company: 'NW Telecom' ,
severity: 'High' ,
source: 'Zabbix' ,
});
track ( 'ticket_resolved' , {
duration: 45 ,
answered: true ,
category: 'network' ,
});
Events are buffered and sent in batches automatically (up to 50 events or every 2 seconds). No manual flushing needed during normal execution.
How It Works
Call track(eventName, properties) anywhere in your agent code
The SDK buffers events and sends them in batches to the Runflow API
Open the Metrics tab in the portal to create dashboard cards
Cards auto-discover your event names and properties — no configuration needed
Parameters
Parameter Type Required Description eventNamestringYes Name of the event (e.g. 'alert_received', 'order_placed') propertiesRecord<string, any>No Key-value pairs with event data optionsTrackOptionsNo Override threadId, executionId, or timestamp
Options
track ( 'payment_processed' , { amount: 150.00 }, {
threadId: 'custom-thread-123' , // Override auto-resolved thread
executionId: 'custom-exec-456' , // Override auto-resolved execution
timestamp: '2026-01-15T10:30:00Z' , // Custom timestamp
});
Flushing Before Exit
For short-lived scripts or CLI tools, call flushTrackEvents() before exiting to ensure all events are sent:
import { track , flushTrackEvents } from '@runflow-ai/sdk/observability' ;
track ( 'batch_completed' , { total: 500 , errors: 2 });
// Ensure events are sent before process exits
await flushTrackEvents ();
Dashboard Cards
In the portal, navigate to your agent’s Metrics tab to create cards:
Number — KPI with a single aggregated value (count, sum, avg)
Rate — Percentage based on a filtered property value
Line / Bar — Time-series charts grouped by hour, day, week, or month
Pie — Distribution chart over time periods
Cards support drag-to-resize, custom colors, and multiple aggregation types:
Aggregation Description Example countTotal events Total alerts received ratePercentage where property matches a value % of alerts answered sumSum of a numeric property Total revenue avgAverage of a numeric property Average response time distinct_countCount of unique property values Unique companies served
Best Practices
Use descriptive event names
Use snake_case names that describe what happened: alert_received, ticket_resolved, payment_processed. Avoid generic names like event or action.
Properties are stored as JSON and queried via keys. Flat key-value pairs work best for dashboard aggregations: // Good
track ( 'order_placed' , { amount: 99.90 , category: 'electronics' , customer_id: 'c_123' });
// Avoid nested objects
track ( 'order_placed' , { order: { amount: 99.90 , details: { category: 'electronics' } } });
Use consistent property types
Keep the same property as the same type across events. If duration is a number in one event, don’t send it as a string in another — aggregations like sum and avg rely on numeric values.
Observability Comparison
Feature Agent Workflow Standalone (log) Automatic tracing Yes Yes Manual Mode (full/standard/minimal) Yes Yes — onTrace interceptorYes Yes configureLogging()Truncation control Yes Yes — Trace hierarchy Automatic Automatic (steps) startSpan + parentIdDefault mode fullfull—
Next Steps
Workflows Workflow tracing and step types
Configuration Configure observability