Skip to main content
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.

Why Use Sidebar Sections?

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

section_id
string
required
Unique identifier for the section (e.g., “status”, “metrics”)
title
string
required
Display title shown in sidebar
content
string
required
Section content with optional formatting
collapsed
boolean
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.

Formatting and Colors

Sidebar sections support HTML-like formatting tags:

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}%\nItem {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.
"""
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