Skip to main content
When building agents, you expose functionality through commands. There are two types:
  • AgentTool - The LLM can call these when needed based on user messages
  • SlashCommand - Users invoke these directly with /command syntax
Most commands should be both types, giving users flexibility.

Command Types

AgentTool

The LLM can call these when it determines they’re needed based on user messages.
from opperator_agent import OpperatorAgent, CommandExposure

class EmailAgent(OpperatorAgent):
    def initialize(self):
        self.register_command(
            name="fetch_emails",
            handler=self.fetch_emails,
            description="Fetch unread emails from the user's inbox",
            expose_as=[CommandExposure.AGENT_TOOL]
        )

    def fetch_emails(self, args):
        emails = self.email_client.get_unread()
        return {"count": len(emails), "emails": emails}
User says: “Check my email” → LLM calls fetch_emails() → Returns result

SlashCommand

Users invoke these directly with /command syntax.
from opperator_agent import OpperatorAgent, CommandExposure

class EmailAgent(OpperatorAgent):
    def initialize(self):
        self.register_command(
            name="fetch_emails",
            handler=self.fetch_emails,
            title="Fetch Emails",  # Shown in command picker
            description="Fetch unread emails from inbox",
            expose_as=[CommandExposure.SLASH_COMMAND]
        )

    def fetch_emails(self, args):
        emails = self.email_client.get_unread()
        return {"count": len(emails), "emails": emails}
User types: /fetch_emails → Executes immediately → Shows result Use slash commands for operations that need explicit user control or have side effects (e.g., /deploy, /delete).

Best Practice: Expose Both

self.register_command(
    name="fetch_emails",
    handler=self.fetch_emails,
    title="Fetch Emails",
    description="Fetch unread emails from the user's inbox",
    expose_as=[CommandExposure.AGENT_TOOL, CommandExposure.SLASH_COMMAND]
)
Now users can:
  • Say “Check my email” → LLM calls it
  • Type /fetch_emails → Direct execution

Command Arguments

Define typed arguments with automatic validation.
from opperator import CommandArgument

self.register_command(
    name="send_email",
    handler=self.send_email,
    description="Send an email to a recipient",
    expose_as=[CommandExposure.AGENT_TOOL, CommandExposure.SLASH_COMMAND],
    arguments=[
        CommandArgument(
          name="to",
          type="string",
          description="Recipient email",
          required=True
        ),
        CommandArgument(
          name="subject",
          type="string",
          description="Subject line",
          required=True
        ),
        CommandArgument(
          name="body",
          type="string",
          description="Email body",
          required=True
        ),
    ]
)

def send_email(self, args):
    self.email_client.send(args["to"], args["subject"], args["body"])
    return {"status": "sent", "to": args["to"]}
Supported types: string, integer, number, boolean, array, object

Argument Parameters Reference

ParameterTypeDescription
namestringArgument name (required)
typestringData type: string, integer, number, boolean, array, object
descriptionstringHelps the LLM understand usage
requiredbooleanWhether argument must be provided (default: False)
defaultanyDefault value when not provided
enumarrayList of allowed values
itemsobjectSchema for array items (use with type="array")
propertiesobjectSchema for object properties (use with type="object")
minLength / maxLengthintegerString length constraints
minimum / maximumnumberNumeric value constraints
minItems / maxItemsintegerArray length constraints
Complex types example:
from opperator import CommandArgument

arguments=[
    CommandArgument(name="title", type="string", required=True),
    CommandArgument(
        name="file_ids",
        type="array",
        description="List of file IDs to process",
        items={"type": "string"}
    ),
    CommandArgument(
        name="tasks",
        type="array",
        description="List of tasks to execute",
        items={
            "type": "object",
            "properties": {
                "name": {"type": "string"},
                "priority": {"type": "number"}
            }
        }
    ),
    CommandArgument(
        name="metadata",
        type="object",
        properties={
            "project": {"type": "string"},
            "estimate_hours": {"type": "number"}
        }
    )
]

Async Commands

Long-running commands can run in background threads and persist after closing the TUI.
self.register_command(
    name="process_dataset",
    handler=self.process_dataset,
    description="Process large dataset in background",
    async_enabled=True,
    progress_label="Processing dataset"
)

def process_dataset(self, args):
    for i in range(100):
        self.report_progress(f"Batch {i}/100", progress=i)
        # Do work...
    return {"status": "complete", "records_processed": 10000}
Use async_enabled=True for operations > 5 seconds (file processing, API calls, etc.).
For persisting data across command executions and agent restarts, see State Management.
Configure thread pool size:
class MyAgent(OpperatorAgent):
    def __init__(self):
        super().__init__(name="my_agent", max_async_workers=4)
Default: max(1, min(8, cpu_count)) - scales with CPU cores, capped at 8.

Additional register_command Parameters

Beyond the basics, register_command accepts these parameters:
ParameterTypeDescription
slash_commandstringCustom slash command name (automatically adds SLASH_COMMAND to expose_as)
slash_scopeSlashCommandScopeLOCAL allows the command to only be invoked when the agent is focused, GLOBAL is from anywhere in the TUI. Default: LOCAL
argument_hintstringHint text shown in slash command picker
argument_requiredbooleanWhether slash command requires arguments. Default: False
Providing the slash_command parameter automatically adds CommandExposure.SLASH_COMMAND to expose_as.

Best Practices

  • Use verb-based names: fetch_emails not emails
  • Write detailed descriptions: Help the LLM understand when to use the tool
  • Return structured data: Use dicts with clear keys, not plain strings
  • Handle errors gracefully: Catch exceptions and return helpful error messages
  • Use async for slow ops: Mark operations > 5 seconds as async_enabled=True

Next Steps