Agents can create custom sections in the TUI sidebar to show status, progress, metrics, and other dynamic information. This helps users understand what your agent is doing in real-time.
Sidebar sections provide a persistent display of agent state that’s always visible in the TUI:
Agent status - Show whether the agent is idle, working, or has errors
Progress updates - Display progress bars for long-running operations
Live metrics - Show request counts, processing time, etc.
Important alerts - Highlight warnings or critical information
Current activity - Display what the agent is currently doing
Coordinate sidebar updates with System Prompts to keep both the visual display and LLM context synchronized.
Basic Usage
Registering a Section
Create a sidebar section during agent initialization:
class MyAgent ( OpperatorAgent ):
def initialize ( self ):
# Register a status section
self .register_section(
section_id = "status" ,
title = "Agent Status" ,
content = "<c fg='gray'>○ Initializing</c>" ,
collapsed = False
)
Updating a Section
Update the section content as your agent works:
def start ( self ):
# Update to running state
self .update_section(
"status" ,
"<c fg='green'>● Running</c>"
)
def handle_request ( self , args ):
# Update with activity
self .request_count += 1
self .update_section(
"status" ,
f """
<c fg='green'>● Running</c>
<b>Requests:</b> { self .request_count }
<b>Last:</b> { time.strftime( '%H:%M:%S' ) }
""" .strip()
)
SDK Methods
Unique identifier for the section (e.g., “status”, “metrics”)
Display title shown in sidebar
Section content with optional formatting
Start collapsed (default: False)
If you call update_section() for a section that doesn’t exist, it will automatically create it. However, it’s recommended to explicitly register sections first for clarity.
Sidebar sections support HTML-like formatting tags:
Colors
Named Colors
Hex Colors
Background Colors
"<c fg='green'>Success</c>"
"<c fg='red'>Error</c>"
"<c fg='yellow'>Warning</c>"
"<c fg='blue'>Info</c>"
# Available: red, green, blue, yellow, cyan, magenta,
# white, black, gray, grey, orange, purple, pink, brown
Text Styling
"<b>Bold text</b>"
"<i>Italic text</i>"
"<b><i>Bold and italic</i></b>"
# Nested tags work
"<c fg='red'><b>Bold Red Text</b></c>"
Status Indicators and Symbols
Use ASCII symbols for status indicators, progress visualization, and animations:
Status Indicators
Progress Bars
Charts
Sparklines
Spinners
# Status states
"<c fg='green'>● Active</c>"
"<c fg='yellow'>○ Inactive</c>"
"<c fg='green'>✓ Success</c>"
"<c fg='red'>✗ Failed</c>"
"<c fg='yellow'>⚠ Warning</c>"
"<c fg='blue'>ℹ Info</c>"
# Loading states
"<c fg='yellow'>◐ Starting</c>"
"<c fg='yellow'>◓ Paused</c>"
Progress Bars
Create progress bars using block characters:
def show_progress ( self , current , total ):
percent = int ((current / total) * 100 )
filled = int ( 20 * percent / 100 )
bar = "█" * filled + "░" * ( 20 - filled)
self .update_section(
"progress" ,
f " { bar } { percent } % \n Item { current } / { total } "
)
# Example output:
# ████████░░░░░░░░░░░░ 40%
# Item 4/10
Common Patterns
Overview + Activity Pattern
Use two sections - one stable, one dynamic:
class TaskAgent ( OpperatorAgent ):
def initialize ( self ):
# Stable overview
self .register_section(
"overview" ,
"Overview" ,
f """
<b>Agent:</b> Task Processor
<b>Version:</b> 1.0
<b>Status:</b> Ready
""" .strip()
)
# Dynamic activity (collapsed by default)
self .register_section(
"activity" ,
"Current Activity" ,
"Idle" ,
collapsed = True
)
def process_task ( self , task_id ):
# Show current activity
self .update_section(
"activity" ,
f """
<c fg='yellow'>◐ Processing task</c>
<b>ID:</b> { task_id }
<b>Started:</b> { time.strftime( '%H:%M:%S' ) }
""" .strip()
)
# Do the work...
result = self .process(task_id)
# Show completion
self .update_section(
"activity" ,
f """
<c fg='green'>✓ Task completed</c>
<b>ID:</b> { task_id }
<b>Result:</b> { result }
""" .strip()
)
# Update overview
self .update_section(
"overview" ,
f """
<b>Agent:</b> Task Processor
<b>Version:</b> 1.0
<b>Status:</b> Ready
<b>Last Task:</b> { task_id }
""" .strip()
)
Lifecycle Status Updates
Show different status as agent moves through lifecycle:
class MonitorAgent ( OpperatorAgent ):
def initialize ( self ):
self .register_section(
"status" ,
"Agent Status" ,
"<c fg='gray'>○ Initializing...</c>"
)
def start ( self ):
# Animate startup
for frame in [ "◐" , "◓" , "◑" , "◒" ] * 2 :
self .update_section(
"status" ,
f "<c fg='yellow'> { frame } Starting...</c>"
)
time.sleep( 0.25 )
# Running state
self .update_section(
"status" ,
"<c fg='green'>● Running</c>"
)
def on_shutdown ( self ):
self .update_section(
"status" ,
"<c fg='yellow'>○ Stopping...</c>"
)
Metrics Dashboard
Show live metrics:
class APIAgent ( OpperatorAgent ):
def initialize ( self ):
self .register_section(
"metrics" ,
"📊 Metrics" ,
"""
<b>Requests:</b> 0
<b>Errors:</b> 0
<b>Avg Response:</b> 0ms
<b>Uptime:</b> 0s
""" .strip()
)
def update_metrics ( self ):
uptime = int (time.time() - self .start_time)
avg_response = sum ( self .response_times) / len ( self .response_times)
self .update_section(
"metrics" ,
f """
<b>Requests:</b> { self .request_count }
<b>Errors:</b> { self .error_count }
<b>Avg Response:</b> { avg_response :.0f} ms
<b>Uptime:</b> { uptime } s
""" .strip()
)
Complete Example
Full agent with multiple sidebar sections:
from opperator_agent import OpperatorAgent, LogLevel
import time
class ProcessingAgent ( OpperatorAgent ):
def __init__ ( self ):
super (). __init__ ( name = "processing_agent" )
self .processed_count = 0
self .error_count = 0
def initialize ( self ):
# Overview section
self .register_section(
"overview" ,
"Processing Agent" ,
"""
<b>Status:</b> <c fg='gray'>Initializing</c>
<b>Processed:</b> 0
<b>Errors:</b> 0
""" .strip()
)
# Activity section (collapsed)
self .register_section(
"activity" ,
"Current Activity" ,
"No activity" ,
collapsed = True
)
# Register command
self .register_command(
"process_file" ,
self .cmd_process_file,
description = "Process a file" ,
async_enabled = True ,
progress_label = "Processing file"
)
def start ( self ):
self .update_section(
"overview" ,
"""
<b>Status:</b> <c fg='green'>● Running</c>
<b>Processed:</b> 0
<b>Errors:</b> 0
""" .strip()
)
def cmd_process_file ( self , args ):
file_path = args.get( "file_path" , "data.csv" )
# Show processing started
self .update_section(
"activity" ,
f """
<c fg='yellow'>◐ Processing file</c>
<b>File:</b> { file_path }
<b>Started:</b> { time.strftime( '%H:%M:%S' ) }
""" .strip()
)
# Simulate processing with progress
items = range ( 10 )
for i, item in enumerate (items):
time.sleep( 0.5 ) # Simulate work
# Update progress
percent = int ((i + 1 ) / len (items) * 100 )
filled = int ( 20 * percent / 100 )
bar = "█" * filled + "░" * ( 20 - filled)
self .report_progress(
text = f "Processing { i + 1 } / { len (items) } " ,
progress = (i + 1 ) / len (items)
)
self .update_section(
"activity" ,
f """
<c fg='yellow'>◐ Processing file</c>
<b>File:</b> { file_path }
{ bar } { percent } %
Item { i + 1 } / { len (items) }
""" .strip()
)
# Complete
self .processed_count += 1
self .update_section(
"activity" ,
f """
<c fg='green'>✓ File processed</c>
<b>File:</b> { file_path }
<b>Items:</b> { len (items) }
<b>Completed:</b> { time.strftime( '%H:%M:%S' ) }
""" .strip()
)
self .update_section(
"overview" ,
f """
<b>Status:</b> <c fg='green'>● Running</c>
<b>Processed:</b> { self .processed_count }
<b>Errors:</b> { self .error_count }
<b>Last File:</b> { file_path }
""" .strip()
)
return { "status" : "success" , "items" : len (items)}
if __name__ == "__main__" :
agent = ProcessingAgent()
agent.run()
Best Practices
Sidebar space is limited. Use short labels and values: Good: """
<b>Status:</b> Running
<b>Requests:</b> 42
<b>Errors:</b> 0
"""
Bad: """
The agent is currently running and has processed
42 requests with 0 errors encountered so far.
"""
Update on meaningful changes
Don’t update too frequently or on trivial changes: Good: # Update when state changes
def handle_request ( self , req ):
self .request_count += 1
if self .request_count % 10 == 0 : # Every 10 requests
self .update_metrics()
Bad: # Updates 10x per second!
def main_loop ( self ):
while self .running:
self .update_section( "time" , f " { time.time() } " )
time.sleep( 0.1 )
Use colors to indicate status, not just for decoration:
🟢 Green: Success, healthy, active
🟡 Yellow: Warning, processing, inactive
🔴 Red: Error, failed, critical
⚪ Gray: Neutral, initializing
if self .error_count > 0 :
status = "<c fg='red'>✗ Errors</c>"
elif self .processing:
status = "<c fg='yellow'>◐ Processing</c>"
else :
status = "<c fg='green'>● Ready</c>"
Use collapsed=True for sections users don’t need to see constantly: # Always visible
self .register_section(
"status" ,
"Status" ,
content,
collapsed = False
)
# Collapsed by default
self .register_section(
"debug" ,
"Debug Info" ,
content,
collapsed = True
)
Troubleshooting
Check :
Agent must be running (not necessarily focused)
Section ID must be non-empty after trimming whitespace
Check agent logs for errors
Debug :def initialize ( self ):
self .log(LogLevel. INFO , "Registering section" )
self .register_section( "test" , "Test" , "Content" )
self .log(LogLevel. INFO , "Section registered" )
Check :
Section ID must match exactly (case-sensitive)
Agent is emitting updates
No exceptions in handler
Section was registered first (or auto-created)
Debug :def update_status ( self ):
self .log(LogLevel. INFO , "Updating section" )
self .update_section( "status" , "New content" )
self .log(LogLevel. INFO , "Section updated" )
Check :
Use quotes around color names: <c fg="green"> or <c fg='green'>
Named colors: red, green, blue, yellow, cyan, magenta, white, black, gray, grey, orange, purple, pink, brown
Hex format: #RRGGBB (6 digits) or #RGB (3 digits)
Must include closing tags: </c>, </b>, </i>
Examples :# Correct
"<c fg='green'>text</c>"
'<c fg="green">text</c>'
"<c fg='#00ff00'>text</c>" # 6-digit hex
"<c fg='#0f0'>text</c>" # 3-digit hex
# Wrong
"<c fg=green>text</c>" # Missing quotes
"<c fg='green'>text" # Missing closing tag
Next Steps